Compare commits

...

787 Commits
1.1 ... 2.6

Author SHA1 Message Date
Karl Tauber
069a7c809c release 2.6 2022-10-18 11:11:24 +02:00
Karl Tauber
883b4d735a changed "since 3" to "since 2.6" 2022-10-18 11:08:14 +02:00
Karl Tauber
9f39b269bb CHANGELOG.md: moved PR #595 from "Fixed bugs" to "New features and improvements" 2022-10-17 18:15:19 +02:00
Karl Tauber
36c405c708 fixed NPE in FlatUIUtils.isCellEditor() (issue #601) 2022-10-17 18:09:12 +02:00
Karl Tauber
bc7c68ebe4 MenuBar: fixed NPE in FlatMenuItemRenderer.getTopLevelFont() if menu item does not have a parent (issue #600; regression since implementing #589 in FlatLaf 2.5; commit f6c5db07f2) 2022-10-17 17:08:12 +02:00
Karl Tauber
6c502ad4c5 2.6-SNAPSHOT 2022-10-17 16:31:54 +02:00
Karl Tauber
100aa0b621 Native libraries: load jawt.dll also on Windows when running in Java 9 and later (to be on the safe side) 2022-10-06 10:58:32 +02:00
Karl Tauber
8e42b19934 Native libraries: support loading via System.loadLibrary()
(for pre-extracted native libs in NetBeans)
2022-10-06 10:49:55 +02:00
Karl Tauber
1a456d5d68 ScaledImageIcon: do not throw exceptions if image has invalid size (e.g. not found); instead paint a red rectangle (similar to FlatSVGIcon) 2022-10-01 20:12:32 +02:00
Karl Tauber
e83c26a76a - ScrollBar: show "pressed" feedback on track/thumb only for left mouse button; if absolute positioning is enabled (the default), then also for middle mouse button
- Arrow buttons in ComboBox, Spinner, ScrollBar and TabbedPane: show "pressed" feedback only for left mouse button
2022-09-30 19:55:42 +02:00
Karl Tauber
6e7c2a616b updated CHANGELOG.md for PR #595 and added tab context menu test 2022-09-30 15:33:37 +02:00
Karl Tauber
0699454df8 Merge PR #595: Switch and close tabs on left mouse click only 2022-09-30 15:10:45 +02:00
Karl Tauber
92c110548a ComboBox and Spinner: no longer use preferred height for arrow button width, because preferred height may be zero, which would hide arrow button (see https://github.com/scijava/scijava-ui-swing/issues/77#issuecomment-1261452712)
- arrow button width depends on combobox/spinner height
- default/max button width is height of a raw combobox/spinner (without insets)
- min button width is 3/4 of default button width
2022-09-30 12:30:46 +02:00
Karl Tauber
ca88023560 GitHub Actions: build using Java 19 (use toolchain because Gradle 7.5.1 does not support running on Java 19) 2022-09-28 19:15:59 +02:00
Karl Tauber
12fc2299ec update to Gradle 7.5.1
./gradlew wrapper --gradle-version=7.5.1
2022-09-28 15:44:16 +02:00
Karl Tauber
2089c77b84 updated sigtest for FlatLaf 2.5
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-09-27 16:50:34 +02:00
Karl Tauber
4f5a3e8d8b release 2.5 2022-09-27 16:11:48 +02:00
ShadelessFox
95522846ac Switch and close tabs on left mouse click only 2022-09-25 21:52:44 +03:00
Karl Tauber
614ac956de updated sigtest to 1.7 2022-09-23 12:31:42 +02:00
Karl Tauber
c228362c01 Window decorations: added UI value TitlePane.font to customize window title font (issue #589) 2022-09-23 11:57:38 +02:00
Karl Tauber
f6c5db07f2 MenuBar: top level menus now use MenuBar.font instead of Menu.font (issue #589) 2022-09-23 00:31:14 +02:00
Karl Tauber
78e7839213 Window decorations: added option to show window icon only in frames, but not in dialogs (issue #589) 2022-09-23 00:13:01 +02:00
Karl Tauber
86a4f306c6 Styling: added convenience methods to invoke StyleableUI interface methods
~~~java
JButton button = new JButton();
int arc = FlatLaf.getStyleableValue( button, "arc" );
Color borderColor = FlatLaf.getStyleableValue( button, "borderColor" );
~~~
2022-09-14 10:51:11 +02:00
Karl Tauber
0e523f1193 SwingX: fixed missing highlighting of "today" in JXMonthView and JXDatePicker 2022-09-12 13:29:53 +02:00
Karl Tauber
9041a16b22 IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2022-09-11 18:03:11 +02:00
Karl Tauber
596ff3382d PasswordField: reveal button is now hidden (and turned off) if password field is disabled (issue #501) 2022-09-11 17:05:48 +02:00
Karl Tauber
cbd80252ed Testing: introduced client property to exclude components in FlatTestFrame.updateComponentsRecur() 2022-09-11 14:17:55 +02:00
Karl Tauber
bcd7a7e3dd FlatClientProperties: fixed typo in javadoc 2022-09-11 13:59:54 +02:00
Karl Tauber
9c98f1a553 fixed compiler warnings 2022-09-11 12:40:27 +02:00
Karl Tauber
c3d214aa23 Merge PR #579: Linux window decorations: native move window and system menu 2022-09-11 12:11:06 +02:00
Karl Tauber
d301f6e104 MenuBar: support different menu selection style UI defaults for MenuBar and MenuItem (issue #587) 2022-09-11 12:00:38 +02:00
Karl Tauber
eb9fa585f7 more fixes for AWT components on macOS (issue #583)
- ScrollBar: disable hover because scroll bar does not receive mouse exited event
2022-09-10 18:56:33 +02:00
Karl Tauber
16c6ffb032 more fixes for AWT components on macOS (issue #583)
- use light theme for AWT components if dark FlatLaf theme is active (AWT is always light)
- made AWT peer background compatible with Aqua Laf
2022-09-10 16:53:29 +02:00
Karl Tauber
7858e42e37 fixed AWT components on macOS (issue #583)
- fixed missing focus indicator
- fixed round corners
- fixed java.awt.Button background
- fixed java.awt.Choice background
- fixed java.awt.Checkbox hover
2022-09-05 14:47:17 +02:00
Karl Tauber
30132aa6b0 added system property flatlaf.updateUIOnSystemFontChange to allow disabling automatic UI update when system font changes (issue #580) 2022-08-24 19:32:38 +02:00
Karl Tauber
bf4d4cc2c5 Linux: fixed double-click on title bar to maximize/restore on Ubuntu 22.04 (issue #482) 2022-08-21 19:49:41 +02:00
Karl Tauber
9f0554c883 Linux: added libflatlaf-linux-x86_64.so (issue #482)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/2898994332
2022-08-21 17:35:44 +02:00
Karl Tauber
218ea6ce47 Linux: fixed double-click on title bar to maximize/restore (issue #482) 2022-08-21 17:24:50 +02:00
Karl Tauber
0baae7da8b Linux: load jawt.so explicitly before loading FlatLaf native library to fix UnsatisfiedLinkError: ... libjawt.so: cannot open shared object file ... (issue #482) 2022-08-20 23:49:14 +02:00
Karl Tauber
fb4576fc1b Linux: use X11 window manager events to move window and to show window menu (right-click on window title bar), if custom window decorations are enabled (issue #482) 2022-08-20 21:09:49 +02:00
Karl Tauber
16f3f9e6ff Window decorations: added client property to mark components in embedded menu bar as "caption" (issue #569) 2022-08-20 19:42:38 +02:00
Karl Tauber
fee7cf6265 FlatPopupFactory: use method handles instead of reflection 2022-08-13 11:18:47 +02:00
Karl Tauber
2dd75c4a64 fixed possible exception in FlatUIUtils.resetRenderingHints() (issue #575) 2022-08-12 15:41:55 +02:00
Karl Tauber
d2f46cd0b5 TabbedPane: option to disable tab run rotation in wrap layout (issue #574) 2022-08-12 15:32:39 +02:00
Karl Tauber
10914083e6 JavaCompatibility: use method handles instead of reflection 2022-08-12 11:20:42 +02:00
Karl Tauber
5d167da55e Styling: fixed styling protected JRE fields using @StyleableField annotation (regression in commit ff00a6c0f0) 2022-08-12 11:15:49 +02:00
Karl Tauber
b381e20e57 UIDefaultsLoader: over() color function should always return a ColorUIResource 2022-08-11 23:52:35 +02:00
Karl Tauber
475cc9a9a5 Testing: extended FlatPaintingIconsTest to paint icons as pixels 2022-08-11 13:51:19 +02:00
Karl Tauber
264d6fbd6d Testing: added FlatPaintingIconsTest 2022-08-11 00:28:44 +02:00
Karl Tauber
2826cf379b added arrow icons to FlatLaf Icons.sketch and exported as SVGs 2022-08-11 00:24:22 +02:00
Karl Tauber
d28745df29 added missing @since 1.2 tags to setup() methods 2022-08-05 11:19:52 +02:00
Karl Tauber
94f9e4a1be fixed missing UI value MenuItem.acceleratorDelimiter on macOS (was null, is now an empty string) 2022-08-03 13:16:43 +02:00
Karl Tauber
ec547e1d65 fixed compiler warnings 2022-08-03 11:55:59 +02:00
Karl Tauber
61d4eb649b Styling: fixed failing unit test TestFlatStyleableValue
- caused by non-english locale
- when running on Java 17
2022-08-03 11:21:06 +02:00
Karl Tauber
52ad15e375 Styling: added StyleableUI.getStyleableValue() for tooling (e.g. GUI builder) 2022-07-31 10:57:28 +02:00
Karl Tauber
ff00a6c0f0 Styling: use annotation on UI classes for fields in Basic* classes to apply style properties (to avoid boilerplate code) 2022-07-30 11:03:17 +02:00
Karl Tauber
9b1ebd658d updated sigtest for FlatLaf 2.4
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-07-13 23:45:28 +02:00
Karl Tauber
f842530537 release 2.4 2022-07-13 23:43:02 +02:00
Karl Tauber
63077bbb19 Merge PR #565: Window title bar usability improvements (Windows 10/11 only) 2022-07-13 23:28:38 +02:00
Karl Tauber
4dad337377 Window decorations: fixed app icon hit test bounds if icon is shown beside title 2022-07-13 23:11:32 +02:00
Karl Tauber
10a965d765 Window decorations: option to show window icon beside window title, if menu bar is embedded or title is centered 2022-07-13 17:58:25 +02:00
Karl Tauber
3e9c9c9066 execute FlatLaf.initialize() and uninitialize() only for current laf
For NetBeans GUI builder, which invokes `FlatLaf.initialize()` multiple times for preview, but never invokes `FlatLaf.uninitialize()`.

(https://github.com/apache/netbeans/issues/4231)
2022-07-12 12:13:19 +02:00
Karl Tauber
8b5a738e65 Menus: avoid that SubMenuUsabilityHelper can be installed multiple times, which can freeze the application caused pushing multiple event queues and popping wrong event queue first
(e.g. NetBeans Form Editor invokes `FlatLaf.initialize()` but not `uninitialize()`)

(PR #490; https://github.com/apache/netbeans/issues/4231)
2022-07-12 10:33:53 +02:00
Karl Tauber
2c041dce3a Window decorations: add small resize area at top of embedded menu bar only if frame is resizable 2022-07-11 17:47:04 +02:00
Karl Tauber
ef151c68f4 Window decorations:
- improved title bar usability by using larger gaps and minimum sizes
- added minimum gap between embedded menu bar and window title
- fixed oscillating title while resizing window width
- fixed lost right-to-left component orientation in title bar when switching Laf
2022-07-11 17:28:30 +02:00
Karl Tauber
52feaac92a Window decorations: no longer reduce height of window title bar if it has an embedded menu bar and is maximized 2022-07-10 14:03:45 +02:00
Karl Tauber
cddbb3d7d4 Window decorations: make sure that a horizontal glue in embedded menu bar has a minimum width and is always visible 2022-07-10 13:57:37 +02:00
Karl Tauber
42764550e6 Window decorations: improved window title bar layout for small window widths:
- width of iconify/maximize/close buttons is reduced to give more space to embedded menu bar and title
- window title now has a minimum width to always allow moving window
2022-07-09 19:54:29 +02:00
Karl Tauber
6ee737b314 Window decorations: small area at top of embedded menu bar to resize window 2022-07-09 10:30:33 +02:00
Karl Tauber
f460ef7685 UI defaults dumps updated for commits b82ee2ef61 and 93e0496fd2 2022-07-09 10:19:54 +02:00
Karl Tauber
9977bcb468 Window decorations: do not center window title if embedded menu bar is empty or has no menus at left side, but some components at right side (issue #558) 2022-07-09 00:04:51 +02:00
Karl Tauber
7437d984c7 Theme Editor: accept @ as identifier character, which includes it in selection when double clicking e.g. on @background 2022-07-08 17:53:49 +02:00
Karl Tauber
5cd0b2403c Theme Editor: find/replace bar improvements:
- always use editor selection to search if `Ctrl+F` is pressed
- keep find/replace bar open if switching to another editor
- mark matches when switching to another editor
2022-07-08 17:52:08 +02:00
Karl Tauber
a372da22f3 Extras: FlatInspector:
- support embedding into SWT
- added "MigLayout visual padding" to tooltip

fixed typo in MigLayoutVisualPadding.java
2022-07-04 11:09:06 +02:00
Karl Tauber
8b10d3ba5a Native window decorations: fixed missing top window border in dark themes if window drop shadows are disabled in system settings (issue #554) 2022-07-02 23:26:34 +02:00
Karl Tauber
a8b15c6a12 MenuItem: fixed sometimes wrapped HTML text on HiDPI screens on Windows 2022-07-02 22:38:37 +02:00
Karl Tauber
23bac7e5fd Native window decorations: do not use window decorations if system property sun.java2d.opengl is true on Windows 10 (issue #540) 2022-07-02 00:29:29 +02:00
Karl Tauber
b82ee2ef61 Typography: no longer use Consolas or Courier New as monospaced font on Windows because they have bad vertically placement 2022-07-02 00:25:07 +02:00
Karl Tauber
b7761f4b71 HiDPIUtils: support rotated graphics (issue #557) 2022-07-01 15:34:04 +02:00
Karl Tauber
f9a4f9771c Testing: FlatPaintingStringTest:
- added "Fonts" combobox to test various fonts
- reworked/fixed text painting/sizing to get correct results
2022-06-07 11:03:34 +02:00
Karl Tauber
d2acb2c98a HiDPIUtils: reimplemented HiDPIUtils.scale() to make it easier to read and more understandable
(no longer re-using dx1 and dy2 variables for different kind of values)
2022-06-05 23:43:13 +02:00
Karl Tauber
d60bd5df14 FlatEmptyBorder: fixed possible NPE if passed component is null 2022-06-05 00:46:43 +02:00
Karl Tauber
73b6ca3762 ComboBox: fixed vertical alignment of text in popup list with text in combo box in IntelliJ/Darcula themes 2022-06-04 20:15:31 +02:00
Karl Tauber
6c18431a30 TableHeader: fixed exception when changing table structure (e.g. removing column) from a table header popup menu action (issue #532) 2022-05-31 18:56:06 +02:00
Karl Tauber
a49d20249f Gradle: do not set Multi-Release: true in META-INF/MANIFEST.MF if not needed 2022-05-31 15:35:56 +02:00
Karl Tauber
ad384acd57 updated sigtest for FlatLaf 2.3
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-05-28 18:43:37 +02:00
Karl Tauber
69851b7f3a release 2.3 2022-05-28 17:53:54 +02:00
Karl Tauber
92b53bf0df Merge PR #522: File chooser shortcuts panel 2022-05-28 17:46:12 +02:00
Karl Tauber
93e0496fd2 ToggleButton: button style "tab": added missing foreground colors for hover, focused and selected states (issue #535) 2022-05-28 15:09:04 +02:00
Karl Tauber
5151951f46 Button and ToggleButton: added missing foreground colors for hover, pressed, focused and selected states (issue #535) 2022-05-28 14:54:31 +02:00
Karl Tauber
58dbccec2d Table: optionally paint alternating rows below table if table is smaller than scroll pane (issue #504) 2022-05-25 11:18:22 +02:00
Karl Tauber
90de14d013 Native library: refactored loading of Windows native library from FlatWindowsNativeWindowBorder to FlatNativeLibrary to make it easier to add native libraries for other platforms (for issues #204 and #482) 2022-05-17 20:24:28 +02:00
Karl Tauber
5f961618bf Demo and Theme Editor: updated macOS related comments 2022-05-13 13:54:10 +02:00
Karl Tauber
37c375e2fa Theme Editor:
- support "themes" sub-directory
- added "generate Java class" checkbox to "New" dialog
2022-05-10 11:01:45 +02:00
Karl Tauber
1758c175ed FlatLafUIKeys.txt: added some missing UI defaults 2022-05-09 23:30:34 +02:00
Karl Tauber
96f2a02cfa UIDefaultsLoader: added over() color function to convert a translucent color into a solid color based on any background color 2022-05-09 23:28:40 +02:00
Karl Tauber
96d4bda6c8 Demo: hide accent color buttons (instead of disabling them) if not supported by selected theme 2022-05-09 22:51:38 +02:00
Karl Tauber
02cf6050a1 updates for PR #530:
- added @since tags
- changed `FlatToggleButton.setTabUnderlinePlacement()` implementation so that is behaves similar to `FlatTabbedPane.setTabIconPlacement()`
2022-05-09 22:28:34 +02:00
Karl Tauber
38cf32a2e9 Merge PR #530: ToggleButton: made underline placement configurable 2022-05-09 22:08:42 +02:00
Karl Tauber
2ae7589d14 Merge PR #525: Create Bundle_es.properties 2022-05-09 21:28:47 +02:00
Julien Fischer
bcb2e1f0a1 ToggleButton: made underline placement configurable
Supported values: TOP, LEFT, BOTTOM, or RIGHT
2022-05-06 12:07:50 +02:00
Karl Tauber
14932d3f07 Theme Editor: on macOS use apple.awt.fullWindowContent and apple.awt.transparentTitleBar 2022-05-05 13:20:23 +02:00
Karl Tauber
c3b9dc397d Demo: on macOS use apple.awt.fullWindowContent and apple.awt.transparentTitleBar 2022-05-01 11:45:36 +02:00
Karl Tauber
58b653f55d updated sigtest for FlatLaf 2.2
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-05-01 11:44:51 +02:00
Jesús Marín
1dcdc42dde Create Bundle_es.properties
Flatlaf Spanish translation
2022-04-29 14:51:11 +01:00
Karl Tauber
58a0a16985 IntelliJ Themes: fixed TitledBorder text color in "Monokai Pro" theme (issue #524) 2022-04-28 15:16:26 +02:00
Karl Tauber
a117243f14 FileChooser: use large system icons in shortcuts panel also in Java 8 to 16 2022-04-26 22:50:34 +02:00
Karl Tauber
22411060be FileChooser: improve layout for shortcuts panel (give it full height) 2022-04-25 23:49:15 +02:00
Karl Tauber
045263ae58 FileChooser: added (optional) shortcuts panel (issue #100) 2022-04-25 23:06:10 +02:00
Karl Tauber
024b6daaf6 release 2.2 2022-04-25 19:36:43 +02:00
Karl Tauber
bd5512c121 SplitPane: allow limiting one-touch expanding to a single side (issue #355) 2022-04-23 17:13:32 +02:00
Karl Tauber
9afce83a02 SplitPane: added missing BasicSplitPaneDivider properties to javadoc 2022-04-23 16:19:04 +02:00
Karl Tauber
07a8bd9486 ComboBox: added missing BasicComboPopup properties to javadoc 2022-04-22 10:55:25 +02:00
Karl Tauber
bcdc0a8fce IntelliJ Themes: added "Monokai Pro" and "Xcode-Dark" themes 2022-04-21 22:03:05 +02:00
Karl Tauber
b295809432 IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2022-04-21 22:02:09 +02:00
Karl Tauber
52763ab932 GitHub Actions:
- natives.yml: include core natives in artifacts
- updated versions of used actions
2022-04-21 14:18:27 +02:00
Karl Tauber
99666265c9 gradle:
- use gradle `cpp-library` plugin instead of 3rd party plugin
- build natives only via task `build-natives`
2022-04-21 12:58:16 +02:00
Karl Tauber
af3e280d74 Table: slightly changed grid colors to make grid better recognizable (issue #514) 2022-04-19 23:00:01 +02:00
Karl Tauber
b57e4c0565 TabbedPane: selected tab underline color now changes depending on whether the focus is within the tab content (issue #398) 2022-04-19 22:19:47 +02:00
Karl Tauber
aca9931560 IntelliJ Themes: TabbedPane: use DefaultTabs.underlinedTabBackground and DefaultTabs.underlinedTabForeground from JSON themes for selected tab background/foreground 2022-04-19 16:50:27 +02:00
Karl Tauber
d09e166e4a SplitPane: fixed StackOverflowError caused by layout loop that may occur under special circumstances (issue #513) 2022-04-12 13:47:04 +02:00
Karl Tauber
68a7a60ff2 FileChooser: enabled full row selection for details view to fix alternate row coloring (issue #512) 2022-04-12 13:28:39 +02:00
Karl Tauber
f21261914b gradle: build target flatlaf-natives-windows only on Windows
(to fix build error on macOS)
2022-04-09 18:34:36 +02:00
Karl Tauber
7b11339fdc update to Gradle 7.4.2
./gradlew wrapper --gradle-version=7.4.2
2022-04-09 18:18:45 +02:00
Karl Tauber
081fd43d98 IntelliJ Themes: Component.accentColor UI property now has useful theme specific values (issue #507) 2022-04-07 18:07:09 +02:00
Karl Tauber
ef2eedfc7c Button: fixed icon layout and preferred width of default buttons that use bold font (issue #506) 2022-04-06 23:36:58 +02:00
Karl Tauber
0dba9265be ToolBar: fixed endless loop in focus navigation that may occur under special circumstances (issue #505) 2022-04-06 18:53:45 +02:00
Karl Tauber
301aae9b8e NativeLibrary: use System.mapLibraryName() instead of own implementation 2022-03-19 11:07:46 +01:00
Karl Tauber
c63f4e9662 Window decorations on Linux: limit window resizing/moving to left mouse button (issue #482) 2022-03-18 00:05:15 +01:00
Karl Tauber
47508dc6ac Native window decorations: updated DLLs (issue #502)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/2000978687
2022-03-17 22:48:01 +01:00
Karl Tauber
3a8879608a Native window decorations: fixed wrong window title character encoding used in Windows taskbar (issue #502) 2022-03-17 22:31:18 +01:00
Karl Tauber
b221889549 updated sigtest for FlatLaf 2.1
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-03-17 18:34:44 +01:00
Karl Tauber
c00d99b85f release 2.1 2022-03-17 12:52:49 +01:00
Karl Tauber
0bf87b753d TabbedPane: disable all items in "Show Hidden Tabs" popup menu if tabbed pane is disabled 2022-03-17 12:42:49 +01:00
Karl Tauber
53f2730064 TextArea, TextPane and EditorPane: no longer select all text when component is focused for the first time (issue #498; regression in FlatLaf 2.0) 2022-03-17 12:21:06 +01:00
Karl Tauber
d487c3b005 JIDE: use FlatLaf menu scrolling for JidePopupMenu (issue #225) 2022-03-14 12:23:52 +01:00
Karl Tauber
fef6ae7ff7 Menus: scroll large menus using mouse wheel or up/down arrows (issue #225) 2022-03-14 11:41:05 +01:00
Karl Tauber
f6b42754de Testing: FlatScreenshotsBackground: also make title bar white/black 2022-03-14 11:20:09 +01:00
Karl Tauber
2ae9bb381d Menus: fixed IllegalComponentStateException: component must be showing on the screen to determine its location when submenu is empty (PR #490; issue #247) 2022-03-14 00:23:53 +01:00
Karl Tauber
53bde84710 fixed compiler warning 2022-03-13 19:17:41 +01:00
Karl Tauber
d006ac27ff Merge PR #490: Menus: improved usability of submenus 2022-03-13 19:07:59 +01:00
Karl Tauber
c478d28b71 PasswordField: fixed reveal button appearance in IntelliJ themes (issue #494) 2022-03-13 18:39:12 +01:00
Karl Tauber
99f7b9ad84 ScrollBar:
- added `ScrollBar.minimumButtonSize` to specify minimum scroll arrow button size
- center and scale arrows in scroll up/down buttons

(issue #493)
2022-03-13 10:58:27 +01:00
Karl Tauber
ab58101ce3 SwingX: test JXStatusBar (issue #492) 2022-03-06 11:41:54 +01:00
Karl Tauber
d8f3682dc0 Menus: improved usability of submenus (issue #247) 2022-02-28 14:45:57 +01:00
Karl Tauber
1fec7ba553 Linux: support using custom window decorations (issue #482) 2022-02-26 23:07:16 +01:00
Karl Tauber
418f55f34e Window decorations: fixed window resizing on Linux (issue #482) 2022-02-26 14:00:16 +01:00
Karl Tauber
05d795b2ae Window decorations: use special fix for maximized bounds only on Windows (issue #469) 2022-02-26 13:33:55 +01:00
Karl Tauber
a365b750d9 core: minor code cleanup:
- add final where possible
- removed "public" from interface methods
- simplified conditional expressions
- removed unnecessary unboxing
- removed unused assignements
- removed redundant casts

(used IntelliJ IDEA 2021.3 inspections)
2022-02-25 21:49:15 +01:00
Karl Tauber
0aecfb565f IntelliJ Themes: removed duplicate key and trailing spaces 2022-02-25 21:12:48 +01:00
Karl Tauber
0cf4edd9e5 core: fixed typos/grammar in comments 2022-02-25 20:40:37 +01:00
Karl Tauber
dd7b7c6aef release 2.0.2 2022-02-25 16:58:22 +01:00
Karl Tauber
0bd677c46b FlatSVGIcon: changed logging when icon resource is not found from "severe" to "config" (issue #476) 2022-02-25 16:55:04 +01:00
Karl Tauber
1a131d5206 Merge PR #484: Fix NPE when painting icon on OS X top menu bar 2022-02-25 15:58:41 +01:00
Karl Tauber
016e515ae2 moved TestFlatIconNullComponent to other package and fixed file name (issue #483) 2022-02-25 15:52:40 +01:00
Karl Tauber
456ceb3c58 Merge PR #486: Request to add Ultorg to list of apps using FlatLAF 2022-02-25 15:27:58 +01:00
Eirik Bakke
2169be1b45 In README, add Ultorg to list of apps using FlatLAF. 2022-02-24 19:00:37 -05:00
Karl Tauber
49eb0b0201 Native window decorations: updated DLLs (issue #477)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/1866639721
2022-02-18 22:44:00 +01:00
Karl Tauber
2e222bcdea Native window decorations (Windows 10/11 only): fixed rendering artifacts on HiDPI screens when dragging window partly offscreen and back into screen bounds (issue #477) 2022-02-18 22:24:36 +01:00
Nicolas Roduit
c7fa475128 NPE when painting icon on OS X top menu bar #483 2022-02-18 18:30:24 +01:00
Karl Tauber
4174b065f3 repaint component when setting client property JComponent.outline (issue #480) 2022-02-16 23:53:21 +01:00
Karl Tauber
df6256d989 release 2.0.1 2022-01-25 18:46:29 +01:00
Karl Tauber
c27db56321 moved module-info.class from META-INF\versions\9\ to root folder of JARs (issue #466) 2022-01-25 16:59:31 +01:00
Karl Tauber
97bed8554a FlatSVGIcon: added copy constructor (issue #465) 2022-01-25 00:47:35 +01:00
Karl Tauber
751c0e16e9 ToolTip: fixed wrong tooltip location if component overrides JComponent.getToolTipLocation() and wants place tooltip under mouse location (issue #468) 2022-01-24 23:24:39 +01:00
Karl Tauber
936de60700 fixed memory leak in Panel, Separator and ToolBarSeparator (issue #471) 2022-01-24 18:28:38 +01:00
Karl Tauber
f6b64d48ec Merge PR #463: Updating MegaMek Suite Information 2022-01-19 17:27:12 +01:00
Justin Bowen
b043da7d4c Updating MegaMek Suite Information 2022-01-13 20:22:30 -05:00
Karl Tauber
7e47cc2443 updated sigtest for FlatLaf 2.0
(generated in clean workspace with gradle task `sigtestGenerate`)
2022-01-13 12:14:51 +01:00
Karl Tauber
b8b45f9442 Theme Editor: added to main readme 2022-01-11 16:17:17 +01:00
Karl Tauber
66337f9af6 release 2.0 2022-01-10 12:25:45 +01:00
Karl Tauber
54646706a0 README.md: added more documentation links 2022-01-10 12:13:30 +01:00
Karl Tauber
e8ee037d09 RootPane: uninstall background, foreground and font because not all Lafs set them 2022-01-10 11:58:49 +01:00
Karl Tauber
6d705e568a Extras: FlatInspector: fixed "NOT SET" for component foreground 2022-01-09 19:40:17 +01:00
Karl Tauber
e768791eba Native window decorations: updated hover and pressed colors of iconify/maximize/close buttons for Windows 11 style 2022-01-09 12:59:08 +01:00
Karl Tauber
2aff7c97f9 Demo: changed theme names from "Flat *" to "FlatLaf *" 2022-01-08 22:56:20 +01:00
Karl Tauber
ca6fc7773e README.md: added MooInfo to applications using FlatLaf (issue #460) 2022-01-06 16:05:28 +01:00
Karl Tauber
a1395a5490 TextField: leading/trailing components (PR #386):
- set cursor only on button and toolbar
- do not replace cursor on if already set (issue #461)
- updated client properties javadoc
2022-01-06 15:17:20 +01:00
Karl Tauber
61dd4d71d6 Theme Editor: added text field leading/trailing buttons/toolbar to preview 2022-01-05 19:43:06 +01:00
Karl Tauber
6beda53238 Button: if boolbar button is in leading/trailing component of a text field, increase toolbar button corner arc to match text field corner arc (issue #451) 2022-01-05 18:46:00 +01:00
Karl Tauber
941441d7e1 TextField: clear button has now component name TextField.clearButton
PasswordField: reveals button has now component name `PasswordField.revealButton` and additional style class `revealButton` (issue #173)
SwingUtils: added `getComponentByName()` for easy getting clear or reveal buttons
2022-01-05 18:32:05 +01:00
Karl Tauber
d10ea41b47 GitHub Actions:
- build on Java 17
- run natives.yml when Gradle version changed
2022-01-04 18:13:12 +01:00
Karl Tauber
9458870f70 update to Gradle 7.3.3
./gradlew wrapper --gradle-version=7.3.3
2022-01-04 16:06:37 +01:00
Karl Tauber
095794bbd1 GitHub Actions:
- use actions/setup-java@v2 (provides caching gradle files)
- use pre-installed Java 8 and 11
- no longer build on Java 9 and 14
- not yet build on Java 17 because used Gradle version 6.8.2 does not support it
  can not yet upgrade to Gradle 7.x because nokee plugins (for C++) does not support it
2022-01-04 12:26:13 +01:00
Karl Tauber
c7fc0aa936 Demo: removed unnecessary setting component name 2022-01-03 23:56:26 +01:00
Karl Tauber
a8d98ced61 Theme Editor:
- support multi-line styles in overlay color preview
- minor preview improvements
2022-01-03 21:51:02 +01:00
Karl Tauber
831b3d851a ColorFunctions: javadoc added; do not mix colors if they are equal 2022-01-03 19:04:28 +01:00
Karl Tauber
8c891c7016 UIDefaultsDump: output base color used for resolving derived color 2022-01-03 18:46:56 +01:00
Karl Tauber
5c4706cbc9 Merge remote-tracking branch 'origin/main' into main 2022-01-03 17:05:37 +01:00
Karl Tauber
db66a6c4f0 Unit tests: re-enable testing scaled UI
avoid using UI_SCALE_ENABLED = false because if this test is executed first, class UIScale does not register listeners to UIManager, which prevents updating user scale factor on font changes and testing scaled UI fails
2022-01-03 16:52:43 +01:00
Karl Tauber
0517e4fc02 Native window decorations: updated maximize and restore icons for Windows 11 style
(requires Java 8u321, 11.0.14, 17.0.2 or 18+)
2021-12-31 17:57:02 +01:00
Karl Tauber
dd7fa4a87d Slider: fixed/improved focused indicator color when changing accent color (PR #375) 2021-12-31 15:33:27 +01:00
Karl Tauber
e5956900ea FileChooser: use Windows system icons in Java 18+ 32bit (issue #403)
only Java 17 32bit does not use Windows system icons because of:
https://bugs.openjdk.java.net/browse/JDK-8277299
2021-12-31 12:59:22 +01:00
Karl Tauber
3755593c14 Windows 11: Native window decorations: do not paint top window border because Windows 11 now paints it (issue #431)
(requires Java 8u321, 11.0.14, 17.0.2 or 18+)
2021-12-31 10:35:19 +01:00
Karl Tauber
8ddd3b6d68 Native window decorations: fixed blurry iconify/maximize/close button hover rectangles at 125%, 150% or 175% scaling (issue #431) 2021-12-31 10:28:34 +01:00
Karl Tauber
840083940d Use FlatLaf native window decorations by default when running in
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki)
(instead of using JetBrains custom decorations). System variable
`flatlaf.useJetBrainsCustomDecorations` is now `false` by default (was `true` in FlatLaf 1.x). (issue #454)
2021-12-30 12:04:22 +01:00
Karl Tauber
0cdfd29ecf Extras: fixed concurrent loading of SVG icons on multiple threads (issue #459) 2021-12-30 11:24:48 +01:00
Karl Tauber
bb32c727b6 TextField:
- improved hover/pressed/selected colors of leading/trailing buttons
  (e.g. "reveal" button in password field) (issue #452)
- clear button no longer paints over round border (issue #451)
2021-12-28 20:24:43 +01:00
Karl Tauber
f978c04750 PasswordField: reveal button did not show password if JPasswordField.setEchoChar() was invoked from application (PR #442; issue #173) 2021-12-27 22:53:07 +01:00
Karl Tauber
b6a504e121 Theme Editor: fixed "Pick Color from Screen" on macOS
On macOS Big Sur (and later), to pick colors outside of theme editor window it is necessary to give "Screen Recording" permission to the application (or to the IDE started from).
For the arrow keys, "Accessibility" permission is necessary.
See "System Preferences > Security & Privacy > Privacy".
2021-12-27 21:49:49 +01:00
Karl Tauber
5fae367fab PasswordField: preserve reveal button state when switching theme (issue #173) 2021-12-27 20:07:27 +01:00
Karl Tauber
6e807f44b2 Search/clear icons: fixed colors for some IntelliJ themes (e.g. "Dark Flat") that use translucent color for Actions.GreyInline 2021-12-27 18:22:31 +01:00
Karl Tauber
53ebed7f89 CHANGELOG.md: added changes for #453 and #456 2021-12-26 22:44:55 +01:00
Karl Tauber
1c10c41808 Theme Editor: Preview: added style classes to all preview components (syntax flatlaf-preview-<componenttype>) to allow experimenting with styles in editor 2021-12-26 21:23:42 +01:00
Karl Tauber
01170b669b Theme Editor: Preview:
- reworked disabling to avoid disabling internal components
- do not disable labels
- removed special code for JTextPane and JEditorPane, which seems to be no longer needed
2021-12-26 20:30:36 +01:00
Karl Tauber
b56215e5e3 Demo: moved leading/trailing icons (on text field) code from end of initComponents() to constructor (so that it easier to find) 2021-12-26 19:57:15 +01:00
Karl Tauber
221e801561 support relative path in system property flatlaf.nativeLibraryPath (PR #453) 2021-12-23 22:19:29 +01:00
Karl Tauber
90edbe23d7 Merge PR #453: Add a system property to load pre-extracted native libraries from a directory 2021-12-23 22:10:11 +01:00
Karl Tauber
5b16a814c8 fixed "endless recursion in font" exception in FlatLaf$ActiveFont.createValue() if UIManager.getFont() is invoked from multiple threads (issue #456) 2021-12-23 21:16:07 +01:00
Ingo Kegel
ef01721464 Added the system property flatlaf.nativeLibraryPath to specify a directory where the native libraries have been extracted.
Avoiding extraction at runtime to the temporary directory is useful in order to prevent anti-virus software from blocking the library loading.
2021-12-21 11:13:40 +01:00
Karl Tauber
efd8cf8236 release 2.0-rc1 2021-12-18 14:09:39 +01:00
Karl Tauber
8dbfc6d5d6 README.md: dark screenshots re-updated for v2 (using black background to avoid that light background shines through window border) 2021-12-16 14:53:29 +01:00
Karl Tauber
ef343397d4 README.md: screenshots updated for v2 2021-12-16 11:42:25 +01:00
Karl Tauber
cae02d31db IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2021-12-16 00:51:03 +01:00
Karl Tauber
96c78cbc16 ComboBox: fixed wrong popup border when using in theme editor preview 2021-12-15 23:39:16 +01:00
Karl Tauber
f8c769644d SwingX: fixed NullPointerException in FlatCaret when using org.jdesktop.swingx.prompt.PromptSupport.setPrompt() on a text field and then switching theme 2021-12-15 11:37:35 +01:00
Karl Tauber
0bd1e413b0 Native window decorations: fixed UnsatisfiedLinkError on Windows 11 for ARM processors (issue #443) 2021-12-15 00:49:08 +01:00
Karl Tauber
07c9ad484a MenuBar: do not fill background if non-opaque and having custom background color (issue #409) 2021-12-15 00:29:37 +01:00
Karl Tauber
5fd5b1206e InternalFrame: fill background to avoid that parent may shine through internal frame if it contains non-opaque components (better fix for issue #274) 2021-12-15 00:27:20 +01:00
Karl Tauber
8e107647bd Merge PR #442: TextField clear button and PasswordField reveal password button 2021-12-14 11:35:44 +01:00
Karl Tauber
12b7389376 TextField: added "clear" callback 2021-12-14 11:28:03 +01:00
Karl Tauber
45332c8126 TextField: added "clear" button to theme editor find/replace and to UI defaults inspector 2021-12-14 10:42:07 +01:00
Karl Tauber
02a9d4e31d PasswordField: support "reveal" button to show password (issue #173) 2021-12-14 01:09:21 +01:00
Karl Tauber
a4377e81cb TextField: support "clear" (or "cancel") button to empty text field 2021-12-13 22:10:21 +01:00
Karl Tauber
8d2ed3faf6 Merge PR #386: TextField: leading and trailing components 2021-12-13 17:50:38 +01:00
Karl Tauber
e7dacb8fef Unit tests: (temporary) disable testing scaled UI until it is clear why it fails now on GitHub Actions (but worked yesterday) 2021-12-13 17:45:46 +01:00
Karl Tauber
60e2ffac5f Demo: added text field leading/trailing components example 2021-12-13 17:27:36 +01:00
Karl Tauber
73c37b2018 Search/clear icons: option to ignore button state (hover/pressed) for usage where button background changes color on hover/pressed 2021-12-13 00:47:23 +01:00
Karl Tauber
1b3cc223da TextField: added styles for buttons and toolbars in leading and trailing components (smaller margins/insets, better hover/pressed/selected colors) 2021-12-12 18:43:29 +01:00
Karl Tauber
51be7ad832 TextField: require JComponent for leading and trailing components (to allow setting a style class client property) 2021-12-12 00:50:56 +01:00
Karl Tauber
f93d035e4e TextField: support leading and trailing components 2021-12-11 14:29:56 +01:00
Karl Tauber
a3885d7a48 Theme Editor: Preview: minor tweeks 2021-12-11 14:13:56 +01:00
Karl Tauber
bbf2331766 ComboBox and Spinner: made buttonBackground optional 2021-12-11 12:19:10 +01:00
Karl Tauber
2164bd363b Styling: MenuItem: support styling acceleratorFont
Theme Editor:
- support font keys in auto-completion
- ignore Menu.acceleratorFont, CheckBoxMenuItem.acceleratorFont and RadioButtonMenuItem.acceleratorFont because they are never used (keep UI values for backward compatibility)
2021-12-11 11:39:31 +01:00
Karl Tauber
6205e18c45 Styling: FlatMenuItemArrowIcon no longer extends FlatMenuArrowIcon because it does not paint anything and therefore should not inherit styling properties from FlatMenuArrowIcon 2021-12-11 01:06:23 +01:00
Karl Tauber
959b3e46fa Styling: fixes and added missing @since tags 2021-12-11 00:50:05 +01:00
Karl Tauber
09d8d09aad Theme Editor: Preview: support usage of variables in styles 2021-12-10 23:14:19 +01:00
Karl Tauber
70336e31c7 Theme Editor: update RSyntaxTextArea from 3.1.3 to 3.1.4 2021-12-10 22:44:24 +01:00
Karl Tauber
600e0f3d67 Panel: support painting background with rounded corners (issue #367)
FlatLineBorder: support rounded corners
2021-12-10 22:40:17 +01:00
Karl Tauber
023e356057 MenuItem: vertically align text if icons have different widths (issue #437) 2021-12-08 11:45:17 +01:00
Karl Tauber
27786ec00a UI defaults dumps updated for commits 269eb0ba29, c9a38f0a13 and dd3ffc64b9 2021-12-08 00:53:21 +01:00
Karl Tauber
e52e72c5a8 Merge PR #415: MenuItem: paint the selected icon when the item is selected 2021-12-07 22:52:25 +01:00
Karl Tauber
802dd08ce7 MenuItem: use isArmedOrSelected() instead of MenuSelectionManager to detect selected item 2021-12-07 22:46:39 +01:00
Karl Tauber
568ec5a1a2 added Jailer version number that first uses FlatLaf; reformatting 2021-12-07 19:46:03 +01:00
Karl Tauber
035d196392 Merge PR #440: Added Jailer to Applications using FlatLaf 2021-12-07 19:45:16 +01:00
Karl Tauber
dd3ffc64b9 SwingX: improved dark/light colors "column control" icon for JXTable (issue #434) 2021-12-07 18:10:09 +01:00
Karl Tauber
c9a38f0a13 SwingX: new "column control" icon for JXTable that scales and uses antialiasing (issue #434) 2021-12-07 17:45:25 +01:00
Wisser
78461a9d5a added Jailer to Applications using FlatLaf 2021-12-07 17:44:00 +01:00
Wisser
79b8fb910a added Jailer to Applications using FlatLaf 2021-12-07 17:39:52 +01:00
Karl Tauber
405e3df1f0 Merge remote-tracking branch 'origin/develop-1.x' into main
# Conflicts:
#	CHANGELOG.md
2021-12-07 16:05:31 +01:00
Karl Tauber
f7126d154f Window decorations: left indent was lost when icon is hidden and switching Laf (regression in commit 005c9f471e6bc3ea5d708a08e8fb0b087b2c3382; PR #429) 2021-12-07 16:00:38 +01:00
Karl Tauber
d8df8c9631 release 1.6.5 2021-12-07 15:05:13 +01:00
Karl Tauber
37b35f9063 updated svgSalamander to version 1.1.3 2021-12-07 14:34:39 +01:00
Karl Tauber
f61a7288eb fixed updating (embedded) menu bar layout when window is narrow and changing TitlePane.menuBarEmbedded 2021-12-07 11:52:33 +01:00
Karl Tauber
47a1122f04 Window decorations: do not exit application with UnsatisfiedLinkError in case that FlatLaf DLL cannot be executed because of restrictions on temporary directory (issue #436) 2021-12-07 00:39:28 +01:00
Karl Tauber
e1bfabbce5 macOS: fixed NullPointerException when using AWT component java.awt.Choice (issue #439) 2021-12-06 22:45:24 +01:00
Karl Tauber
9708fec0e0 GitHub Actions: produce snapshots only on develop-* branches; change version to 1.6.5-SNAPSHOT 2021-12-06 17:27:49 +01:00
Karl Tauber
7f4efaf0a3 ComboBox: fixed occasional StackOverflowError when modifying combo box not on AWT thread (issue #432) 2021-12-06 17:15:37 +01:00
Karl Tauber
269eb0ba29 MenuItem: changed accelerator delimiter from - to + (Windows and Linux) 2021-12-04 12:15:53 +01:00
Karl Tauber
428c6b7813 Styling: comment fixes 2021-11-30 18:37:03 +01:00
Karl Tauber
db2452a4ec Styling: support Panel 2021-11-30 18:36:48 +01:00
Karl Tauber
7dac3825d7 Linux: Fixed font problems when running on Oracle Java 8 (OpenJDK 8 is not affected):
- oversized text if system font is "Inter" (issue #427)
- missing text if system font is "Cantarell" (on Fedora)
2021-11-24 10:47:58 +01:00
Karl Tauber
7c99872278 Typography:
- fixed semibold font on Ubuntu
- use font "Montserrat SemiBold" on Fedora

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

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

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

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

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

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

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

(issue #233)
2021-09-26 23:56:56 +02:00
Karl Tauber
a4ea88f4be UIDefaultsLoader: added if() function (inspired by Less CSS) 2021-09-26 23:54:06 +02:00
Karl Tauber
36d5747fbf Accent color: changed javadoc since version from 1.6 to 2 2021-09-25 23:53:57 +02:00
Karl Tauber
3d8c535ffa Styling: catch runtime exceptions while applying styles (and log them) to avoid that wrong/invalid styles could result in "damaged" UI 2021-09-25 19:34:46 +02:00
Karl Tauber
1c067d0284 behaviour --> behavior 2021-09-25 18:32:11 +02:00
Karl Tauber
b6be0462a5 Styling: basic support for "classes" (similar to CSS classes) using client property FlatLaf.styleClass 2021-09-25 14:34:21 +02:00
Karl Tauber
cce91ea16d changed multi-line javadoc since tags to single-line 2021-09-25 13:27:26 +02:00
Karl Tauber
d756041b06 Styling: fixed "Illegal reflective access" warning on stdout for BasicMenuItemUI fields when running on Java 9+ 2021-09-22 23:40:10 +02:00
Karl Tauber
2d0eb0a05b Styling: fixed build error on GitHub Actions 2021-09-22 23:15:00 +02:00
Karl Tauber
02f3239669 ComboBox (not editable): fixed background painted outside of border if round edges are enabled (similar to issue #382; regression since fixing #330 in FlatLaf 1.4) 2021-09-16 22:55:05 +02:00
Karl Tauber
14a9240c45 FlatUIUtils: joined the 3 component painting methods (for focus border, border and background) into a single method paintOutlinedComponent()
- this allows optimized painting if focus color and border color are equal
- avoids duplicate code
- support focusWidthFraction for future animations
2021-09-16 18:09:32 +02:00
Karl Tauber
c659638fb4 Styling: support styling for recently merged PR #378 2021-09-15 23:43:41 +02:00
Karl Tauber
fd15b63044 Merge PR #378: TextField: leading and trailing icons
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java
2021-09-15 23:32:57 +02:00
Karl Tauber
263e6c34b5 Merge PR #341: Styling individual components 2021-09-15 20:00:06 +02:00
Karl Tauber
eb62a3dc17 UI defaults inspector: avoid NPE if DerivedColorKeys.properties missing 2021-09-15 19:44:09 +02:00
Karl Tauber
161ee090a8 Tree: Fixed editing cell issue with custom cell renderer and cell editor that use same component for rendering and editing (fixes #385) 2021-09-15 19:39:44 +02:00
Karl Tauber
560ec437b9 Styling: avoid duplicate applying styles to buttons, labels and separators (which use shared UI delegates) 2021-09-15 10:57:52 +02:00
Karl Tauber
ccd0597b35 Styling: support styling for recently merged changes 2021-09-14 22:43:44 +02:00
Karl Tauber
c5c0a3768a Merge remote-tracking branch 'origin/main' into styling 2021-09-14 19:02:21 +02:00
Karl Tauber
5aa2d24d58 added sigtest to flatlaf-core subproject to check for incompatible API changes in packages com.formdev.flatlaf and com.formdev.flatlaf.util
added FlatLaf 1.6 API signature (generated in clean workspace with gradle task `sigtestGenerate`)
2021-09-14 18:14:21 +02:00
Karl Tauber
ae28c595f9 release 1.6 2021-09-14 15:00:29 +02:00
Karl Tauber
1d08ddda60 InternalFrame: added missing since 1.6 2021-09-14 14:56:21 +02:00
Karl Tauber
578379fd00 Table and TableHeader: renamed UI keys Table[Header].showLastVerticalLine to Table[Header].showTrailingVerticalLine (issue #332) 2021-09-14 14:31:17 +02:00
Karl Tauber
7c9f550d4c ComboBox: fixed popup location if shown above of combo box (Java 8 only) 2021-09-14 14:16:53 +02:00
Karl Tauber
84d4510d70 ComboBox: fixed popup border painting on HiDPI screens (e.g. at 150% scaling) 2021-09-14 12:46:51 +02:00
Karl Tauber
fa194ec258 TableHeader: fixed missing trailing vertical separator line if used in upper left corner of scroll pane (issue #332) 2021-09-14 00:52:59 +02:00
Karl Tauber
fd56de403d Slider: fixed calculation of baseline (see also PR #214) 2021-09-13 22:05:48 +02:00
Karl Tauber
85fde46504 Testing: FlatSingleComponentTest: revalidate and repaint when changing component orientation using Alt+R 2021-09-13 10:11:09 +02:00
Karl Tauber
b283178979 Spinner: fixed painting of border corners on left side (issue #382; regression since FlatLaf 1.4)
ComboBox (editable): fixed wrong border of internal text field under special circumstances
2021-09-10 16:37:07 +02:00
Karl Tauber
e0dddfceba Styling: Menu: support top-level underline selection 2021-09-08 14:55:41 +02:00
Karl Tauber
bddef38a7c Theme Editor: preview: added "editable" check box for text components 2021-09-08 00:22:02 +02:00
Karl Tauber
b5f2f77944 Theme Editor: layout of "All" preview tab improvements:
- right align "enabled" and "focused" check boxes
- two columns for controls
- removed help button
- JTextArea, JEditorPane and JTextPane in single line
- reduced some vertical gaps
2021-09-08 00:10:53 +02:00
Karl Tauber
fca0718ed0 Native window decorations: fixed unwanted uninstall of native window border when using JInternalFrame (which has its own JRootPane) and invoking updateUI() on internal frame (e.g. in preview of FlatLaf Theme Editor) 2021-09-07 17:55:45 +02:00
Karl Tauber
0d44ade6ea Theme Editor: preview improvements:
- remember state of "enabled", "focused" and "buttonType" and sync it with all editors
- added "_" button near "JMenuBar" label to test menu underline selection
2021-09-07 14:31:09 +02:00
Karl Tauber
08ca2aa266 Styling:
- support references in color functions
- added test for using color functions in styling
2021-09-06 22:53:04 +02:00
Karl Tauber
fe15758e59 Styling: updated "since" javadoc tags 2021-09-06 15:39:19 +02:00
Karl Tauber
674efae184 Styling: Extras: add style getters and setters to component classes 2021-09-06 15:23:15 +02:00
Karl Tauber
4a65bc88d5 Theme Editor: highlight selected editor tab 2021-09-05 23:25:16 +02:00
Karl Tauber
a8f3d59729 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java
2021-09-05 23:12:38 +02:00
Karl Tauber
6018f83a22 Theme Editor: center some labels and help buttons in preview 2021-09-05 22:15:49 +02:00
Karl Tauber
0b6247851b Theme Editor: fixed preview of focused list and table selection 2021-09-05 22:05:21 +02:00
Karl Tauber
8640dee053 Theme Editor: preview improvements:
- added "focused" checkbox to "All" tab to preview focused components
- added "button type" selector to "Buttons" tab
- added "unfocused"/"focused" header labels to "Buttons" and "Switches" tabs
- use local variables instead of fields where possible
2021-09-05 21:08:36 +02:00
Karl Tauber
824db2e3bd Table and TableHeader: added UI defaults to show last vertical line (issue #332) 2021-09-05 11:51:28 +02:00
Karl Tauber
c2c79c4676 Theme Editor:
- remember last used preview tab
- sync selected preview tab with all editors
2021-09-05 11:13:29 +02:00
Karl Tauber
4795fe5687 Theme Editor:
- added preview of buttons, checkboxes, radiobuttons, etc in various states (hover, pressed, focused, selected, disabled)
  (copied from `FlatComponentStateTest`)
- moved components preview panel from FlatThemePreview.jfd` to FlatThemePreviewAll.jfd`
- added tabs at top of preview area
2021-09-05 10:55:51 +02:00
Karl Tauber
d508f339c1 TableHeader: do not show resize cursor for last column if resizing last column is not possible because auto resize mode of table is not off (issue #332) 2021-09-04 18:20:21 +02:00
Karl Tauber
c7054537e7 Testing: FlatSingleComponentTest: support changing component orientation using Alt+R 2021-09-04 13:28:02 +02:00
Karl Tauber
b98b904023 added missing UI defaults to javadoc 2021-09-04 13:24:19 +02:00
Karl Tauber
253df9325d Extras: FlatAnimatedLafChange: made animated Laf change transition smoother 2021-09-04 13:19:12 +02:00
Karl Tauber
78a9cc1d0c Theme Editor: fixed: editor was not focused after startup or when switching directory 2021-09-03 17:32:59 +02:00
Karl Tauber
b25fcc3381 OptionPane: fixed rendering of longer HTML text if it is passed as StringBuilder, StringBuffer, or any other object that returns HTML text in method toString() (similar to issue #12) 2021-09-03 11:26:30 +02:00
Karl Tauber
a2c0df5891 TextField: consider widths of leading and trailing icons for minimum/preferred text field size 2021-09-03 11:01:44 +02:00
Karl Tauber
dc33c26960 TextField: support leading and trailing icons (issue #368) 2021-09-02 17:45:33 +02:00
Karl Tauber
51d7bc2c37 TextField, FormattedTextField, PasswordField and ComboBox: fixed alignment of placeholder text in right-to-left component orientation 2021-09-02 16:18:53 +02:00
Karl Tauber
cdbdccf1ad Styling: support styling any component property that has public getter and setter methods 2021-09-01 13:32:31 +02:00
Karl Tauber
397c369114 Styling: renamed class FlatStyleSupport to FlatStylingSupport 2021-09-01 00:21:47 +02:00
Karl Tauber
6f9bbb184a Styling: support specifying explicit value type for parsing CSS values (for future use) 2021-08-31 23:53:12 +02:00
Karl Tauber
b12c818862 Styling: support styling for recently merged changes 2021-08-31 16:12:03 +02:00
Karl Tauber
9118dcf925 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java
2021-08-31 15:41:46 +02:00
Karl Tauber
d333d0c9e4 Accent color:
- Demo: added accent color switching to toolbar
- added `FlatLaf.setGlobalExtraDefaults()` for easy setting accent color at runtime

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

(issue #233)
2021-08-30 23:06:06 +02:00
Karl Tauber
dd14843f2e Accent color: reduced number of individual blueish accent colors with color functions (issue #233) 2021-08-30 23:06:06 +02:00
Karl Tauber
09a18b2305 Theme Editor:
- highlight opened directory in history combobox list
- support removing directories from history ('x' button in combobox list)
2021-08-29 18:39:40 +02:00
Karl Tauber
31f2feee2e Theme Editor:
- special renderer for directory history combobox list that dimes path parts
- disable menu items and buttons if no directory or editor is open
- set editor font size when opening new editor if increased/decreased
2021-08-29 16:06:50 +02:00
Karl Tauber
218bb62bfd Theme Editor:
- fixed increasing layout and wrong top border color of find/replace bar when switching Laf
- fixed duplicate keys in reference auto-completion
2021-08-29 14:28:11 +02:00
Karl Tauber
694c2ad767 Theme Editor: preview improvements:
- added JSplitPane (contains JList and JTree)
- sort first column in JTable to have preview of sort direction arrow
- fixed background color of JEditorPane and JTextPane when enabling/disabling
- keep first JMenu enabled, but disable the menu items
2021-08-29 11:44:38 +02:00
Karl Tauber
97943fcd38 Theme Editor: changed main class to com.formdev.flatlaf.themeeditor.FlatThemeEditor 2021-08-28 17:38:51 +02:00
Karl Tauber
77f33467d2 Theme Editor: fixed endless look in "replace all" when replacing e.g. "a" with "aa" 2021-08-28 17:37:02 +02:00
Karl Tauber
651454170d Theme Editor:
- fixed duplicate lines action if selection includes line separator at the end
- preview: do not disable internal components of JInternalFrame
2021-08-28 14:30:11 +02:00
Karl Tauber
7ca48bd136 Theme Editor:
- support lazy values and icon colors in overlay color preview
- support icon colors in preview
- support icon colors in reference auto-completion
- support changing preview theme when editing FlatLaf.properties via adding `@baseTheme = dark|darcula|intellij`
2021-08-28 14:18:11 +02:00
Karl Tauber
968e508bb5 Theme Editor:
- change border color of find field to red if noting found/matches
- update search match highlighting after reloading file on external change
2021-08-27 22:53:04 +02:00
Karl Tauber
a6d318a197 Theme Editor: fixed missing keys (e.g. Button.foreground) in reference auto-completion, which are defined as wildcards or have some prefix 2021-08-27 22:32:59 +02:00
Karl Tauber
cd20f4086b Theme Editor: fixed preview of focused button in FlatDarkLaf (and probably other null value related issues) 2021-08-27 18:32:42 +02:00
Karl Tauber
ebd5905947 Theme Editor: preview improvements:
- added JToolBar, JProgressBar with string painted, JDesktopPane and JInternalFrame
- placed text components side-by-side to save vertical space
- changed button texts and removed tooltips
- fixed painting of table cell focus border
- added missing add.svg
2021-08-27 16:10:10 +02:00
Karl Tauber
817a3c62bb Theme Editor: preview improvements:
- fixed table header borders (runWithUIDefaultsGetter() in paint())
- cache lazy values
- use runWithUIDefaultsGetter() in layout(), validateTree() and paint()
2021-08-24 22:32:48 +02:00
Karl Tauber
f8f58400fe Theme Editor: added list, tree and table to preview 2021-08-24 21:49:09 +02:00
Karl Tauber
ef06840649 Theme Editor: basic README.md with shapshot download link 2021-08-24 18:38:21 +02:00
Karl Tauber
b17c14d62e Theme Editor: use UTF-8 encoding to load properties files 2021-08-24 17:50:03 +02:00
Karl Tauber
19dba94064 IntelliJ Themes: removed deprecated install() methods
but keep them in the flatlaf-core for API compatibility in NetBeans plugin
2021-08-24 17:38:19 +02:00
Karl Tauber
601e24f9e7 Theme Editor: fixed "..." in menus 2021-08-24 16:52:45 +02:00
Karl Tauber
c7f323ee13 Theme Editor: added window icon 2021-08-24 16:49:55 +02:00
Karl Tauber
e4522f3af4 Theme Editor: added "About" dialog
Demo: updated "About" dialog
2021-08-24 16:40:17 +02:00
Karl Tauber
79af461a5b Theme Editor: instead of creating empty FlatLightLaf.properties and FlatDarkLaf.properties, add some how-to-use description to those files 2021-08-24 15:38:15 +02:00
Karl Tauber
2e8e07faf6 Theme Editor: auto-completion for keys improved:
- now also contains variables and keys defined in current and base themes
- appended " = "
- removes some unsupported keys (fonts and input maps)
2021-08-24 15:13:30 +02:00
Karl Tauber
ecdb000818 Theme Editor: avoid changing editor text (and adding item to undo history) when simply pressing OK button in "Insert Color" dialog without changing anything 2021-08-23 23:56:27 +02:00
Karl Tauber
999fd0d4da Theme Editor: generate .java file when creating new theme 2021-08-23 23:52:22 +02:00
Karl Tauber
705dd9558f Theme Editor:
- added "New Properties File" action to "File" menu
- added "+" button to tabbed pane
- ask to create .properties files when opening a directory that does not contain .properties files
- fixed Darcula baseTheme/preview
2021-08-23 23:02:41 +02:00
Karl Tauber
97ca866ffa OptionPane: fixed OptionPane.sameSizeButtons, which did not work as expected when setting to false 2021-08-23 16:53:23 +02:00
Karl Tauber
543b977db7 updated SVG icons from IntelliJ IDEA Community Edition to latest versions that include license header; added license header to SVGs where it were missing 2021-08-23 15:57:26 +02:00
Karl Tauber
ebb8a6d025 Theme Editor: ignore custom UI delegates in preview 2021-08-23 15:00:28 +02:00
Karl Tauber
506543281e Theme Editor: "Insert Color" dialog now immediately updates editor with new color, which updates "live" preview; also save/restore location of dialog 2021-08-23 14:41:53 +02:00
Karl Tauber
60322be22a Theme Editor: added "Insert Color" action to "Edit" menu that opens a color chooser dialog and inserts/edits a color at caret position 2021-08-23 14:06:18 +02:00
Karl Tauber
e1f30f24a8 Theme Editor: to toggle comment, add Ctrl+7 for German keyboards where Ctrl+/ does not work 2021-08-22 17:34:18 +02:00
Karl Tauber
1759f6b25c Theme Editor: increment/decrement color parts (red, green, blue or alpha) at caret using Ctrl+UP/Ctrl+DOWN 2021-08-22 17:26:29 +02:00
Karl Tauber
6578f25cc9 GitHub Actions: upload theme editor 2021-08-22 16:10:29 +02:00
Karl Tauber
8c26e0323f Theme Editor: increment/decrement numbers at caret using Ctrl+UP/Ctrl+DOWN 2021-08-22 15:33:29 +02:00
Karl Tauber
a5575894ab Theme Editor:
- update preview after 300ms (was 500ms)
- added separator between editor and preview
2021-08-22 14:26:18 +02:00
Karl Tauber
357823a027 Theme Editor: added "Show HSL/RGB colors" menu items to "View" menu to control display of color models in overlay color preview 2021-08-22 12:34:48 +02:00
Karl Tauber
a6d3f6b3eb Theme Editor: added menu components to preview 2021-08-22 11:19:47 +02:00
Karl Tauber
ae4c69e75c Theme Editor: fixed preview when switching Laf 2021-08-22 11:13:05 +02:00
Karl Tauber
31cadc532b Theme Editor:
- F12 now activates editor if focus is in preview or in find/replace
- changed accelerators for light/dark themes from F11/F12 to Alt+F1/F2
2021-08-22 00:33:34 +02:00
Karl Tauber
6e8443473b Theme Editor: special order for tabs: first core themes, then other themes 2021-08-21 23:27:43 +02:00
Karl Tauber
cca4ab3cd8 Theme Editor: fixed tabbed pane "more tabs" popup in preview 2021-08-21 18:24:57 +02:00
Karl Tauber
dab0ee3306 Theme Editor: added "live" preview 2021-08-21 18:09:59 +02:00
Karl Tauber
c6d1ed91a7 Menus: fixed missing modifiers flags in ActionEvent (issue #371; regression since FlatLaf 1.3) 2021-08-13 20:32:07 +02:00
Karl Tauber
a613a244f4 InternalFrame: double-click on icon in internal frame title bar now closes the internal frame (issue #374) 2021-08-13 19:11:03 +02:00
Karl Tauber
268fe15004 Tree: improved support for JTree.getPathForLocation(int x, int y) in wide selection (issue #373)
this is experimental and disabled by default; enable with:
`UIManager.put( "FlatLaf.experimental.tree.widePathForLocation", true );`
2021-08-13 00:19:34 +02:00
Karl Tauber
7bc9be686f FlatLaf: use larger initial capacity for UI defaults table to avoid resizing hash table and to save some memory 2021-08-13 00:13:54 +02:00
Karl Tauber
751919ec5a Theme Editor: find/replace improvements:
- while typing in find field, select match near caret and scroll to it
- PageUp/PageDown keys scroll editor if find/replace fields have focus
2021-08-12 22:05:08 +02:00
Karl Tauber
da913b426e Theme Editor: paint current line highlight always in the line where the caret is, which makes it easier to locate current match when using find/replace
RSyntaxTextArea paints line highlight only if selection is empty (caret dot == mark)
2021-08-11 23:59:33 +02:00
Karl Tauber
d8ef99cd8f Theme Editor: support resolving properties that use wildcards 2021-08-11 23:24:55 +02:00
Karl Tauber
d08a6d7dd3 Theme Editor: support loading/resolving base properties from core themes 2021-08-11 22:38:35 +02:00
Karl Tauber
896e9bca8e Theme Editor: re-implemented support loading/resolving base properties from other editors in opened directory 2021-08-11 21:53:10 +02:00
Karl Tauber
1df9597bb1 Theme Editor: support Ctrl+PageDown/PageUp to switch to next/previous editor 2021-08-09 10:19:24 +02:00
Karl Tauber
eaf55f2099 Theme Editor: store unscaled window bounds in preferences so that using Java 8 or 9+ results in same size on screen 2021-08-08 19:15:10 +02:00
Karl Tauber
5018a1f9eb Theme Editor: increase/decrease editor font size 2021-08-08 19:14:46 +02:00
Karl Tauber
71ba8f55a7 Theme Editor:
- support dark theme (menu "View > Dark Laf")
- moved RSyntaxTextArea theme config from XML to properties files
- bracket matching enabled
- highlight selected tab background
2021-08-08 17:43:59 +02:00
Karl Tauber
b65db707ed Theme Editor: auto-completion improved: support auto-activate after spaces, tabs or ',' 2021-08-07 14:59:20 +02:00
Karl Tauber
ed62266a43 Theme Editor: always select all text in find/replace text fields 2021-08-07 12:55:25 +02:00
Karl Tauber
49913b7dad Theme Editor: duplicate lines with Ctrl+Alt+Up or Ctrl+Alt+Down 2021-08-07 12:51:00 +02:00
Karl Tauber
3eeeb9e00b Theme Editor: update RSyntaxTextArea from 3.1.2 to 3.1.3 2021-08-07 11:18:00 +02:00
Karl Tauber
bfb1642284 UIDefaultsDump: dump HSL color values 2021-08-06 10:45:57 +02:00
Karl Tauber
0544a605c3 UIDefaultsLoader: added tint() and shade() color functions (inspired by Less CSS) 2021-08-05 23:37:42 +02:00
Karl Tauber
3f5acda132 UI defaults inspector: round HSL values (as also done in theme editor) 2021-08-05 18:49:03 +02:00
Karl Tauber
02b1ba2926 UIDefaultsLoader: added mix() color function (inspired by Less CSS) 2021-08-05 18:19:42 +02:00
Karl Tauber
7f7f9e3c7c UIDefaultsLoader: added changeHue(), changeSaturation(), changeLightness() and changeAlpha() color functions (inspired by Sass CSS color.change() function) 2021-08-05 17:08:20 +02:00
Karl Tauber
6fcee03752 release 1.5 2021-08-04 15:13:58 +02:00
Karl Tauber
5782ceeb5d README.md: added descriptions to addons 2021-08-04 14:27:57 +02:00
Karl Tauber
f752db5892 FileChooser: fixed missing (localized) texts when FlatLaf is loaded in special classloader
(e.g. plugin system in Apache NetBeans)

https://issues.apache.org/jira/browse/NETBEANS-5865
2021-08-04 11:15:18 +02:00
Karl Tauber
bce58bc97b SwingX: added search and clear icons to JXSearchField (issue #359) 2021-08-03 17:52:49 +02:00
Karl Tauber
d373687bc4 Testing: added FlatSingleComponentTest to easier test/debug single components 2021-08-03 15:16:04 +02:00
Karl Tauber
e5e510c825 Demo: fixed inconsistent behavior when first changing font size and then font family, which did loose user scale factor on Windows in Java 9+ (issue #352) 2021-08-02 19:16:38 +02:00
Karl Tauber
29064ec72f Button and TextComponent: do not apply minimum width/height if margins are set (issue #364) 2021-08-02 18:36:10 +02:00
Karl Tauber
953eee1dc8 TableHeader: made getRolloverColumn() public to allow usage in custom renderers (issue #336) 2021-08-02 18:01:08 +02:00
Karl Tauber
75f76f4875 ComboBox and Spinner: limit arrow button width if component has large preferred height (issue #361) 2021-08-02 15:27:25 +02:00
Karl Tauber
ecfbe68c33 Native window decorations: updated DLLs (issues #357 and #339)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/1085691279
2021-07-31 21:22:09 +02:00
Karl Tauber
7f02eb9cf0 Native window decorations: when window is initially shown, fill background with window background color (instead of white), which avoids flickering in dark themes (issue #339) 2021-07-31 21:05:01 +02:00
Karl Tauber
4ab90065dc Native window decorations: when resizing a window to the right or to the bottom, then first fill the new space with the window background color (instead of black) before the layout is updated (issue #339) 2021-07-31 18:02:10 +02:00
Karl Tauber
d3e39a1359 Native window decorations: fixed occasional application crash on Windows 10 in flatlaf-windows.dll (issue #357) 2021-07-30 23:06:09 +02:00
Karl Tauber
60e5861de4 InternalFrame: limit internal frame bounds to parent bounds on resize; honor maximum size of internal frame (issue #362) 2021-07-29 16:44:50 +02:00
Karl Tauber
ca7f5045ae Popup: fixed incorrectly placed drop shadow for medium-weight popups in maximized windows (issue #358) 2021-07-29 15:39:16 +02:00
Karl Tauber
299bd67151 Styling: support PopupMenu 2021-07-23 08:54:50 +02:00
Karl Tauber
4d4bb3fd7f Styling: added StyleableUI.getStyleableInfos() for tooling (e.g. GUI builder) 2021-07-21 11:51:19 +02:00
Karl Tauber
7fd64a1b73 Styling: support InternalFrame 2021-07-20 08:57:21 +02:00
Karl Tauber
e3e8765b91 Styling: support MenuBar 2021-07-19 14:39:57 +02:00
Karl Tauber
b0997fb5d2 release 1.4 2021-07-13 11:02:10 +02:00
Karl Tauber
435cf05f9f TabbedPane: reviewed PR #343 (active tab border painting style)
- moved focus listener to class `Handler`
- instead of repainting content border in `repaintContentBorder()`, increase size of repaint region in `repaintTab()` to include part of content border
- simplified `paintContentBorder()` by using `getTabBounds()` and `Rectangle2D.intersect()` to compute gap rectangle
- slightly make code smaller
- minor formatting, renaming, ...
2021-07-12 13:37:24 +02:00
Karl Tauber
37dab9fb22 TabbedPane: fixed rendering of tab separators in scroll layout if scaled on HiDPI screens 2021-07-12 11:48:34 +02:00
Karl Tauber
943dfe0619 Styling: support styling for recently merged changes 2021-07-11 01:41:52 +02:00
Karl Tauber
be7114d3e6 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java
2021-07-11 01:26:11 +02:00
Karl Tauber
fb44c8fbe4 TextField: fixed location of placeholder text if paddings are used (e.g. in ComboBox) (for commit a9dcf09d13) 2021-07-10 21:05:26 +02:00
Karl Tauber
94375b7d36 Extras: added support for client property JTextField.padding (for commit a9dcf09d13) 2021-07-10 20:59:34 +02:00
Karl Tauber
8b585deb78 ToolBar: support focusable buttons in toolbar (issue #346)
fixed focusable state when switching to/from other Laf
2021-07-10 13:32:30 +02:00
Karl Tauber
4d8b544aed UIDefaultsKeysDump: also use FlatTestLaf, which adds missing keys to FlatLafUIKeys.txt 2021-07-10 13:28:02 +02:00
Karl Tauber
548d651d29 PasswordField: move the lower bar of the caps lock icon up a half pixel 2021-07-10 11:03:13 +02:00
Karl Tauber
0b342acec9 PasswordField: paint caps lock icon on left side in right-to-left component orientation 2021-07-09 15:14:29 +02:00
Karl Tauber
cc6d3c1b1a PasswordField: Caps lock icon no longer painted over long text (issue #172) 2021-07-09 15:03:16 +02:00
Karl Tauber
74a748d92e use LoggingFacade instead of printStackTrace() in flatlaf-extras and flatlaf-demo 2021-07-09 13:22:37 +02:00
Karl Tauber
1de81d0af5 ComboBox: fixed StackOverflowError when using single renderer instance in multiple comboboxes (regression since commit 4507ce359d) 2021-07-09 11:39:35 +02:00
Karl Tauber
ff9ef21f67 OptionPane: align wrapped lines to the right if component orientation is right-to-left (issue #350) 2021-07-08 17:53:44 +02:00
Karl Tauber
266a546478 Window decorations: window title bar width is no longer considered when calculating preferred/minimum width of window (issue #351) 2021-07-08 16:54:34 +02:00
Karl Tauber
87407ca832 Table and PopupFactory: use StackWalker in Java 9+ for better performance (issue #334) 2021-07-08 14:02:50 +02:00
Karl Tauber
90282d4436 UI defaults dumps updated for issue #335 2021-07-08 00:02:33 +02:00
Karl Tauber
abbe6d6c1f ToolBar: paint focus indicator for focused button in toolbar (issue #346) 2021-07-07 18:17:45 +02:00
Karl Tauber
a28f701e6f OptionPane: do not make child components, which are derived from JPanel, non-opaque (issue #349) 2021-07-07 10:57:54 +02:00
Karl Tauber
4cdc995a7f ComboBox: simplified code in configureEditor() 2021-07-05 23:14:05 +02:00
Karl Tauber
713a01bfa9 Styling: set "shared" flag to true when shared icon is assigned 2021-07-05 23:12:45 +02:00
Karl Tauber
ac291b688d Styling: fixed detection of value type if key prefix ix given (e.g. [light]padding) 2021-07-05 22:40:55 +02:00
Karl Tauber
84f7e244f2 Styling:
- support ComboBox.padding
- fixed updating of Spinner.padding
2021-07-05 22:36:57 +02:00
Karl Tauber
4a8207f367 Styling: use TestUtils.setup() in unit tests; 2021-07-05 21:58:48 +02:00
Karl Tauber
9cfd4d27e9 Styling: renamed unit tests (so that all unit test classes start with Test) 2021-07-05 21:48:41 +02:00
Karl Tauber
1b23cfd747 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/build.gradle.kts
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java
2021-07-05 21:38:07 +02:00
Karl Tauber
c708205593 TestFlatComponentSizes: shortened combobox text because unit tests on GitHub Actions use font size 15 2021-07-05 20:06:07 +02:00
Karl Tauber
a22c6c8013 ComboBox (not editable):
- increased size of internal renderer pane to the component border so that it can paint within the whole component
- increase combo box size if a custom renderer uses a border with insets that are larger than the default combo box padding (`2,6,2,6`)
2021-07-05 18:41:17 +02:00
Karl Tauber
b576f473e5 fixed component heights at 1.25x, 1.75x and 2.25x scaling factors (Java 8 only) so that Button, ComboBox, Spinner and TextField components (including subclasses) have same heights 2021-07-05 15:15:37 +02:00
Karl Tauber
0b127caa83 ComboBox: fixed minimum width if focusWidth > 0 (to be equal with button minimum width)
added some unit tests to compare component sizes
2021-07-05 11:07:32 +02:00
Karl Tauber
5801bf3bdf Styling: use FlatLightLaf in unit tests and get UI delegates from components 2021-07-04 10:43:08 +02:00
Karl Tauber
4507ce359d ComboBox: reworked uninstall of CellPaddingBorder, which is temporary used for cell renderers, to make it easier to understand and reliable
(tested using FlatCustomBordersTest, FlatNetBeansTest, etc)
2021-07-03 10:43:55 +02:00
Karl Tauber
3e14f28dc2 ComboBox (editable) and Spinner: increased size of internal text field to the component border so that it behaves like plain text field (issue #330) 2021-07-02 18:43:37 +02:00
Karl Tauber
a9dcf09d13 TextField: support adding extra padding
(for #172, #173 and #330)
2021-07-02 15:38:45 +02:00
Karl Tauber
c8998c2bcf PasswordField: UI delegate FlatPasswordFieldUI now extends FlatTextFieldUI (instead of BasicPasswordFieldUI) to avoid duplicate code and for easier extensibility (e.g. for #173 and #341) 2021-07-02 14:03:54 +02:00
Karl Tauber
10bf1295bc CHANGELOG.md: fixed UI key ComboBox.popupFocusedBackground to ComboBox.popupBackground 2021-07-02 10:52:30 +02:00
Karl Tauber
1e869973d4 release 1.3 2021-07-02 10:40:27 +02:00
Karl Tauber
731c8962c9 added missing since 1.3 2021-07-02 10:21:55 +02:00
Karl Tauber
294b8bb789 Extras: FlatInspector: fixed border value when class hierarchy is enabled 2021-07-02 10:14:51 +02:00
Karl Tauber
4f9b819f48 Spinner: reduced gap between up and down arrows, which was increased by previous commit (issue #329) 2021-06-30 18:57:54 +02:00
Karl Tauber
5318d5fa8e ScrollBar: fixed left/top arrow icon location (if visible) (issue #329)
(tested using FlatPaintingTest)
2021-06-30 18:47:16 +02:00
Karl Tauber
98b156bdde TextComponents: use focusedBackground also if not editable (but enabled)
(PR #338)
2021-06-30 00:01:33 +02:00
Karl Tauber
511dd02107 JIDE: build using latest version of JIDE library com.formdev:jide-oss:3.7.12 2021-06-29 22:58:42 +02:00
Karl Tauber
f1f7a2e7b6 Extras: FlatInspector: fixed missing "UI" row on Java 9+ 2021-06-27 23:19:36 +02:00
Karl Tauber
d557cf5427 FlatTestFrame: do not print stack trace if lafs.properties does not exist 2021-06-27 21:36:49 +02:00
Karl Tauber
2b1c55ee67 Styling: support TabbedPane 2021-06-27 17:12:46 +02:00
Karl Tauber
925ddaa63a Styling: update MigLayout visual padding 2021-06-25 21:40:19 +02:00
Karl Tauber
2b60b18d47 Styling: support ScrollPane 2021-06-25 21:39:52 +02:00
Karl Tauber
d502406fa2 Styling: support borders of ComboBox and Spinner
reduced code size of Button, TextField and PasswordField
2021-06-25 21:38:39 +02:00
Karl Tauber
afdbf711f7 Styling: support Help button 2021-06-25 14:27:44 +02:00
Karl Tauber
39d2941099 removed duplicate ; 2021-06-25 10:48:00 +02:00
Karl Tauber
b4f7b1d71d Styling: support ToolBar and ToolBar.Separator 2021-06-24 20:06:00 +02:00
Karl Tauber
69061cd41c Styling: support TableHeader 2021-06-24 17:57:12 +02:00
Karl Tauber
8ba7f7f961 Styling: reduce duplicate code in list and table cell borders 2021-06-24 17:53:19 +02:00
Karl Tauber
5e5aa17e14 Styling: add property change listener only for FlatLaf.style (where possible) 2021-06-24 14:05:24 +02:00
Karl Tauber
551f5fc929 Styling: support Label 2021-06-24 14:02:32 +02:00
Karl Tauber
4e7b0d11d0 Styling: support Tree icons 2021-06-24 13:43:19 +02:00
Karl Tauber
06bc53692a Styling: support cell borders of List and Table 2021-06-24 10:45:48 +02:00
Karl Tauber
007ee38cb4 Styling: support List, Table and Tree 2021-06-22 17:59:55 +02:00
Karl Tauber
2a732306a1 ComboBox: renamed UI key ComboBox.popupFocusedBackground to ComboBox.popupBackground 2021-06-22 08:59:11 +02:00
Karl Tauber
82192bef91 Styling: clear field oldStyleValues on UI delegate uninstall 2021-06-21 21:06:39 +02:00
Karl Tauber
0c51dfe19c Styling: support ComboBox 2021-06-21 20:58:04 +02:00
Karl Tauber
24a9fa1ccc Styling: renamed "update" methods 2021-06-21 20:49:31 +02:00
Karl Tauber
14b06507cb Styling: support Spinner 2021-06-21 19:45:42 +02:00
Karl Tauber
b46233087b Styling: use FlatStyleSupport.createPropertyChangeListener() where possible/useful 2021-06-21 17:27:46 +02:00
Karl Tauber
28fb2e2a08 Styling: support Menu, MenuItem, CheckBoxMenuItem and RadioButtonMenuItem 2021-06-21 17:24:45 +02:00
DUDSS
943e211cf1 TabbedPane: ActiveTabBorder: Added handling for thick content separators and 0 height selection indicator. Fixed scaling issues (presumably). 2021-06-20 14:01:01 +02:00
DUDSS
ad0a13004e TabbedPane: Changed name in demo and added separator repaint on focus gained. 2021-06-20 02:56:19 +02:00
DUDSS
04bb6a5275 TabbedPane: Adjustements 2021-06-20 02:14:31 +02:00
DUDSS
d3c917eac1 TabbedPane: Added the new style option to the flatlaf demo. 2021-06-20 02:14:27 +02:00
DUDSS
4c13271a5b TabbedPane: Added activeTabBorder option to tabbed panes. Changes the active tab design to more closely resemble classic tabbed pane designs. 2021-06-20 02:13:54 +02:00
Karl Tauber
20027c2db7 Styling: support platform and light/dark theme specific styling with key prefixes [win], [mac], [linux], [light] and [dark]
e.g. `mySlider.putClientProperty( "FlatLaf.style", "[light]trackColor: #00f; [dark]trackColor: #f00" );`
2021-06-19 23:24:04 +02:00
Karl Tauber
6affc70a66 Styling: support Button and ToggleButton (including border) 2021-06-19 22:31:21 +02:00
Karl Tauber
ab4c9bdeda Styling: renamed client property JComponent.style to FlatLaf.style 2021-06-19 11:16:14 +02:00
Karl Tauber
b4a9c9b7f5 Styling: support styling disabledBackground and inactiveBackground of text components 2021-06-19 11:11:57 +02:00
Karl Tauber
5e20d50abf Styling: support TextArea, TextPane and EditorPane 2021-06-18 14:21:17 +02:00
Karl Tauber
53abbbbe56 Styling: support TextField, FormattedTextField and PasswordField 2021-06-18 13:22:19 +02:00
Karl Tauber
1938cb586d Styling: support SplitPane 2021-06-17 20:59:09 +02:00
Karl Tauber
50490ece84 Styling: support Separator and PopupMenu.Separator 2021-06-17 15:21:19 +02:00
Karl Tauber
f291cc2bd3 Styling: support ProgressBar 2021-06-17 14:57:10 +02:00
Karl Tauber
2542c8bd53 Styling: support ScrollBar 2021-06-17 13:56:38 +02:00
Karl Tauber
b457fd634e Styling: (try to) fix errors on GitHub Actions 2021-06-16 22:59:00 +02:00
Karl Tauber
041fd0e0cd Styling: fixed javadoc error 2021-06-16 22:57:56 +02:00
Karl Tauber
a983edde1e Styling: support CheckBox and RadioButton icons 2021-06-16 22:31:56 +02:00
Karl Tauber
7eb642dd13 Styling: added simple unit tests 2021-06-16 21:28:51 +02:00
Karl Tauber
e0bc93371e Styling: use annotation on fields to apply style properties (to avoid boilerplate code) 2021-06-16 21:21:33 +02:00
Karl Tauber
db56486506 Styling: support CheckBox and RadioButton (without icons) 2021-06-16 12:07:13 +02:00
Karl Tauber
c99be13697 Styling: support using java.util.Map as style
e.g. `mySlider.putClientProperty( "JComponent.style", Collections.singletonMap( "thumbSize", new Dimension( 8, 24 ) ) );`
2021-06-15 23:23:11 +02:00
Karl Tauber
0830c78728 Styling: support using simple references to UI defaults
e.g. `mySlider.putClientProperty( "JComponent.style", "thumbColor: $TextField.background; thumbBorderColor: $Component.borderColor" );`
2021-06-15 23:20:33 +02:00
Karl Tauber
edade93054 Styling: basic implementation of styling support using client property JComponent.style and CSS syntax
only for JSlider (at the moment)

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

(issues #117 and #340)
2021-06-15 14:35:26 +02:00
Karl Tauber
8a72b30cbc Merge pull request #338 from Chrriis/focusedBackground
Issue #335: allow a different background on focus
2021-06-15 11:57:17 +02:00
Karl Tauber
ed9cb0f918 Spinner: support Spinner.focusedBackground
ComboBox:
- prefer explicit set background color over focusedBackground
- if ComboBox.buttonFocusedBackground is not specified use ComboBox.focusedBackground
- added ComboBox.popupFocusedBackground

(issue #335)
2021-06-15 11:50:30 +02:00
Karl Tauber
7e0915cb9c FlatBorder: refractored ComboBox, ScrollPane and Spinner focus owner checking to UI delegates (for later usage) 2021-06-13 11:21:55 +02:00
Karl Tauber
a51294d570 TextComponents:
- use focusedBackground only if editable (and enabled)
- prefer explicit set background color over focusedBackground
- added FlatTextFieldUI.getBackground() used by all text components
- support EditorPane.focusedBackground
- support TextPane.focusedBackground

(issue #335)
2021-06-12 20:46:59 +02:00
Karl Tauber
d962f218a1 ToolTip: fixed positioning of huge tooltips (issue #333) 2021-06-11 20:53:09 +02:00
Karl Tauber
7b248427f0 fixed white lines at bottom and right side of window (in dark themes on HiDPI screens with scaling enabled) 2021-06-11 16:16:41 +02:00
Christopher Deckers
b99fb8b11f Use focused background color for combo popups. 2021-06-09 09:56:50 +02:00
Christopher Deckers
26250e790f Issue #335: allow a different background on focus. 2021-06-08 10:12:59 +02:00
Karl Tauber
b26dbe81f4 README.md: changed deprecated FlatLightLaf.install() to FlatLightLaf.setup() 2021-06-01 16:23:54 +02:00
Karl Tauber
903212345b .gitbugtraq added (for SmartGit) 2021-06-01 16:21:58 +02:00
Karl Tauber
025f6564dc release 1.2 2021-05-18 18:23:41 +02:00
Karl Tauber
35f97368fa Native window decorations: double-click at upper-left corner of maximized frame did not close window (issue #326) 2021-05-18 18:00:53 +02:00
Karl Tauber
09e5c86488 FlatLaf.getDisabledIcon() now returns a instanceof UIResource for disabled SVG icons to allow recreation of disabled icons when switching to another Laf 2021-05-15 17:51:33 +02:00
Karl Tauber
8998371cae Extras: FlatSVGUtils.createWindowIconImages(): return multi-resolution image only on Windows because Java implementations for macOS and Linux do not support multi-resolution images for window title icons
(issue #323)
2021-05-14 17:33:40 +02:00
Karl Tauber
29e1dc6b55 FlatTitlePaneIcon: use getResolutionVariant(width, height) instead of getResolutionVariants() to allow creation of requested size on demand and to avoids creation of all resolution variants
Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single multi-resolution image that creates requested image sizes on demand from SVG

(issue #323)
2021-05-14 16:43:47 +02:00
Karl Tauber
439e63b52f Native window decorations: updated DLLs (issue #283)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/838543378
2021-05-13 13:43:45 +02:00
Karl Tauber
eea341fb33 Native window decorations: fixed broken maximizing window (under special conditions) when restoring frame state at startup (issue #283) 2021-05-13 12:10:11 +02:00
Karl Tauber
359eedf773 Native window decorations: fixed slow application startup under particular conditions (issue #319) 2021-05-13 00:54:22 +02:00
Karl Tauber
866751ffc1 Extras: FlatInspector: show class hierarchies when pressing Alt key and prettified class names (dimmed package name) 2021-05-12 19:03:13 +02:00
Karl Tauber
38a3a0768d Tree: fill cell background if DefaultTreeCellRenderer.setBackgroundNonSelectionColor(Color) was used (issue #322) 2021-05-12 15:45:36 +02:00
Karl Tauber
03b42749cd replaced deprecated (since Java 9) KeyEvent.*_MASK with KeyEvent.*_DOWN_MASK 2021-05-12 14:03:16 +02:00
Karl Tauber
60fd78e082 build.gradle.kts: removed unnecessary mapOf() and fixed formatting 2021-05-12 13:59:50 +02:00
Karl Tauber
9edaf58929 Linux: fixed/improved detection of user font settings (issue #309) 2021-05-04 22:41:00 +02:00
Karl Tauber
5000186f85 Linux: enable text anti-aliasing if no Gnome or KDE Desktop properties are available (issue #218) 2021-05-04 22:11:15 +02:00
Karl Tauber
cacf0ea987 ComboBox: support using as cell renderer (e.g. in JTable) 2021-05-04 21:39:08 +02:00
Karl Tauber
067501cbe7 Native window decorations: avoid double window title bar if enabling native window border failed (issue #315) 2021-04-23 21:12:40 +02:00
Karl Tauber
9fe0cf496b Native window decorations: updated DLLs (issue #315)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/778322373
2021-04-23 18:23:44 +02:00
Karl Tauber
9d0823038e Native window decorations: fixed occasional double window title bar when creating many frames or dialogs (issue #315) 2021-04-23 18:14:00 +02:00
Karl Tauber
5a05efefdd build.gradle.kts:
- moved javadoc options from subprojects to root project
- removed "API" from titles in HTML files
- added subproject name and version to header and footer
- use links to Java 11 API
2021-04-22 23:00:28 +02:00
Karl Tauber
988d171bdd fixed javadoc warnings/errors when building with Java 15 2021-04-22 16:20:50 +02:00
Karl Tauber
e6f72bf343 fixed some deprecation warnings when compiling with Java 11 2021-04-22 15:53:02 +02:00
Karl Tauber
89c5a0c57b FlatSVGIcon: fixed javadoc issues 2021-04-22 14:27:14 +02:00
Karl Tauber
d97146393c renamed Flat*Laf.install() methods to Flat*Laf.setup() to avoid confusion with UIManager.installLookAndFeel(LookAndFeelInfo info); the old Flat*Laf.install() methods are still there, but marked as deprecated 2021-04-22 14:20:09 +02:00
Karl Tauber
1c52f1f76c CheckBox and RadioButton: do not fill background if used as cell renderer, except if cell is selected or has different background color (issue #311) 2021-04-22 00:14:42 +02:00
Karl Tauber
9bd3a68115 update miglayout-swing from 5.3-SNAPSHOT to 5.3 2021-04-20 21:01:55 +02:00
Karl Tauber
f58780d36b FlatSVGIcon: share color filter in derived icons 2021-04-18 18:30:56 +02:00
Karl Tauber
6eb15ab437 FlatSVGIcon: added missing javadoc and updated CHANGELOG.md 2021-04-18 17:43:12 +02:00
Karl Tauber
00dc7004f5 Merge pull request #303 from xDUDSSx/extras-svg-icon-filter
FlatSVGIcon color filters
2021-04-18 17:31:01 +02:00
Karl Tauber
8ec0e57235 FlatSVGIcon: use fluent API for color filter 2021-04-18 17:05:22 +02:00
Karl Tauber
d75dc9e70c FlatSVGIcon: support light and dark mappings in single color filter 2021-04-18 16:37:24 +02:00
Karl Tauber
ec2fccbb0e FlatSVGIcon: if icon has color filter and did change the color, then do not apply global color filter 2021-04-16 23:25:22 +02:00
Karl Tauber
34861166e8 Demo: ExtrasPanel: added "Toggle RED" button 2021-04-16 23:03:38 +02:00
Karl Tauber
584fa0a26e Demo: ExtrasPanel:
- animate "rainbow" icon only if extras tab is visible
- recreated added components in JFormDesigner
2021-04-16 22:56:44 +02:00
Karl Tauber
6c48489d89 FlatSVGIcon:
- added getters for all fields passed to constructors
- preserve disabled state in derive() methods
- ColorFilter: create hash maps only if needed/used
2021-04-16 21:53:15 +02:00
Karl Tauber
ba9c884a0c FlatSVGIcon:
- renamed FlatSVGIcon.setFilter(...) to setColorFilter()
- renamed ColorFilter.setFilter(Function) to setMapper(Function)
- replaced ColorFilter.createGrayFilterFunction(int,int,int) with universal createRGBImageFilterFunction(RGBImageFilter)
- ColorFilter: use default color palette mapping only in global filter
2021-04-16 21:33:23 +02:00
Karl Tauber
360f0bafe0 Extras: FlatInspector: always show tooltip over highlight figures 2021-04-16 15:12:55 +02:00
Karl Tauber
4327c13dca FlatTestFrame: moved 3rd party lafs to lafs.properties 2021-04-16 14:57:43 +02:00
Karl Tauber
4f2256f713 TableHeader: Moved table header column border painting from FlatTableHeaderUI to new border FlatTableHeaderBorder to improve compatibility with custom table header implementations (issue #228) 2021-04-14 19:34:44 +02:00
Karl Tauber
5167cd368f JIDE: JideTabbedPane: updated CHANGELOG.md 2021-04-13 16:32:20 +02:00
Karl Tauber
ef7289d11a Merge pull request #306 from JFormDesigner/jidetabbedpane
JideTabbedPane improvements
2021-04-13 16:29:49 +02:00
Karl Tauber
cb11d98bf7 JIDE: JideTabbedPane: hide tab selection and tab area separator for tabbedPane.setHideOneTab(true) if tabbed pane contains only one tab 2021-04-13 12:20:11 +02:00
Karl Tauber
992349da8c JIDE: JideTabbedPane: fixed close button in tab area, which was visible even if shown on tabs (regression in previous commit) 2021-04-13 12:06:28 +02:00
Karl Tauber
2e7637f274 JIDE: JideTabbedPane: fixed close button in tab area 2021-04-13 11:25:42 +02:00
Karl Tauber
1f8eaf4a64 JIDE: JideTabbedPane: fixed scroll and list buttons 2021-04-13 10:51:04 +02:00
Karl Tauber
46ac7a9dc7 IntelliJ Themes: fixed background colors of DesktopPane and DesktopIcon in all themes 2021-04-11 19:39:47 +02:00
Karl Tauber
0d86d39217 IntelliJ Themes: minor fixes to text in progress bars for some themes 2021-04-11 18:59:23 +02:00
Karl Tauber
1f591f3d1b IntelliJ Themes: added "Material Theme UI Lite / GitHub Dark" theme 2021-04-11 17:42:57 +02:00
Karl Tauber
30c6ddba37 IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2021-04-11 17:35:25 +02:00
Karl Tauber
406eeaec96 PopupFactory: fixed occasional NullPointerException in FlatPopupFactory.fixToolTipLocation() (issue #305) 2021-04-11 16:00:36 +02:00
Karl Tauber
2fe5652bc6 DesktopPane: automatically layout icons in dock (without invoking from DesktopManager), which eliminates the need for FlatDesktopManager 2021-04-11 15:10:59 +02:00
Karl Tauber
39bf68a6bd DesktopIcon: automatically update preview (without invoking from DesktopManager) 2021-04-11 14:58:20 +02:00
Karl Tauber
a7a4a19824 DesktopIcon: use derived color for icon background, based on background color of JDesktopPane 2021-04-11 14:48:19 +02:00
Karl Tauber
7f906ba0ea DesktopPane: fixed empty minimized icon when switching LaF (regression since commit ab1ce7fab16597c518dd00a4c4e86320d98410c1; see PR #294) 2021-04-10 15:53:45 +02:00
Karl Tauber
07bf6e4506 DesktopPane: on HiDPI screens, use high-resolution images for preview of iconified internal frames in dock 2021-04-10 14:36:46 +02:00
Karl Tauber
a331760321 DesktopPane: made private methods/fields protected to allow overriding 2021-04-10 13:36:44 +02:00
Karl Tauber
d9c240d729 DesktopPane: fixed incomplete minimized icon when switching LaF 2021-04-10 12:46:13 +02:00
Karl Tauber
d9526c19e7 DesktopPane: improved layout of iconified internal frames in dock 2021-04-10 12:39:26 +02:00
Karl Tauber
1798ccd284 Merge pull request #294 from lsimediasarl/main
Fixed JInternalFrame iconified content snapshot for already installed DesktopManager
2021-04-10 00:05:02 +02:00
Karl Tauber
ab1ce7fab1 DesktopPane: avoid using two instances of DefaultDesktopManager if a custom desktop manager is used/wrapped (see PR #294) 2021-04-09 18:17:15 +02:00
DUDSS
e9b2f17171 FlatSVGIcon: Fixed an oversight 2021-04-09 13:41:08 +02:00
DUDSS
d3bf4433b7 FlatSVGIcon: Removed unnecessary getInstance method. Changed the demo a little and added a utility method to ColorFilter to easily create a brightness/contrast/alpha filter. 2021-04-09 13:36:49 +02:00
DUDSS
ba0f43455b Reworked how the FlatSVGIcon filters work. Filters are now set using the ColorFilter class and can work globally too. Added related demo components to flatlaf-demo extras tab. 2021-04-09 13:36:49 +02:00
DUDSS
638af4bcd7 Added an option to specify an RGBImageFilter to a FlatSVGIcon 2021-04-09 13:36:48 +02:00
Karl Tauber
5eab843d97 Button and ToggleButton:
- updated CHANGELOG.md for #276
- FlatComponentsTest: use FlatButton and FlatToggleButton
- FlatButtonUI: avoid unnecessary reading client property if shadowColor is null, which is the case in most themes
2021-04-09 11:44:59 +02:00
Karl Tauber
c55f0e239e Merge pull request #276 from ingokegel/border_less_button
Added ButtonType.borderLess
2021-04-09 11:17:11 +02:00
Ingo Kegel
32d9381745 Renamed borderLess to borderless 2021-04-08 22:36:42 +02:00
Karl Tauber
77fc564e70 TabbedPane: fixed actions scrollTabsForwardAction and scrollTabsBackwardAction when used from outside (e.g. in NetBeans) 2021-04-08 01:15:29 +02:00
Karl Tauber
3b84314c45 client/system properties: javadoc fixes 2021-04-07 16:25:24 +02:00
Karl Tauber
5729c20386 release 1.1.2 2021-04-07 11:53:18 +02:00
Karl Tauber
a4d70d8095 FlatTextComponentsTest: fixed compiler warnings (for previous commit) 2021-04-07 10:34:11 +02:00
Karl Tauber
8fcce349d5 ComboBox and Spinner: fixed too wide arrow button if component is higher than preferred (issue #302) 2021-04-07 01:39:29 +02:00
Karl Tauber
5a94676a3a Merge pull request #269 from SchiopuMatei/main
Added option for downscaling
2021-04-07 00:24:18 +02:00
Karl Tauber
f32d72ee62 UIScale:
- allow scale factors less than 100% for system property `flatlaf.uiScale`
- no longer round scale factor of system property `flatlaf.uiScale` to 1/4
- renamed system property `flatlaf.uiDowncale.enabled` to `flatlaf.uiScale.allowScaleDown`
- round smaller scale factors to 1/10
- absolute minimum user scale factor is now 0.1
2021-04-07 00:21:15 +02:00
Karl Tauber
e35fc8620c JIDE: fixed null font in other Lafs if (wrongly) using LookAndFeelFactory.addUIDefaultsInitializer() or LookAndFeelFactory.addUIDefaultsCustomizer() (issue #288) 2021-04-06 18:35:48 +02:00
Karl Tauber
277c288952 IntelliJ Themes: fixed system colors 2021-04-06 11:29:55 +02:00
Karl Tauber
240b08e55c IntelliJ Themes: fixed window title bar background if unified background is enabled 2021-04-06 11:04:52 +02:00
Karl Tauber
fe7f345661 Native window decorations: support changing title bar background and foreground colors per window (via client property) also if unified window title bar is enabled 2021-04-06 10:46:28 +02:00
Karl Tauber
c8db01c958 SplitPane: fixed JSplitPane.setContinuousLayout(false) (issue #301) 2021-04-05 14:24:49 +02:00
Karl Tauber
f456185f7d Native window decorations: support changing title bar background and foreground colors per window (via client property) 2021-04-05 14:19:41 +02:00
Karl Tauber
801b555835 Window decorations: fixed random window title bar background for unified backgrounds in cases were background is not filled by custom window/rootpane components (issue #254) 2021-04-04 11:47:15 +02:00
Karl Tauber
eee177e64b Window decorations: enabling/disabling menu bar embedding via system and client properties now works the same way as for window decorations
(previously it was only possible to disable menu bar embedding)
2021-04-03 16:19:11 +02:00
Karl Tauber
63639f8e96 Native window decorations: cleaned-up/simplified JetBrains Runtime custom window decorations "enabled" checking:
- `FlatSystemProperties.USE_WINDOW_DECORATIONS` is now also used for JBR custom window decorations
- `FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS` is now only used to disable JBR custom window decorations; then FlatLaf native window decorations are used
- JBR custom window decorations are now disabled when running in JetBrains Projector, Webswing or WinPE
2021-04-03 13:32:46 +02:00
Karl Tauber
de1b0b1bb6 MenuBar: do not use TitlePane.unifiedBackground if window decorations are disabled for the window 2021-04-03 11:51:45 +02:00
Karl Tauber
bbdd7fc2b4 Demo:
- keep "Options > Window decorations" selected for JetBrains Runtime
- disable "Options > Use underline menu selection" on macOS
- added font size `11`
2021-04-03 11:49:57 +02:00
Karl Tauber
6addb5c4b4 Native window decorations:
- API to check whether current platform supports window decorations `FlatLaf.supportsNativeWindowDecorations()`
- API to toggle window decorations of all windows `FlatLaf.setUseNativeWindowDecorations(boolean)`
- `FlatClientProperties.USE_WINDOW_DECORATIONS` can now used to toggle window decorations for single window
- cleaned-up/fixed/simplified window decorations "enabled" checking:
  1. if `FlatSystemProperties.USE_WINDOW_DECORATIONS` is set, its value is used
  2. if `FlatClientProperties.USE_WINDOW_DECORATIONS` is set, its value is used
  3. use value of UI default `TitlePane.useWindowDecorations`
2021-04-03 11:13:57 +02:00
Karl Tauber
b47e0c88d6 Merge pull request #298 from Bios-Marcel/fix-demo-menu-item-states
Fix selected states for native window border related menu items
2021-04-02 16:13:36 +02:00
Marcel Schramm
d06993d940 Add comment explaining why the use of JBR results in not having custom decorations 2021-04-01 22:14:39 +02:00
Karl Tauber
d31f167b9e TabbedPane: fixed NPE when creating/modifying in another thread (issue #299) 2021-04-01 12:35:50 +02:00
Karl Tauber
f12ee6c167 added dummy class to empty opend module packages 2021-04-01 09:40:22 +02:00
Karl Tauber
983b341f33 Native window decorations: fixed loading of native library when using JPMS for application (issue #289) 2021-04-01 01:07:35 +02:00
Karl Tauber
f3e6642f05 Button and ToggleButton: simplified/unified code of FlatButtonUI.getBackground() (issue #292) 2021-03-31 23:14:45 +02:00
Karl Tauber
0a63990d21 Button and ToggleButton: do not paint background of disabled (and unselected) toolBar buttons (issue #292; regression since fixing #112) 2021-03-31 22:28:43 +02:00
Karl Tauber
6909bb4b03 Native window decorations: removed superfluous pixel-line at top of screen when window is maximized (issue #296) 2021-03-31 20:56:17 +02:00
Marcel Schramm
620aa8bcee Fix selected states for native window border related menu items
The menu items for custom window decorations and embeded menu bar aren't selected anymore if the feature isn't supported.
On top of that, there's now a tooltip indicating that these aren't supported.
2021-03-31 19:59:29 +02:00
Stephan Bodmer
6db39d1860 Implemented desktop manager wrapper for already installed desktop manager so the iconifyFrame with small
content snapshot are still available

Signed-off-by: Stephan Bodmer <sbodmer@lsi-media.ch>
2021-03-31 13:58:25 +02:00
Stephan Bodmer
1762ead89f s
Signed-off-by: Stephan Bodmer <sbodmer@lsi-media.ch>
2021-03-31 13:54:40 +02:00
Karl Tauber
d13ddeb944 use larger font when running on WinPE (issue #279) 2021-03-30 11:00:27 +02:00
Karl Tauber
1b5da0e1d1 Window decorations: support enabling/disabling unified title bar backgrounds at runtime without FlatLaf.updateUI() 2021-03-30 01:34:34 +02:00
Karl Tauber
7a2d0e7fcb fixed crash when running in Webswing (issue #290) 2021-03-30 01:06:30 +02:00
Karl Tauber
477c3b6b1e README.md: added link to FlatLaf 1.0 announcement on Reddit 2021-03-28 18:44:21 +02:00
Karl Tauber
95312c3650 release 1.1.1 2021-03-28 16:04:14 +02:00
Karl Tauber
98a3c4b0f5 JIDE: JideTabbedPane: fixed disabled tab text, which was unreadable in dark themes 2021-03-27 19:19:17 +01:00
Karl Tauber
6e990a7e31 JIDE: JideTabbedPane: fixed hover background of close button on selected tab 2021-03-27 18:46:37 +01:00
Karl Tauber
8e49904f8d JIDE: JideTabbedPane: fixed location of tab title editing box 2021-03-27 18:22:10 +01:00
Karl Tauber
69f52c8abd JIDE: JideTabbedPane: scale tab gripper 2021-03-27 17:48:58 +01:00
Karl Tauber
d7b0754327 JIDE: JideTabbedPane: tab layout fixes for compact resize mode 2021-03-27 17:03:49 +01:00
Karl Tauber
2a00de11f1 JIDE: JideTabbedPane: fixed tab icon and title locations in vertical tabs 2021-03-27 14:28:21 +01:00
Karl Tauber
923cc51f3e JIDE: JideTabbedPane: FlatJideOssContainerTest updated (based on FlatContainerTest) 2021-03-27 12:18:06 +01:00
Karl Tauber
c8f7478170 JIDE: JideTabbedPane:
- use `FlatTabbedPaneCloseIcon` for tab close buttons
- scale close buttons
- fix close buttons location
2021-03-27 11:02:33 +01:00
Karl Tauber
9006e835c6 natives.yml: exclude ~/.gradle/caches/modules-2/modules-2.lock from Gradle cache 2021-03-26 21:52:28 +01:00
Karl Tauber
f801d61929 support running on WinPE (issue #279) 2021-03-26 21:51:11 +01:00
Karl Tauber
a143e5777c Extras: FlatInspector: fixed InaccessibleObjectException when running in Java 16 2021-03-26 21:44:41 +01:00
Karl Tauber
bffac60bf8 JIDE: JideTabbedPane:
- support selected tab background
- support tab separators
2021-03-25 18:49:16 +01:00
Karl Tauber
bf500e46e7 Window decorations: fixed wrong/missing window icon when application replaces InternalFrame.icon (issue #284) 2021-03-25 16:14:41 +01:00
Karl Tauber
4a2f79f390 Native window decorations: updated DLLs (issues #282 and #283)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/686023039
2021-03-25 11:10:13 +01:00
Karl Tauber
c24ce7c5bc Native window decorations: fixed broken maximizing window when restoring frame state at startup (issue #283) 2021-03-25 10:32:24 +01:00
Karl Tauber
8a6a0c7971 Native window decorations: fixed missing animations when minimizing, maximizing or restoring a window using window title bar buttons (issue #282) 2021-03-24 23:59:59 +01:00
Karl Tauber
de6e5bd800 fixed missing focus indicators in heavy-weight popups (issue #273) 2021-03-24 11:43:06 +01:00
Karl Tauber
e18a04f9e6 Merge pull request #278 from ingokegel/native_provider_setter
Add a setter for the native provider
2021-03-24 11:34:22 +01:00
Karl Tauber
14fc652f4b Window decorations: fixed right aligned progress bar in embedded menu bar was overlapping window title (issue #272) 2021-03-23 19:23:18 +01:00
Ingo Kegel
9a876e747a Added setter for native provider
This makes it possible to support situations where the extraction of a DLL at runtime is not possible
2021-03-23 16:47:08 +01:00
Karl Tauber
f8ee8b27fb InternalFrame: fixed translucent internal frame menu bar background if TitlePane.unifiedBackground is true (issue #274) 2021-03-23 15:08:01 +01:00
Karl Tauber
ce1a1487aa support menu bars in JDialog 2021-03-23 14:58:53 +01:00
Karl Tauber
fe1e364a1d Native window decorations: support disabling native window decorations per window via client property (issue #277) 2021-03-23 13:18:07 +01:00
Karl Tauber
eabb052107 Native window decorations: fixed double window title bar when first disposing a window and then showing it again (issue #277) 2021-03-23 10:07:43 +01:00
Karl Tauber
734f3621f1 Window decorations: Fixed NPE in FlatTitlePane.findHorizontalGlue() (issue #275) 2021-03-22 18:47:53 +01:00
Ingo Kegel
ae8323e2f8 Added ButtonType.borderLess for buttons that look like toolbar buttons but have a focus indicator.
This behavior can be achieved with JideButton, but it would be preferable to use FlatButton instead.
2021-03-22 16:45:37 +01:00
SchiopuMatei
ed91aa4648 Added option for downscaling 2021-03-15 20:41:40 +02:00
603 changed files with 100744 additions and 32534 deletions

8
.gitbugtraq Normal file
View File

@@ -0,0 +1,8 @@
# links issue numbers in git commit messages to issue tracker
# https://github.com/mstrap/bugtraq
# for SmartGit - https://www.syntevo.com/smartgit/
[bugtraq]
url = "https://github.com/JFormDesigner/FlatLaf/issues/%BUGID%"
loglinkregex = "#[0-9]{1,5}"
logregex = "[0-9]{1,5}"

View File

@@ -19,45 +19,36 @@ jobs:
strategy:
matrix:
# test against
# - Java 1.8 (minimum requirement)
# - Java 9 (first version with JPMS)
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - lastest Java version(s)
java:
- 1.8
- 9
- 8
- 11 # LTS
- 14
- 15
- 17 # LTS
toolchain: [""]
include:
- java: 17
toolchain: 19 # latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
if: matrix.java == '8'
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
- name: Cache Gradle wrapper
uses: actions/cache@v1
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
distribution: adopt # Java 8 and 11 are pre-installed on ubuntu-latest
cache: gradle
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: matrix.java == '11'
with:
name: FlatLaf-build-artifacts
@@ -72,36 +63,36 @@ jobs:
needs: build
if: |
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 11
- name: Cache Gradle wrapper
uses: actions/cache@v1
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
distribution: adopt # pre-installed on ubuntu-latest
cache: gradle
- name: Publish snapshot to oss.sonatype.org
run: ./gradlew publish -Dorg.gradle.internal.publish.checksums.insecure=true
run: ./gradlew publish :flatlaf-theme-editor:build -Dorg.gradle.internal.publish.checksums.insecure=true
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "snapshots"
options: "--only-newer --no-recursion --verbose=1"
release:
runs-on: ubuntu-latest
@@ -112,28 +103,17 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 11
- name: Cache Gradle wrapper
uses: actions/cache@v1
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
distribution: adopt # pre-installed on ubuntu-latest
cache: gradle
- name: Release a new stable version to Maven Central
run: ./gradlew publish :flatlaf-demo:build -Drelease=true
run: ./gradlew publish :flatlaf-demo:build :flatlaf-theme-editor:build -Drelease=true
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
@@ -150,3 +130,14 @@ jobs:
localDir: "flatlaf-demo/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"

View File

@@ -9,48 +9,48 @@ on:
tags:
- '[0-9]*'
paths:
- 'flatlaf-natives/flatlaf-natives-windows/**'
- 'flatlaf-natives/**'
- '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
pull_request:
branches:
- '*'
paths:
- 'flatlaf-natives/flatlaf-natives-windows/**'
- 'flatlaf-natives/**'
- '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
jobs:
Windows:
runs-on: windows-latest
Natives:
strategy:
matrix:
os:
- windows
- ubuntu
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- name: Setup Java 1.8
uses: actions/setup-java@v1
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 1.8
- name: Cache Gradle wrapper
uses: actions/cache@v1
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle cache
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle
java-version: 11
distribution: adopt
cache: gradle
- name: Build with Gradle
run: ./gradlew :flatlaf-natives-windows:build
# --no-daemon is necessary on Windows otherwise caching Gradle would fail with:
# tar.exe: Couldn't open ~/.gradle/caches/modules-2/modules-2.lock: Permission denied
run: ./gradlew build-natives --no-daemon
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: FlatLaf-natives-windows-build-artifacts
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: |
flatlaf-natives/flatlaf-natives-windows/build
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
flatlaf-natives/flatlaf-natives-*/build

View File

@@ -1,6 +1,677 @@
FlatLaf Change Log
==================
## 2.6
#### New features and improvements
- If value of system property `flatlaf.nativeLibraryPath` is `system`, then
`System.loadLibrary(String)` is used to load the native library.
- TabbedPane: Switch and close tabs on left mouse click only. (PR #595)
#### Fixed bugs
- ComboBox and Spinner: Fixed missing arrow buttons if preferred height is zero.
Minimum width of arrow buttons is 3/4 of default width.
- MenuBar: Fixed NPE in `FlatMenuItemRenderer.getTopLevelFont()` if menu item
does not have a parent. (issue #600; regression since implementing #589 in
FlatLaf 2.5)
- ScrollBar: Show "pressed" feedback on track/thumb only for left mouse button.
If absolute positioning is enabled (the default), then also for middle mouse
button.
- Arrow buttons in ComboBox, Spinner, ScrollBar and TabbedPane: Show "pressed"
feedback only for left mouse button.
- ScaledImageIcon: Do not throw exceptions if image was has invalid size (e.g.
not found). Instead, paint a red rectangle (similar to `FlatSVGIcon`).
- Fixed NPE in `FlatUIUtils.isCellEditor()`. (issue #601)
## 2.5
#### New features and improvements
- Linux: Use X11 window manager events to move window and to show window menu
(right-click on window title bar), if custom window decorations are enabled.
This gives FlatLaf windows a more "native" feeling. (issue #482)
- MenuBar: Support different menu selection style UI defaults for `MenuBar` and
`MenuItem`. (issue #587)
- MenuBar: Top level menus now use `MenuBar.font` instead of `Menu.font`. (issue
#589)
- PasswordField: Reveal button is now hidden (and turned off) if password field
is disabled. (issue #501)
- TabbedPane: New option to disable tab run rotation in wrap layout. Set UI
value `TabbedPane.rotateTabRuns` to `false`. (issue #574)
- Window decorations:
- Added client property to mark components in embedded menu bar as "caption"
(allow moving window). (issue #569)
- Option to show window icon only in frames, but not in dialogs. Set UI value
`TitlePane.showIconInDialogs` to `false`. (issue #589)
- Added UI value `TitlePane.font` to customize window title font. (issue #589)
- Added system property `flatlaf.updateUIOnSystemFontChange` to allow disabling
automatic UI update when system font changes. (issue #580)
#### Fixed bugs
- Fixed missing UI value `MenuItem.acceleratorDelimiter` on macOS. (was `null`,
is now an empty string)
- Fixed possible exception in `FlatUIUtils.resetRenderingHints()`. (issue #575)
- Fixed AWT components on macOS, which use Swing components internally. (issue
#583)
- SwingX: Fixed missing highlighting of "today" in `JXMonthView` and
`JXDatePicker`.
## 2.4
#### New features and improvements
- Native window decorations (Windows 10/11 only):
- There is now a small area at top of the embedded menu bar to resize the
window.
- Improved window title bar layout for small window widths:
- Width of iconify/maximize/close buttons is reduced (if necessary) to give
more space to embedded menu bar and title.
- Window title now has a minimum width to always allow moving window
(click-and-drag on window title). Instead, embedded menu bar is made
smaller.
- Option to show window icon beside window title, if menu bar is embedded or
title is centered. Set UI value `TitlePane.showIconBesideTitle` to `true`.
- No longer reduce height of window title bar if it has an embedded menu bar
and is maximized.
#### Fixed bugs
- ComboBox: Fixed vertical alignment of text in popup list with text in combo
box in IntelliJ/Darcula themes.
- Menus: Fixed application freeze under very special conditions (invoking
`FlatLaf.initialize()` twice in NetBeans GUI builder) and using menu that has
submenus. See
[NetBeans issue #4231](https://github.com/apache/netbeans/issues/4231#issuecomment-1179611682)
for details.
- MenuItem: Fixed sometimes wrapped HTML text on HiDPI screens on Windows.
- TableHeader: Fixed exception when changing table structure (e.g. removing
column) from a table header popup menu action. (issue #532)
- `HiDPIUtils.paintAtScale1x()` now supports rotated graphics. (issue #557)
- Typography: No longer use `Consolas` or `Courier New` as monospaced font on
Windows because they have bad vertically placement.
- Native window decorations (Windows 10/11 only):
- Do not center window title if embedded menu bar is empty or has no menus at
left side, but some components at right side. (issue #558)
- Do not use window decorations if system property `sun.java2d.opengl` is
`true` on Windows 10. (issue #540)
- Fixed missing top window border in dark themes if window drop shadows are
disabled in system settings. (issue #554; Windows 10 only)
- Right-to-left component orientation of title bar was lost when switching
theme.
## 2.3
#### New features and improvements
- FileChooser: Added (optional) shortcuts panel. On Windows it contains "Recent
Items", "Desktop", "Documents", "This PC" and "Network". On macOS and Linux it
is empty/hidden. (issue #100)
- Button and ToggleButton: Added missing foreground colors for hover, pressed,
focused and selected states. (issue #535)
- Table: Optionally paint alternating rows below table if table is smaller than
scroll pane. Set UI value `Table.paintOutsideAlternateRows` to `true`.
Requires that `Table.alternateRowColor` is set to a color. (issue #504)
- ToggleButton: Made the underline placement of tab-style toggle buttons
configurable. (PR #530; issue #529)
- Added spanish translation. (PR #525)
#### Fixed bugs
- IntelliJ Themes: Fixed `TitledBorder` text color in "Monokai Pro" theme.
(issue #524)
## 2.2
#### New features and improvements
- SplitPane: Allow limiting one-touch expanding to a single side (set client
property `JSplitPane.expandableSide` to `"left"` or `"right"`). (issue #355)
- TabbedPane: Selected tab underline color now changes depending on whether the
focus is within the tab content. (issue #398)
- IntelliJ Themes:
- Added "Monokai Pro" and "Xcode-Dark" themes.
- TabbedPane now use different background color for selected tabs in all "Arc"
themes, in "Hiberbee Dark" and in all "Material UI Lite" themes.
#### Fixed bugs
- Native window decorations (Windows 10/11 only): Fixed wrong window title
character encoding used in Windows taskbar. (issue #502)
- Button: Fixed icon layout and preferred width of default buttons that use bold
font. (issue #506)
- FileChooser: Enabled full row selection for details view to fix alternate row
coloring. (issue #512)
- SplitPane: Fixed `StackOverflowError` caused by layout loop that may occur
under special circumstances. (issue #513)
- Table: Slightly changed grid colors to make grid better recognizable. (issue
#514)
- ToolBar: Fixed endless loop in focus navigation that may occur under special
circumstances. (issue #505)
- IntelliJ Themes: `Component.accentColor` UI property now has useful theme
specific values. (issue #507)
## 2.1
#### New features and improvements
- Menus: Improved usability of submenus. (PR #490; issue #247)
- Menus: Scroll large menus using mouse wheel or up/down arrows. (issue #225)
- Linux: Support using custom window decorations. Enable with
`JFrame.setDefaultLookAndFeelDecorated(true)` and
`JDialog.setDefaultLookAndFeelDecorated(true)` before creating a window.
(issue #482)
- ScrollBar: Added UI value `ScrollBar.minimumButtonSize` to specify minimum
scroll arrow button size (if shown). (issue #493)
#### Fixed bugs
- PasswordField: Fixed reveal button appearance in IntelliJ themes. (issue #494)
- ScrollBar: Center and scale arrows in scroll up/down buttons (if shown).
(issue #493)
- TextArea, TextPane and EditorPane: No longer select all text when component is
focused for the first time. (issue #498; regression in FlatLaf 2.0)
- TabbedPane: Disable all items in "Show Hidden Tabs" popup menu if tabbed pane
is disabled.
#### Incompatibilities
- Method `FlatUIUtils.paintArrow()` (and class `FlatArrowButton`) now paints
arrows one pixel smaller than before. To fix this, increase parameter
`arrowSize` by one.
## 2.0.2
- Native window decorations (Windows 10/11 only): Fixed rendering artifacts on
HiDPI screens when dragging window partly offscreen and back into screen
bounds. (issue #477)
- Repaint component when setting client property `JComponent.outline` (issue
#480).
- macOS: Fixed NPE when using some icons in main menu items. (issue #483)
## 2.0.1
- Fixed memory leak in Panel, Separator and ToolBarSeparator. (issue #471;
regression in FlatLaf 2.0)
- ToolTip: Fixed wrong tooltip location if component overrides
`JComponent.getToolTipLocation()` and wants place tooltip under mouse
location. (issue #468)
- Extras: Added copy constructor to `FlatSVGIcon`. (issue #465)
- Moved `module-info.class` from `META-INF\versions\9\` to root folder of JARs.
(issue #466)
## 2.0
- Added system property `flatlaf.nativeLibraryPath` to load native libraries
from a directory. (PR #453)
- Fixed "endless recursion in font" exception in
`FlatLaf$ActiveFont.createValue()` if `UIManager.getFont()` is invoked from
multiple threads. (issue #456)
- PasswordField: Preserve reveal button state when switching theme. (PR #442;
issue #173)
- PasswordField: Reveal button did not show password if
`JPasswordField.setEchoChar()` was invoked from application. (PR #442; issue
#173)
- Slider: Fixed/improved focused indicator color when changing accent color. (PR
#375)
- TextField:
- Improved hover/pressed/selected colors of leading/trailing buttons (e.g.
"reveal" button in password field). (issue #452)
- Clear button no longer paints over round border. (issue #451)
- Extras: Fixed concurrent loading of SVG icons on multiple threads. (issue
#459)
- Use FlatLaf native window decorations by default when running in
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki)
(instead of using JetBrains custom decorations). System variable
`flatlaf.useJetBrainsCustomDecorations` is now `false` by default (was `true`
in FlatLaf 1.x). (issue #454)
- Native window decorations:
- Fixed blurry iconify/maximize/close button hover rectangles at 125%, 150% or
175% scaling. (issue #431)
- Updated maximize and restore icons for Windows 11 style. (requires Java
8u321, 11.0.14, 17.0.2 or 18+)
- Updated hover and pressed colors of iconify/maximize/close buttons for
Windows 11 style.
## 2.0-rc1
#### New features and improvements
- Styling:
- Styling individual components using string in CSS syntax or `java.util.Map`.
(PR #341)\
E.g.: `mySlider.putClientProperty( "FlatLaf.style", "trackWidth: 2" );`
- Style classes allow defining style rules at a single place (in UI defaults)
and use them in any component. (PR #388)\
E.g.: `mySlider.putClientProperty( "FlatLaf.styleClass", "myclass" );`
- Typography defines several font styles for headers and various text sizes,
which makes it easy to use consistent font styles across the application. (PR
#396)
- Native window decorations (Windows 10/11 only):
- Unified backgrounds for window title bar is now enabled by default (window
title bar has now same background color as window content). Bottom separator
for menu bars is no longer painted (if unified background is enabled).
- Show Windows 11 snap layouts menu when hovering the mouse over the maximize
button. (issues #397 and #407)
- Possibility to hide window title bar icon (for single window set client
property `JRootPane.titleBarShowIcon` to `false`; for all windows set UI
value `TitlePane.showIcon` to `false`).
- OptionPane: Hide window title bar icon by default. Can be made visibly by
setting UI default `OptionPane.showIcon` to `true`. (issue #416)
- No longer show the Java "duke/cup" icon if no window icon image is set.
(issue #416)
- TextField, FormattedTextField and PasswordField:
- Support leading and trailing icons (set client property
`JTextField.leadingIcon` or `JTextField.trailingIcon` to a
`javax.swing.Icon`). (PR #378; issue #368)
- Support leading and trailing components (set client property
`JTextField.leadingComponent` or `JTextField.trailingComponent` to a
`java.awt.Component`). (PR #386)
- Support "clear" (or "cancel") button to empty text field. Only shown if text
field is not empty, editable and enabled. (set client property
`JTextField.showClearButton` to `true`). (PR #442)
- PasswordField: Support reveal (or "eye") button to show password. (see UI
value `PasswordField.showRevealButton`) (PR #442; issue #173)
- TextComponents: Double/triple-click-and-drag now extends selection by whole
words/lines.
- Theming improvements: Reworks core themes to make it easier to create new
themes (e.g. reduced explicit colors by using color functions). **Note**:
There are minor incompatible changes in FlatLaf properties files. (PR #390)
- ToolBar:
- Toolbars are no longer floatable by default (dots on left side of toolbar
that allows dragging toolbar). Use `UIManager.put( "ToolBar.floatable", true
)` if you want the old behavior.
- Skip components with empty input map (e.g. `JLabel`) when using arrow keys
to navigate in focusable buttons (if UI value `ToolBar.focusableButtons` is
`true`).
- Support arrow-keys-only navigation within focusable buttons of toolbar (if
UI value `ToolBar.focusableButtons` is `true`):
- arrow keys move focus within toolbar
- tab-key moves focus out of toolbar
- if moving focus into the toolbar, focus recently focused toolbar button
- ComboBox, Spinner, TextField and subclasses: Support specifying width of
border (see UI value `Component.borderWidth`).
- CheckBox and RadioButton:
- Made selected icon better recognizable in **FlatLaf Light** (use blue
border), **Dark** and **Darcula** (use lighter border) themes. **IntelliJ**
theme is not changed.
- Support specifying width of icon border (see UI value
`CheckBox.icon.borderWidth`).
- Reworked icon UI defaults and added missing ones. **Note**: There are minor
incompatible changes in FlatLaf properties files.
- Slider: Support specifying width of thumb border (see UI value
`Slider.thumbBorderWidth`).
- TabbedPane: Optionally paint selected tab as card. (PR #343)
- MenuItem:
- Paint the selected icon when the item is selected. (PR #415)
- Vertically align text if icons have different widths. (issue #437)
- Panel: Support painting background with rounded corners. (issue #367)
- Added more color functions to class `ColorFunctions` for easy use in
applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`,
`tint()`, `shade()` and `luma()`.
- Support defining fonts in FlatLaf properties files. (issue #384)
- Added method `FlatLaf.registerCustomDefaultsSource(URL packageUrl)` for JPMS.
(issue #325)
- Extras:
- Added class `FlatDesktop` for easy integration into macOS screen menu
(About, Preferences and Quit) when using Java 8.
- `FlatSVGIcon`: Support loading SVG from `URL` (for JPMS), `URI`, `File` or
`InputStream`. (issues #419 and #325)
- `FlatSVGUtils`: Support loading SVG from `URL` (for JPMS). (issue #325)
- SwingX:
- New "column control" icon for `JXTable` that scales and uses antialiasing.
(issue #434)
#### Fixed bugs
- Native window decorations: Fixed `UnsatisfiedLinkError` on Windows 11 for ARM
processors. (issue #443)
- MenuBar: Do not fill background if non-opaque and having custom background
color. (issue #409)
- InternalFrame: Fill background to avoid that parent may shine through internal
frame if it contains non-opaque components. (better fix for issue #274)
- SwingX: Fixed `NullPointerException` in `FlatCaret` when using
`org.jdesktop.swingx.prompt.PromptSupport.setPrompt()` on a text field and
then switching theme.
## 1.6.5
#### Fixed bugs
- Linux: Fixed font problems when running on Oracle Java (OpenJDK is not
affected):
- oversized text if system font is "Inter" (issue #427)
- missing text if system font is "Cantarell" (on Fedora)
- MenuItem: Changed accelerator delimiter from `-` to `+`. (Windows and Linux).
- ComboBox: Fixed occasional `StackOverflowError` when modifying combo box not
on AWT thread. (issue #432)
- macOS: Fixed `NullPointerException` when using AWT component
`java.awt.Choice`. (issue #439)
- Native window decorations: Do not exit application with `UnsatisfiedLinkError`
in case that FlatLaf DLL cannot be executed because of restrictions on
temporary directory. Instead, continue with default window decorations. (issue
#436)
## 1.6.4
#### Fixed bugs
- ComboBox: Fixed regression in FlatLaf 1.6.3 that makes selected item invisible
in popup list if `DefaultListCellRenderer` is used as renderer. If using
default renderer, it works. (issue #426)
## 1.6.3
#### Fixed bugs
- ComboBox (not editable): Fixed regression in FlatLaf 1.6.2 that may display
text in non-editable combo boxes in bold. (issue #423)
- Tree: Fixed editing cell issue with custom cell renderer and cell editor that
use same component for rendering and editing. (issue #385)
## 1.6.2
#### Fixed bugs
- ComboBox (not editable): Fixed background painted outside of border if round
edges are enabled (client property `JComponent.roundRect` is `true`). (similar
to issue #382; regression since fixing #330 in FlatLaf 1.4)
- ComboBox: Fixed `NullPointerException`, which may occur under special
circumstances. (issue #408)
- Table: Do not select text in cell editor when it gets focus (when
`JTable.surrendersFocusOnKeystroke` is `true`) and
`TextComponent.selectAllOnFocusPolicy` is `once` (the default) or `always`.
(issue #395)
- Linux: Fixed NPE when using `java.awt.TrayIcon`. (issue #405)
- FileChooser: Workaround for crash on Windows with Java 17 32-bit (disabled
Windows icons). Java 17 64-bit is not affected. (issue #403)
- Native window decorations: Fixed layout loop, which may occur under special
circumstances and slows down the application. (issue #420)
## 1.6.1
#### Fixed bugs
- Native window decorations: Catch `UnsatisfiedLinkError` when trying to load
`jawt.dll` to avoid an application crash (Java 8 on Windows 10 only).
## 1.6
#### New features and improvements
- InternalFrame: Double-click on icon in internal frame title bar now closes the
internal frame. (issue #374)
- IntelliJ Themes: Removed deprecated `install()` methods.
#### Fixed bugs
- Menus: Fixed missing modifiers flags in `ActionEvent` (e.g. `Ctrl` key
pressed) when running in Java 9+ on Linux, macOS. Occurs also on Windows in
large popup menus that do not fit into the window. (issue #371; regression
since FlatLaf 1.3)
- OptionPane: Fixed `OptionPane.sameSizeButtons`, which did not work as expected
when setting to `false`.
- OptionPane: Fixed rendering of longer HTML text if it is passed as
`StringBuilder`, `StringBuffer`, or any other object that returns HTML text in
method `toString()`. (similar to issue #12)
- ComboBox: Fixed popup border painting on HiDPI screens (e.g. at 150% scaling).
- ComboBox: Fixed popup location if shown above of combo box (Java 8 only).
- ComboBox (editable): Fixed wrong border of internal text field under special
circumstances.
- Spinner: Fixed painting of border corners on left side. (issue #382;
regression since fixing #330 in FlatLaf 1.4)
- TableHeader: Do not show resize cursor for last column if resizing last column
is not possible because auto resize mode of table is not off. (issue #332)
- TableHeader: Fixed missing trailing vertical separator line if used in upper
left corner of scroll pane. (issue #332)
- TextField, FormattedTextField, PasswordField and ComboBox: Fixed alignment of
placeholder text in right-to-left component orientation.
- Slider: Fixed calculation of baseline, which was wrong under some
circumstances.
## 1.5
#### New features and improvements
- SwingX: Added search and clear icons to `JXSearchField`. (issue #359)
#### Fixed bugs
- Button and TextComponent: Do not apply minimum width/height if margins are
set. (issue #364)
- ComboBox and Spinner: Limit arrow button width if component has large
preferred height. (issue #361)
- FileChooser: Fixed missing (localized) texts when FlatLaf is loaded in special
classloader (e.g. plugin system in Apache NetBeans).
- InternalFrame: Limit internal frame bounds to parent bounds on resize. Also
honor maximum size of internal frame. (issue #362)
- Popup: Fixed incorrectly placed drop shadow for medium-weight popups in
maximized windows. (issue #358)
- Native window decorations (Windows 10 only):
- Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357)
- When window is initially shown, fill background with window background color
(instead of white), which avoids flickering in dark themes. (issue 339)
- When resizing a window at the right/bottom edge, then first fill the new
space with the window background color (instead of black) before the layout
is updated.
- When resizing a window at the left/top edge, then first fill the new space
with the window background color (instead of garbage) before the layout is
updated.
## 1.4
#### New features and improvements
- TextField, FormattedTextField and PasswordField: Support adding extra padding
(set client property `JTextField.padding` to an `Insets`).
- PasswordField: UI delegate `FlatPasswordFieldUI` now extends `FlatTextFieldUI`
(instead of `BasicPasswordFieldUI`) to avoid duplicate code and for easier
extensibility.
- Table and PopupFactory: Use `StackWalker` in Java 9+ for better performance.
(issue #334)
- ToolBar: Paint focus indicator for focused button in toolbar. (issue #346)
- ToolBar: Support focusable buttons in toolbar (set UI value
`ToolBar.focusableButtons` to `true`). (issue #346)
#### Fixed bugs
- ComboBox (editable) and Spinner: Increased size of internal text field to the
component border so that it behaves like plain text field (mouse click to left
of text now positions caret to first character instead of opening ComboBox
popup; mouse cursor is now of type "text" within the whole component, except
for arrow buttons). (issue #330)
- ComboBox (not editable): Increased size of internal renderer pane to the
component border so that it can paint within the whole component. Also
increase combo box size if a custom renderer uses a border with insets that
are larger than the default combo box padding (`2,6,2,6`).
- Fixed component heights at `1.25x`, `1.75x` and `2.25x` scaling factors (Java
8 only) so that Button, ComboBox, Spinner and TextField components (including
subclasses) have same heights. This increases heights of Button and TextField
components by:
- `2px` at `1.75x` in **Light** and **Dark** themes
- `2px` at `1.25x` and `2.25x` in **IntelliJ** and **Darcula** themes
- OptionPane: Do not make child components, which are derived from `JPanel`,
non-opaque. (issue #349)
- OptionPane: Align wrapped lines to the right if component orientation is
right-to-left. (issue #350)
- PasswordField: Caps lock icon no longer painted over long text. (issue #172)
- PasswordField: Paint caps lock icon on left side in right-to-left component
orientation.
- Window decorations: Window title bar width is no longer considered when
calculating preferred/minimum width of window. (issue #351)
## 1.3
#### New features and improvements
- TextComponents, ComboBox and Spinner: Support different background color when
component is focused (use UI values `TextField.focusedBackground`,
`PasswordField.focusedBackground`, `FormattedTextField.focusedBackground`,
`TextArea.focusedBackground`, `TextPane.focusedBackground`,
`EditorPane.focusedBackground`, `ComboBox.focusedBackground`,
`ComboBox.buttonFocusedBackground`, `ComboBox.popupBackground` and
`Spinner.focusedBackground`). (issue #335)
#### Fixed bugs
- Fixed white lines at bottom and right side of window (in dark themes on HiDPI
screens with scaling enabled).
- ScrollBar: Fixed left/top arrow icon location (if visible). (issue #329)
- Spinner: Fixed up/down arrow icon location.
- ToolTip: Fixed positioning of huge tooltips. (issue #333)
## 1.2
#### New features and improvements
- Renamed `Flat*Laf.install()` methods to `Flat*Laf.setup()` to avoid confusion
with `UIManager.installLookAndFeel(LookAndFeelInfo info)`. The old
`Flat*Laf.install()` methods are still there, but marked as deprecated. They
will be removed in a future version.
- Button and ToggleButton: Support borderless button style (set client property
`JButton.buttonType` to `borderless`). (PR #276)
- ComboBox: Support using as cell renderer (e.g. in `JTable`).
- DesktopPane: Improved layout of iconified internal frames in dock:
- Always placed at bottom-left in desktop pane.
- Newly iconified frames are added to the right side of the dock.
- If frame is deiconified, dock is compacted (icons move to the left).
- If dock is wider than desktop width, additional rows are used.
- If desktop pane is resized, layout of dock is updated.
- TableHeader: Moved table header column border painting from
`FlatTableHeaderUI` to new border `FlatTableHeaderBorder` to improve
compatibility with custom table header implementations. (issue #228)
- Linux: Enable text anti-aliasing if no Gnome or KDE Desktop properties are
available. (issue #218)
- IntelliJ Themes: Added "Material Theme UI Lite / GitHub Dark" theme.
- JIDE Common Layer: Improved support for `JideTabbedPane`. (PR #306)
- Extras: `FlatSVGIcon` improvements:
- Each icon can now have its own color filter. (PR #303)
- Use mapper function in color filter to dynamically map colors. (PR #303)
- Color filter supports light and dark themes.
- Getters for icon name, classloader, etc.
- Extras: UI Inspector: Show class hierarchies when pressing <kbd>Alt</kbd> key
and prettified class names (dimmed package name).
- Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single
multi-resolution image that creates requested image sizes on demand from SVG
(only on Windows with Java 9+).
#### Fixed bugs
- CheckBox and RadioButton: Do not fill background if used as cell renderer,
except if cell is selected or has different background color. (issue #311)
- DesktopPane:
- Fixed missing preview of iconified internal frames in dock when using a
custom desktop manager. (PR #294)
- Fixed incomplete preview of iconified internal frames in dock when switching
LaF.
- On HiDPI screens, use high-resolution images for preview of iconified
internal frames in dock.
- PopupFactory: Fixed occasional `NullPointerException` in
`FlatPopupFactory.fixToolTipLocation()`. (issue #305)
- Tree: Fill cell background if
`DefaultTreeCellRenderer.setBackgroundNonSelectionColor(Color)` was used.
(issue #322)
- IntelliJ Themes: Fixed background colors of DesktopPane and DesktopIcon in all
themes.
- Native window decorations:
- Fixed slow application startup under particular conditions. (e.g. incomplete
custom JRE) (issue #319)
- Fixed occasional double window title bar when creating many frames or
dialogs. (issue #315)
- Fixed broken maximizing window (under special conditions) when restoring
frame state at startup.
- Title icon: For multi-resolution images now use `getResolutionVariant(width,
height)` (instead of `getResolutionVariants()`) to allow creation of
requested size on demand. This also avoids creation of all resolution
variants.
- Double-click at upper-left corner of maximized frame did not close window.
(issue #326)
- Linux: Fixed/improved detection of user font settings. (issue #309)
## 1.1.2
#### New features and improvements
- Native window decorations: Added API to check whether current platform
supports window decorations (`FlatLaf.supportsNativeWindowDecorations()`) and
to toggle window decorations of all windows
(`FlatLaf.setUseNativeWindowDecorations(boolean)`).
- Native window decorations: Support changing title bar background and
foreground colors per window. (set client properties
`JRootPane.titleBarBackground` and `JRootPane.titleBarForeground` on root pane
to a `java.awt.Color`).
#### Fixed bugs
- Native window decorations: Fixed loading of native library when using Java
Platform Module System (JPMS) for application. (issue #289)
- Native window decorations: Removed superfluous pixel-line at top of screen
when window is maximized. (issue #296)
- Window decorations: Fixed random window title bar background in cases were
background is not filled by custom window or root pane components and unified
background is enabled.
- IntelliJ Themes: Fixed window title bar background if unified background is
enabled.
- IntelliJ Themes: Fixed system colors.
- Button and ToggleButton: Do not paint background of disabled (and unselected)
toolBar buttons. (issue #292; regression since fixing #112)
- ComboBox and Spinner: Fixed too wide arrow button if component is higher than
preferred. (issue #302)
- SplitPane: `JSplitPane.setContinuousLayout(false)` did not work. (issue #301)
- TabbedPane: Fixed NPE when creating/modifying in another thread. (issue #299)
- Fixed crash when running in Webswing. (issue #290)
## 1.1.1
#### New features and improvements
- Native window decorations: Support disabling native window decorations per
window. (set client property `JRootPane.useWindowDecorations` on root pane to
`false`).
- Support running on WinPE. (issue #279)
#### Fixed bugs
- Native window decorations: Fixed missing animations when minimizing,
maximizing or restoring a window using window title bar buttons. (issue #282)
- Native window decorations: Fixed broken maximizing window when restoring frame
state at startup. (issue #283)
- Native window decorations: Fixed double window title bar when first disposing
a window with `frame.dispose()` and then showing it again with
`frame.setVisible(true)`. (issue #277)
- Custom window decorations: Fixed NPE in `FlatTitlePane.findHorizontalGlue()`.
(issue #275)
- Custom window decorations: Fixed right aligned progress bar in embedded menu
bar was overlapping window title. (issue #272)
- Fixed missing focus indicators in heavy-weight popups. (issue #273)
- InternalFrame: Fixed translucent internal frame menu bar background if
`TitlePane.unifiedBackground` is `true`. (issue #274)
- Extras: UI Inspector: Fixed `InaccessibleObjectException` when running in Java 16.
## 1.1
#### New features and improvements
@@ -102,7 +773,7 @@ FlatLaf Change Log
- CheckBox and RadioButton: Fill component background as soon as background
color is different to default background color, even if component is not
opaque (which is the default). This paints selection if using the component as
cell renderer a Table, Tree or List.
cell renderer in Table, Tree or List.
- TextComponents: Border of focused non-editable text components had wrong
color.
- Custom window decorations: Fixed top window border in dark themes when running

View File

@@ -11,9 +11,9 @@ 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 almost the same colors and icons.
![Flat Light](images/flat_light.png)
![FlatLaf Light](images/flat_light.png)
![Flat Dark](images/flat_dark.png)
![FlatLaf Dark](images/flat_dark.png)
IntelliJ Platform Themes
@@ -67,20 +67,23 @@ docs).
Addons
------
- [IntelliJ Themes Pack](flatlaf-intellij-themes)
- [Extras](flatlaf-extras)
- [SwingX](flatlaf-swingx)
- [JIDE Common Layer](flatlaf-jide-oss)
- [IntelliJ Themes Pack](flatlaf-intellij-themes) - bundles many popular
open-source 3rd party themes
- [Extras](flatlaf-extras) - SVG icon, tri-state check box, UI inspectors, and
more
- [SwingX](flatlaf-swingx) - support for SwingX components
- [JIDE Common Layer](flatlaf-jide-oss) - support for JIDE Common Layer
components
Getting started
---------------
To enable FlatLaf, add following code to your main method before you create any
To use FlatLaf, add following code to your main method before you create any
Swing component:
~~~java
FlatLightLaf.install();
FlatLightLaf.setup();
// create UI here...
~~~
@@ -96,20 +99,40 @@ For more information and documentation visit
- [Customizing](https://www.formdev.com/flatlaf/customizing/)
- [How to Customize](https://www.formdev.com/flatlaf/how-to-customize/)
- [Properties Files](https://www.formdev.com/flatlaf/properties-files/)
- [Components UI Properties](https://www.formdev.com/flatlaf/components/)
- [Typography](https://www.formdev.com/flatlaf/typography/)
- [Client Properties](https://www.formdev.com/flatlaf/client-properties/)
- [System Properties](https://www.formdev.com/flatlaf/system-properties/)
Theme Editor
------------
The Theme Editor that supports editing FlatLaf theme properties files. See
[Theme Editor documentation](https://www.formdev.com/flatlaf/theme-editor/) for
details and downloads.
![Theme Editor](images/theme-editor@1.5x.png)
Buzz
----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)
Applications using FlatLaf
--------------------------
- ![New](images/new.svg) [Ultorg](https://www.ultorg.com/) (**commercial**) - a
visual query system for relational databases
- ![New](images/new.svg) [MooInfo](https://github.com/rememberber/MooInfo) -
visual implementation of OSHI, to view information about the system and
hardware
- ![New](images/new.svg) [Jailer](https://github.com/Wisser/Jailer) 11.2 -
database subsetting and relational data browsing tool
- [Apache NetBeans](https://netbeans.apache.org/) 11.3 - IDE for Java, PHP, HTML
and much more
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
@@ -137,13 +160,16 @@ Applications using FlatLaf
[OpenStreetMap](https://www.openstreetmap.org/) (requires FlatLaf JOSM plugin)
- [jAlbum](https://jalbum.net/) 21 (**commercial**) - creates photo album
websites
- ![New](images/new.svg) [PDF Studio](https://www.qoppa.com/pdfstudio/) 2021
(**commercial**) - create, review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (**commercial**)
- [Total Validator](https://www.totalvalidator.com/) 15 (**commercial**) -
checks your website
- [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- [MegaMek](https://github.com/MegaMek/megamek) v0.47.4 and
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5 - a turn-based sci-fi board
game
- [MegaMek](https://github.com/MegaMek/megamek),
[MegaMekLab](https://github.com/MegaMek/megameklab) and
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5+ - a sci-fi tabletop
BattleTech simulator suite handling battles, unit building, and campaigns
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder)
0.13.b024 - GUI builder for
[GUIslice](https://github.com/ImpulseAdventure/GUIslice), a lightweight GUI
@@ -169,7 +195,7 @@ Applications using FlatLaf
systems development platform
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
encryption
- [Jes - Die Java-E<EFBFBD>R](https://www.jes-eur.de)
- [Jes - Die Java-EÜR](https://www.jes-eur.de)
- [Mapton](https://mapton.org/) 2.0
([source code](https://github.com/trixon/mapton)) - some kind of map
application (based on NetBeans platform)

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
val releaseVersion = "1.1"
val developmentVersion = "1.2-SNAPSHOT"
val releaseVersion = "2.6"
val developmentVersion = "3.0-SNAPSHOT"
version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion
@@ -37,6 +37,9 @@ println( "----------------------------------------------------------------------
println( "FlatLaf Version: ${version}" )
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
println( "Java ${System.getProperty( "java.version" )}" )
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() )
println( "Java toolchain ${toolchainJavaVersion}" )
println()
@@ -47,19 +50,35 @@ allprojects {
targetCompatibility = "1.8"
options.encoding = "ISO-8859-1"
options.isDeprecation = false
}
withType<Jar>().configureEach {
// manifest for all created JARs
manifest.attributes(mapOf(
manifest.attributes(
"Implementation-Vendor" to "FormDev Software GmbH",
"Implementation-Copyright" to "Copyright (C) 2019-${java.time.LocalDate.now().year} FormDev Software GmbH. All rights reserved.",
"Implementation-Version" to project.version))
"Implementation-Version" to project.version
)
// add META-INF/LICENSE to all created JARs
from("${rootDir}/LICENSE") {
into("META-INF")
from( "${rootDir}/LICENSE" ) {
into( "META-INF" )
}
}
withType<Javadoc>().configureEach {
options {
this as StandardJavadocDocletOptions
title = "${project.name} $version"
header = title
isUse = true
tags = listOf( "uiDefault", "clientProperty" )
addStringOption( "Xdoclint:all,-missing", "-Xdoclint:all,-missing" )
links( "https://docs.oracle.com/en/java/javase/11/docs/api/" )
}
isFailOnError = false
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
`cpp-library`
}
library {
// disable debuggable for release builds to make shared libraries smaller
binaries.configureEach( CppSharedLibrary::class ) {
with( compileTask.get() ) {
if( name.contains( "Release" ) )
isDebuggable = false
}
with( linkTask.get() ) {
if( name.contains( "Release" ) )
debuggable.set( false )
}
}
}
tasks {
withType<CppCompile>().configureEach {
doFirst {
println( "Used Tool Chain:" )
println( " - ${toolChain.get()}" )
println( "Available Tool Chains:" )
toolChains.forEach {
println( " - $it" )
}
}
}
}

View File

@@ -28,7 +28,7 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
}
dependencies {
add( "java9Compile", sourceSets.main.get().output )
add( "java9Implementation", sourceSets.main.get().output )
}
tasks {

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
open class JniHeadersExtension {
var headers: List<String> = emptyList()
}
val extension = project.extensions.create<JniHeadersExtension>( "flatlafJniHeaders" )
tasks {
register<Copy>( "jni-headers" ) {
// depend on :flatlaf-core:compileJava because it generates the JNI headers
dependsOn( ":flatlaf-core:compileJava" )
from( project( ":flatlaf-core" ).buildDir.resolve( "generated/jni-headers" ) )
into( "src/main/headers" )
include( extension.headers )
filter<org.apache.tools.ant.filters.FixCrLfFilter>(
"eol" to org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance( "lf" )
)
}
}

View File

@@ -61,12 +61,8 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
}
jar {
manifest.attributes( "Multi-Release" to "true" )
into( "META-INF/versions/9" ) {
from( sourceSets["module-info"].output ) {
include( "module-info.class" )
}
from( sourceSets["module-info"].output ) {
include( "module-info.class" )
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
java
}
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() ) {
java.toolchain {
languageVersion.set( JavaLanguageVersion.of( toolchainJavaVersion ) )
}
}

View File

@@ -16,11 +16,23 @@
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-java9`
`flatlaf-publish`
}
val sigtest = configurations.create( "sigtest" )
dependencies {
testImplementation( "org.junit.jupiter:junit-jupiter-api:5.7.2" )
testImplementation( "org.junit.jupiter:junit-jupiter-params" )
testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine" )
// https://github.com/jtulach/netbeans-apitest
sigtest( "org.netbeans.tools:sigtest-maven-plugin:1.7" )
}
java {
withSourcesJar()
withJavadocJar()
@@ -32,11 +44,6 @@ tasks {
options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) )
}
processResources {
// build native libraries
dependsOn( ":flatlaf-natives-windows:assemble" )
}
jar {
archiveBaseName.set( "flatlaf" )
@@ -45,22 +52,70 @@ tasks {
}
}
javadoc {
options {
this as StandardJavadocDocletOptions
use( true )
tags = listOf( "uiDefault", "clientProperty" )
addStringOption( "Xdoclint:all,-missing", "-Xdoclint:all,-missing" )
named<Jar>( "sourcesJar" ) {
archiveBaseName.set( "flatlaf" )
}
named<Jar>( "javadocJar" ) {
archiveBaseName.set( "flatlaf" )
}
check {
dependsOn( "sigtestCheck" )
}
test {
useJUnitPlatform()
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
jvmArgs( listOf( "--add-opens", "java.desktop/javax.swing.plaf.basic=ALL-UNNAMED" ) )
}
register( "sigtestGenerate" ) {
group = "verification"
dependsOn( "jar" )
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "generate",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
}
}
isFailOnError = false
}
named<Jar>("sourcesJar" ) {
archiveBaseName.set( "flatlaf" )
}
register( "sigtestCheck" ) {
group = "verification"
dependsOn( "jar" )
named<Jar>("javadocJar" ) {
archiveBaseName.set( "flatlaf" )
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "check",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -39,8 +39,9 @@ public interface FlatClientProperties
* {@link #BUTTON_TYPE_SQUARE},
* {@link #BUTTON_TYPE_ROUND_RECT},
* {@link #BUTTON_TYPE_TAB},
* {@link #BUTTON_TYPE_HELP} or
* {@link BUTTON_TYPE_TOOLBAR_BUTTON}
* {@link #BUTTON_TYPE_HELP},
* {@link #BUTTON_TYPE_TOOLBAR_BUTTON} or
* {@link #BUTTON_TYPE_BORDERLESS}
*/
String BUTTON_TYPE = "JButton.buttonType";
@@ -89,6 +90,16 @@ public interface FlatClientProperties
*/
String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarButton";
/**
* Paint the button without a border in the unfocused state.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
*
* @see #BUTTON_TYPE
* @since 1.2
*/
String BUTTON_TYPE_BORDERLESS = "borderless";
/**
* Specifies selected state of a checkbox.
* <p>
@@ -115,6 +126,57 @@ public interface FlatClientProperties
//---- JComponent ---------------------------------------------------------
/**
* Specifies the style of a component as String in CSS syntax ("key1: value1; key2: value2; ...")
* or as {@link java.util.Map}&lt;String, Object&gt; with binary values.
* <p>
* The keys are the same as used in UI defaults, but without component type prefix.
* E.g. for UI default {@code Slider.thumbSize} use key {@code thumbSize}.
* <p>
* The syntax of the CSS values is the same as used in FlatLaf properties files
* (<a href="https://www.formdev.com/flatlaf/properties-files/">https://www.formdev.com/flatlaf/properties-files/</a>),
* but some features are not supported (e.g. variables).
* When using a map, the values are not parsed from a string. They must be binary.
* <p>
* <strong>Components</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.String} or {@link java.util.Map}&lt;String, Object&gt;<br>
*
* @since 2
*/
String STYLE = "FlatLaf.style";
/**
* Specifies the style class(es) of a component as String (single class or multiple classes separated by space characters)
* or as {@code String[]} or {@link java.util.List}&lt;String&gt; (multiple classes).
* <p>
* The style rules must be defined in UI defaults either as strings (in CSS syntax)
* or as {@link java.util.Map}&lt;String, Object&gt; (with binary values).
* The key must be in syntax: {@code [style]type.styleClass}, where the type is optional.
* E.g. in FlatLaf properties file:
* <pre>{@code
* [style]Button.primary = borderColor: #08f; background: #08f; foreground: #fff
* [style].secondary = borderColor: #0f8; background: #0f8
* }</pre>
* or in Java code:
* <pre>{@code
* UIManager.put( "[style]Button.primary", "borderColor: #08f; background: #08f; foreground: #fff" );
* UIManager.put( "[style].secondary", "borderColor: #0f8; background: #0f8" );
* }</pre>
* The rule "Button.primary" can be applied to buttons only.
* The rule ".secondary" can be applied to any component.
* <p>
* To have similar behavior as in CSS, first the rule without type is applied,
* then the rule with type.
* E.g. setting style class to "foo" on a {@code JButton} uses rules
* from UI default keys "[style].foo" and "[style]Button.foo".
* <p>
* <strong>Components</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.String}, {@code String[]} or {@link java.util.List}&lt;String&gt;<br>
*
* @since 2
*/
String STYLE_CLASS = "FlatLaf.styleClass";
/**
* Specifies minimum width of a component.
* <p>
@@ -191,6 +253,19 @@ public interface FlatClientProperties
*/
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";
/**
* Specifies whether a component in an embedded menu bar should behave as caption
* (left-click allows moving window, right-click shows window system menu).
* The component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events.
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2.5
*/
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
//---- Popup --------------------------------------------------------------
/**
@@ -232,14 +307,85 @@ public interface FlatClientProperties
//---- JRootPane ----------------------------------------------------------
/**
* Specifies whether the menu bar is embedded into the title pane if custom
* window decorations are enabled. Default is {@code true}.
* Specifies whether FlatLaf native window decorations should be used
* for {@code JFrame} or {@code JDialog}.
* <p>
* Setting this enables/disables using FlatLaf native window decorations
* for the window that contains the root pane.
* <p>
* This client property has lower priority than system property
* {@link FlatSystemProperties#USE_WINDOW_DECORATIONS}, but higher priority
* than UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 1.1.1
*/
String USE_WINDOW_DECORATIONS = "JRootPane.useWindowDecorations";
/**
* Specifies whether the menu bar is embedded into the window title pane
* if window decorations are enabled.
* <p>
* Setting this enables/disables embedding
* for the window that contains the root pane.
* <p>
* This client property has lower priority than system property
* {@link FlatSystemProperties#MENUBAR_EMBEDDED}, but higher priority
* than UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations).
* <p>
* Setting this shows/hides the windows icon
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* This client property has higher priority than UI default {@code TitlePane.showIcon}.
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2
*/
String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon";
/**
* Background color of window title bar (requires enabled window decorations).
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Color}
*
* @since 1.1.2
*/
String TITLE_BAR_BACKGROUND = "JRootPane.titleBarBackground";
/**
* Foreground color of window title bar (requires enabled window decorations).
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Color}
*
* @since 1.1.2
*/
String TITLE_BAR_FOREGROUND = "JRootPane.titleBarForeground";
//---- JScrollBar / JScrollPane -------------------------------------------
/**
@@ -258,8 +404,71 @@ public interface FlatClientProperties
*/
String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling";
//---- JSplitPane ---------------------------------------------------------
/**
* Specifies what side of the spilt pane is allowed to expand
* via one-touch expanding arrow buttons.
* Requires that one-touch expanding is enabled with
* {@link javax.swing.JSplitPane#setOneTouchExpandable(boolean)}.
* <p>
* <strong>Component</strong> {@link javax.swing.JSplitPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #SPLIT_PANE_EXPANDABLE_SIDE_LEFT} or
* {@link #SPLIT_PANE_EXPANDABLE_SIDE_RIGHT}
*
* @since 2.2
*/
String SPLIT_PANE_EXPANDABLE_SIDE = "JSplitPane.expandableSide";
/**
* Allow expanding only left/top side of the split pane.
*
* @see #SPLIT_PANE_EXPANDABLE_SIDE
* @since 2.2
*/
String SPLIT_PANE_EXPANDABLE_SIDE_LEFT = "left";
/**
* Allow expanding only right/bottom side of the split pane.
*
* @see #SPLIT_PANE_EXPANDABLE_SIDE
* @since 2.2
*/
String SPLIT_PANE_EXPANDABLE_SIDE_RIGHT = "right";
//---- JTabbedPane --------------------------------------------------------
/**
* Specifies type of the selected tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #TABBED_PANE_TAB_TYPE_UNDERLINED} or
* {@link #TABBED_PANE_TAB_TYPE_CARD}
*
* @since 2
*/
String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType";
/**
* Paint the selected tab underlined.
*
* @see #TABBED_PANE_TAB_TYPE
* @since 2
*/
String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined";
/**
* Paint the selected tab as card.
*
* @see #TABBED_PANE_TAB_TYPE
* @since 2
*/
String TABBED_PANE_TAB_TYPE_CARD = "card";
/**
* Specifies whether separators are shown between tabs.
* <p>
@@ -601,9 +810,9 @@ public interface FlatClientProperties
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
* <p>
* For top and bottom tab placement, the layed out component size will be
* For top and bottom tab placement, the laid out component size will be
* the preferred component width and the tab area height.<br>
* For left and right tab placement, the layed out component size will be
* For left and right tab placement, the laid out component size will be
* the tab area width and the preferred component height.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
@@ -614,9 +823,9 @@ public interface FlatClientProperties
/**
* Specifies a component that will be placed at the trailing edge of the tabs area.
* <p>
* For top and bottom tab placement, the layed out component size will be
* For top and bottom tab placement, the laid out component size will be
* the available horizontal space (minimum is preferred component width) and the tab area height.<br>
* For left and right tab placement, the layed out component size will be
* For left and right tab placement, the laid out component size will be
* the tab area width and the available vertical space (minimum is preferred component height).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
@@ -669,10 +878,149 @@ public interface FlatClientProperties
*/
String PLACEHOLDER_TEXT = "JTextField.placeholderText";
/**
* Specifies the padding of the text.
* This changes the location and size of the text view within the component bounds,
* but does not affect the size of the component.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.awt.Insets}
*
* @since 1.4
*/
String TEXT_FIELD_PADDING = "JTextField.padding";
/**
* Specifies an icon that will be placed at the leading edge of the text field.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.Icon}
*
* @since 2
*/
String TEXT_FIELD_LEADING_ICON = "JTextField.leadingIcon";
/**
* Specifies an icon that will be placed at the trailing edge of the text field.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.Icon}
*
* @since 2
*/
String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon";
/**
* Specifies a component that will be placed at the leading edge of the text field.
* <p>
* The component will be positioned inside and aligned to the visible text field border.
* There is no gap between the visible border and the component.
* The laid out component size will be the preferred component width
* and the inner text field height.
* <p>
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #OUTLINE}).
* <p>
* The component is prepared in the following way:
* <ul>
* <li>Component client property {@link #STYLE_CLASS} is set to {@code inTextField}.
* <li>If component is a button or toggle button, client property {@link #BUTTON_TYPE}
* is set to {@link #BUTTON_TYPE_TOOLBAR_BUTTON}
* and button cursor is set to default cursor (if not set).
* <li>If component is a toolbar, client property {@link #STYLE_CLASS}
* is set to {@code inTextField} on all toolbar children
* and toolbar cursor is set to default cursor (if not set).
* </ul>
* Because text fields use the text cursor by default and the cursor is inherited by child components,
* it may be necessary to explicitly set component cursor if you e.g. need the default arrow cursor.
* E.g. {@code comp.setCursor( Cursor.getDefaultCursor() )}.
* <p>
* Styling is used to modify insets/margins and appearance of buttons and toolbars
* so that they fit nicely into the text field and do not increase text field height.
* See styles {@code [style]Button.inTextField} and {@code [style]ToolBar.inTextField}
* in {@code Flat[Light|Dark]Laf.properties}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.JComponent}
*
* @since 2
*/
String TEXT_FIELD_LEADING_COMPONENT = "JTextField.leadingComponent";
/**
* Specifies a component that will be placed at the trailing edge of the text field.
* <p>
* See {@link #TEXT_FIELD_LEADING_COMPONENT} for details.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.JComponent}
*
* @since 2
*/
String TEXT_FIELD_TRAILING_COMPONENT = "JTextField.trailingComponent";
/**
* Specifies whether a "clear" (or "cancel") button is shown on the trailing side
* if the text field is not empty, editable and enabled. Default is {@code false}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2
*/
String TEXT_FIELD_SHOW_CLEAR_BUTTON = "JTextField.showClearButton";
/**
* Specifies the callback that is invoked when a "clear" (or "cancel") button is clicked.
* If a callback is specified than it is responsible for clearing the text field.
* Without callback, the text field clears itself.
* <p>
* Either use a {@link java.lang.Runnable}:
* <pre>{@code
* myTextField.putClientProperty( "JTextField.clearCallback",
* (Runnable) () -> {
* // clear field here or cancel search
* } );
* }</pre>
* Or use a {@link java.util.function.Consumer}&lt;javax.swing.text.JTextComponent&gt;
* that receives the text field as parameter:
* <pre>{@code
* myTextField.putClientProperty( "JTextField.clearCallback",
* (Consumer<JTextComponent>) textField -> {
* // clear field here or cancel search
* } );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.Runnable}
* or {@link java.util.function.Consumer}&lt;javax.swing.text.JTextComponent&gt;
*
* @see #TEXT_FIELD_SHOW_CLEAR_BUTTON
* @since 2
*/
String TEXT_FIELD_CLEAR_CALLBACK = "JTextField.clearCallback";
//---- JToggleButton ------------------------------------------------------
/**
* Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* Placement of underline if toggle button type is {@link #BUTTON_TYPE_TAB}
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
* <strong>SupportedValues:</strong>
* {@link SwingConstants#BOTTOM} (default)
* {@link SwingConstants#TOP},
* {@link SwingConstants#LEFT} or
* {@link SwingConstants#RIGHT}
*
* @since 2.3
*/
String TAB_BUTTON_UNDERLINE_PLACEMENT = "JToggleButton.tab.underlinePlacement";
/**
* Thickness of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}
@@ -737,8 +1085,7 @@ public interface FlatClientProperties
* If the client property is not set, or not a {@link Boolean}, defaultValue is returned.
*/
static Boolean clientPropertyBooleanStrict( JComponent c, String key, Boolean defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Boolean) ? (Boolean) value : defaultValue;
return clientProperty( c, key, defaultValue, Boolean.class );
}
/**
@@ -755,7 +1102,18 @@ public interface FlatClientProperties
* If the client property is not set, or not a color, defaultValue is returned.
*/
static Color clientPropertyColor( JComponent c, String key, Color defaultValue ) {
return clientProperty( c, key, defaultValue, Color.class );
}
/**
* Returns the value of the specified client property if it is an instance of
* the specified type. Otherwise, defaultValue is returned.
*
* @since 2
*/
@SuppressWarnings( "unchecked" )
static <T> T clientProperty( JComponent c, String key, T defaultValue, Class<T> type ) {
Object value = c.getClientProperty( key );
return (value instanceof Color) ? (Color) value : defaultValue;
return type.isInstance( value ) ? (T) value : defaultValue;
}
}

View File

@@ -16,6 +16,8 @@
package com.formdev.flatlaf;
import javax.swing.UIManager;
/**
* A Flat LaF that has a dark color scheme and looks like Darcula LaF.
* <p>
@@ -29,10 +31,30 @@ public class FlatDarculaLaf
{
public static final String NAME = "FlatLaf Darcula";
public static boolean install() {
return install( new FlatDarculaLaf() );
/**
* Sets the application look and feel to this LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
*
* @since 1.2
*/
public static boolean setup() {
return setup( new FlatDarculaLaf() );
}
/**
* @deprecated use {@link #setup()} instead; this method will be removed in a future version
*/
@Deprecated
public static boolean install() {
return setup();
}
/**
* Adds this look and feel to the set of available look and feels.
* <p>
* Useful if your application uses {@link UIManager#getInstalledLookAndFeels()}
* to query available LaFs and display them to the user in a combobox.
*/
public static void installLafInfo() {
installLafInfo( NAME, FlatDarculaLaf.class );
}

View File

@@ -33,9 +33,19 @@ public class FlatDarkLaf
/**
* Sets the application look and feel to this LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
*
* @since 1.2
*/
public static boolean setup() {
return setup( new FlatDarkLaf() );
}
/**
* @deprecated use {@link #setup()} instead; this method will be removed in a future version
*/
@Deprecated
public static boolean install() {
return install( new FlatDarkLaf() );
return setup();
}
/**

View File

@@ -26,7 +26,7 @@ import javax.swing.UIDefaults;
* 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
* If you extend this class in an 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>
@@ -61,7 +61,7 @@ public abstract class FlatDefaultsAddon
/**
* Returns the priority used to sort addon loading.
* The order is only important if you want overwrite UI defaults of other addons.
* The order is only important if you want to overwrite UI defaults of other addons.
* Lower numbers mean higher priority.
* Returns 10000 by default.
*/

View File

@@ -19,7 +19,7 @@ package com.formdev.flatlaf;
/**
* Default color palette for action icons and object icons.
* <p>
* The idea is to use only this well defined set of colors in SVG icons and
* The idea is to use only this well-defined set of colors in SVG icons, and
* then they are replaced at runtime to dark variants or to other theme colors.
* Then a single SVG icon (light variant) can be used for dark themes too.
* IntelliJ Platform uses this mechanism to allow themes to change IntelliJ Platform icons.
@@ -35,7 +35,7 @@ package com.formdev.flatlaf;
* <p>
* You may use these colors also in your application (outside of SVG icons), but do
* not use the RGB values defined in this enum.<br>
* Instead use {@code UIManager.getColor( FlatIconColors.ACTIONS_GREY.key )}.
* Instead, use {@code UIManager.getColor( FlatIconColors.ACTIONS_GREY.key )}.
*
* @author Karl Tauber
*/

View File

@@ -596,7 +596,7 @@ class FlatInputMaps
//---- class LazyInputMapEx -----------------------------------------------
/**
* Lazily creates a input map.
* Lazily creates an input map.
* Similar to {@link UIDefaults.LazyInputMap}, but can use multiple bindings arrays.
*/
private static class LazyInputMapEx

View File

@@ -16,6 +16,8 @@
package com.formdev.flatlaf;
import javax.swing.UIManager;
/**
* A Flat LaF that has a light color scheme and looks like IntelliJ LaF.
* <p>
@@ -29,10 +31,30 @@ public class FlatIntelliJLaf
{
public static final String NAME = "FlatLaf IntelliJ";
public static boolean install() {
return install( new FlatIntelliJLaf() );
/**
* Sets the application look and feel to this LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
*
* @since 1.2
*/
public static boolean setup() {
return setup( new FlatIntelliJLaf() );
}
/**
* @deprecated use {@link #setup()} instead; this method will be removed in a future version
*/
@Deprecated
public static boolean install() {
return setup();
}
/**
* Adds this look and feel to the set of available look and feels.
* <p>
* Useful if your application uses {@link UIManager#getInstalledLookAndFeels()}
* to query available LaFs and display them to the user in a combobox.
*/
public static void installLafInfo() {
installLafInfo( NAME, FlatIntelliJLaf.class );
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,9 +33,19 @@ public class FlatLightLaf
/**
* Sets the application look and feel to this LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
*
* @since 1.2
*/
public static boolean setup() {
return setup( new FlatLightLaf() );
}
/**
* @deprecated use {@link #setup()} instead; this method will be removed in a future version
*/
@Deprecated
public static boolean install() {
return install( new FlatLightLaf() );
return setup();
}
/**

View File

@@ -16,6 +16,9 @@
package com.formdev.flatlaf;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.util.UIScale;
/**
* Defines/documents own system properties used in FlatLaf.
*
@@ -32,6 +35,8 @@ public interface FlatSystemProperties
* To replace the Java 9+ system scale factor, use system property "sun.java2d.uiScale",
* which has the same syntax as this one.
* <p>
* Since FlatLaf 1.1.2: Scale factors less than 100% are allowed.
* <p>
* <strong>Allowed Values</strong> e.g. {@code 1.5}, {@code 1.5x}, {@code 150%} or {@code 144dpi} (96dpi is 100%)<br>
*/
String UI_SCALE = "flatlaf.uiScale";
@@ -44,6 +49,17 @@ public interface FlatSystemProperties
*/
String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled";
/**
* Specifies whether values smaller than 100% are allowed for the user scale factor
* (see {@link UIScale#getUserScaleFactor()}).
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false}
*
* @since 1.1.2
*/
String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown";
/**
* Specifies whether Ubuntu font should be used on Ubuntu Linux.
* By default, if not running in a JetBrains Runtime, the Liberation Sans font
@@ -55,15 +71,18 @@ public interface FlatSystemProperties
String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont";
/**
* Specifies whether FlatLaf native window decorations should be used
* Specifies whether native window decorations should be used
* when creating {@code JFrame} or {@code JDialog}.
* <p>
* Setting this to {@code true} forces using FlatLaf native window decorations
* even if they are not enabled by the application.
* Setting this to {@code true} forces using native window decorations
* even if they are not enabled by the application.<br>
* Setting this to {@code false} disables using native window decorations.
* <p>
* Setting this to {@code false} disables using FlatLaf native window decorations.
* This system property has higher priority than client property
* {@link FlatClientProperties#USE_WINDOW_DECORATIONS} and
* UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Window 10)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -74,28 +93,34 @@ public interface FlatSystemProperties
* Specifies whether JetBrains Runtime custom window decorations should be used
* when creating {@code JFrame} or {@code JDialog}.
* Requires that the application runs in a
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime</a>
* <a href="https://github.com/JetBrains/JetBrainsRuntime/wiki">JetBrains Runtime</a>
* (based on OpenJDK).
* <p>
* Setting this to {@code true} forces using JetBrains Runtime custom window decorations
* even if they are not enabled by the application.
* <p>
* Setting this to {@code false} disables using JetBrains Runtime custom window decorations.
* Then FlatLaf native window decorations are used.
* <p>
* (requires Window 10)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*/
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/**
* Specifies whether menubar is embedded into custom window decorations.
* Specifies whether the menu bar is embedded into the window title pane
* if window decorations are enabled.
* <p>
* (requires Window 10)
* Setting this to {@code true} forces embedding.<br>
* Setting this to {@code false} disables embedding.
* <p>
* This system property has higher priority than client property
* {@link FlatClientProperties#MENU_BAR_EMBEDDED} and
* UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
* <strong>Default</strong> none
*/
String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded";
@@ -115,6 +140,39 @@ public interface FlatSystemProperties
*/
String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection";
/**
* Specifies whether FlatLaf updates the UI when the system font changes.
* If {@code true}, {@link SwingUtilities#updateComponentTreeUI(java.awt.Component)}
* gets invoked for all windows if the system font has changed.
* This is the similar to when switching to another look and feel (theme).
* Applications that do not work correctly when switching look and feel,
* should disable this option to avoid corrupted UI.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
* @since 2.5
*/
String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange";
/**
* Specifies a directory in which the native FlatLaf libraries have been extracted.
* The path can be absolute or relative to current application working directory.
* This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p>
* If the value is {@code "system"}, then {@link System#loadLibrary(String)} is
* used to load the native library.
* Searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p>
* If the native library can not loaded from the given path (or via {@link System#loadLibrary(String)}),
* then the embedded native library is extracted to the temporary directory and loaded from there.
*
* @since 2
*/
String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath";
/**
* Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

View File

@@ -34,8 +34,10 @@ import javax.swing.UIDefaults;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.json.Json;
import com.formdev.flatlaf.json.ParseException;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* This class supports loading IntelliJ .theme.json files and using them as a Laf.
@@ -67,20 +69,30 @@ public class IntelliJTheme
/**
* Loads a IntelliJ .theme.json file from the given input stream,
* creates a Laf instance for it and installs it.
* creates a Laf instance for it and sets it up.
*
* The input stream is automatically closed.
* Using a buffered input stream is not necessary.
*
* @since 1.2
*/
public static boolean install( InputStream in ) {
public static boolean setup( InputStream in ) {
try {
return FlatLaf.install( createLaf( in ) );
return FlatLaf.setup( createLaf( in ) );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load IntelliJ theme", ex );
return false;
}
}
/**
* @deprecated use {@link #setup(InputStream)} instead; this method will be removed in a future version
*/
@Deprecated
public static boolean install( InputStream in ) {
return setup( in );
}
/**
* Loads a IntelliJ .theme.json file from the given input stream and
* creates a Laf instance for it.
@@ -153,8 +165,11 @@ public class IntelliJTheme
applyCheckBoxColors( defaults );
// copy values
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() )
defaults.put( e.getKey(), defaults.get( e.getValue() ) );
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() ) {
Object value = defaults.get( e.getValue() );
if( value != null )
defaults.put( e.getKey(), value );
}
// IDEA does not paint button background if disabled, but FlatLaf does
Object panelBackground = defaults.get( "Panel.background" );
@@ -166,7 +181,7 @@ public class IntelliJTheme
defaults.put( "Button.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
// IDEA uses a SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
// IDEA uses an SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
Object helpButtonBackground = defaults.get( "Button.startBackground" );
Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" );
if( helpButtonBackground == null )
@@ -215,6 +230,12 @@ public class IntelliJTheme
if( !uiKeys.contains( "ToggleButton.foreground" ) && uiKeys.contains( "Button.foreground" ) )
defaults.put( "ToggleButton.foreground", defaults.get( "Button.foreground" ) );
// fix DesktopPane background (use Panel.background and make it 5% darker/lighter)
Color desktopBackgroundBase = defaults.getColor( "Panel.background" );
Color desktopBackground = ColorFunctions.applyFunctions( desktopBackgroundBase,
new ColorFunctions.HSLIncreaseDecrease( 2, dark, 5, false, true ) );
defaults.put( "Desktop.background", new ColorUIResource( desktopBackground ) );
// fix List and Table background colors in Material UI Lite themes
if( isMaterialUILite ) {
defaults.put( "List.background", defaults.get( "Tree.background" ) );
@@ -227,23 +248,36 @@ public class IntelliJTheme
defaults.put( "Tree.rowHeight", 22 );
// apply theme specific UI defaults at the end to allow overwriting
defaults.putAll( themeSpecificDefaults );
for( Map.Entry<Object, Object> e : themeSpecificDefaults.entrySet() ) {
Object key = e.getKey();
Object value = e.getValue();
// append styles to existing styles
if( key instanceof String && ((String)key).startsWith( "[style]" ) ) {
Object oldValue = defaults.get( key );
if( oldValue != null )
value = oldValue + "; " + value;
}
defaults.put( key, value );
}
}
private Map<Object, Object> removeThemeSpecificDefaults( UIDefaults defaults ) {
// search for theme specific UI defaults keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) )
if( key instanceof String && ((String)key).startsWith( "[" ) && !((String)key).startsWith( "[style]" ) )
themeSpecificKeys.add( (String) key );
}
// remove theme specific UI defaults and remember only those for current theme
Map<Object, Object> themeSpecificDefaults = new HashMap<>();
String currentThemePrefix = '[' + name.replace( ' ', '_' ) + ']';
String currentThemeAndAuthorPrefix = '[' + name.replace( ' ', '_' ) + "---" + author.replace( ' ', '_' ) + ']';
String currentAuthorPrefix = "[author-" + author.replace( ' ', '_' ) + ']';
String allThemesPrefix = "[*]";
String[] prefixes = { currentThemePrefix, currentAuthorPrefix, allThemesPrefix };
String[] prefixes = { currentThemePrefix, currentThemeAndAuthorPrefix, currentAuthorPrefix, allThemesPrefix };
for( String key : themeSpecificKeys ) {
Object value = defaults.remove( key );
for( String prefix : prefixes ) {
@@ -283,8 +317,19 @@ public class IntelliJTheme
@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 );
Map<String, Object> map = (Map<String, Object>)value;
if( map.containsKey( "os.default" ) || map.containsKey( "os.windows" ) || map.containsKey( "os.mac" ) || map.containsKey( "os.linux" ) ) {
String osKey = SystemInfo.isWindows ? "os.windows"
: SystemInfo.isMacOS ? "os.mac"
: SystemInfo.isLinux ? "os.linux" : null;
if( osKey != null && map.containsKey( osKey ) )
apply( key, map.get( osKey ), defaults, defaultsKeysCache, uiKeys );
else if( map.containsKey( "os.default" ) )
apply( key, map.get( "os.default" ), defaults, defaultsKeysCache, uiKeys );
} else {
for( Map.Entry<String, Object> e : map.entrySet() )
apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
}
} else {
if( "".equals( value ) )
return; // ignore empty value
@@ -322,7 +367,7 @@ public class IntelliJTheme
// parse value
try {
uiValue = UIDefaultsLoader.parseValue( key, valueStr );
uiValue = UIDefaultsLoader.parseValue( key, valueStr, null );
} catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( key, valueStr, ex, false );
return; // ignore invalid value
@@ -344,6 +389,10 @@ public class IntelliJTheme
// replace all values in UI defaults that match the wildcard key
for( Object k : defaultsKeysCache ) {
if( k.equals( "Desktop.background" ) ||
k.equals( "DesktopIcon.background" ) )
continue;
if( k instanceof String ) {
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
@@ -484,7 +533,7 @@ public class IntelliJTheme
// for filled checkbox/radiobutton used in light themes
defaults.remove( "CheckBox.icon[filled].focusWidth" );
defaults.put( "CheckBox.icon[filled].hoverBorderColor", defaults.get( "CheckBox.icon[filled].focusedBorderColor" ) );
defaults.put( "CheckBox.icon[filled].selectedFocusedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
defaults.put( "CheckBox.icon[filled].focusedSelectedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
if( dark ) {
// IDEA Darcula checkBoxFocused.svg, checkBoxSelectedFocused.svg,
@@ -493,9 +542,9 @@ public class IntelliJTheme
// --> add alpha to focused border colors
String[] focusedBorderColorKeys = new String[] {
"CheckBox.icon.focusedBorderColor",
"CheckBox.icon.selectedFocusedBorderColor",
"CheckBox.icon.focusedSelectedBorderColor",
"CheckBox.icon[filled].focusedBorderColor",
"CheckBox.icon[filled].selectedFocusedBorderColor",
"CheckBox.icon[filled].focusedSelectedBorderColor",
};
for( String key : focusedBorderColorKeys ) {
Color color = defaults.getColor( key );
@@ -514,12 +563,12 @@ public class IntelliJTheme
}
/** Rename UI default keys (key --> value). */
private static Map<String, String> uiKeyMapping = new HashMap<>();
private static final Map<String, String> uiKeyMapping = new HashMap<>();
/** Copy UI default keys (value --> key). */
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 final Map<String, String> uiKeyCopying = new HashMap<>();
private static final Map<String, String> uiKeyInverseMapping = new HashMap<>();
private static final Map<String, String> checkboxKeyMapping = new HashMap<>();
private static final Map<String, String> checkboxDuplicateColors = new HashMap<>();
static {
// ComboBox
@@ -529,6 +578,8 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.disabledIconColor", "ComboBox.buttonDisabledArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
uiKeyCopying.put( "ComboBox.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "ComboBox.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// Component
uiKeyMapping.put( "Component.inactiveErrorFocusColor", "Component.error.borderColor" );
@@ -574,6 +625,15 @@ public class IntelliJTheme
uiKeyCopying.put( "Slider.thumbColor", "ProgressBar.foreground" );
uiKeyCopying.put( "Slider.trackColor", "ProgressBar.background" );
// Spinner
uiKeyCopying.put( "Spinner.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "Spinner.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// TabbedPane
uiKeyCopying.put( "TabbedPane.selectedBackground", "DefaultTabs.underlinedTabBackground" );
uiKeyCopying.put( "TabbedPane.selectedForeground", "DefaultTabs.underlinedTabForeground" );
uiKeyCopying.put( "TabbedPane.inactiveUnderlineColor", "DefaultTabs.inactiveUnderlineColor" );
// TitlePane
uiKeyCopying.put( "TitlePane.inactiveBackground", "TitlePane.background" );
uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
@@ -598,7 +658,7 @@ public class IntelliJTheme
checkboxKeyMapping.put( "Checkbox.Background.Selected", "CheckBox.icon.selectedBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Selected", "CheckBox.icon.selectedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Foreground.Selected", "CheckBox.icon.checkmarkColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Selected", "CheckBox.icon.selectedFocusedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Selected", "CheckBox.icon.focusedSelectedBorderColor" );
checkboxDuplicateColors.put( "Checkbox.Background.Default.Dark", "Checkbox.Background.Selected.Dark" );
checkboxDuplicateColors.put( "Checkbox.Border.Default.Dark", "Checkbox.Border.Selected.Dark" );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
@@ -28,7 +29,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
@@ -55,24 +56,38 @@ class LinuxFontPolicy
String family = "";
int style = Font.PLAIN;
int size = 10;
double dsize = 10;
// parse pango font description
// see https://developer.gnome.org/pango/1.46/pango-Fonts.html#pango-font-description-from-string
StringTokenizer st = new StringTokenizer( (String) fontName );
while( st.hasMoreTokens() ) {
String word = st.nextToken();
if( word.equalsIgnoreCase( "italic" ) )
// remove trailing ',' (e.g. in "Ubuntu Condensed, 11" or "Ubuntu Condensed, Bold 11")
if( word.endsWith( "," ) )
word = word.substring( 0, word.length() - 1 ).trim();
String lword = word.toLowerCase();
if( lword.equals( "italic" ) || lword.equals( "oblique" ) )
style |= Font.ITALIC;
else if( word.equalsIgnoreCase( "bold" ) )
else if( lword.equals( "bold" ) )
style |= Font.BOLD;
else if( Character.isDigit( word.charAt( 0 ) ) ) {
try {
size = Integer.parseInt( word );
dsize = Double.parseDouble( word );
} catch( NumberFormatException ex ) {
// ignore
}
} else
} else {
// remove '-' from "Semi-Bold", "Extra-Light", etc
if( lword.startsWith( "semi-" ) || lword.startsWith( "demi-" ) )
word = word.substring( 0, 4 ) + word.substring( 5 );
else if( lword.startsWith( "extra-" ) || lword.startsWith( "ultra-" ) )
word = word.substring( 0, 5 ) + word.substring( 6 );
family = family.isEmpty() ? word : (family + ' ' + word);
}
}
// Ubuntu font is rendered poorly (except if running in JetBrains VM)
@@ -83,8 +98,8 @@ class LinuxFontPolicy
family = "Liberation Sans";
// scale font size
double dsize = size * getGnomeFontScale();
size = (int) (dsize + 0.5);
dsize *= getGnomeFontScale();
int size = (int) (dsize + 0.5);
if( size < 1 )
size = 1;
@@ -93,7 +108,48 @@ class LinuxFontPolicy
if( logicalFamily != null )
family = logicalFamily;
return createFont( family, style, size, dsize );
return createFontEx( family, style, size, dsize );
}
/**
* Create a font for the given family, style and size.
* If the font family does not match any font on the system,
* then the last word (usually a font weight) from the family name is removed and tried again.
* E.g. family 'URW Bookman Light' is not found, but 'URW Bookman' is found.
* If still not found, then font of family 'Dialog' is returned.
*/
private static Font createFontEx( String family, int style, int size, double dsize ) {
for(;;) {
Font font = createFont( family, style, size, dsize );
if( Font.DIALOG.equals( family ) )
return font;
// if the font family does not match any font on the system, "Dialog" family is returned
if( !Font.DIALOG.equals( font.getFamily() ) ) {
// check for font problems
// - font height much larger than expected (e.g. font Inter; Oracle Java 8)
// - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8)
FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font );
if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 )
return createFont( Font.DIALOG, style, size, dsize );
return font;
}
// find last word in family
int index = family.lastIndexOf( ' ' );
if( index < 0 )
return createFont( Font.DIALOG, style, size, dsize );
// check whether last work contains some font weight (e.g. Ultra-Bold or Heavy)
String lastWord = family.substring( index + 1 ).toLowerCase();
if( lastWord.contains( "bold" ) || lastWord.contains( "heavy" ) || lastWord.contains( "black" ) )
style |= Font.BOLD;
// remove last word from family and try again
family = family.substring( 0, index );
}
}
private static Font createFont( String family, int style, int size, double dsize ) {
@@ -114,7 +170,7 @@ class LinuxFontPolicy
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" );
if( value instanceof Integer ) {
int dpi = ((Integer)value).intValue() / 1024;
int dpi = (Integer) value / 1024;
if( dpi == -1 )
dpi = 96;
if( dpi < 50 )
@@ -141,7 +197,7 @@ class LinuxFontPolicy
}
/**
* Gets the default font for KDE for KDE configuration files.
* Gets the default font for KDE from KDE configuration files.
*
* The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). A application restart is necessary.
@@ -222,7 +278,7 @@ class LinuxFontPolicy
// read config file
ArrayList<String> lines = new ArrayList<>( 200 );
try( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
String line = null;
String line;
while( (line = reader.readLine()) != null )
lines.add( line );
} catch( IOException ex ) {
@@ -265,6 +321,9 @@ class LinuxFontPolicy
* - running on JetBrains Runtime 11 or later and scaling is enabled in system Settings
*/
private static boolean isSystemScaling() {
if( GraphicsEnvironment.isHeadless() )
return true;
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
return UIScale.getSystemScaleFactor( gc ) > 1;

View File

@@ -28,6 +28,7 @@ import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.lang.ref.WeakReference;
import javax.swing.AbstractButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
@@ -137,10 +138,17 @@ class MnemonicHandler
// get menu bar and first menu
Component c = e.getComponent();
JRootPane rootPane = SwingUtilities.getRootPane( c );
Window window = (rootPane != null) ? SwingUtilities.getWindowAncestor( rootPane ) : null;
JMenuBar menuBar = (rootPane != null) ? rootPane.getJMenuBar() : null;
if( menuBar == null && window instanceof JFrame )
menuBar = ((JFrame)window).getJMenuBar();
if( menuBar == null ) {
// get menu bar from frame/dialog because there
// may be multiple nested root panes in a frame/dialog
// (e.g. each internal frame has its own root pane)
Window window = SwingUtilities.getWindowAncestor( c );
if( window instanceof JFrame )
menuBar = ((JFrame)window).getJMenuBar();
else if( window instanceof JDialog )
menuBar = ((JDialog)window).getJMenuBar();
}
JMenu firstMenu = (menuBar != null) ? menuBar.getMenu( 0 ) : null;
// select first menu and show mnemonics

View File

@@ -0,0 +1,336 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* Improves usability of submenus by using a
* <a href="https://height.app/blog/guide-to-build-context-menus#safe-triangle">safe triangle</a>
* to avoid that the submenu closes while the user moves the mouse to it.
*
* @author Karl Tauber
*/
class SubMenuUsabilityHelper
implements ChangeListener
{
private static final String KEY_USE_SAFE_TRIANGLE = "Menu.useSafeTriangle";
private static final String KEY_SHOW_SAFE_TRIANGLE = "FlatLaf.debug.menu.showSafeTriangle";
// Using a static field to ensure that there is only one instance in the system.
// Multiple instances would freeze the application.
// https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607
private static SubMenuUsabilityHelper instance;
private SubMenuEventQueue subMenuEventQueue;
private SafeTrianglePainter safeTrianglePainter;
private boolean changePending;
// mouse location in screen coordinates
private int mouseX;
private int mouseY;
// target popup bounds in screen coordinates
private int targetX;
private int targetTopY;
private int targetBottomY;
private Rectangle invokerBounds;
static synchronized boolean install() {
if( instance != null )
return false;
instance = new SubMenuUsabilityHelper();
MenuSelectionManager.defaultManager().addChangeListener( instance );
return true;
}
static synchronized void uninstall() {
if( instance == null )
return;
MenuSelectionManager.defaultManager().removeChangeListener( instance );
instance.uninstallEventQueue();
instance = null;
}
@Override
public void stateChanged( ChangeEvent e ) {
if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
return;
// handle menu selection change later, but only once in case of temporary changes
// e.g. moving mouse from one menu item to another one, fires two events:
// 1. old menu item is removed from menu selection
// 2. new menu item is added to menu selection
synchronized( this ) {
if( changePending )
return;
changePending = true;
}
EventQueue.invokeLater( () -> {
synchronized( this ) {
changePending = false;
}
menuSelectionChanged();
} );
}
private void menuSelectionChanged() {
MenuElement[] path = MenuSelectionManager.defaultManager().getSelectedPath();
/*debug
System.out.println( "--- " + path.length );
for( int i = 0; i < path.length; i++ )
System.out.println( " " + i + ": " + path[i].getClass().getName() );
debug*/
// find submenu in menu selection
int subMenuIndex = findSubMenu( path );
// uninstall if there is no submenu in selection
if( subMenuIndex < 0 || subMenuIndex != path.length - 1 ) {
uninstallEventQueue();
return;
}
// get current mouse location
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
Point mouseLocation = (pointerInfo != null) ? pointerInfo.getLocation() : new Point();
mouseX = mouseLocation.x;
mouseY = mouseLocation.y;
// check whether popup is showing, which is e.g. not the case if it is empty
JPopupMenu popup = (JPopupMenu) path[subMenuIndex];
if( !popup.isShowing() ) {
uninstallEventQueue();
return;
}
// get invoker screen bounds
Component invoker = popup.getInvoker();
invokerBounds = (invoker != null)
? new Rectangle( invoker.getLocationOnScreen(), invoker.getSize() )
: null;
// check whether mouse location is within invoker
if( invokerBounds != null && !invokerBounds.contains( mouseX, mouseY ) ) {
uninstallEventQueue();
return;
}
// compute top/bottom target locations
Point popupLocation = popup.getLocationOnScreen();
Dimension popupSize = popup.getSize();
targetX = (mouseX < popupLocation.x + (popupSize.width / 2))
? popupLocation.x
: popupLocation.x + popupSize.width;
targetTopY = popupLocation.y;
targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to supress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null )
subMenuEventQueue = new SubMenuEventQueue();
// create safe triangle painter
if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) )
safeTrianglePainter = new SafeTrianglePainter( popup );
}
private void uninstallEventQueue() {
if( subMenuEventQueue != null ) {
subMenuEventQueue.uninstall();
subMenuEventQueue = null;
}
if( safeTrianglePainter != null ) {
safeTrianglePainter.uninstall();
safeTrianglePainter = null;
}
}
private int findSubMenu( MenuElement[] path ) {
for( int i = path.length - 1; i >= 1; i-- ) {
if( path[i] instanceof JPopupMenu &&
path[i - 1] instanceof JMenu &&
!((JMenu)path[i - 1]).isTopLevelMenu() )
return i;
}
return -1;
}
private Polygon createSafeTriangle() {
return new Polygon(
new int[] { mouseX, targetX, targetX },
new int[] { mouseY, targetTopY, targetBottomY },
3 );
}
//---- class SubMenuEventQueue --------------------------------------------
private class SubMenuEventQueue
extends EventQueue
{
private Timer mouseUpdateTimer;
private Timer timeoutTimer;
private int newMouseX;
private int newMouseY;
private AWTEvent lastMouseEvent;
SubMenuEventQueue() {
// timer used to slightly delay update of mouse location used for safe triangle
mouseUpdateTimer = new Timer( 50, e -> {
mouseX = newMouseX;
mouseY = newMouseY;
if( safeTrianglePainter != null )
safeTrianglePainter.repaint();
} );
mouseUpdateTimer.setRepeats( false );
// timer used to timeout safe triangle when mouse stops moving
timeoutTimer = new Timer( 200, e -> {
if( invokerBounds != null && !invokerBounds.contains( newMouseX, newMouseY ) ) {
// post last mouse event, which selects menu item at mouse location
if( lastMouseEvent != null ) {
postEvent( lastMouseEvent );
lastMouseEvent = null;
}
uninstallEventQueue();
return;
}
} );
timeoutTimer.setRepeats( false );
Toolkit.getDefaultToolkit().getSystemEventQueue().push( this );
}
void uninstall() {
mouseUpdateTimer.stop();
mouseUpdateTimer = null;
timeoutTimer.stop();
timeoutTimer = null;
lastMouseEvent = null;
super.pop();
}
@Override
protected void dispatchEvent( AWTEvent e ) {
int id = e.getID();
if( e instanceof MouseEvent &&
(id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_DRAGGED) )
{
newMouseX = ((MouseEvent)e).getXOnScreen();
newMouseY = ((MouseEvent)e).getYOnScreen();
if( safeTrianglePainter != null )
safeTrianglePainter.repaint();
mouseUpdateTimer.stop();
timeoutTimer.stop();
// check whether mouse moved within safe triangle
if( createSafeTriangle().contains( newMouseX, newMouseY ) ) {
// update mouse location delayed (this changes the safe triangle)
mouseUpdateTimer.start();
timeoutTimer.start();
// remember last mouse event, which will be posted if the mouse stops moving
lastMouseEvent = e;
// ignore mouse event
return;
}
// update mouse location immediately (this changes the safe triangle)
mouseX = newMouseX;
mouseY = newMouseY;
}
super.dispatchEvent( e );
}
}
//---- class SafeTrianglePainter ------------------------------------------
private class SafeTrianglePainter
extends JComponent
{
SafeTrianglePainter( JPopupMenu popup ) {
Window window = SwingUtilities.windowForComponent( popup.getInvoker() );
if( window instanceof RootPaneContainer ) {
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
setSize( layeredPane.getSize() );
layeredPane.add( this, Integer.valueOf( JLayeredPane.POPUP_LAYER + 1 ) );
}
}
void uninstall() {
Container parent = getParent();
if( parent != null ) {
parent.remove( this );
parent.repaint();
}
}
@Override
protected void paintComponent( Graphics g ) {
Point locationOnScreen = getLocationOnScreen();
g.translate( -locationOnScreen.x, -locationOnScreen.y );
g.setColor( Color.red );
((Graphics2D)g).draw( createSafeTriangle() );
}
}
}

View File

@@ -18,11 +18,16 @@ package com.formdev.flatlaf;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -33,8 +38,10 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.function.Function;
import javax.swing.Icon;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.plaf.ColorUIResource;
@@ -48,6 +55,7 @@ import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SoftCache;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -72,8 +80,12 @@ class UIDefaultsLoader
private static final String OPTIONAL_PREFIX = "?";
private static final String WILDCARD_PREFIX = "*.";
static final String KEY_VARIABLES = "FlatLaf.internal.variables";
private static int parseColorDepth;
private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
@@ -146,6 +158,18 @@ class UIDefaultsLoader
properties.load( in );
}
}
} else if( source instanceof URL ) {
// load from package URL
URL packageUrl = (URL) source;
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + lafClass.getSimpleName() + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
} catch( FileNotFoundException ex ) {
// ignore
}
}
} else if( source instanceof File ) {
// load from folder
File folder = (File) source;
@@ -234,18 +258,24 @@ class UIDefaultsLoader
};
// parse and add properties to UI defaults
Map<String, String> variables = new HashMap<>( 50 );
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( key.startsWith( VARIABLE_PREFIX ) )
if( key.startsWith( VARIABLE_PREFIX ) ) {
variables.put( key, (String) e.getValue() );
continue;
}
String value = resolveValue( (String) e.getValue(), propertiesGetter );
try {
defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) );
defaults.put( key, parseValue( key, value, null, null, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) {
logParseError( key, value, ex, true );
}
}
// remember variables in defaults to allow using them in styles
defaults.put( KEY_VARIABLES, variables );
} catch( IOException ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex );
}
@@ -288,87 +318,204 @@ class UIDefaultsLoader
return resolveValue( newValue, propertiesGetter );
}
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
static String resolveValueFromUIManager( String value ) {
if( value.startsWith( VARIABLE_PREFIX ) ) {
@SuppressWarnings( "unchecked" )
Map<String, String> variables = (Map<String, String>) UIManager.get( KEY_VARIABLES );
String newValue = (variables != null) ? variables.get( value ) : null;
if( newValue == null )
throw new IllegalArgumentException( "variable '" + value + "' not found" );
private static ValueType[] tempResultValueType = new ValueType[1];
return newValue;
}
static Object parseValue( String key, String value ) {
return parseValue( key, value, null, v -> v, Collections.emptyList() );
if( !value.startsWith( PROPERTY_PREFIX ) )
return value;
String key = value.substring( PROPERTY_PREFIX.length() );
Object newValue = UIManager.get( key );
if( newValue == null )
throw new IllegalArgumentException( "property '" + key + "' not found" );
// convert binary color to string
if( newValue instanceof Color ) {
Color color = (Color) newValue;
int alpha = color.getAlpha();
return String.format( (alpha != 255) ? "#%06x%02x" : "#%06x", color.getRGB() & 0xffffff, alpha );
}
throw new IllegalArgumentException( "property value type '" + newValue.getClass().getName() + "' not supported in references" );
}
static Object parseValue( String key, String value, ValueType[] resultValueType,
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, INTEGERORFLOAT, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static final ValueType[] tempResultValueType = new ValueType[1];
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
static Object parseValue( String key, String value, Class<?> valueType ) {
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
}
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
{
if( resultValueType == null )
resultValueType = tempResultValueType;
value = value.trim();
// null, false, true
switch( value ) {
case "null": resultValueType[0] = ValueType.NULL; return null;
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType[0] = ValueType.STRING;
return value;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
String uiKey = value.substring( 5, value.length() - 1 ).trim();
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
};
value = value.trim();
// null
if( value.equals( "null" ) || value.isEmpty() ) {
resultValueType[0] = ValueType.NULL;
return null;
}
// check for function "if"
// Syntax: if(condition,trueValue,falseValue)
// - condition: evaluates to true if:
// - is not "null"
// - is not "false"
// - is not an integer with zero value
// - trueValue: used if condition is true
// - falseValue: used if condition is false
if( value.startsWith( "if(" ) && value.endsWith( ")" ) ) {
List<String> params = splitFunctionParams( value.substring( 3, value.length() - 1 ), ',' );
if( params.size() != 3 )
throwMissingParametersException( value );
boolean ifCondition = parseCondition( params.get( 0 ), resolver, addonClassLoaders );
String ifValue = params.get( ifCondition ? 1 : 2 );
return parseValue( key, resolver.apply( ifValue ), javaValueType, resultValueType, resolver, addonClassLoaders );
}
ValueType valueType = ValueType.UNKNOWN;
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
valueType = ValueType.STRING;
value = value.substring( 1, value.length() - 1 );
} else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
String typeStr = value.substring( TYPE_PREFIX.length(), end );
valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) );
if( javaValueType != null ) {
if( javaValueTypes == null ) {
// create lazy
javaValueTypes = new HashMap<>();
javaValueTypes.put( String.class, ValueType.STRING );
javaValueTypes.put( boolean.class, ValueType.BOOLEAN );
javaValueTypes.put( Boolean.class, ValueType.BOOLEAN );
javaValueTypes.put( char.class, ValueType.CHARACTER );
javaValueTypes.put( Character.class, ValueType.CHARACTER );
javaValueTypes.put( int.class, ValueType.INTEGER );
javaValueTypes.put( Integer.class, ValueType.INTEGER );
javaValueTypes.put( float.class, ValueType.FLOAT );
javaValueTypes.put( Float.class, ValueType.FLOAT );
javaValueTypes.put( Border.class, ValueType.BORDER );
javaValueTypes.put( Icon.class, ValueType.ICON );
javaValueTypes.put( Insets.class, ValueType.INSETS );
javaValueTypes.put( Dimension.class, ValueType.DIMENSION );
javaValueTypes.put( Color.class, ValueType.COLOR );
javaValueTypes.put( Font.class, ValueType.FONT );
}
// remove type from value
value = value.substring( end + TYPE_PREFIX_END.length() );
} catch( IllegalArgumentException ex ) {
// ignore
// map java value type to parser value type
valueType = javaValueTypes.get( javaValueType );
if( valueType == null )
throw new IllegalArgumentException( "unsupported value type '" + javaValueType.getName() + "'" );
// remove '"' from strings
if( valueType == ValueType.STRING && value.startsWith( "\"" ) && value.endsWith( "\"" ) )
value = value.substring( 1, value.length() - 1 );
} else {
// false, true
switch( value ) {
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
};
}
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
String typeStr = value.substring( TYPE_PREFIX.length(), end );
valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) );
// remove type from value
value = value.substring( end + TYPE_PREFIX_END.length() );
} catch( IllegalArgumentException ex ) {
// ignore
}
}
}
}
// determine value type from key
if( valueType == ValueType.UNKNOWN ) {
if( key.endsWith( "UI" ) )
valueType = ValueType.STRING;
else if( key.endsWith( "Color" ) ||
(key.endsWith( "ground" ) &&
(key.endsWith( ".background" ) || key.endsWith( "Background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.endsWith( ".padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) )
valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGER;
else if( key.endsWith( "Char" ) )
valueType = ValueType.CHARACTER;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
if( valueType == ValueType.UNKNOWN ) {
if( knownValueTypes == null ) {
// create lazy
knownValueTypes = new HashMap<>();
// system colors
knownValueTypes.put( "activeCaptionBorder", ValueType.COLOR );
knownValueTypes.put( "inactiveCaptionBorder", ValueType.COLOR );
knownValueTypes.put( "windowBorder", ValueType.COLOR );
// SplitPane
knownValueTypes.put( "SplitPane.dividerSize", ValueType.INTEGER );
knownValueTypes.put( "SplitPaneDivider.gripDotSize", ValueType.INTEGER );
knownValueTypes.put( "dividerSize", ValueType.INTEGER );
knownValueTypes.put( "gripDotSize", ValueType.INTEGER );
// TabbedPane
knownValueTypes.put( "TabbedPane.closeCrossPlainSize", ValueType.FLOAT );
knownValueTypes.put( "TabbedPane.closeCrossFilledSize", ValueType.FLOAT );
knownValueTypes.put( "closeCrossPlainSize", ValueType.FLOAT );
knownValueTypes.put( "closeCrossFilledSize", ValueType.FLOAT );
// Table
knownValueTypes.put( "Table.intercellSpacing", ValueType.DIMENSION );
knownValueTypes.put( "intercellSpacing", ValueType.DIMENSION );
}
valueType = knownValueTypes.getOrDefault( key, ValueType.UNKNOWN );
}
// determine value type from key
if( valueType == ValueType.UNKNOWN ) {
if( key.endsWith( "UI" ) )
valueType = ValueType.STRING;
else if( key.endsWith( "Color" ) ||
(key.endsWith( "ground" ) &&
(key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".font" ) || key.endsWith( "Font" ) || key.equals( "font" ) )
valueType = ValueType.FONT;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) || key.equals( "border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) || key.equals( "icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.equals( "margin" ) ||
key.endsWith( ".padding" ) || key.equals( "padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) )
valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGERORFLOAT;
else if( key.endsWith( "Char" ) )
valueType = ValueType.CHARACTER;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
}
}
resultValueType[0] = valueType;
@@ -376,14 +523,17 @@ class UIDefaultsLoader
// parse value
switch( valueType ) {
case STRING: return value;
case BOOLEAN: return parseBoolean( value );
case CHARACTER: return parseCharacter( value );
case INTEGER: return parseInteger( value, true );
case INTEGERORFLOAT:return parseIntegerOrFloat( value, true );
case FLOAT: return parseFloat( value, true );
case BORDER: return parseBorder( value, resolver, addonClassLoaders );
case ICON: return parseInstance( value, addonClassLoaders );
case INSETS: return parseInsets( value );
case DIMENSION: return parseDimension( value );
case COLOR: return parseColorOrFunction( value, resolver, true );
case FONT: return parseFont( value );
case SCALEDINTEGER: return parseScaledInteger( value );
case SCALEDFLOAT: return parseScaledFloat( value );
case SCALEDINSETS: return parseScaledInsets( value );
@@ -393,6 +543,12 @@ class UIDefaultsLoader
case GRAYFILTER: return parseGrayFilter( value );
case UNKNOWN:
default:
// string
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
resultValueType[0] = ValueType.STRING;
return value.substring( 1, value.length() - 1 );
}
// colors
Object color = parseColorOrFunction( value, resolver, false );
if( color != null ) {
@@ -420,19 +576,34 @@ class UIDefaultsLoader
}
}
private static boolean parseCondition( String condition,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
{
try {
Object conditionValue = parseValue( "", resolver.apply( condition ), null, null, resolver, addonClassLoaders );
return (conditionValue != null &&
!conditionValue.equals( false ) &&
!conditionValue.equals( 0 ) );
} catch( IllegalArgumentException ex ) {
// ignore errors (e.g. variable or property not found) and evaluate to false
return false;
}
}
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor[,lineThickness]]
List<String> parts = split( value, ',' );
// top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null;
float lineThickness = (parts.size() >= 6) ? parseFloat( parts.get( 5 ), true ) : 1f;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ), true ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ), true ) : 0;
return (LazyValue) t -> {
return (lineColor != null)
? new FlatLineBorder( insets, lineColor, lineThickness )
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} else
@@ -442,8 +613,8 @@ class UIDefaultsLoader
private static Object parseInstance( String value, List<ClassLoader> addonClassLoaders ) {
return (LazyValue) t -> {
try {
return findClass( value, addonClassLoaders ).newInstance();
} catch( InstantiationException | IllegalAccessException | ClassNotFoundException ex ) {
return findClass( value, addonClassLoaders ).getDeclaredConstructor().newInstance();
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to instantiate '" + value + "'.", ex );
return null;
}
@@ -480,7 +651,7 @@ class UIDefaultsLoader
}
private static Insets parseInsets( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
return new InsetsUIResource(
Integer.parseInt( numbers.get( 0 ) ),
@@ -493,7 +664,7 @@ class UIDefaultsLoader
}
private static Dimension parseDimension( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
@@ -581,10 +752,10 @@ class UIDefaultsLoader
return null;
}
String function = value.substring( 0, paramsStart ).trim();
String function = StringUtils.substringTrimmed( value, 0, paramsStart );
List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
if( params.isEmpty() )
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
throwMissingParametersException( value );
if( parseColorDepth > 100 )
throw new IllegalArgumentException( "endless recursion in color function '" + value + "'" );
@@ -592,6 +763,7 @@ class UIDefaultsLoader
parseColorDepth++;
try {
switch( function ) {
case "if": return parseColorIf( value, params, resolver, reportError );
case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError );
case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError );
case "hsl": return parseColorHslOrHsla( false, params );
@@ -604,6 +776,15 @@ class UIDefaultsLoader
case "fadeout": return parseColorHSLIncreaseDecrease( 3, false, params, resolver, reportError );
case "fade": return parseColorFade( params, resolver, reportError );
case "spin": return parseColorSpin( params, resolver, reportError );
case "changeHue": return parseColorChange( 0, params, resolver, reportError );
case "changeSaturation":return parseColorChange( 1, params, resolver, reportError );
case "changeLightness": return parseColorChange( 2, params, resolver, reportError );
case "changeAlpha": return parseColorChange( 3, params, resolver, reportError );
case "mix": return parseColorMix( null, params, resolver, reportError );
case "tint": return parseColorMix( "#fff", params, resolver, reportError );
case "shade": return parseColorMix( "#000", params, resolver, reportError );
case "contrast": return parseColorContrast( params, resolver, reportError );
case "over": return parseColorOver( params, resolver, reportError );
}
} finally {
parseColorDepth--;
@@ -612,6 +793,21 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "unknown color function '" + value + "'" );
}
/**
* Syntax: if(condition,trueValue,falseValue)
* <p>
* This "if" function is only used if the "if" is passed as parameter to another
* color function. Otherwise, the general "if" function is used.
*/
private static Object parseColorIf( String value, List<String> params, Function<String, String> resolver, boolean reportError ) {
if( params.size() != 3 )
throwMissingParametersException( value );
boolean ifCondition = parseCondition( params.get( 0 ), resolver, Collections.emptyList() );
String ifValue = params.get( ifCondition ? 1 : 2 );
return parseColorOrFunction( resolver.apply( ifValue ), resolver, reportError );
}
/**
* Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha)
* - red: an integer 0-255 or a percentage 0-100%
@@ -656,7 +852,7 @@ class UIDefaultsLoader
int lightness = parsePercentage( params.get( 2 ) );
int alpha = hasAlpha ? parsePercentage( params.get( 3 ) ) : 100;
float[] hsl = new float[] { hue, saturation, lightness };
float[] hsl = { hue, saturation, lightness };
return new ColorUIResource( HSLColor.toRGB( hsl, alpha / 100f ) );
}
@@ -711,21 +907,32 @@ class UIDefaultsLoader
* Syntax: fade(color,amount[,options])
* - color: a color (e.g. #f00) or a color function
* - amount: percentage 0-100%
* - options: [derived]
* - options: [derived] [lazy]
*/
private static Object parseColorFade( List<String> params, Function<String, String> resolver, boolean reportError ) {
String colorStr = params.get( 0 );
int amount = parsePercentage( params.get( 1 ) );
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// create function
ColorFunction function = new ColorFunctions.Fade( amount );
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError );
}
@@ -753,6 +960,120 @@ class UIDefaultsLoader
return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError );
}
/**
* Syntax: changeHue(color,value[,options]) or
* changeSaturation(color,value[,options]) or
* changeLightness(color,value[,options]) or
* changeAlpha(color,value[,options])
* - color: a color (e.g. #f00) or a color function
* - value: for hue: number of degrees; otherwise: percentage 0-100%
* - options: [derived]
*/
private static Object parseColorChange( int hslIndex,
List<String> params, Function<String, String> resolver, boolean reportError )
{
String colorStr = params.get( 0 );
int value = (hslIndex == 0)
? parseInteger( params.get( 1 ), true )
: parsePercentage( params.get( 1 ) );
boolean derived = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
}
// create function
ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError );
}
/**
* Syntax: mix(color1,color2[,weight]) or
* tint(color[,weight]) or
* shade(color[,weight])
* - color1: a color (e.g. #f00) or a color function
* - color2: a color (e.g. #f00) or a color function
* - weight: the weight (in range 0-100%) to mix the two colors
* larger weight uses more of first color, smaller weight more of second color
*/
private static Object parseColorMix( String color1Str, List<String> params, Function<String, String> resolver, boolean reportError ) {
int i = 0;
if( color1Str == null )
color1Str = params.get( i++ );
String color2Str = params.get( i++ );
int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50;
// parse second color
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver, reportError );
if( color2 == null )
return null;
// create function
ColorFunction function = new ColorFunctions.Mix( color2, weight );
// parse first color, apply function and create mixed color
return parseFunctionBaseColor( color1Str, function, false, resolver, reportError );
}
/**
* Syntax: contrast(color,dark,light[,threshold])
* - color: a color to compare against
* - dark: a designated dark color (e.g. #000) or a color function
* - light: a designated light color (e.g. #fff) or a color function
* - threshold: the threshold (in range 0-100%) to specify where the transition
* from "dark" to "light" is (default is 43%)
*/
private static Object parseColorContrast( List<String> params, Function<String, String> resolver, boolean reportError ) {
String colorStr = params.get( 0 );
String darkStr = params.get( 1 );
String lightStr = params.get( 2 );
int threshold = (params.size() > 3) ? parsePercentage( params.get( 3 ) ) : 43;
// parse color to compare against
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
if( color == null )
return null;
// check luma and determine whether to use dark or light color
String darkOrLightColor = (ColorFunctions.luma( color ) * 100 < threshold)
? lightStr
: darkStr;
// parse dark or light color
return parseColorOrFunction( resolver.apply( darkOrLightColor ), resolver, reportError );
}
/**
* Syntax: over(foreground,background)
* - foreground: a foreground color (e.g. #f00) or a color function;
* the alpha of this color is used as weight to mix the two colors
* - background: a background color (e.g. #f00) or a color function
*/
private static ColorUIResource parseColorOver( List<String> params, Function<String, String> resolver, boolean reportError ) {
String foregroundStr = params.get( 0 );
String backgroundStr = params.get( 1 );
// parse foreground color
ColorUIResource foreground = (ColorUIResource) parseColorOrFunction( resolver.apply( foregroundStr ), resolver, reportError );
if( foreground == null || foreground.getAlpha() == 255 )
return foreground;
// foreground color without alpha
ColorUIResource foreground2 = new ColorUIResource( foreground.getRGB() );
// parse background color
ColorUIResource background = (ColorUIResource) parseColorOrFunction( resolver.apply( backgroundStr ), resolver, reportError );
if( background == null )
return foreground2;
// create new color
float weight = foreground.getAlpha() / 255f;
return new ColorUIResource( ColorFunctions.mix( foreground2, background, weight ) );
}
private static Object parseFunctionBaseColor( String colorStr, ColorFunction function,
boolean derived, Function<String, String> resolver, boolean reportError )
{
@@ -783,6 +1104,107 @@ class UIDefaultsLoader
return new ColorUIResource( newColor );
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]] [$baseFontKey]
*/
private static Object parseFont( String value ) {
Object font = fontCache.get( value );
if( font != null )
return font;
int style = -1;
int styleChange = 0;
int absoluteSize = 0;
int relativeSize = 0;
float scaleSize = 0;
List<String> families = null;
String baseFontKey = null;
// use StreamTokenizer to split string because it supports quoted strings
StreamTokenizer st = new StreamTokenizer( new StringReader( value ) );
st.resetSyntax();
st.wordChars( ' ' + 1, 255 );
st.whitespaceChars( 0, ' ' );
st.whitespaceChars( ',', ',' ); // ignore ','
st.quoteChar( '"' );
st.quoteChar( '\'' );
try {
while( st.nextToken() != StreamTokenizer.TT_EOF ) {
String param = st.sval;
switch( param ) {
// font style
case "normal":
style = 0;
break;
case "bold":
if( style == -1 )
style = 0;
style |= Font.BOLD;
break;
case "italic":
if( style == -1 )
style = 0;
style |= Font.ITALIC;
break;
case "+bold": styleChange |= Font.BOLD; break;
case "-bold": styleChange |= Font.BOLD << 16; break;
case "+italic": styleChange |= Font.ITALIC; break;
case "-italic": styleChange |= Font.ITALIC << 16; break;
default:
char firstChar = param.charAt( 0 );
if( Character.isDigit( firstChar ) || firstChar == '+' || firstChar == '-' ) {
// font size
if( absoluteSize != 0 || relativeSize != 0 || scaleSize != 0 )
throw new IllegalArgumentException( "size specified more than once in '" + value + "'" );
if( firstChar == '+' || firstChar == '-' )
relativeSize = parseInteger( param, true );
else if( param.endsWith( "%" ) )
scaleSize = parseInteger( param.substring( 0, param.length() - 1 ), true ) / 100f;
else
absoluteSize = parseInteger( param, true );
} else if( firstChar == '$' ) {
// reference to base font
if( baseFontKey != null )
throw new IllegalArgumentException( "baseFontKey specified more than once in '" + value + "'" );
baseFontKey = param.substring( 1 );
} else {
// font family
if( families == null )
families = Collections.singletonList( param );
else {
if( families.size() == 1 )
families = new ArrayList<>( families );
families.add( param );
}
}
break;
}
}
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
}
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
fontCache.put( value, font );
return font;
}
private static int parsePercentage( String value ) {
if( !value.endsWith( "%" ) )
throw new NumberFormatException( "invalid percentage '" + value + "'" );
@@ -799,6 +1221,14 @@ class UIDefaultsLoader
return val;
}
private static Boolean parseBoolean( String value ) {
switch( value ) {
case "false": return false;
case "true": return true;
}
throw new IllegalArgumentException( "invalid boolean '" + value + "'" );
}
private static Character parseCharacter( String value ) {
if( value.length() != 1 )
throw new IllegalArgumentException( "invalid character '" + value + "'" );
@@ -812,7 +1242,7 @@ class UIDefaultsLoader
}
Integer integer = parseInteger( value, true );
if( integer.intValue() < min || integer.intValue() > max )
if( integer < min || integer > max )
throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' );
return integer;
}
@@ -827,6 +1257,20 @@ class UIDefaultsLoader
return null;
}
private static Number parseIntegerOrFloat( String value, boolean reportError ) {
try {
return Integer.parseInt( value );
} catch( NumberFormatException ex ) {
try {
return Float.parseFloat( value );
} catch( NumberFormatException ex2 ) {
if( reportError )
throw new NumberFormatException( "invalid integer or float '" + value + "'" );
}
}
return null;
}
private static Float parseFloat( String value, boolean reportError ) {
try {
return Float.parseFloat( value );
@@ -839,34 +1283,34 @@ class UIDefaultsLoader
private static ActiveValue parseScaledInteger( String value ) {
int val = parseInteger( value, true );
return (ActiveValue) t -> {
return t -> {
return UIScale.scale( val );
};
}
private static ActiveValue parseScaledFloat( String value ) {
float val = parseFloat( value, true );
return (ActiveValue) t -> {
return t -> {
return UIScale.scale( val );
};
}
private static ActiveValue parseScaledInsets( String value ) {
Insets insets = parseInsets( value );
return (ActiveValue) t -> {
return t -> {
return UIScale.scale( insets );
};
}
private static ActiveValue parseScaledDimension( String value ) {
Dimension dimension = parseDimension( value );
return (ActiveValue) t -> {
return t -> {
return UIScale.scale( dimension );
};
}
private static Object parseGrayFilter( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
int brightness = Integer.parseInt( numbers.get( 0 ) );
int contrast = Integer.parseInt( numbers.get( 1 ) );
@@ -880,20 +1324,6 @@ class UIDefaultsLoader
}
}
/**
* Split string and trim parts.
*/
private static List<String> split( String str, char delim ) {
List<String> result = StringUtils.split( str, delim );
// trim strings
int size = result.size();
for( int i = 0; i < size; i++ )
result.set( i, result.get( i ).trim() );
return result;
}
/**
* Splits function parameters and allows using functions as parameters.
* In other words: Delimiters surrounded by '(' and ')' are ignored.
@@ -910,11 +1340,11 @@ class UIDefaultsLoader
else if( ch == ')' )
nestLevel--;
else if( nestLevel == 0 && ch == delim ) {
strs.add( str.substring( start, i ).trim() );
strs.add( StringUtils.substringTrimmed( str, start, i ) );
start = i + 1;
}
}
strs.add( str.substring( start ).trim() );
strs.add( StringUtils.substringTrimmed( str, start ) );
return strs;
}
@@ -923,7 +1353,7 @@ class UIDefaultsLoader
* For use in LazyValue to get value for given key from UIManager and report error
* if not found. If key is prefixed by '?', then no error is reported.
*/
private static Object lazyUIManagerGet( String uiKey ) {
static Object lazyUIManagerGet( String uiKey ) {
boolean optional = false;
if( uiKey.startsWith( OPTIONAL_PREFIX ) ) {
uiKey = uiKey.substring( OPTIONAL_PREFIX.length() );
@@ -935,4 +1365,8 @@ class UIDefaultsLoader
LoggingFacade.INSTANCE.logSevere( "FlatLaf: '" + uiKey + "' not found in UI defaults.", null );
return value;
}
private static void throwMissingParametersException( String value ) {
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
}
}

View File

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

View File

@@ -23,13 +23,13 @@ import java.awt.Graphics2D;
import com.formdev.flatlaf.util.AnimatedIcon;
/**
* Base class for animated icons that scales width and height, creates and initializes
* Base class for animated icons that scale width and height, creates and initializes
* a scaled graphics context for icon painting.
* <p>
* Subclasses do not need to scale icon painting.
* <p>
* This class does not store any state information (needed for animation) in its instance.
* Instead a client property is set on the painted component.
* Instead, a client property is set on the painted component.
* This makes it possible to use a share icon instance for multiple components.
*
* @author Karl Tauber

View File

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

View File

@@ -16,12 +16,14 @@
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -38,13 +40,30 @@ public class FlatCapsLockIcon
super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue;
default: throw new UnknownStyleException( key );
}
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
switch( key ) {
case "capsLockIconColor": return color;
default: return null;
}
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<rect width="16" height="16" fill="#6E6E6E" rx="3"/>
<rect width="6" height="2" x="5" y="12" fill="#FFF"/>
<rect width="6" height="2" x="5" y="11.5" fill="#FFF"/>
<path fill="#FFF" d="M2,8 L8,2 L14,8 L11,8 L11,10 L5,10 L5,8 L2,8 Z"/>
</g>
</svg>
@@ -52,7 +71,7 @@ public class FlatCapsLockIcon
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new RoundRectangle2D.Float( 0, 0, 16, 16, 6, 6 ), false );
path.append( new Rectangle2D.Float( 5, 12, 6, 2 ), false );
path.append( new Rectangle2D.Float( 5, 11.5f, 6, 2 ), false );
path.append( FlatUIUtils.createPath( 2,8, 8,2, 14,8, 11,8, 11,10, 5,10, 5,8 ), false );
g.fill( path );
}

View File

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

View File

@@ -21,15 +21,18 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JMenuItem;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/**
* Icon for {@link javax.swing.JCheckBoxMenuItem}.
*
* @uiDefault MenuItemCheckBox.icon.checkmarkColor Color
* @uiDefault MenuItemCheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBoxMenuItem.icon.checkmarkColor Color
* @uiDefault CheckBoxMenuItem.icon.disabledCheckmarkColor Color
* @uiDefault MenuItem.selectionForeground Color
* @uiDefault MenuItem.selectionType String
*
@@ -38,14 +41,29 @@ import javax.swing.UIManager;
public class FlatCheckBoxMenuItemIcon
extends FlatAbstractIcon
{
protected final Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" );
protected final Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" );
protected final Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" );
@Styleable protected Color checkmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.checkmarkColor" );
@Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.disabledCheckmarkColor" );
@Styleable protected Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" );
public FlatCheckBoxMenuItemIcon() {
super( 15, 15, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected();

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "clear" icon for search fields.
*
* @uiDefault SearchField.clearIconColor Color
* @uiDefault SearchField.clearIconHoverColor Color
* @uiDefault SearchField.clearIconPressedColor Color
*
* @author Karl Tauber
* @since 1.5
*/
public class FlatClearIcon
extends FlatAbstractIcon
{
@Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
@Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
@Styleable protected Color clearIconPressedColor = UIManager.getColor( "SearchField.clearIconPressedColor" );
private final boolean ignoreButtonState;
public FlatClearIcon() {
this( false );
}
/** @since 2 */
public FlatClearIcon( boolean ignoreButtonState ) {
super( 16, 16, null );
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( !ignoreButtonState && c instanceof AbstractButton ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isPressed() || model.isRollover() ) {
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#7F8B91" fill-opacity=".5" fill-rule="evenodd" d="M8,1.75 C11.4517797,1.75 14.25,4.54822031 14.25,8 C14.25,11.4517797 11.4517797,14.25 8,14.25 C4.54822031,14.25 1.75,11.4517797 1.75,8 C1.75,4.54822031 4.54822031,1.75 8,1.75 Z M10.5,4.5 L8,7 L5.5,4.5 L4.5,5.5 L7,8 L4.5,10.5 L5.5,11.5 L8,9 L10.5,11.5 L11.5,10.5 L9,8 L11.5,5.5 L10.5,4.5 Z"/>
</svg>
*/
// paint filled circle with cross
g.setColor( model.isPressed() ? clearIconPressedColor : clearIconHoverColor );
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Ellipse2D.Float( 1.75f, 1.75f, 12.5f, 12.5f ), false );
path.append( FlatUIUtils.createPath( 4.5,5.5, 5.5,4.5, 8,7, 10.5,4.5, 11.5,5.5, 9,8, 11.5,10.5, 10.5,11.5, 8,9, 5.5,11.5, 4.5,10.5, 7,8 ), false );
g.fill( path );
return;
}
}
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="none" stroke="#7F8B91" stroke-linecap="square" stroke-opacity=".5" d="M5,5 L11,11 M5,11 L11,5"/>
</svg>
*/
// paint cross
g.setColor( clearIconColor );
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( 5,5, 11,11 ), false );
path.append( new Line2D.Float( 5,11, 11,5 ), false );
g.draw( path );
}
}

View File

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

View File

@@ -22,8 +22,11 @@ import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -50,29 +53,42 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatHelpButtonIcon
extends FlatAbstractIcon
{
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) );
protected final int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 );
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) );
@Styleable protected int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 );
protected final Color borderColor = UIManager.getColor( "HelpButton.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" );
protected final Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" );
protected final Color background = UIManager.getColor( "HelpButton.background" );
protected final Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" );
protected final Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" );
protected final Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" );
protected final Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" );
protected final Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" );
protected final Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" );
protected final int iconSize = 22 + (focusWidth * 2);
@Styleable protected Color borderColor = UIManager.getColor( "HelpButton.borderColor" );
@Styleable protected Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" );
@Styleable protected Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" );
@Styleable protected Color background = UIManager.getColor( "HelpButton.background" );
@Styleable protected Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" );
@Styleable protected Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" );
@Styleable protected Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" );
@Styleable protected Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" );
@Styleable protected Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" );
@Styleable protected Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" );
public FlatHelpButtonIcon() {
super( 0, 0, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
/*
@@ -85,11 +101,11 @@ public class FlatHelpButtonIcon
</svg>
*/
boolean enabled = c.isEnabled();
boolean focused = FlatUIUtils.isPermanentFocusOwner( c );
boolean enabled = c == null || c.isEnabled();
boolean focused = c != null && FlatUIUtils.isPermanentFocusOwner( c );
float xy = 0.5f;
float wh = iconSize - 1;
float wh = iconSize() - 1;
// paint outer focus border
if( focused && FlatButtonUI.isFocusPainted( c ) ) {
@@ -151,11 +167,15 @@ public class FlatHelpButtonIcon
@Override
public int getIconWidth() {
return scale( iconSize );
return scale( iconSize() );
}
@Override
public int getIconHeight() {
return scale( iconSize );
return scale( iconSize() );
}
private int iconSize() {
return 22 + (focusWidth * 2);
}
}

View File

@@ -21,9 +21,12 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.JMenu;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/**
* "arrow" icon for {@link javax.swing.JMenu}.
@@ -39,22 +42,37 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatMenuArrowIcon
extends FlatAbstractIcon
{
protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) );
protected final Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
protected final Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" );
protected final Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" );
@Styleable protected String arrowType = UIManager.getString( "Component.arrowType" );
@Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
@Styleable protected Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" );
@Styleable protected Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" );
public FlatMenuArrowIcon() {
super( 6, 10, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( !c.getComponentOrientation().isLeftToRight() )
if( c != null && !c.getComponentOrientation().isLeftToRight() )
g.rotate( Math.toRadians( 180 ), width / 2., height / 2. );
g.setColor( getArrowColor( c ) );
if( chevron ) {
if( FlatUIUtils.isChevron( arrowType ) ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,1, 5,5, 1,9 );
g.setStroke( new BasicStroke( 1f ) );
@@ -69,7 +87,7 @@ public class FlatMenuArrowIcon
if( c instanceof JMenu && ((JMenu)c).isSelected() && !isUnderlineSelection() )
return selectionForeground;
return c.isEnabled() ? arrowColor : disabledArrowColor;
return c == null || c.isEnabled() ? arrowColor : disabledArrowColor;
}
protected boolean isUnderlineSelection() {

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import javax.swing.UIManager;
/**
* "eye" icon for {@link javax.swing.JPasswordField}.
*
* @uiDefault PasswordField.revealIconColor Color
*
* @author Karl Tauber
* @since 2
*/
public class FlatRevealIcon
extends FlatAbstractIcon
{
public FlatRevealIcon() {
super( 16, 16, UIManager.getColor( "PasswordField.revealIconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Ellipse2D.Float( 5.15f, 6.15f, 5.7f, 5.7f ), false );
path.append( new Ellipse2D.Float( 6, 7, 4, 4 ), false );
g.fill( path );
Path2D path2 = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path2.append( new Ellipse2D.Float( 2.15f, 4.15f, 11.7f, 11.7f ), false );
path2.append( new Ellipse2D.Float( 3, 5, 10, 10 ), false );
Area area = new Area( path2 );
area.subtract( new Area( new Rectangle2D.Float( 0, 9.5f, 16, 16 ) ) );
g.fill( area );
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "search" icon for search fields.
*
* @uiDefault SearchField.searchIconColor Color
* @uiDefault SearchField.searchIconHoverColor Color
* @uiDefault SearchField.searchIconPressedColor Color
*
* @author Karl Tauber
* @since 1.5
*/
public class FlatSearchIcon
extends FlatAbstractIcon
{
@Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
@Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
@Styleable protected Color searchIconPressedColor = UIManager.getColor( "SearchField.searchIconPressedColor" );
private final boolean ignoreButtonState;
public FlatSearchIcon() {
this( false );
}
/** @since 2 */
public FlatSearchIcon( boolean ignoreButtonState ) {
super( 16, 16, null );
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-opacity=".9" fill-rule="evenodd">
<polygon fill="#7F8B91" points="10.813 9.75 14 12.938 12.938 14 9.75 10.813"/>
<path fill="#7F8B91" d="M7,2 C9.76142375,2 12,4.23857625 12,7 C12,9.76142375 9.76142375,12 7,12 C4.23857625,12 2,9.76142375 2,7 C2,4.23857625 4.23857625,2 7,2 Z M7,3 C4.790861,3 3,4.790861 3,7 C3,9.209139 4.790861,11 7,11 C9.209139,11 11,9.209139 11,7 C11,4.790861 9.209139,3 7,3 Z"/>
</g>
</svg>
*/
g.setColor( ignoreButtonState
? searchIconColor
: FlatButtonUI.buttonStateColor( c, searchIconColor, searchIconColor,
null, searchIconHoverColor, searchIconPressedColor ) );
// paint magnifier
Area area = new Area( new Ellipse2D.Float( 2, 2, 10, 10 ) );
area.subtract( new Area( new Ellipse2D.Float( 3, 3, 8, 8 ) ) );
area.add( new Area( FlatUIUtils.createPath( 10.813,9.75, 14,12.938, 12.938,14, 9.75,10.813 ) ) );
g.fill( area );
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "search with history" icon for search fields.
*
* @author Karl Tauber
* @since 1.5
*/
public class FlatSearchWithHistoryIcon
extends FlatSearchIcon
{
public FlatSearchWithHistoryIcon() {
this( false );
}
/** @since 2 */
public FlatSearchWithHistoryIcon( boolean ignoreButtonState ) {
super( ignoreButtonState );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-opacity=".9" fill-rule="evenodd">
<polygon fill="#7F8B91" points="8.813 9.75 12 12.938 10.938 14 7.75 10.813"/>
<path fill="#7F8B91" d="M5,2 C7.76142375,2 10,4.23857625 10,7 C10,9.76142375 7.76142375,12 5,12 C2.23857625,12 0,9.76142375 0,7 C0,4.23857625 2.23857625,2 5,2 Z M5,3 C2.790861,3 1,4.790861 1,7 C1,9.209139 2.790861,11 5,11 C7.209139,11 9,9.209139 9,7 C9,4.790861 7.209139,3 5,3 Z"/>
<polygon fill="#7F8B91" points="11 7 16 7 13.5 10"/>
</g>
</svg>
*/
// paint magnifier
g.translate( -2, 0 );
super.paintIcon( c, g );
g.translate( 2, 0 );
// paint history arrow
g.fill( FlatUIUtils.createPath( 11,7, 16,7, 13.5,10 ) );
}
}

View File

@@ -23,8 +23,11 @@ import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -47,39 +50,54 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
{
protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" );
protected final int arc = UIManager.getInt( "TabbedPane.closeArc" );
protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize );
protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" );
protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" );
protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
@Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" );
@Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" );
@Styleable protected float closeCrossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
@Styleable protected float closeCrossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", closeCrossPlainSize );
@Styleable protected float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
@Styleable protected Color closeBackground = UIManager.getColor( "TabbedPane.closeBackground" );
@Styleable protected Color closeForeground = UIManager.getColor( "TabbedPane.closeForeground" );
@Styleable protected Color closeHoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
@Styleable protected Color closeHoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
@Styleable protected Color closePressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
@Styleable protected Color closePressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
public FlatTabbedPaneCloseIcon() {
super( 16, 16, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background
Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground );
Color bg = FlatButtonUI.buttonStateColor( c, closeBackground, null, null, closeHoverBackground, closePressedBackground );
if( bg != null ) {
g.setColor( FlatUIUtils.deriveColor( bg, c.getBackground() ) );
g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2,
size.width, size.height, arc, arc );
g.fillRoundRect( (width - closeSize.width) / 2, (height - closeSize.height) / 2,
closeSize.width, closeSize.height, closeArc, closeArc );
}
// set cross color
Color fg = FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground );
Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground );
g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) );
float mx = width / 2;
float my = height / 2;
float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2;
float r = ((bg != null) ? closeCrossFilledSize : closeCrossPlainSize) / 2;
// paint cross
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
@@ -65,8 +66,14 @@ public abstract class FlatWindowAbstractIcon
protected void paintBackground( Component c, Graphics2D g ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) {
// disable antialiasing for background rectangle painting to avoid blury edges when scaled (e.g. at 125% or 175%)
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
g.fillRect( 0, 0, width, height );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, oldHint );
}
}

View File

@@ -24,6 +24,7 @@ import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.util.SystemInfo;
/**
* "close" icon for windows (frames and dialogs).
@@ -54,7 +55,7 @@ public class FlatWindowCloseIcon
int iy = y + ((height - iwh) / 2);
int ix2 = ix + iwh - 1;
int iy2 = iy + iwh - 1;
int thickness = (int) scaleFactor;
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( ix, iy, ix2, iy2 ), false );

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.icons;
import java.awt.Graphics2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* "maximize" icon for windows (frames and dialogs).
@@ -35,8 +36,11 @@ public class FlatWindowMaximizeIcon
int iwh = (int) (10 * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int thickness = (int) scaleFactor;
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
g.fill( FlatUIUtils.createRectangle( ix, iy, iwh, iwh, thickness ) );
g.fill( SystemInfo.isWindows_11_orLater
? FlatUIUtils.createRoundRectangle( ix, iy, iwh, iwh, thickness, arc, arc, arc, arc )
: FlatUIUtils.createRectangle( ix, iy, iwh, iwh, thickness ) );
}
}

View File

@@ -21,6 +21,7 @@ import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* "restore" icon for windows (frames and dialogs).
@@ -38,18 +39,33 @@ public class FlatWindowRestoreIcon
int iwh = (int) (10 * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int thickness = (int) scaleFactor;
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
int arcOuter = (int) (arc + (1.5 * scaleFactor));
int rwh = (int) (8 * scaleFactor);
int ro2 = iwh - rwh;
Path2D r1 = FlatUIUtils.createRectangle( ix + ro2, iy, rwh, rwh, thickness );
Path2D r2 = FlatUIUtils.createRectangle( ix, iy + ro2, rwh, rwh, thickness );
// upper-right rectangle
Path2D r1 = SystemInfo.isWindows_11_orLater
? FlatUIUtils.createRoundRectangle( ix + ro2, iy, rwh, rwh, thickness, arc, arcOuter, arc, arc )
: FlatUIUtils.createRectangle( ix + ro2, iy, rwh, rwh, thickness );
// lower-left rectangle
Path2D r2 = SystemInfo.isWindows_11_orLater
? FlatUIUtils.createRoundRectangle( ix, iy + ro2, rwh, rwh, thickness, arc, arc, arc, arc )
: FlatUIUtils.createRectangle( ix, iy + ro2, rwh, rwh, thickness );
// paint upper-right rectangle
Area area = new Area( r1 );
area.subtract( new Area( new Rectangle2D.Float( ix, iy + ro2, rwh, rwh ) ) );
if( SystemInfo.isWindows_11_orLater ) {
area.subtract( new Area( new Rectangle2D.Float( ix, (float) (iy + scaleFactor), rwh, rwh ) ) );
area.subtract( new Area( new Rectangle2D.Float( (float) (ix + scaleFactor), iy + ro2, rwh, rwh ) ) );
} else
area.subtract( new Area( new Rectangle2D.Float( ix, iy + ro2, rwh, rwh ) ) );
g.fill( area );
// paint lower-left rectangle
g.fill( r2 );
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.resources;
/**
* The only purpose of this file is to add a .class file to this package to make it non-empty.
* Otherwise, the compiler outputs a warning because this package is opened in module-info.java.
* Also, when using --patch-module (e.g. from an IDE), an error would occur for empty packages.
*
* @author Karl Tauber
*/
interface EmptyPackage
{
}

View File

@@ -25,6 +25,7 @@ import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicArrowButton;
@@ -37,19 +38,19 @@ public class FlatArrowButton
extends BasicArrowButton
implements UIResource
{
public static final int DEFAULT_ARROW_WIDTH = 8;
public static final int DEFAULT_ARROW_WIDTH = 9;
protected final boolean chevron;
protected final Color foreground;
protected final Color disabledForeground;
protected final Color hoverForeground;
protected final Color hoverBackground;
protected final Color pressedForeground;
protected final Color pressedBackground;
protected boolean chevron;
protected Color foreground;
protected Color disabledForeground;
protected Color hoverForeground;
protected Color hoverBackground;
protected Color pressedForeground;
protected Color pressedBackground;
private int arrowWidth = DEFAULT_ARROW_WIDTH;
private int xOffset = 0;
private int yOffset = 0;
private float xOffset = 0;
private float yOffset = 0;
private boolean hover;
private boolean pressed;
@@ -58,14 +59,8 @@ public class FlatArrowButton
Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground )
{
super( direction, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE );
this.chevron = FlatUIUtils.isChevron( type );
this.foreground = foreground;
this.disabledForeground = disabledForeground;
this.hoverForeground = hoverForeground;
this.hoverBackground = hoverBackground;
this.pressedForeground = pressedForeground;
this.pressedBackground = pressedBackground;
updateStyle( type, foreground, disabledForeground, hoverForeground, hoverBackground,
pressedForeground, pressedBackground );
setOpaque( false );
setBorder( null );
@@ -88,19 +83,36 @@ public class FlatArrowButton
@Override
public void mousePressed( MouseEvent e ) {
pressed = true;
repaint();
if( SwingUtilities.isLeftMouseButton( e ) ) {
pressed = true;
repaint();
}
}
@Override
public void mouseReleased( MouseEvent e ) {
pressed = false;
repaint();
if( SwingUtilities.isLeftMouseButton( e ) ) {
pressed = false;
repaint();
}
}
} );
}
}
/** @since 2 */
public void updateStyle( String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground )
{
this.chevron = FlatUIUtils.isChevron( type );
this.foreground = foreground;
this.disabledForeground = disabledForeground;
this.hoverForeground = hoverForeground;
this.hoverBackground = hoverBackground;
this.pressedForeground = pressedForeground;
this.pressedBackground = pressedBackground;
}
public int getArrowWidth() {
return arrowWidth;
}
@@ -117,19 +129,19 @@ public class FlatArrowButton
return pressed;
}
public int getXOffset() {
public float getXOffset() {
return xOffset;
}
public void setXOffset( int xOffset ) {
public void setXOffset( float xOffset ) {
this.xOffset = xOffset;
}
public int getYOffset() {
public float getYOffset() {
return yOffset;
}
public void setYOffset( int yOffset ) {
public void setYOffset( float yOffset ) {
this.yOffset = yOffset;
}
@@ -141,6 +153,21 @@ public class FlatArrowButton
return FlatUIUtils.deriveColor( foreground, this.foreground );
}
/**
* Returns the color used to paint the arrow.
*
* @since 1.2
*/
protected Color getArrowColor() {
return isEnabled()
? (pressedForeground != null && isPressed()
? pressedForeground
: (hoverForeground != null && isHover()
? hoverForeground
: foreground))
: disabledForeground;
}
@Override
public Dimension getPreferredSize() {
return scale( super.getPreferredSize() );
@@ -170,13 +197,7 @@ public class FlatArrowButton
}
// paint arrow
g.setColor( deriveForeground( isEnabled()
? (pressedForeground != null && isPressed()
? pressedForeground
: (hoverForeground != null && isHover()
? hoverForeground
: foreground))
: disabledForeground ) );
g.setColor( deriveForeground( getArrowColor() ) );
paintArrow( (Graphics2D) g );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
@@ -195,6 +216,6 @@ public class FlatArrowButton
if( vert && parent instanceof JComponent && FlatUIUtils.hasRoundBorder( (JComponent) parent ) )
x -= scale( parent.getComponentOrientation().isLeftToRight() ? 1 : -1 );
FlatUIUtils.paintArrow( g, x, 0, getWidth(), getHeight(), getDirection(), chevron, arrowWidth, xOffset, yOffset );
FlatUIUtils.paintArrow( g, x, 0, getWidth(), getHeight(), getDirection(), chevron, getArrowWidth(), getXOffset(), getYOffset() );
}
}

View File

@@ -22,34 +22,35 @@ import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Paint;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.util.DerivedColor;
/**
* Border for various components (e.g. {@link javax.swing.JTextField}).
*
* <p>
* There is empty space around the component border, if Component.focusWidth is greater than zero,
* which is used to paint outer focus border.
*
* <p>
* Because there is empty space (if outer focus border is not painted),
* UI delegates that use this border (or subclasses) must invoke
* {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly.
* {@link FlatUIUtils#paintParentBackground} to fill the empty space correctly.
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.innerFocusWidth int or float
* @uiDefault Component.innerOutlineWidth int or float
* @uiDefault Component.borderWidth int or float
*
* @uiDefault Component.focusColor Color
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
@@ -65,20 +66,46 @@ import com.formdev.flatlaf.util.DerivedColor;
*/
public class FlatBorder
extends BasicBorders.MarginBorder
implements StyleableBorder
{
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
protected final float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final Color borderColor = UIManager.getColor( "Component.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" );
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
@Styleable protected float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 );
/** @since 2 */ @Styleable protected float borderWidth = FlatUIUtils.getUIFloat( "Component.borderWidth", 1 );
protected final Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" );
protected final Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
protected final Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
protected final Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
protected final Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@Styleable protected Color borderColor = UIManager.getColor( "Component.borderColor" );
@Styleable protected Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
@Styleable protected Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" );
@Styleable(dot=true) protected Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" );
@Styleable(dot=true) protected Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
@Styleable(dot=true) protected Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
@Styleable(dot=true) protected Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
@Styleable(dot=true) protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected String outline;
/** @since 2 */ @Styleable protected Color outlineColor;
/** @since 2 */ @Styleable protected Color outlineFocusedColor;
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
@@ -87,9 +114,11 @@ public class FlatBorder
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = scale( (float) getFocusWidth( c ) );
float borderWidth = scale( (float) getBorderWidth( c ) );
float focusInnerWidth = 0;
float borderWidth = scale( getBorderWidth( c ) );
float arc = scale( (float) getArc( c ) );
Color outlineColor = getOutlineColor( c );
Color focusColor = null;
// paint outer border
if( outlineColor != null || isFocused( c ) ) {
@@ -98,15 +127,16 @@ public class FlatBorder
: 0;
if( focusWidth > 0 || innerWidth > 0 ) {
g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height,
focusWidth, borderWidth + scale( innerWidth ), arc );
focusColor = (outlineColor != null) ? outlineColor : getFocusColor( c );
focusInnerWidth = borderWidth + scale( innerWidth );
}
}
// paint border
g2.setPaint( (outlineColor != null) ? outlineColor : getBorderColor( c ) );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null );
} finally {
g2.dispose();
}
@@ -121,6 +151,17 @@ public class FlatBorder
return null;
Object outline = ((JComponent)c).getClientProperty( FlatClientProperties.OUTLINE );
if( outline == null )
outline = this.outline;
if( outline == null ) {
if( outlineColor != null && outlineFocusedColor != null )
outline = new Color[] { outlineFocusedColor, outlineColor };
else if( outlineColor != null )
outline = outlineColor;
else if( outlineFocusedColor != null )
outline = outlineFocusedColor;
}
if( outline instanceof String ) {
switch( (String) outline ) {
case FlatClientProperties.OUTLINE_ERROR:
@@ -164,37 +205,13 @@ public class FlatBorder
}
protected boolean isFocused( Component c ) {
if( c instanceof JScrollPane ) {
JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view != null ) {
if( FlatUIUtils.isPermanentFocusOwner( view ) )
return true;
if( (view instanceof JTable && ((JTable)view).isEditing()) ||
(view instanceof JTree && ((JTree)view).isEditing()) )
{
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if( focusOwner != null )
return SwingUtilities.isDescendingFrom( focusOwner, view );
}
}
return false;
} else if( c instanceof JComboBox && ((JComboBox<?>)c).isEditable() ) {
Component editorComponent = ((JComboBox<?>)c).getEditor().getEditorComponent();
return (editorComponent != null) ? FlatUIUtils.isPermanentFocusOwner( editorComponent ) : false;
} else if( c instanceof JSpinner ) {
if( FlatUIUtils.isPermanentFocusOwner( c ) )
return true;
JComponent editor = ((JSpinner)c).getEditor();
if( editor instanceof JSpinner.DefaultEditor ) {
JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField();
if( textField != null )
return FlatUIUtils.isPermanentFocusOwner( textField );
}
return false;
} else
if( c instanceof JScrollPane )
return FlatScrollPaneUI.isPermanentFocusOwner( (JScrollPane) c );
else if( c instanceof JComboBox )
return FlatComboBoxUI.isPermanentFocusOwner( (JComboBox<?>) c );
else if( c instanceof JSpinner )
return FlatSpinnerUI.isPermanentFocusOwner( (JSpinner) c );
else
return FlatUIUtils.isPermanentFocusOwner( c );
}
@@ -205,13 +222,14 @@ public class FlatBorder
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
float focusWidth = scale( (float) getFocusWidth( c ) );
float ow = focusWidth + scale( (float) getLineWidth( c ) );
int ow = Math.round( focusWidth + scale( (float) getLineWidth( c ) ) );
insets = super.getBorderInsets( c, insets );
insets.top = Math.round( scale( (float) insets.top ) + ow );
insets.left = Math.round( scale( (float) insets.left ) + ow );
insets.bottom = Math.round( scale( (float) insets.bottom ) + ow );
insets.right = Math.round( scale( (float) insets.right ) + ow );
insets.top = scale( insets.top ) + ow;
insets.left = scale( insets.left ) + ow;
insets.bottom = scale( insets.bottom ) + ow;
insets.right = scale( insets.right ) + ow;
if( isCellEditor( c ) ) {
// remove top and bottom insets if used as cell editor
@@ -256,8 +274,8 @@ public class FlatBorder
* Returns the (unscaled) line thickness used to paint the border.
* This may be different to {@link #getLineWidth}.
*/
protected int getBorderWidth( Component c ) {
return getLineWidth( c );
protected float getBorderWidth( Component c ) {
return borderWidth;
}
/**

View File

@@ -20,69 +20,115 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import javax.swing.AbstractButton;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Border for {@link javax.swing.JButton}.
*
* @uiDefault Button.arc int
* @uiDefault Button.innerFocusWidth int or float optional; defaults to Component.innerFocusWidth
* @uiDefault Button.borderWidth int or float optional; defaults to Component.borderWidth
*
* @uiDefault Button.borderColor Color
* @uiDefault Button.startBorderColor Color optional; if set, a gradient paint is used and Button.borderColor is ignored
* @uiDefault Button.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional
*
* @uiDefault Button.default.borderWidth int or float
* @uiDefault Button.default.borderColor Color
* @uiDefault Button.default.startBorderColor Color optional; if set, a gradient paint is used and Button.default.borderColor is ignored
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.hoverBorderColor Color optional
* @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color
* @uiDefault Button.borderWidth int
* @uiDefault Button.default.borderWidth int
* @uiDefault Button.innerFocusWidth int or float optional; defaults to Component.innerFocusWidth
* @uiDefault Button.default.hoverBorderColor Color optional
*
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5
* @uiDefault Button.toolbar.focusColor Color optional; defaults to Component.focusColor
* @uiDefault Button.toolbar.margin Insets
* @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.arc int
*
* @author Karl Tauber
*/
public class FlatButtonBorder
extends FlatBorder
{
protected final Color borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" );
protected final Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
protected final Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
protected final Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
protected final Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" );
protected final Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
protected final int borderWidth = UIManager.getInt( "Button.borderWidth" );
protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" );
protected final float buttonInnerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth );
protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
protected final int arc = UIManager.getInt( "Button.arc" );
@Styleable protected int arc = UIManager.getInt( "Button.arc" );
protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
@Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 );
@Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
protected Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" );
@Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
@Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
/** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
/** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@Styleable(dot=true) protected Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
@Styleable(dot=true) protected Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
public FlatButtonBorder() {
innerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth );
borderWidth = FlatUIUtils.getUIFloat( "Button.borderWidth", borderWidth );
borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" );
disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" );
focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( FlatButtonUI.isContentAreaFilled( c ) &&
!FlatButtonUI.isToolBarButton( c ) &&
(!FlatButtonUI.isBorderlessButton( c ) || FlatUIUtils.isPermanentFocusOwner( c )) &&
!FlatButtonUI.isHelpButton( c ) &&
!FlatToggleButtonUI.isTabButton( c ) )
super.paintBorder( c, g, x, y, width, height );
else if( FlatButtonUI.isToolBarButton( c ) && isFocused( c ) )
paintToolBarFocus( c, g, x, y, width, height );
}
/** @since 1.4 */
protected void paintToolBarFocus( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = UIScale.scale( toolbarFocusWidth );
float arc = UIScale.scale( (float) getArc( c ) );
Color outlineColor = getOutlineColor( c );
Insets spacing = UIScale.scale( toolbarSpacingInsets );
x += spacing.left;
y += spacing.top;
width -= spacing.left + spacing.right;
height -= spacing.top + spacing.bottom;
Color color = (outlineColor != null) ? outlineColor : getFocusColor( c );
// not using focus border painting of paintOutlinedComponent() here
// because its round edges look too "thick"
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, 0, 0, 0, focusWidth, arc, null, color, null );
} finally {
g2.dispose();
}
}
@Override
protected Color getFocusColor( Component c ) {
return FlatButtonUI.isDefaultButton( c ) ? defaultFocusColor : super.getFocusColor( c );
return (toolbarFocusColor != null && FlatButtonUI.isToolBarButton( c ))
? toolbarFocusColor
: (FlatButtonUI.isDefaultButton( c ) ? defaultFocusColor : super.getFocusColor( c ));
}
@Override
@@ -113,7 +159,7 @@ public class FlatButtonBorder
public Insets getBorderInsets( Component c, Insets insets ) {
if( FlatButtonUI.isToolBarButton( c ) ) {
// In toolbars, use button margin only if explicitly set.
// Otherwise use toolbar margin specified in UI defaults.
// Otherwise, use toolbar margin specified in UI defaults.
Insets margin = (c instanceof AbstractButton)
? ((AbstractButton)c).getMargin()
: null;
@@ -137,12 +183,7 @@ public class FlatButtonBorder
}
@Override
protected float getInnerFocusWidth( Component c ) {
return buttonInnerFocusWidth;
}
@Override
protected int getBorderWidth( Component c ) {
protected float getBorderWidth( Component c ) {
return FlatButtonUI.isDefaultButton( c ) ? defaultBorderWidth : borderWidth;
}

View File

@@ -30,20 +30,33 @@ import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -65,20 +78,27 @@ import com.formdev.flatlaf.util.UIScale;
* @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.focusedForeground Color optional
* @uiDefault Button.hoverBackground Color optional
* @uiDefault Button.hoverForeground Color optional
* @uiDefault Button.pressedBackground Color optional
* @uiDefault Button.pressedForeground Color optional
* @uiDefault Button.selectedBackground Color
* @uiDefault Button.selectedForeground Color
* @uiDefault Button.disabledBackground Color optional
* @uiDefault Button.disabledText Color
* @uiDefault Button.disabledSelectedBackground Color
* @uiDefault Button.disabledSelectedForeground Color optional
* @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.focusedForeground Color optional
* @uiDefault Button.default.hoverBackground Color optional
* @uiDefault Button.default.hoverForeground Color optional
* @uiDefault Button.default.pressedBackground Color optional
* @uiDefault Button.default.pressedForeground Color optional
* @uiDefault Button.default.boldText boolean
* @uiDefault Button.paintShadow boolean default is false
* @uiDefault Button.shadowWidth int default is 2
@@ -86,15 +106,21 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.default.shadowColor Color optional
* @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.toolbar.hoverBackground Color
* @uiDefault Button.toolbar.hoverForeground Color optional
* @uiDefault Button.toolbar.pressedBackground Color
* @uiDefault Button.toolbar.pressedForeground Color optional
* @uiDefault Button.toolbar.selectedBackground Color
* @uiDefault Button.toolbar.selectedForeground Color optional
* @uiDefault Button.toolbar.disabledSelectedBackground Color optional
* @uiDefault Button.toolbar.disabledSelectedForeground Color optional
*
* @author Karl Tauber
*/
public class FlatButtonUI
extends BasicButtonUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected int iconTextGap;
protected Color background;
@@ -102,38 +128,81 @@ public class FlatButtonUI
protected Color startBackground;
protected Color endBackground;
protected Color focusedBackground;
protected Color hoverBackground;
protected Color pressedBackground;
protected Color selectedBackground;
protected Color selectedForeground;
protected Color disabledBackground;
protected Color disabledText;
protected Color disabledSelectedBackground;
@Styleable protected Color focusedBackground;
/** @since 2.3 */ @Styleable protected Color focusedForeground;
@Styleable protected Color hoverBackground;
/** @since 2.3 */ @Styleable protected Color hoverForeground;
@Styleable protected Color pressedBackground;
/** @since 2.3 */ @Styleable protected Color pressedForeground;
@Styleable protected Color selectedBackground;
@Styleable protected Color selectedForeground;
@Styleable protected Color disabledBackground;
@Styleable protected Color disabledText;
@Styleable protected Color disabledSelectedBackground;
/** @since 2.3 */ @Styleable protected Color disabledSelectedForeground;
protected Color defaultBackground;
@Styleable(dot=true) protected Color defaultBackground;
protected Color defaultEndBackground;
protected Color defaultForeground;
protected Color defaultFocusedBackground;
protected Color defaultHoverBackground;
protected Color defaultPressedBackground;
protected boolean defaultBoldText;
@Styleable(dot=true) protected Color defaultForeground;
@Styleable(dot=true) protected Color defaultFocusedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color defaultFocusedForeground;
@Styleable(dot=true) protected Color defaultHoverBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color defaultHoverForeground;
@Styleable(dot=true) protected Color defaultPressedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color defaultPressedForeground;
@Styleable(dot=true) protected boolean defaultBoldText;
protected int shadowWidth;
protected Color shadowColor;
protected Color defaultShadowColor;
@Styleable protected boolean paintShadow;
@Styleable protected int shadowWidth;
@Styleable protected Color shadowColor;
@Styleable(dot=true) protected Color defaultShadowColor;
protected Insets toolbarSpacingInsets;
protected Color toolbarHoverBackground;
protected Color toolbarPressedBackground;
protected Color toolbarSelectedBackground;
@Styleable(dot=true) protected Color toolbarHoverBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color toolbarHoverForeground;
@Styleable(dot=true) protected Color toolbarPressedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color toolbarPressedForeground;
@Styleable(dot=true) protected Color toolbarSelectedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color toolbarSelectedForeground;
/** @since 2.3 */ @Styleable(dot=true) protected Color toolbarDisabledSelectedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color toolbarDisabledSelectedForeground;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected String buttonType;
/** @since 2 */ @Styleable protected boolean squareSize;
/** @since 2 */ @Styleable protected int minimumHeight;
private Icon helpButtonIcon;
private Insets defaultMargin;
private final boolean shared;
private boolean helpButtonIconShared = true;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatButtonUI.class, FlatButtonUI::new );
return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c )
? FlatUIUtils.createSharedUI( FlatButtonUI.class, () -> new FlatButtonUI( true ) )
: new FlatButtonUI( false );
}
/** @since 2 */
protected FlatButtonUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
if( FlatUIUtils.needsLightAWTPeer( c ) )
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) );
else
installUIImpl( c );
}
private void installUIImpl( JComponent c ) {
super.installUI( c );
installStyle( (AbstractButton) c );
}
@Override
@@ -152,39 +221,47 @@ public class FlatButtonUI
startBackground = UIManager.getColor( prefix + "startBackground" );
endBackground = UIManager.getColor( prefix + "endBackground" );
focusedBackground = UIManager.getColor( prefix + "focusedBackground" );
focusedForeground = UIManager.getColor( prefix + "focusedForeground" );
hoverBackground = UIManager.getColor( prefix + "hoverBackground" );
hoverForeground = UIManager.getColor( prefix + "hoverForeground" );
pressedBackground = UIManager.getColor( prefix + "pressedBackground" );
pressedForeground = UIManager.getColor( prefix + "pressedForeground" );
selectedBackground = UIManager.getColor( prefix + "selectedBackground" );
selectedForeground = UIManager.getColor( prefix + "selectedForeground" );
disabledBackground = UIManager.getColor( prefix + "disabledBackground" );
disabledText = UIManager.getColor( prefix + "disabledText" );
disabledSelectedBackground = UIManager.getColor( prefix + "disabledSelectedBackground" );
if( UIManager.getBoolean( "Button.paintShadow" ) ) {
shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 );
shadowColor = UIManager.getColor( "Button.shadowColor" );
defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" );
} else {
shadowWidth = 0;
shadowColor = null;
defaultShadowColor = null;
}
disabledSelectedForeground = UIManager.getColor( prefix + "disabledSelectedForeground" );
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" );
defaultFocusedForeground = UIManager.getColor( "Button.default.focusedForeground" );
defaultHoverBackground = UIManager.getColor( "Button.default.hoverBackground" );
defaultHoverForeground = UIManager.getColor( "Button.default.hoverForeground" );
defaultPressedBackground = UIManager.getColor( "Button.default.pressedBackground" );
defaultPressedForeground = UIManager.getColor( "Button.default.pressedForeground" );
defaultBoldText = UIManager.getBoolean( "Button.default.boldText" );
toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
paintShadow = UIManager.getBoolean( "Button.paintShadow" );
shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 );
shadowColor = UIManager.getColor( "Button.shadowColor" );
defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" );
toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" );
toolbarHoverForeground = UIManager.getColor( prefix + "toolbar.hoverForeground" );
toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" );
toolbarPressedForeground = UIManager.getColor( prefix + "toolbar.pressedForeground" );
toolbarSelectedBackground = UIManager.getColor( prefix + "toolbar.selectedBackground" );
toolbarSelectedForeground = UIManager.getColor( prefix + "toolbar.selectedForeground" );
toolbarDisabledSelectedBackground = UIManager.getColor( prefix + "toolbar.disabledSelectedBackground" );
toolbarDisabledSelectedForeground = UIManager.getColor( prefix + "toolbar.disabledSelectedForeground" );
helpButtonIcon = UIManager.getIcon( "HelpButton.icon" );
defaultMargin = UIManager.getInsets( prefix + "margin" );
helpButtonIconShared = true;
defaults_initialized = true;
}
@@ -204,6 +281,9 @@ public class FlatButtonUI
protected void uninstallDefaults( AbstractButton b ) {
super.uninstallDefaults( b );
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
@@ -225,9 +305,86 @@ public class FlatButtonUI
b.revalidate();
b.repaint();
break;
case OUTLINE:
b.repaint();
break;
case STYLE:
case STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
b.revalidate();
b.repaint();
break;
}
}
/** @since 2 */
protected void installStyle( AbstractButton b ) {
try {
applyStyle( b, FlatStylingSupport.getResolvedStyle( b, getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "Button";
}
/** @since 2 */
protected void applyStyle( AbstractButton b, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( b, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) ) {
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
return new UnknownStyleException( key );
if( helpButtonIconShared ) {
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
helpButtonIconShared = false;
}
key = key.substring( "help.".length() );
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, b, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() );
if( helpButtonIcon instanceof FlatHelpButtonIcon )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() );
return infos;
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.startsWith( "help." ) ) {
return (helpButtonIcon instanceof FlatHelpButtonIcon)
? ((FlatHelpButtonIcon)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
: null;
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, c.getBorder(), key );
}
static boolean isContentAreaFilled( Component c ) {
return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled();
}
@@ -242,7 +399,7 @@ public class FlatButtonUI
/**
* Returns true if the button has an icon but no text,
* or it it does not have an icon and the text is either "..." or one character.
* or it does not have an icon and the text is either "..." or one character.
*/
static boolean isIconOnlyOrSingleCharacterButton( Component c ) {
if( !(c instanceof JButton) && !(c instanceof JToggleButton) )
@@ -265,11 +422,11 @@ public class FlatButtonUI
if( !(c instanceof AbstractButton) )
return TYPE_OTHER;
Object value = ((AbstractButton)c).getClientProperty( BUTTON_TYPE );
if( !(value instanceof String) )
String value = getButtonTypeStr( (AbstractButton) c );
if( value == null )
return TYPE_OTHER;
switch( (String) value ) {
switch( value ) {
case BUTTON_TYPE_SQUARE: return TYPE_SQUARE;
case BUTTON_TYPE_ROUND_RECT: return TYPE_ROUND_RECT;
default: return TYPE_OTHER;
@@ -277,12 +434,27 @@ public class FlatButtonUI
}
static boolean isHelpButton( Component c ) {
return c instanceof JButton && clientPropertyEquals( (JButton) c, BUTTON_TYPE, BUTTON_TYPE_HELP );
return c instanceof JButton && BUTTON_TYPE_HELP.equals( getButtonTypeStr( (JButton) c ) );
}
static boolean isToolBarButton( Component c ) {
return c.getParent() instanceof JToolBar ||
(c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ));
(c instanceof AbstractButton && BUTTON_TYPE_TOOLBAR_BUTTON.equals( getButtonTypeStr( (AbstractButton) c ) ));
}
static boolean isBorderlessButton( Component c ) {
return c instanceof AbstractButton && BUTTON_TYPE_BORDERLESS.equals( getButtonTypeStr( (AbstractButton) c ) );
}
static String getButtonTypeStr( AbstractButton c ) {
// get from client property
Object value = c.getClientProperty( BUTTON_TYPE );
if( value instanceof String )
return (String) value;
// get from styling property
ButtonUI ui = c.getUI();
return (ui instanceof FlatButtonUI) ? ((FlatButtonUI)ui).buttonType : null;
}
@Override
@@ -311,29 +483,48 @@ public class FlatButtonUI
try {
FlatUIUtils.setRenderingHints( g2 );
boolean def = isDefaultButton( c );
boolean isToolBarButton = isToolBarButton( c );
float focusWidth = isToolBarButton ? 0 : FlatUIUtils.getBorderFocusWidth( c );
float arc = FlatUIUtils.getBorderArc( c );
float textFieldArc = 0;
boolean def = isDefaultButton( c );
// if toolbar button is in leading/trailing component of a text field,
// increase toolbar button arc to match text field arc (if necessary)
if( isToolBarButton &&
FlatClientProperties.clientProperty( c, STYLE_CLASS, "", String.class ).contains( "inTextField" ) )
{
JTextField textField = (JTextField) SwingUtilities.getAncestorOfClass( JTextField.class, c );
if( textField != null )
textFieldArc = FlatUIUtils.getBorderArc( textField );
}
int x = 0;
int y = 0;
int width = c.getWidth();
int height = c.getHeight();
if( isToolBarButton ) {
Insets spacing = UIScale.scale( toolbarSpacingInsets );
if( isToolBarButton && c.getBorder() instanceof FlatButtonBorder ) {
Insets spacing = UIScale.scale( ((FlatButtonBorder)c.getBorder()).toolbarSpacingInsets );
x += spacing.left;
y += spacing.top;
width -= spacing.left + spacing.right;
height -= spacing.top + spacing.bottom;
// reduce text field arc
textFieldArc -= spacing.top + spacing.bottom;
}
// increase toolbar button arc to match text field arc (if necessary)
if( arc < textFieldArc )
arc = textFieldArc;
// paint shadow
Color shadowColor = def ? defaultShadowColor : this.shadowColor;
if( !isToolBarButton && shadowColor != null && shadowWidth > 0 && focusWidth > 0 &&
!(isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c )) && c.isEnabled() )
if( paintShadow &&
shadowColor != null && shadowWidth > 0 && focusWidth > 0 && c.isEnabled() &&
!isToolBarButton && !isBorderlessButton( c ) &&
!(isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c )) )
{
g2.setColor( shadowColor );
g2.fill( new RoundRectangle2D.Float( focusWidth, focusWidth + UIScale.scale( (float) shadowWidth ),
@@ -359,6 +550,23 @@ public class FlatButtonUI
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
}
@Override
protected void paintIcon( Graphics g, JComponent c, Rectangle iconRect ) {
// correct icon location when using bold font for default button
int xOffset = defaultBoldPlainWidthDiff( c ) / 2;
if( xOffset > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight();
switch( ((AbstractButton)c).getHorizontalTextPosition() ) {
case SwingConstants.RIGHT: iconRect.x -= xOffset; break;
case SwingConstants.LEFT: iconRect.x += xOffset; break;
case SwingConstants.TRAILING: iconRect.x -= ltr ? xOffset : -xOffset; break;
case SwingConstants.LEADING: iconRect.x += ltr ? xOffset : -xOffset; break;
}
}
super.paintIcon( g, c, iconRect );
}
@Override
protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) {
if( isHelpButton( b ) )
@@ -379,6 +587,8 @@ public class FlatButtonUI
}
public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
if(foreground == null)
foreground=Color.red;
FontMetrics fm = b.getFontMetrics( b.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -388,47 +598,47 @@ public class FlatButtonUI
}
protected Color getBackground( JComponent c ) {
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state
if( ((AbstractButton)c).isSelected() ) {
// in toolbar use same colors for disabled and enabled because
// in toolbar, if toolbarDisabledSelectedBackground is null,
// use same background colors for disabled and enabled because
// we assume that toolbar icon is shown disabled
boolean toolBarButton = isToolBarButton( c );
return buttonStateColor( c,
toolBarButton ? toolbarSelectedBackground : selectedBackground,
toolBarButton ? toolbarSelectedBackground : disabledSelectedBackground,
null, null,
toolBarButton
? (toolbarDisabledSelectedBackground != null ? toolbarDisabledSelectedBackground : toolbarSelectedBackground)
: disabledSelectedBackground,
null,
null,
toolBarButton ? toolbarPressedBackground : pressedBackground );
}
if( !c.isEnabled() )
return disabledBackground;
// toolbar button
if( isToolBarButton( c ) ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isPressed() )
return toolbarPressedBackground;
if( model.isRollover() )
return toolbarHoverBackground;
// use component background if explicitly set
if( toolBarButton ) {
Color bg = c.getBackground();
if( isCustomBackground( bg ) )
return bg;
// do not paint background
return null;
return buttonStateColor( c,
isCustomBackground( bg ) ? bg : null,
null,
null,
toolbarHoverBackground,
toolbarPressedBackground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getBackgroundBase( c, def ),
null,
disabledBackground,
isCustomBackground( c.getBackground() ) ? null : (def ? defaultFocusedBackground : focusedBackground),
def ? defaultHoverBackground : hoverBackground,
def ? defaultPressedBackground : pressedBackground );
}
protected Color getBackgroundBase( JComponent c, boolean def ) {
if( FlatUIUtils.isAWTPeer( c ) )
return background;
// use component background if explicitly set
Color bg = c.getBackground();
if( isCustomBackground( bg ) )
@@ -444,16 +654,21 @@ public class FlatButtonUI
public static Color buttonStateColor( Component c, Color enabledColor, Color disabledColor,
Color focusedColor, Color hoverColor, Color pressedColor )
{
AbstractButton b = (c instanceof AbstractButton) ? (AbstractButton) c : null;
if( c == null )
return enabledColor;
if( !c.isEnabled() )
return disabledColor;
if( pressedColor != null && b != null && b.getModel().isPressed() )
return pressedColor;
if( c instanceof AbstractButton ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( hoverColor != null && b != null && b.getModel().isRollover() )
return hoverColor;
if( pressedColor != null && model.isPressed() )
return pressedColor;
if( hoverColor != null && model.isRollover() )
return hoverColor;
}
if( focusedColor != null && isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c ) )
return focusedColor;
@@ -462,18 +677,48 @@ public class FlatButtonUI
}
protected Color getForeground( JComponent c ) {
if( !c.isEnabled() )
return disabledText;
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
if( ((AbstractButton)c).isSelected() && !isToolBarButton( c ) )
return selectedForeground;
// selected state
if( ((AbstractButton)c).isSelected() ) {
return buttonStateColor( c,
toolBarButton
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : c.getForeground())
: selectedForeground,
toolBarButton
? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText)
: (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText),
null,
null,
toolBarButton ? toolbarPressedForeground : pressedForeground );
}
// toolbar button
if( toolBarButton ) {
return buttonStateColor( c,
c.getForeground(),
disabledText,
null,
toolbarHoverForeground,
toolbarPressedForeground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getForegroundBase( c, def ),
disabledText,
isCustomForeground( c.getForeground() ) ? null : (def ? defaultFocusedForeground : focusedForeground),
def ? defaultHoverForeground : hoverForeground,
def ? defaultPressedForeground : pressedForeground );
}
/** @since 2.3 */
protected Color getForegroundBase( JComponent c, boolean def ) {
// use component foreground if explicitly set
Color fg = c.getForeground();
if( isCustomForeground( fg ) )
return fg;
boolean def = isDefaultButton( c );
return def ? defaultForeground : fg;
}
@@ -490,24 +735,51 @@ public class FlatButtonUI
if( prefSize == null )
return null;
// increase width when using bold font for default button
prefSize.width += defaultBoldPlainWidthDiff( c );
// make square or apply minimum width/height
boolean isIconOnlyOrSingleCharacter = isIconOnlyOrSingleCharacterButton( c );
if( clientPropertyBoolean( c, SQUARE_SIZE, false ) ) {
if( clientPropertyBoolean( c, SQUARE_SIZE, squareSize ) ) {
// make button square (increase width or height so that they are equal)
prefSize.width = prefSize.height = Math.max( prefSize.width, prefSize.height );
} else if( isIconOnlyOrSingleCharacter && ((AbstractButton)c).getIcon() == null ) {
// make single-character-no-icon button square (increase width)
prefSize.width = Math.max( prefSize.width, prefSize.height );
} else if( !isIconOnlyOrSingleCharacter && !isToolBarButton( c ) && c.getBorder() instanceof FlatButtonBorder ) {
} else if( !isIconOnlyOrSingleCharacter && !isToolBarButton( c ) &&
c.getBorder() instanceof FlatButtonBorder && hasDefaultMargins( c ) )
{
// apply minimum width/height
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + Math.round( focusWidth * 2 ) );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) ) + Math.round( focusWidth * 2 ) );
int fw = Math.round( FlatUIUtils.getBorderFocusWidth( c ) * 2 );
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + fw );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, minimumHeight ) ) + fw );
}
return prefSize;
}
private int defaultBoldPlainWidthDiff( JComponent c ) {
if( defaultBoldText && isDefaultButton( c ) && c.getFont() instanceof UIResource ) {
String text = ((AbstractButton)c).getText();
if( text == null || text.isEmpty() )
return 0;
Font font = c.getFont();
Font boldFont = font.deriveFont( Font.BOLD );
int boldWidth = c.getFontMetrics( boldFont ).stringWidth( text );
int plainWidth = c.getFontMetrics( font ).stringWidth( text );
if( boldWidth > plainWidth )
return boldWidth - plainWidth;
}
return 0;
}
private boolean hasDefaultMargins( JComponent c ) {
Insets margin = ((AbstractButton)c).getMargin();
return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
}
//---- class FlatButtonListener -------------------------------------------
protected class FlatButtonListener

View File

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

View File

@@ -18,11 +18,19 @@ package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBoxMenuItem}.
@@ -52,15 +60,30 @@ import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicMenuItemUI.class, key="selectionBackground" )
@StyleableField( cls=BasicMenuItemUI.class, key="selectionForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="disabledForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorSelectionForeground" )
public class FlatCheckBoxMenuItemUI
extends BasicCheckBoxMenuItemUI
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatCheckBoxMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +97,59 @@ public class FlatCheckBoxMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "CheckBoxMenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( this, renderer );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatMenuItemUI.getStyleableValue( this, renderer, key );
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

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

View File

@@ -16,12 +16,15 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.util.UIScale.scale;
import static com.formdev.flatlaf.util.UIScale.unscale;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
@@ -39,10 +42,12 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxEditor;
import javax.swing.CellRendererPane;
import javax.swing.DefaultListCellRenderer;
import javax.swing.InputMap;
import javax.swing.JButton;
@@ -60,13 +65,17 @@ import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JComboBox}.
@@ -80,6 +89,11 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ComboBox.padding Insets
* @uiDefault ComboBox.squareButton boolean default is true
*
* <!-- BasicComboPopup -->
*
* @uiDefault ComboBox.selectionBackground Color
* @uiDefault ComboBox.selectionForeground Color
*
* <!-- FlatComboBoxUI -->
*
* @uiDefault ComboBox.minimumWidth int
@@ -88,52 +102,82 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ComboBox.buttonStyle String auto (default), button or none
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
* @uiDefault ComboBox.editableBackground Color optional; defaults to ComboBox.background
* @uiDefault ComboBox.focusedBackground Color optional
* @uiDefault ComboBox.disabledBackground Color
* @uiDefault ComboBox.disabledForeground Color
* @uiDefault ComboBox.buttonBackground Color
* @uiDefault ComboBox.buttonEditableBackground Color
* @uiDefault ComboBox.buttonBackground Color optional
* @uiDefault ComboBox.buttonEditableBackground Color optional
* @uiDefault ComboBox.buttonFocusedBackground Color optional; defaults to ComboBox.focusedBackground
* @uiDefault ComboBox.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault ComboBox.buttonSeparatorColor Color optional
* @uiDefault ComboBox.buttonDisabledSeparatorColor Color optional
* @uiDefault ComboBox.buttonArrowColor Color
* @uiDefault ComboBox.buttonDisabledArrowColor Color
* @uiDefault ComboBox.buttonHoverArrowColor Color
* @uiDefault ComboBox.buttonPressedArrowColor Color
* @uiDefault ComboBox.popupBackground Color optional
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicComboBoxUI.class, key="padding" )
public class FlatComboBoxUI
extends BasicComboBoxUI
implements StyleableUI, StyleableLookupProvider
{
protected int minimumWidth;
protected int editorColumns;
protected String buttonStyle;
protected String arrowType;
@Styleable protected int minimumWidth;
@Styleable protected int editorColumns;
@Styleable protected String buttonStyle;
@Styleable protected String arrowType;
protected boolean isIntelliJTheme;
protected Color borderColor;
protected Color disabledBorderColor;
protected Color editableBackground;
protected Color disabledBackground;
protected Color disabledForeground;
private Color background;
@Styleable protected Color editableBackground;
@Styleable protected Color focusedBackground;
@Styleable protected Color disabledBackground;
@Styleable protected Color disabledForeground;
protected Color buttonBackground;
protected Color buttonEditableBackground;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
protected Color buttonHoverArrowColor;
protected Color buttonPressedArrowColor;
@Styleable protected Color buttonBackground;
@Styleable protected Color buttonEditableBackground;
@Styleable protected Color buttonFocusedBackground;
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
@Styleable protected Color buttonArrowColor;
@Styleable protected Color buttonDisabledArrowColor;
@Styleable protected Color buttonHoverArrowColor;
@Styleable protected Color buttonPressedArrowColor;
@Styleable protected Color popupBackground;
private MouseListener hoverListener;
protected boolean hover;
protected boolean pressed;
private WeakReference<Component> lastRendererComponent;
private CellPaddingBorder paddingBorder;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatComboBoxUI();
}
@Override
public void installUI( JComponent c ) {
if( FlatUIUtils.needsLightAWTPeer( c ) )
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) );
else
installUIImpl( c );
}
private void installUIImpl( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installListeners() {
super.installListeners();
@@ -190,27 +234,32 @@ public class FlatComboBoxUI
buttonStyle = UIManager.getString( "ComboBox.buttonStyle" );
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" );
disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
background = UIManager.getColor( "ComboBox.background" );
editableBackground = UIManager.getColor( "ComboBox.editableBackground" );
focusedBackground = UIManager.getColor( "ComboBox.focusedBackground" );
disabledBackground = UIManager.getColor( "ComboBox.disabledBackground" );
disabledForeground = UIManager.getColor( "ComboBox.disabledForeground" );
buttonBackground = UIManager.getColor( "ComboBox.buttonBackground" );
buttonFocusedBackground = UIManager.getColor( "ComboBox.buttonFocusedBackground" );
buttonEditableBackground = UIManager.getColor( "ComboBox.buttonEditableBackground" );
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "ComboBox.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
buttonSeparatorColor = UIManager.getColor( "ComboBox.buttonSeparatorColor" );
buttonDisabledSeparatorColor = UIManager.getColor( "ComboBox.buttonDisabledSeparatorColor" );
buttonArrowColor = UIManager.getColor( "ComboBox.buttonArrowColor" );
buttonDisabledArrowColor = UIManager.getColor( "ComboBox.buttonDisabledArrowColor" );
buttonHoverArrowColor = UIManager.getColor( "ComboBox.buttonHoverArrowColor" );
buttonPressedArrowColor = UIManager.getColor( "ComboBox.buttonPressedArrowColor" );
popupBackground = UIManager.getColor( "ComboBox.popupBackground" );
// set maximumRowCount
int maximumRowCount = UIManager.getInt( "ComboBox.maximumRowCount" );
if( maximumRowCount > 0 && maximumRowCount != 8 && comboBox.getMaximumRowCount() == 8 )
comboBox.setMaximumRowCount( maximumRowCount );
// scale
padding = UIScale.scale( padding );
paddingBorder = new CellPaddingBorder( padding );
MigLayoutVisualPadding.install( comboBox );
}
@@ -219,20 +268,29 @@ public class FlatComboBoxUI
protected void uninstallDefaults() {
super.uninstallDefaults();
borderColor = null;
disabledBorderColor = null;
background = null;
editableBackground = null;
focusedBackground = null;
disabledBackground = null;
disabledForeground = null;
buttonBackground = null;
buttonEditableBackground = null;
buttonFocusedBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
buttonDisabledArrowColor = null;
buttonHoverArrowColor = null;
buttonPressedArrowColor = null;
popupBackground = null;
paddingBorder.uninstall();
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@@ -243,9 +301,31 @@ public class FlatComboBoxUI
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
if ( editor != null && padding != null ) {
// fix editor bounds by subtracting padding
editor.setBounds( FlatUIUtils.subtractInsets( editor.getBounds(), padding ) );
// on macOS, a Swing combo box is used for AWT component java.awt.Choice
// and the font may be (temporary) null
if( arrowButton != null && comboBox.getFont() != null ) {
// limit button width to height of a raw combobox (without insets)
FontMetrics fm = comboBox.getFontMetrics( comboBox.getFont() );
int maxButtonWidth = fm.getHeight() + scale( padding.top ) + scale( padding.bottom );
int minButtonWidth = (maxButtonWidth * 3) / 4;
// make button square (except if width is limited)
Insets insets = getInsets();
int buttonWidth = Math.min( Math.max( parent.getHeight() - insets.top - insets.bottom, minButtonWidth ), maxButtonWidth );
if( buttonWidth != arrowButton.getWidth() ) {
// set width of arrow button
int xOffset = comboBox.getComponentOrientation().isLeftToRight()
? arrowButton.getWidth() - buttonWidth
: 0;
arrowButton.setBounds( arrowButton.getX() + xOffset, arrowButton.getY(),
buttonWidth, arrowButton.getHeight() );
// update editor bounds
if( editor != null )
editor.setBounds( rectangleForCurrentValue() );
}
}
}
};
@@ -289,12 +369,30 @@ public class FlatComboBoxUI
} else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
editor.applyComponentOrientation( o );
} else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint();
else if( FlatClientProperties.COMPONENT_ROUND_RECT.equals( propertyName ) )
comboBox.repaint();
else if( FlatClientProperties.MINIMUM_WIDTH.equals( propertyName ) )
comboBox.revalidate();
} else {
switch( propertyName ) {
case PLACEHOLDER_TEXT:
if( editor != null )
editor.repaint();
break;
case COMPONENT_ROUND_RECT:
case OUTLINE:
comboBox.repaint();
break;
case MINIMUM_WIDTH:
comboBox.revalidate();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
comboBox.revalidate();
comboBox.repaint();
break;
}
}
};
}
@@ -304,39 +402,32 @@ public class FlatComboBoxUI
}
@Override
protected ComboBoxEditor createEditor() {
ComboBoxEditor comboBoxEditor = super.createEditor();
protected void configureEditor() {
super.configureEditor();
Component editor = comboBoxEditor.getEditorComponent();
if( editor instanceof JTextField ) {
JTextField textField = (JTextField) editor;
textField.setColumns( editorColumns );
// assign a non-null and non-javax.swing.plaf.UIResource border to the text field,
// otherwise it is replaced with default text field border when switching LaF
// because javax.swing.plaf.basic.BasicComboBoxEditor.BorderlessTextField.setBorder()
// uses "border instanceof javax.swing.plaf.basic.BasicComboBoxEditor.UIResource"
// instead of "border instanceof javax.swing.plaf.UIResource"
textField.setBorder( BorderFactory.createEmptyBorder() );
// remove default text field border from editor
Border border = textField.getBorder();
if( border == null || border instanceof UIResource ) {
// assign a non-null and non-javax.swing.plaf.UIResource border to the text field,
// otherwise it is replaced with default text field border when switching LaF
// because javax.swing.plaf.basic.BasicComboBoxEditor.BorderlessTextField.setBorder()
// uses "border instanceof javax.swing.plaf.basic.BasicComboBoxEditor.UIResource"
// instead of "border instanceof javax.swing.plaf.UIResource"
textField.setBorder( BorderFactory.createEmptyBorder() );
}
}
return comboBoxEditor;
}
@Override
protected void configureEditor() {
super.configureEditor();
// remove default text field border from editor
if( editor instanceof JTextField && ((JTextField)editor).getBorder() instanceof FlatTextBorder )
((JTextField)editor).setBorder( BorderFactory.createEmptyBorder() );
// explicitly make non-opaque
if( editor instanceof JComponent )
((JComponent)editor).setOpaque( false );
editor.applyComponentOrientation( comboBox.getComponentOrientation() );
updateEditorPadding();
updateEditorColors();
// macOS
@@ -353,6 +444,25 @@ public class FlatComboBoxUI
}
}
private void updateEditorPadding() {
if( !(editor instanceof JTextField) )
return;
JTextField textField = (JTextField) editor;
Insets insets = textField.getInsets();
Insets pad = padding;
if( insets.top != 0 || insets.left != 0 || insets.bottom != 0 || insets.right != 0 ) {
// if text field has custom border, subtract text field insets from padding
pad = new Insets(
unscale( Math.max( scale( padding.top ) - insets.top, 0 ) ),
unscale( Math.max( scale( padding.left ) - insets.left, 0 ) ),
unscale( Math.max( scale( padding.bottom ) - insets.bottom, 0 ) ),
unscale( Math.max( scale( padding.right ) - insets.right, 0 ) )
);
}
textField.putClientProperty( TEXT_FIELD_PADDING, pad );
}
private void updateEditorColors() {
// use non-UIResource colors because when SwingUtilities.updateComponentTreeUI()
// is used, then the editor is updated after the combobox and the
@@ -369,10 +479,74 @@ public class FlatComboBoxUI
return new FlatComboBoxButton();
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( comboBox, "ComboBox" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
Insets oldPadding = padding;
int oldEditorColumns = editorColumns;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( !padding.equals( oldPadding ) ) {
paddingBorder.padding = padding;
updateEditorPadding();
}
if( arrowButton instanceof FlatComboBoxButton )
((FlatComboBoxButton)arrowButton).updateStyle();
if( popup instanceof FlatComboPopup )
((FlatComboPopup)popup).updateStyle();
if( editorColumns != oldEditorColumns && editor instanceof JTextField )
((JTextField)editor).setColumns( editorColumns );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, comboBox, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, comboBox.getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, comboBox.getBorder(), key );
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
public void update( Graphics g, JComponent c ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
float arc = FlatUIUtils.getBorderArc( c );
boolean paintBackground = true;
// check whether used as cell renderer
boolean isCellRenderer = c.getParent() instanceof CellRendererPane;
if( isCellRenderer ) {
focusWidth = 0;
arc = 0;
paintBackground = isCellRendererBackgroundChanged();
}
// fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && (focusWidth > 0 || arc > 0) )
@@ -390,27 +564,39 @@ public class FlatComboBoxUI
boolean isLeftToRight = comboBox.getComponentOrientation().isLeftToRight();
// paint background
g2.setColor( getBackground( enabled ) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background
if( enabled ) {
g2.setColor( paintButton ? buttonEditableBackground : buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
if( paintBackground || c.isOpaque() ) {
g2.setColor( getBackground( enabled ) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
// paint vertical line between value and arrow button
if( paintButton ) {
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
// paint arrow button background
if( enabled && !isCellRenderer ) {
Color buttonColor = paintButton
? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground;
if( buttonColor != null ) {
g2.setColor( buttonColor );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
}
// paint vertical line between value and arrow button
if( paintButton ) {
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
}
}
}
// avoid that the "current value" renderer is invoked with enabled antialiasing
@@ -422,30 +608,33 @@ public class FlatComboBoxUI
@Override
@SuppressWarnings( "unchecked" )
public void paintCurrentValue( Graphics g, Rectangle bounds, boolean hasFocus ) {
paddingBorder.uninstall();
ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer );
if( renderer == null )
renderer = new DefaultListCellRenderer();
Component c = renderer.getListCellRendererComponent( listBox, comboBox.getSelectedItem(), -1, false, false );
c.setFont( comboBox.getFont() );
c.applyComponentOrientation( comboBox.getComponentOrientation() );
uninstallCellPaddingBorder( c );
boolean enabled = comboBox.isEnabled();
c.setBackground( getBackground( enabled ) );
c.setForeground( getForeground( enabled ) );
// make renderer component temporary non-opaque to avoid that renderer paints
// background outside of border if combobox uses larger arc for edges
// (e.g. FlatClientProperties.COMPONENT_ROUND_RECT is true)
if( c instanceof JComponent )
((JComponent)c).setOpaque( false );
boolean shouldValidate = (c instanceof JPanel);
if( padding != null )
bounds = FlatUIUtils.subtractInsets( bounds, padding );
// increase the size of the rendering area to make sure that the text
// is vertically aligned with other component types (e.g. JTextField)
Insets rendererInsets = getRendererComponentInsets( c );
if( rendererInsets != null )
bounds = FlatUIUtils.addInsets( bounds, rendererInsets );
paddingBorder.install( c, 0 );
currentValuePane.paintComponent( g, c, comboBox, bounds.x, bounds.y, bounds.width, bounds.height, shouldValidate );
paddingBorder.uninstall();
if( c instanceof JComponent )
((JComponent)c).setOpaque( true );
}
@Override
@@ -454,9 +643,23 @@ public class FlatComboBoxUI
}
protected Color getBackground( boolean enabled ) {
return enabled
? (editableBackground != null && comboBox.isEditable() ? editableBackground : comboBox.getBackground())
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( comboBox ) : disabledBackground);
if( enabled ) {
if( FlatUIUtils.isAWTPeer( comboBox ) )
return background;
Color background = comboBox.getBackground();
// always use explicitly set color
if( !(background instanceof UIResource) )
return background;
// focused
if( focusedBackground != null && isPermanentFocusOwner( comboBox ) )
return focusedBackground;
return (editableBackground != null && comboBox.isEditable()) ? editableBackground : background;
} else
return isIntelliJTheme ? FlatUIUtils.getParentBackground( comboBox ) : disabledBackground;
}
protected Color getForeground( boolean enabled ) {
@@ -466,75 +669,69 @@ public class FlatComboBoxUI
@Override
public Dimension getMinimumSize( JComponent c ) {
Dimension minimumSize = super.getMinimumSize( c );
minimumSize.width = Math.max( minimumSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) );
int fw = Math.round( FlatUIUtils.getBorderFocusWidth( c ) * 2 );
minimumSize.width = Math.max( minimumSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + fw );
return minimumSize;
}
@Override
protected Dimension getDefaultSize() {
@SuppressWarnings( "unchecked" )
ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer );
paddingBorder.uninstall();
Dimension size = super.getDefaultSize();
uninstallCellPaddingBorder( renderer );
paddingBorder.uninstall();
return size;
}
@Override
protected Dimension getDisplaySize() {
@SuppressWarnings( "unchecked" )
ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer );
paddingBorder.uninstall();
Dimension displaySize = super.getDisplaySize();
paddingBorder.uninstall();
// remove padding added in super.getDisplaySize()
int displayWidth = displaySize.width - padding.left - padding.right;
int displayHeight = displaySize.height - padding.top - padding.bottom;
// recalculate width without hardcoded 100 under special conditions
if( displaySize.width == 100 + padding.left + padding.right &&
if( displayWidth == 100 &&
comboBox.isEditable() &&
comboBox.getItemCount() == 0 &&
comboBox.getPrototypeDisplayValue() == null )
{
int width = getDefaultSize().width;
width = Math.max( width, editor.getPreferredSize().width );
width += padding.left + padding.right;
displaySize = new Dimension( width, displaySize.height );
displayWidth = Math.max( getDefaultSize().width, editor.getPreferredSize().width );
}
uninstallCellPaddingBorder( renderer );
return displaySize;
return new Dimension( displayWidth, displayHeight );
}
@Override
protected Dimension getSizeForComponent( Component comp ) {
paddingBorder.install( comp, 0 );
Dimension size = super.getSizeForComponent( comp );
// remove the renderer border top/bottom insets from the size to make sure that
// the combobox gets the same height as other component types (e.g. JTextField)
Insets rendererInsets = getRendererComponentInsets( comp );
if( rendererInsets != null )
size = new Dimension( size.width, size.height - rendererInsets.top - rendererInsets.bottom );
paddingBorder.uninstall();
return size;
}
private Insets getRendererComponentInsets( Component rendererComponent ) {
if( rendererComponent instanceof JComponent ) {
Border rendererBorder = ((JComponent)rendererComponent).getBorder();
if( rendererBorder != null )
return rendererBorder.getBorderInsets( rendererComponent );
}
return null;
private boolean isCellRenderer() {
return comboBox.getParent() instanceof CellRendererPane;
}
private void uninstallCellPaddingBorder( Object o ) {
CellPaddingBorder.uninstall( o );
if( lastRendererComponent != null ) {
CellPaddingBorder.uninstall( lastRendererComponent );
lastRendererComponent = null;
}
private boolean isCellRendererBackgroundChanged() {
// parent is a CellRendererPane, parentParent is e.g. a JTable
Container parentParent = comboBox.getParent().getParent();
return parentParent != null && !comboBox.getBackground().equals( parentParent.getBackground() );
}
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JComboBox<?> comboBox ) {
if( comboBox.isEditable() ) {
if( FlatUIUtils.isPermanentFocusOwner( comboBox ) )
return true;
Component editorComponent = comboBox.getEditor().getEditorComponent();
return editorComponent != null && FlatUIUtils.isPermanentFocusOwner( editorComponent );
} else
return FlatUIUtils.isPermanentFocusOwner( comboBox );
}
//---- class FlatComboBoxButton -------------------------------------------
@@ -554,6 +751,11 @@ public class FlatComboBoxUI
hoverForeground, hoverBackground, pressedForeground, pressedBackground );
}
protected void updateStyle() {
updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor,
buttonHoverArrowColor, null, buttonPressedArrowColor, null );
}
@Override
protected boolean isHover() {
return super.isHover() || (!comboBox.isEditable() ? hover : false);
@@ -563,6 +765,14 @@ public class FlatComboBoxUI
protected boolean isPressed() {
return super.isPressed() || (!comboBox.isEditable() ? pressed : false);
}
@Override
protected Color getArrowColor() {
if( isCellRenderer() && isCellRendererBackgroundChanged() )
return comboBox.getForeground();
return super.getArrowColor();
}
}
//---- class FlatComboPopup -----------------------------------------------
@@ -571,13 +781,11 @@ public class FlatComboBoxUI
protected class FlatComboPopup
extends BasicComboPopup
{
private CellPaddingBorder paddingBorder;
protected FlatComboPopup( JComboBox combo ) {
super( combo );
// BasicComboPopup listens to JComboBox.componentOrientation and updates
// the component orientation of the list, scroller and popup, but when
// the component orientation of the list, scroll pane and popup, but when
// switching the LaF and a new combo popup is created, the component
// orientation is not applied.
ComponentOrientation o = comboBox.getComponentOrientation();
@@ -631,9 +839,15 @@ public class FlatComboBoxUI
protected void configurePopup() {
super.configurePopup();
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
Border border = UIManager.getBorder( "PopupMenu.border" );
if( border != null )
setBorder( border );
setBorder( FlatUIUtils.nonUIResource( border ) );
}
@Override
@@ -641,6 +855,17 @@ public class FlatComboBoxUI
super.configureList();
list.setCellRenderer( new PopupListCellRenderer() );
updateStyle();
}
void updateStyle() {
if( popupBackground != null )
list.setBackground( popupBackground );
// set popup background because it may shine thru when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
}
@Override
@@ -654,6 +879,34 @@ public class FlatComboBoxUI
};
}
@Override
protected int getPopupHeightForRowCount( int maxRowCount ) {
int height = super.getPopupHeightForRowCount( maxRowCount );
paddingBorder.uninstall();
return height;
}
@Override
public void show( Component invoker, int x, int y ) {
// Java 8: fix y coordinate if popup is shown above the combobox
// (already fixed in Java 9+ https://bugs.openjdk.java.net/browse/JDK-7072653)
if( y < 0 && !SystemInfo.isJava_9_orLater ) {
Border popupBorder = getBorder();
if( popupBorder != null ) {
Insets insets = popupBorder.getBorderInsets( this );
y -= insets.top + insets.bottom;
}
}
super.show( invoker, x, y );
}
@Override
protected void paintChildren( Graphics g ) {
super.paintChildren( g );
paddingBorder.uninstall();
}
//---- class PopupListCellRenderer -----
private class PopupListCellRenderer
@@ -663,22 +916,15 @@ public class FlatComboBoxUI
public Component getListCellRendererComponent( JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus )
{
ListCellRenderer renderer = comboBox.getRenderer();
CellPaddingBorder.uninstall( renderer );
CellPaddingBorder.uninstall( lastRendererComponent );
paddingBorder.uninstall();
ListCellRenderer renderer = comboBox.getRenderer();
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 );
}
lastRendererComponent = (c != renderer) ? new WeakReference<>( c ) : null;
paddingBorder.install( c, Math.round( FlatUIUtils.getBorderFocusWidth( comboBox ) ) );
return c;
}
@@ -688,50 +934,75 @@ public class FlatComboBoxUI
//---- class CellPaddingBorder --------------------------------------------
/**
* Cell padding border used only in popup list.
*
* Cell padding border used in popup list and for current value if not editable.
* <p>
* The insets are the union of the cell padding and the renderer border insets,
* which vertically aligns text in popup list with text in combobox.
*
* The renderer border is painted on the outside of this border.
* <p>
* The renderer border is painted on the outer side of this border.
*/
private static class CellPaddingBorder
extends AbstractBorder
{
private final Insets padding;
private Insets padding;
private JComponent rendererComponent;
private Border rendererBorder;
private int focusWidth;
CellPaddingBorder( Insets padding ) {
this.padding = padding;
}
void install( JComponent rendererComponent ) {
Border oldBorder = rendererComponent.getBorder();
if( !(oldBorder instanceof CellPaddingBorder) ) {
rendererBorder = oldBorder;
rendererComponent.setBorder( this );
}
}
static void uninstall( Object o ) {
if( o instanceof WeakReference )
o = ((WeakReference<?>)o).get();
if( !(o instanceof JComponent) )
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) )
return;
JComponent rendererComponent = (JComponent) o;
Border border = rendererComponent.getBorder();
if( border instanceof CellPaddingBorder ) {
CellPaddingBorder paddingBorder = (CellPaddingBorder) border;
rendererComponent.setBorder( paddingBorder.rendererBorder );
paddingBorder.rendererBorder = null;
}
this.focusWidth = focusWidth;
JComponent jc = (JComponent) c;
Border oldBorder = jc.getBorder();
if( oldBorder == this )
return; // already installed
// component already has a padding border --> uninstall it
// (may happen if single renderer instance is used in multiple comboboxes)
if( oldBorder instanceof CellPaddingBorder )
((CellPaddingBorder)oldBorder).uninstall();
// this border can be installed only at one component
// (may happen if a renderer returns varying components)
uninstall();
// remember component where this border was installed for uninstall
rendererComponent = jc;
// remember old border and replace it
rendererBorder = jc.getBorder();
jc.setBorder( this );
}
/**
* Uninstall border from previously installed component.
* Because this border is installed in PopupListCellRenderer.getListCellRendererComponent(),
* there is no single place to uninstall it.
* This is the reason why this method is called from various places.
*/
synchronized void uninstall() {
if( rendererComponent == null )
return;
if( rendererComponent.getBorder() == this )
rendererComponent.setBorder( rendererBorder );
rendererComponent = null;
rendererBorder = null;
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( rendererBorder != null ) {
synchronized public Insets getBorderInsets( Component c, Insets insets ) {
Insets padding = scale( this.padding );
if( rendererBorder != null && !(rendererBorder instanceof CellPaddingBorder) ) {
Insets insideInsets = rendererBorder.getBorderInsets( c );
insets.top = Math.max( padding.top, insideInsets.top );
insets.left = Math.max( padding.left, insideInsets.left );
@@ -743,6 +1014,12 @@ public class FlatComboBoxUI
insets.bottom = padding.bottom;
insets.right = padding.right;
}
// if used in popup list, add focus width for exact vertical alignment
// of text in popup list with text in combobox
insets.left += focusWidth;
insets.right += focusWidth;
return insets;
}

View File

@@ -16,10 +16,12 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
@@ -28,11 +30,13 @@ import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import javax.swing.JLabel;
@@ -45,6 +49,7 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicDesktopIconUI;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -75,11 +80,21 @@ public class FlatDesktopIconUI
private JToolTip titleTip;
private ActionListener closeListener;
private MouseInputListener mouseInputListener;
private PropertyChangeListener ancestorListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatDesktopIconUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
// update dock icon preview if already iconified
if( c.isDisplayable() )
updateDockIconPreviewLater();
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
@@ -136,6 +151,17 @@ public class FlatDesktopIconUI
};
closeButton.addActionListener( closeListener );
closeButton.addMouseListener( mouseInputListener );
ancestorListener = e -> {
if( e.getNewValue() != null ) {
// update dock icon preview if desktopIcon is added to desktop (internal frame was iconified)
updateDockIconPreviewLater();
} else {
// remove preview icon to release memory
dockIcon.setIcon( null );
}
};
desktopIcon.addPropertyChangeListener( "ancestor", ancestorListener );
}
@Override
@@ -146,6 +172,9 @@ public class FlatDesktopIconUI
closeButton.removeMouseListener( mouseInputListener );
closeListener = null;
mouseInputListener = null;
desktopIcon.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
}
@Override
@@ -228,15 +257,30 @@ public class FlatDesktopIconUI
return getPreferredSize( c );
}
void updateDockIcon() {
@Override
public void update( Graphics g, JComponent c ) {
if( c.isOpaque() ) {
// fill background with color derived from desktop pane
Color background = c.getBackground();
JDesktopPane desktopPane = desktopIcon.getDesktopPane();
g.setColor( (desktopPane != null)
? FlatUIUtils.deriveColor( background, desktopPane.getBackground() )
: background );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
paint( g, c );
}
private void updateDockIconPreviewLater() {
// use invoke later to make sure that components are updated when switching LaF
EventQueue.invokeLater( () -> {
if( dockIcon != null )
updateDockIconLater();
updateDockIconPreview();
} );
}
private void updateDockIconLater() {
protected void updateDockIconPreview() {
// make sure that frame is not selected
if( frame.isSelected() ) {
try {
@@ -246,13 +290,22 @@ public class FlatDesktopIconUI
}
}
// layout internal frame title pane, which was recreated when switching Laf
// (directly invoke doLayout() because frame.validate() does not work here
// because frame is not displayable)
if( !frame.isValid() )
frame.doLayout();
for( Component c : frame.getComponents() ) {
if( !c.isValid() )
c.doLayout();
}
// paint internal frame to buffered image
int frameWidth = Math.max( frame.getWidth(), 1 );
int frameHeight = Math.max( frame.getHeight(), 1 );
BufferedImage frameImage = new BufferedImage( frameWidth, frameHeight, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = frameImage.createGraphics();
try {
//TODO fix missing internal frame header when switching LaF
frame.paint( g );
} finally {
g.dispose();
@@ -270,6 +323,27 @@ public class FlatDesktopIconUI
// scale preview
Image previewImage = frameImage.getScaledInstance( previewWidth, previewHeight, Image.SCALE_SMOOTH );
if( MultiResolutionImageSupport.isAvailable() ) {
// On HiDPI screens, create preview images for 1x, 2x and current scale factor.
// The icon then chooses the best resolution for painting, which is usually
// the one for the current scale factor. But if changing scale factor or
// moving window to another screen with different scale factor, then another
// resolution may be used because the preview icon is not updated.
Image previewImage2x = frameImage.getScaledInstance( previewWidth * 2, previewHeight * 2, Image.SCALE_SMOOTH );
double scaleFactor = UIScale.getSystemScaleFactor( desktopIcon.getGraphicsConfiguration() );
if( scaleFactor != 1 && scaleFactor != 2 ) {
Image previewImageCurrent = frameImage.getScaledInstance(
(int) Math.round( previewWidth * scaleFactor ),
(int) Math.round( previewHeight * scaleFactor ),
Image.SCALE_SMOOTH );
// the images must be ordered by resolution
previewImage = (scaleFactor < 2)
? MultiResolutionImageSupport.create( 0, previewImage, previewImageCurrent, previewImage2x )
: MultiResolutionImageSupport.create( 0, previewImage, previewImage2x, previewImageCurrent );
} else
previewImage = MultiResolutionImageSupport.create( 0, previewImage, previewImage2x );
}
dockIcon.setIcon( new ImageIcon( previewImage ) );
}

View File

@@ -16,11 +16,16 @@
package com.formdev.flatlaf.ui;
import javax.swing.DefaultDesktopManager;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JInternalFrame.JDesktopIcon;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicDesktopPaneUI;
/**
@@ -36,30 +41,96 @@ import javax.swing.plaf.basic.BasicDesktopPaneUI;
public class FlatDesktopPaneUI
extends BasicDesktopPaneUI
{
private LayoutDockListener layoutDockListener;
private boolean layoutDockPending;
public static ComponentUI createUI( JComponent c ) {
return new FlatDesktopPaneUI();
}
@Override
protected void installDesktopManager() {
desktopManager = desktop.getDesktopManager();
if( desktopManager == null ) {
desktopManager = new FlatDesktopManager();
desktop.setDesktopManager( desktopManager );
public void installUI( JComponent c ) {
super.installUI( c );
layoutDockLaterOnce();
}
@Override
protected void installListeners() {
super.installListeners();
layoutDockListener = new LayoutDockListener();
desktop.addContainerListener( layoutDockListener );
desktop.addComponentListener( layoutDockListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
desktop.removeContainerListener( layoutDockListener );
desktop.removeComponentListener( layoutDockListener );
layoutDockListener = null;
}
private void layoutDockLaterOnce() {
if( layoutDockPending )
return;
layoutDockPending = true;
EventQueue.invokeLater( () -> {
layoutDockPending = false;
if( desktop != null )
layoutDock();
} );
}
protected void layoutDock() {
Dimension desktopSize = desktop.getSize();
int x = 0;
int y = desktopSize.height;
int rowHeight = 0;
for( Component c : desktop.getComponents() ) {
if( !(c instanceof JDesktopIcon) )
continue;
JDesktopIcon icon = (JDesktopIcon) c;
Dimension iconSize = icon.getPreferredSize();
if( x + iconSize.width > desktopSize.width ) {
// new row
x = 0;
y -= rowHeight;
rowHeight = 0;
}
icon.setLocation( x, y - iconSize.height );
x += iconSize.width;
rowHeight = Math.max( iconSize.height, rowHeight );
}
}
//---- class FlatDesktopManager -------------------------------------------
//---- class LayoutDockListener -------------------------------------------
private class FlatDesktopManager
extends DefaultDesktopManager
implements UIResource
private class LayoutDockListener
extends ComponentAdapter
implements ContainerListener
{
@Override
public void iconifyFrame( JInternalFrame f ) {
super.iconifyFrame( f );
public void componentAdded( ContainerEvent e ) {
layoutDockLaterOnce();
}
((FlatDesktopIconUI)f.getDesktopIcon().getUI()).updateDockIcon();
@Override
public void componentRemoved( ContainerEvent e ) {
layoutDockLaterOnce();
}
@Override
public void componentResized( ComponentEvent e ) {
layoutDockLaterOnce();
}
}
}

View File

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

View File

@@ -17,19 +17,26 @@
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.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.Caret;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}.
@@ -37,8 +44,8 @@ import com.formdev.flatlaf.util.HiDPIUtils;
* <!-- BasicEditorPaneUI -->
*
* @uiDefault EditorPane.font Font
* @uiDefault EditorPane.background Color also used if not editable
* @uiDefault EditorPane.foreground Color
* @uiDefault EditorPane.background Color
* @uiDefault EditorPane.foreground Color also used if not editable
* @uiDefault EditorPane.caretForeground Color
* @uiDefault EditorPane.selectionBackground Color
* @uiDefault EditorPane.selectionForeground Color
@@ -53,27 +60,54 @@ import com.formdev.flatlaf.util.HiDPIUtils;
*
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault EditorPane.focusedBackground Color optional
*
* @author Karl Tauber
*/
public class FlatEditorPaneUI
extends BasicEditorPaneUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected boolean isIntelliJTheme;
private Color background;
@Styleable protected Color disabledBackground;
@Styleable protected Color inactiveBackground;
@Styleable protected Color focusedBackground;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
private Insets defaultMargin;
private Object oldHonorDisplayProperties;
private FocusListener focusListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatEditorPaneUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( prefix + ".background" );
disabledBackground = UIManager.getColor( prefix + ".disabledBackground" );
inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" );
focusedBackground = UIManager.getColor( prefix + ".focusedBackground" );
defaultMargin = UIManager.getInsets( prefix + ".margin" );
// use component font and foreground for HTML text
oldHonorDisplayProperties = getComponent().getClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES );
@@ -84,34 +118,124 @@ public class FlatEditorPaneUI
protected void uninstallDefaults() {
super.uninstallDefaults();
background = null;
disabledBackground = null;
inactiveBackground = null;
focusedBackground = null;
oldDisabledBackground = null;
oldInactiveBackground = null;
oldStyleValues = null;
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
propertyChange( getComponent(), e );
protected void installListeners() {
super.installListeners();
// necessary to update focus background
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent(), c -> focusedBackground != null );
getComponent().addFocusListener( focusListener );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e ) {
@Override
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener( focusListener );
focusListener = null;
}
@Override
protected Caret createCaret() {
return new FlatCaret( null, false );
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
// invoke updateBackground() before super.propertyChange()
String propertyName = e.getPropertyName();
if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) )
updateBackground();
super.propertyChange( e );
propertyChange( getComponent(), e, this::installStyle );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
switch( e.getPropertyName() ) {
case FlatClientProperties.MINIMUM_WIDTH:
c.revalidate();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle.run();
c.revalidate();
c.repaint();
break;
}
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( getComponent(), "EditorPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
private void updateBackground() {
FlatTextFieldUI.updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
oldDisabledBackground, oldInactiveBackground );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth, defaultMargin );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
return applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth, defaultMargin );
}
static Dimension applyMinimumWidth( JComponent c, Dimension size, int minimumWidth ) {
static Dimension applyMinimumWidth( JComponent c, Dimension size, int minimumWidth, Insets defaultMargin ) {
// do not apply minimum width if JTextComponent.margin is set
if( !FlatTextFieldUI.hasDefaultMargins( c, defaultMargin ) )
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
@@ -128,14 +252,11 @@ public class FlatEditorPaneUI
@Override
protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent();
paintBackground( g, getComponent(), isIntelliJTheme, focusedBackground );
}
// for compatibility with IntelliJ themes
if( isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) && (c.getBackground() instanceof UIResource) ) {
FlatUIUtils.paintParentBackground( g, c );
return;
}
super.paintBackground( g );
static void paintBackground( Graphics g, JTextComponent c, boolean isIntelliJTheme, Color focusedBackground ) {
g.setColor( FlatTextFieldUI.getBackground( c, isIntelliJTheme, focusedBackground ) );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
}

View File

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

View File

@@ -19,11 +19,21 @@ package com.formdev.flatlaf.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.RenderingHints;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
@@ -34,12 +44,16 @@ import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalFileChooserUI;
import javax.swing.table.TableCellRenderer;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -133,12 +147,21 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault FileChooser.listViewActionLabelText String
* @uiDefault FileChooser.detailsViewActionLabelText String
*
* <!-- FlatFileChooserUI -->
*
* @uiDefault FileChooser.shortcuts.buttonSize Dimension optional; default is 84,64
* @uiDefault FileChooser.shortcuts.iconSize Dimension optional; default is 32,32
* @uiDefault FileChooser.shortcuts.filesFunction Function&lt;File[], File[]&gt;
* @uiDefault FileChooser.shortcuts.displayNameFunction Function&lt;File, String&gt;
* @uiDefault FileChooser.shortcuts.iconFunction Function&lt;File, Icon&gt;
*
* @author Karl Tauber
*/
public class FlatFileChooserUI
extends MetalFileChooserUI
{
private final FlatFileView fileView = new FlatFileView();
private FlatShortcutsPanel shortcutsPanel;
public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c );
@@ -153,6 +176,25 @@ public class FlatFileChooserUI
super.installComponents( fc );
patchUI( fc );
if( !UIManager.getBoolean( "FileChooser.noPlacesBar" ) ) { // same as in Windows L&F
FlatShortcutsPanel panel = createShortcutsPanel( fc );
if( panel.getComponentCount() > 0 ) {
shortcutsPanel = panel;
fc.add( shortcutsPanel, BorderLayout.LINE_START );
fc.addPropertyChangeListener( shortcutsPanel );
}
}
}
@Override
public void uninstallComponents( JFileChooser fc ) {
super.uninstallComponents( fc );
if( shortcutsPanel != null ) {
fc.removePropertyChangeListener( shortcutsPanel );
shortcutsPanel = null;
}
}
private void patchUI( JFileChooser fc ) {
@@ -192,6 +234,25 @@ public class FlatFileChooserUI
} catch( ArrayIndexOutOfBoundsException ex ) {
// ignore
}
// put north, center and south components into a new panel so that
// the shortcuts panel (at west) gets full height
LayoutManager layout = fc.getLayout();
if( layout instanceof BorderLayout ) {
BorderLayout borderLayout = (BorderLayout) layout;
borderLayout.setHgap( 8 );
Component north = borderLayout.getLayoutComponent( BorderLayout.NORTH );
Component center = borderLayout.getLayoutComponent( BorderLayout.CENTER );
Component south = borderLayout.getLayoutComponent( BorderLayout.SOUTH );
if( north != null && center != null && south != null ) {
JPanel p = new JPanel( new BorderLayout( 0, 11 ) );
p.add( north, BorderLayout.NORTH );
p.add( center, BorderLayout.CENTER );
p.add( south, BorderLayout.SOUTH );
fc.add( p, BorderLayout.CENTER );
}
}
}
@Override
@@ -250,9 +311,19 @@ public class FlatFileChooserUI
return p;
}
/** @since 2.3 */
protected FlatShortcutsPanel createShortcutsPanel( JFileChooser fc ) {
return new FlatShortcutsPanel( fc );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( super.getPreferredSize( c ) );
Dimension prefSize = super.getPreferredSize( c );
Dimension minSize = getMinimumSize( c );
int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0;
return new Dimension(
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
Math.max( prefSize.height, minSize.height ) );
}
@Override
@@ -262,12 +333,23 @@ public class FlatFileChooserUI
@Override
public FileView getFileView( JFileChooser fc ) {
return fileView;
return doNotUseSystemIcons() ? super.getFileView( fc ) : fileView;
}
@Override
public void clearIconCache() {
fileView.clearIconCache();
if( doNotUseSystemIcons() )
super.clearIconCache();
else
fileView.clearIconCache();
}
private boolean doNotUseSystemIcons() {
// Java 17 32bit craches on Windows when using system icons
// fixed in Java 18+ (see https://bugs.openjdk.java.net/browse/JDK-8277299)
return SystemInfo.isWindows &&
SystemInfo.isX86 &&
(SystemInfo.isJava_17_orLater && !SystemInfo.isJava_18_orLater);
}
//---- class FlatFileView -------------------------------------------------
@@ -305,4 +387,234 @@ public class FlatFileChooserUI
return icon;
}
}
//---- class FlatShortcutsPanel -------------------------------------------
/** @since 2.3 */
public static class FlatShortcutsPanel
extends JToolBar
implements PropertyChangeListener
{
private final JFileChooser fc;
private final Dimension buttonSize;
private final Dimension iconSize;
private final Function<File[], File[]> filesFunction;
private final Function<File, String> displayNameFunction;
private final Function<File, Icon> iconFunction;
protected final File[] files;
protected final JToggleButton[] buttons;
protected final ButtonGroup buttonGroup;
@SuppressWarnings( "unchecked" )
public FlatShortcutsPanel( JFileChooser fc ) {
super( JToolBar.VERTICAL );
this.fc = fc;
setFloatable( false );
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
filesFunction = (Function<File[], File[]>) UIManager.get( "FileChooser.shortcuts.filesFunction" );
displayNameFunction = (Function<File, String>) UIManager.get( "FileChooser.shortcuts.displayNameFunction" );
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView();
File[] files = getChooserShortcutPanelFiles( fsv );
if( filesFunction != null )
files = filesFunction.apply( files );
this.files = files;
// create toolbar buttons
buttons = new JToggleButton[files.length];
buttonGroup = new ButtonGroup();
for( int i = 0; i < files.length; i++ ) {
// wrap drive path
if( fsv.isFileSystemRoot( files[i] ) )
files[i] = fsv.createFileObject( files[i].getAbsolutePath() );
File file = files[i];
String name = getDisplayName( fsv, file );
Icon icon = getIcon( fsv, file );
// remove path from name
int lastSepIndex = name.lastIndexOf( File.separatorChar );
if( lastSepIndex >= 0 && lastSepIndex < name.length() - 1 )
name = name.substring( lastSepIndex + 1 );
// scale icon (if necessary)
if( icon instanceof ImageIcon )
icon = new ScaledImageIcon( (ImageIcon) icon, iconSize.width, iconSize.height );
else if( icon != null )
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
// create button
JToggleButton button = createButton( name, icon );
button.addActionListener( e -> {
fc.setCurrentDirectory( file );
} );
add( button );
buttonGroup.add( button );
buttons[i] = button;
}
directoryChanged( fc.getCurrentDirectory() );
}
private Dimension getUIDimension( String key, int defaultWidth, int defaultHeight ) {
Dimension size = UIManager.getDimension( key );
if( size == null )
size = new Dimension( defaultWidth, defaultHeight );
return size;
}
protected JToggleButton createButton( String name, Icon icon ) {
JToggleButton button = new JToggleButton( name, icon );
button.setVerticalTextPosition( SwingConstants.BOTTOM );
button.setHorizontalTextPosition( SwingConstants.CENTER );
button.setAlignmentX( Component.CENTER_ALIGNMENT );
button.setIconTextGap( 0 );
button.setPreferredSize( buttonSize );
button.setMaximumSize( buttonSize );
return button;
}
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file );
if( name != null )
return name;
}
return fsv.getSystemDisplayName( file );
}
protected Icon getIcon( FileSystemView fsv, File file ) {
if( iconFunction != null ) {
Icon icon = iconFunction.apply( file );
if( icon != null )
return icon;
}
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
}
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
}
protected void directoryChanged( File file ) {
if( file != null ) {
String absolutePath = file.getAbsolutePath();
for( int i = 0; i < files.length; i++ ) {
// also compare path because otherwise selecting "Documents"
// in "Look in" combobox would not select "Documents" shortcut item
if( files[i].equals( file ) || files[i].getAbsolutePath().equals( absolutePath ) ) {
buttons[i].setSelected( true );
return;
}
}
}
buttonGroup.clearSelection();
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case JFileChooser.DIRECTORY_CHANGED_PROPERTY:
directoryChanged( fc.getCurrentDirectory() );
break;
}
}
}
//---- class ShortcutIcon -------------------------------------------------
private static class ShortcutIcon
implements Icon
{
private final Icon icon;
private final int iconWidth;
private final int iconHeight;
ShortcutIcon( Icon icon, int iconWidth, int iconHeight ) {
this.icon = icon;
this.iconWidth = iconWidth;
this.iconHeight = iconHeight;
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
// set rendering hint for the case that the icon is a bitmap (not used for vector icons)
g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC );
double scale = (double) getIconWidth() / (double) icon.getIconWidth();
g2.translate( x, y );
g2.scale( scale, scale );
icon.paintIcon( c, g2, 0, 0 );
} finally {
g2.dispose();
}
}
@Override
public int getIconWidth() {
return UIScale.scale( iconWidth );
}
@Override
public int getIconHeight() {
return UIScale.scale( iconHeight );
}
}
}

View File

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

View File

@@ -21,6 +21,7 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
@@ -146,6 +147,19 @@ public class FlatInternalFrameTitlePane
closeButton.setVisible( frame.isClosable() );
}
Rectangle getFrameIconBounds() {
Icon icon = titleLabel.getIcon();
if( icon == null )
return null;
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
boolean leftToRight = titleLabel.getComponentOrientation().isLeftToRight();
int x = titleLabel.getX() + (leftToRight ? 0 : (titleLabel.getWidth() - iconWidth));
int y = titleLabel.getY() + ((titleLabel.getHeight() - iconHeight) / 2);
return new Rectangle( x, y, iconWidth, iconHeight );
}
/**
* Does nothing because FlatLaf internal frames do not have system menus.
*/
@@ -199,6 +213,13 @@ public class FlatInternalFrameTitlePane
case "componentOrientation":
applyComponentOrientation( frame.getComponentOrientation() );
break;
case "opaque":
// Do not invoke super.propertyChange() here because it always
// invokes repaint(), which would cause endless repainting.
// The opaque flag is temporary changed in FlatUIUtils.hasOpaqueBeenExplicitlySet(),
// invoked from FlatInternalFrameUI.update().
return;
}
super.propertyChange( e );

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,12 +18,19 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicListUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JList}.
@@ -63,16 +70,38 @@ import javax.swing.plaf.basic.BasicListUI;
*/
public class FlatListUI
extends BasicListUI
implements StyleableUI
{
protected Color selectionBackground;
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
@Styleable protected Color selectionBackground;
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
// for FlatListCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color cellFocusColor;
/** @since 2 */ @Styleable protected Boolean showCellFocusIndicator;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatListUI();
}
@Override
public void installUI( JComponent c ) {
if( FlatUIUtils.needsLightAWTPeer( c ) )
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) );
else
installUIImpl( c );
}
private void installUIImpl( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -93,6 +122,8 @@ public class FlatListUI
selectionForeground = null;
selectionInactiveBackground = null;
selectionInactiveForeground = null;
oldStyleValues = null;
}
@Override
@@ -116,10 +147,85 @@ public class FlatListUI
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
return e -> {
superListener.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
list.revalidate();
list.repaint();
break;
}
};
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( list, "List" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
Color oldSelectionBackground = selectionBackground;
Color oldSelectionForeground = selectionForeground;
Color oldSelectionInactiveBackground = selectionInactiveBackground;
Color oldSelectionInactiveForeground = selectionInactiveForeground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
// update selection background
if( selectionBackground != oldSelectionBackground ) {
Color selBg = list.getSelectionBackground();
if( selBg == oldSelectionBackground )
list.setSelectionBackground( selectionBackground );
else if( selBg == oldSelectionInactiveBackground )
list.setSelectionBackground( selectionInactiveBackground );
}
// update selection foreground
if( selectionForeground != oldSelectionForeground ) {
Color selFg = list.getSelectionForeground();
if( selFg == oldSelectionForeground )
list.setSelectionForeground( selectionForeground );
else if( selFg == oldSelectionInactiveForeground )
list.setSelectionForeground( selectionInactiveForeground );
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, list, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
/**
* Toggle selection colors from focused to inactive and vice versa.
*
* This is not a optimal solution but much easier than rewriting the whole paint methods.
* This is not an optimal solution but much easier than rewriting the whole paint methods.
*
* Using a LaF specific renderer was avoided because often a custom renderer is
* already used in applications. Then either the inactive colors are not used,

View File

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

View File

@@ -21,8 +21,11 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.Map;
import javax.swing.JMenuBar;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
/**
* Border for {@link javax.swing.JMenuBar}.
@@ -33,11 +36,32 @@ import javax.swing.UIManager;
*/
public class FlatMenuBarBorder
extends FlatMarginBorder
implements StyleableBorder
{
private final Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
@Styleable protected Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showBottomSeparator( c ) )
return;
float lineHeight = scale( (float) 1 );
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
}
@@ -53,4 +77,9 @@ public class FlatMenuBarBorder
insets.right = scale( margin.right );
return insets;
}
/** @since 2 */
protected boolean showBottomSeparator( Component c ) {
return !FlatMenuBarUI.useUnifiedBackground( c );
}
}

View File

@@ -16,10 +16,20 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
@@ -33,8 +43,13 @@ import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicMenuBarUI;
import javax.swing.plaf.basic.DefaultMenuLayout;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuBar}.
@@ -45,14 +60,31 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault MenuBar.background Color
* @uiDefault MenuBar.foreground Color
* @uiDefault MenuBar.border Border
*
* <!-- FlatMenuBarUI -->
*
* @uiDefault TitlePane.unifiedBackground boolean
*
* @author Karl Tauber
*/
public class FlatMenuBarUI
extends BasicMenuBarUI
implements StyleableUI
{
protected boolean unifiedBackground;
// used in FlatMenuItemBorder
/** @since 2 */ @Styleable protected Insets itemMargins;
// used in FlatMenuUI
/** @since 2 */ @Styleable protected Color hoverBackground;
/** @since 2.5 */ @Styleable protected Color selectionBackground;
/** @since 2.5 */ @Styleable protected Color selectionForeground;
/** @since 2 */ @Styleable protected Color underlineSelectionBackground;
/** @since 2 */ @Styleable protected Color underlineSelectionColor;
/** @since 2 */ @Styleable protected int underlineSelectionHeight = -1;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuBarUI();
@@ -63,13 +95,46 @@ public class FlatMenuBarUI
* Do not add any functionality here.
*/
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installProperty( menuBar, "opaque", false );
unifiedBackground = UIManager.getBoolean( "TitlePane.unifiedBackground" );
LayoutManager layout = menuBar.getLayout();
if( layout == null || layout instanceof UIResource )
menuBar.setLayout( new FlatMenuBarLayout( menuBar ) );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
oldStyleValues = null;
borderShared = null;
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( menuBar, this::installStyle, null );
menuBar.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
menuBar.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
@Override
@@ -84,33 +149,217 @@ public class FlatMenuBarUI
map.put( "takeFocus", new TakeFocus() );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuBar, "MenuBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, menuBar, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, menuBar.getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, menuBar.getBorder(), key );
}
@Override
public void update( Graphics g, JComponent c ) {
// do not fill background if menubar is embedded into title pane
if( isFillBackground( c ) ) {
g.setColor( c.getBackground() );
// paint background
Color background = getBackground( c );
if( background != null ) {
g.setColor( background );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
paint( g, c );
}
protected boolean isFillBackground( JComponent c ) {
// paint background in opaque or having custom background color
if( c.isOpaque() || !(c.getBackground() instanceof UIResource) )
return true;
protected Color getBackground( JComponent c ) {
Color background = c.getBackground();
// do not paint background for unified title pane
if( unifiedBackground )
return false;
// paint background if opaque
if( c.isOpaque() )
return background;
// do not paint background if non-opaque and having custom background color
if( !(background instanceof UIResource) )
return null;
// paint background if menu bar is not the "main" menu bar (e.g. in internal frame)
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null || !(rootPane.getParent() instanceof Window) || rootPane.getJMenuBar() != c )
return background;
// use parent background for unified title pane
if( useUnifiedBackground( c ) )
background = FlatUIUtils.getParentBackground( c );
// paint background in full screen mode
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null || FlatUIUtils.isFullScreen( rootPane ) )
return true;
if( FlatUIUtils.isFullScreen( rootPane ) )
return background;
// do not paint background if menu bar is embedded into title pane
return rootPane.getJMenuBar() != c || !FlatRootPaneUI.isMenuBarEmbedded( rootPane );
return FlatRootPaneUI.isMenuBarEmbedded( rootPane ) ? null : background;
}
/**@since 2 */
static boolean useUnifiedBackground( Component c ) {
// check whether:
// - TitlePane.unifiedBackground is true and
// - menu bar is the "main" menu bar and
// - window root pane has custom decoration style
JRootPane rootPane;
// (not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime)
return UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
(rootPane = SwingUtilities.getRootPane( c )) != null &&
rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == c &&
rootPane.getWindowDecorationStyle() != JRootPane.NONE;
}
//---- class FlatMenuBarLayout --------------------------------------------
/**
* @since 2.4
*/
protected static class FlatMenuBarLayout
extends DefaultMenuLayout
{
public FlatMenuBarLayout( Container target ) {
super( target, BoxLayout.LINE_AXIS );
}
@Override
public void layoutContainer( Container target ) {
super.layoutContainer( target );
// The only purpose of the code below is to make sure that a horizontal glue,
// which can be used to move window and displays the window title in embedded menu bar,
// is always visible within the menu bar bounds and has a minimum width.
// If this is not the case, the horizontal glue is made larger and
// components that are on the left side of the glue are made smaller.
// get root pane and check whether this menu bar is the root pane menu bar
JRootPane rootPane = SwingUtilities.getRootPane( target );
if( rootPane == null || rootPane.getJMenuBar() != target )
return;
// get title pane and check whether menu bar is embedded
FlatTitlePane titlePane = FlatRootPaneUI.getTitlePane( rootPane );
if( titlePane == null || !titlePane.isMenuBarEmbedded() )
return;
// check whether there is a horizontal glue (used for window title in embedded menu bar)
// and check minimum width of horizontal glue
Component horizontalGlue = titlePane.findHorizontalGlue( (JMenuBar) target );
int minTitleWidth = UIScale.scale( titlePane.titleMinimumWidth );
if( horizontalGlue != null && horizontalGlue.getWidth() < minTitleWidth ) {
// get index of glue component
int glueIndex = -1;
Component[] components = target.getComponents();
for( int i = components.length - 1; i >= 0; i-- ) {
if( components[i] == horizontalGlue ) {
glueIndex = i;
break;
}
}
if( glueIndex < 0 )
return; // should never happen
if( target.getComponentOrientation().isLeftToRight() ) {
// left-to-right
// make horizontal glue wider (minimum title width)
int offset = minTitleWidth - horizontalGlue.getWidth();
horizontalGlue.setSize( minTitleWidth, horizontalGlue.getHeight() );
// check whether glue is fully visible
int minGlueX = target.getWidth() - target.getInsets().right - minTitleWidth;
if( minGlueX < horizontalGlue.getX() ) {
// move glue to the left to make it fully visible
offset -= (horizontalGlue.getX() - minGlueX);
horizontalGlue.setLocation( minGlueX, horizontalGlue.getY() );
// shrink and move components that are on the left side of the glue
for( int i = glueIndex - 1; i >= 0; i-- ) {
Component c = components[i];
if( c.getX() > minGlueX ) {
// move component and set width to zero
c.setBounds( minGlueX, c.getY(), 0, c.getHeight() );
} else {
// reduce size of component
c.setSize( minGlueX - c.getX(), c.getHeight() );
break;
}
}
}
// move components that are on the right side of the glue
for( int i = glueIndex + 1; i < components.length; i++ ) {
Component c = components[i];
c.setLocation( c.getX() + offset, c.getY() );
}
} else {
// right-to-left
// make horizontal glue wider (minimum title width)
int offset = minTitleWidth - horizontalGlue.getWidth();
horizontalGlue.setBounds( horizontalGlue.getX() - offset, horizontalGlue.getY(),
minTitleWidth, horizontalGlue.getHeight() );
// check whether glue is fully visible
int minGlueX = target.getInsets().left;
if( minGlueX > horizontalGlue.getX() ) {
// move glue to the right to make it fully visible
offset -= (horizontalGlue.getX() - minGlueX);
horizontalGlue.setLocation( minGlueX, horizontalGlue.getY() );
// shrink and move components that are on the right side of the glue
int x = horizontalGlue.getX() + horizontalGlue.getWidth();
for( int i = glueIndex - 1; i >= 0; i-- ) {
Component c = components[i];
if( c.getX() + c.getWidth() < x ) {
// move component and set width to zero
c.setBounds( x, c.getY(), 0, c.getHeight() );
} else {
// move component and reduce size
c.setBounds( x, c.getY(), c.getWidth() - (x - c.getX()), c.getHeight() );
break;
}
}
}
// move components that are on the left side of the glue
for( int i = glueIndex + 1; i < components.length; i++ ) {
Component c = components[i];
c.setLocation( c.getX() - offset, c.getY() );
}
}
}
}
}
//---- class TakeFocus ----------------------------------------------------

View File

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

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
@@ -30,7 +31,9 @@ import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.text.AttributedCharacterIterator;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
@@ -38,7 +41,12 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatCheckBoxMenuItemIcon;
import com.formdev.flatlaf.icons.FlatMenuArrowIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -47,43 +55,49 @@ import com.formdev.flatlaf.util.SystemInfo;
/**
* Renderer for menu items.
*
* @uiDefault MenuItem.verticallyAlignText boolean
* @uiDefault MenuItem.minimumWidth int
* @uiDefault MenuItem.minimumIconSize Dimension
* @uiDefault MenuItem.textAcceleratorGap int
* @uiDefault MenuItem.textNoAcceleratorGap int
* @uiDefault MenuItem.acceleratorArrowGap int
* @uiDefault MenuItem.checkBackground Color
* @uiDefault MenuItem.checkMargins Insets
* @uiDefault MenuItem.selectionType String null (default) or underline
* @uiDefault MenuItem.underlineSelectionBackground Color
* @uiDefault MenuItem.underlineSelectionCheckBackground Color
* @uiDefault MenuItem.underlineSelectionColor Color
* @uiDefault MenuItem.underlineSelectionHeight int
* @uiDefault MenuItem.selectionBackground Color
*
* @author Karl Tauber
*/
public class FlatMenuItemRenderer
{
private static final String KEY_MAX_ICONS_WIDTH = "FlatLaf.internal.FlatMenuItemRenderer.maxIconWidth";
protected final JMenuItem menuItem;
protected final Icon checkIcon;
protected final Icon arrowIcon;
protected final Font acceleratorFont;
protected Icon checkIcon;
protected Icon arrowIcon;
@Styleable protected Font acceleratorFont;
protected final String acceleratorDelimiter;
protected final int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" );
protected final Dimension minimumIconSize;
protected final int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 );
protected final int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 );
protected final int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 );
/** @since 2 */ @Styleable protected boolean verticallyAlignText = FlatUIUtils.getUIBoolean( "MenuItem.verticallyAlignText", true );
@Styleable protected int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" );
@Styleable protected Dimension minimumIconSize;
@Styleable protected int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 );
@Styleable protected int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 );
@Styleable protected int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 );
protected final Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" );
protected final Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" );
@Styleable protected Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" );
@Styleable protected Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" );
protected final Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" );
protected final Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" );
protected final Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" );
protected final int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" );
@Styleable protected Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" );
@Styleable protected Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" );
@Styleable protected Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" );
@Styleable protected int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" );
protected final Color selectionBackground = UIManager.getColor( "MenuItem.selectionBackground" );
private boolean iconsShared = true;
private final Font menuFont = UIManager.getFont( "Menu.font" );
protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
@@ -98,6 +112,77 @@ public class FlatMenuItemRenderer
this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) || key.equals( "selectionForeground" ) ) {
if( iconsShared ) {
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
checkIcon = FlatStylingSupport.cloneIcon( checkIcon );
if( arrowIcon instanceof FlatMenuArrowIcon )
arrowIcon = FlatStylingSupport.cloneIcon( arrowIcon );
iconsShared = false;
}
if( key.startsWith( "icon." ) ) {
String key2 = key.substring( "icon.".length() );
try {
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
return ((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key2, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
try {
if( arrowIcon instanceof FlatMenuArrowIcon )
return ((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key2, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
// keys with prefix "icon." are only for icons
throw new UnknownStyleException( key );
} else if( key.equals( "selectionForeground" ) ) {
// special case: same key is used in icons and in menuitem
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key, value );
if( arrowIcon instanceof FlatMenuArrowIcon )
((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key, value );
// throw exception because the caller should also apply this key
throw new UnknownStyleException( key );
}
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatCheckBoxMenuItemIcon)checkIcon).getStyleableInfos() );
infos.remove( "icon.selectionForeground" );
if( arrowIcon instanceof FlatMenuArrowIcon )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatMenuArrowIcon)arrowIcon).getStyleableInfos() );
infos.remove( "icon.selectionForeground" );
return infos;
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
if( key.startsWith( "icon." ) ) {
String key2 = key.substring( "icon.".length() );
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
return ((FlatCheckBoxMenuItemIcon)checkIcon).getStyleableValue( key2 );
if( arrowIcon instanceof FlatMenuArrowIcon )
return ((FlatMenuArrowIcon)arrowIcon).getStyleableValue( key2 );
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
protected Dimension getPreferredMenuItemSize() {
int width = 0;
int height = 0;
@@ -109,7 +194,8 @@ public class FlatMenuItemRenderer
// layout icon and text
SwingUtilities.layoutCompoundLabel( menuItem,
menuItem.getFontMetrics( menuItem.getFont() ), menuItem.getText(), getIconForLayout(),
menuItem.getFontMetrics( isTopLevelMenu ? getTopLevelFont() : menuItem.getFont() ),
menuItem.getText(), getIconForLayout(),
menuItem.getVerticalAlignment(), menuItem.getHorizontalAlignment(),
menuItem.getVerticalTextPosition(), menuItem.getHorizontalTextPosition(),
viewRect, iconRect, textRect, scale( menuItem.getIconTextGap() ) );
@@ -211,7 +297,8 @@ public class FlatMenuItemRenderer
// layout icon and text
SwingUtilities.layoutCompoundLabel( menuItem,
menuItem.getFontMetrics( menuItem.getFont() ), menuItem.getText(), getIconForLayout(),
menuItem.getFontMetrics( isTopLevelMenu ? getTopLevelFont() : menuItem.getFont() ),
menuItem.getText(), getIconForLayout(),
menuItem.getVerticalAlignment(), menuItem.getHorizontalAlignment(),
menuItem.getVerticalTextPosition(), menuItem.getHorizontalTextPosition(),
labelRect, iconRect, textRect, scale( menuItem.getIconTextGap() ) );
@@ -254,7 +341,7 @@ debug*/
paintBackground( g, underlineSelection ? underlineSelectionBackground : selectionBackground );
if( underlineSelection && isArmedOrSelected( menuItem ) )
paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight );
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground );
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( !isTopLevelMenu( menuItem ) )
@@ -301,7 +388,7 @@ debug*/
return FlatUIUtils.deriveColor( background, baseColor );
}
protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground ) {
protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground, Color selectionBackground ) {
// if checkbox/radiobutton menu item is selected and also has a custom icon,
// then use filled icon background to indicate selection (instead of using checkIcon)
if( menuItem.isSelected() && checkIcon != null && icon != checkIcon ) {
@@ -321,9 +408,10 @@ debug*/
}
int mnemonicIndex = FlatLaf.isShowMnemonics() ? menuItem.getDisplayedMnemonicIndex() : -1;
Color foreground = (isTopLevelMenu( menuItem ) ? menuItem.getParent() : menuItem).getForeground();
boolean isTopLevelMenu = isTopLevelMenu( menuItem );
Color foreground = (isTopLevelMenu ? menuItem.getParent() : menuItem).getForeground();
paintText( g, menuItem, textRect, text, mnemonicIndex, menuItem.getFont(),
paintText( g, menuItem, textRect, text, mnemonicIndex, isTopLevelMenu ? getTopLevelFont() : menuItem.getFont(),
foreground, isUnderlineSelection() ? foreground : selectionForeground, disabledForeground );
}
@@ -343,11 +431,10 @@ debug*/
return;
// center because the real icon may be smaller than dimension in iconRect
int x = iconRect.x + centerOffset( iconRect.width, icon.getIconWidth() );
int y = iconRect.y + centerOffset( iconRect.height, icon.getIconHeight() );
// paint
icon.paintIcon( menuItem, g, x, y );
icon.paintIcon( menuItem, g, iconRect.x, y );
}
protected static void paintText( Graphics g, JMenuItem menuItem,
@@ -376,12 +463,29 @@ debug*/
protected static void paintHTMLText( Graphics g, JMenuItem menuItem,
Rectangle textRect, View htmlView, Color selectionForeground )
{
// On Windows, using Segoe UI font, Java 15 or older and scaling greater than 1,
// the width of the HTML view may be initially too small (because component
// is not connected to a GraphicsConfiguration when getPreferredSize() is invoked).
// Since Java 16, this seems to be fixed somehow by doing popup menu layout twice.
//
// If using a too small width for htmlView.paint(), the view would rearrange
// its children and wrap them to two lines. To avoid this, use view preferred X span
// for painting. Core Lafs actually do the same in class MenuItemLayoutHelper.
//
// Example HTML text that causes the problem: "<html>some <b>HTML</b> <i>text</i></html>"
textRect = new Rectangle( textRect );
textRect.width = (int) htmlView.getPreferredSpan( View.X_AXIS );
if( isArmedOrSelected( menuItem ) && selectionForeground != null )
g = new GraphicsProxyWithTextColor( (Graphics2D) g, selectionForeground );
htmlView.paint( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ), textRect );
}
/**
* Returns {@code true} if either the menu item is armed (mouse over item)
* or it is a {@code JMenu} and selected (shows submenu).
*/
protected static boolean isArmedOrSelected( JMenuItem menuItem ) {
return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected());
}
@@ -394,6 +498,15 @@ debug*/
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
}
private Font getTopLevelFont() {
Font font = menuItem.getFont();
// menu item parent may be null if JMenu.isTopLevelMenu() is overridden
// and does not check parent (e.g. com.jidesoft.swing.JideMenu.isTopLevelMenu())
return (font != menuFont || menuItem.getParent() == null)
? font
: menuItem.getParent().getFont();
}
private Icon getIconForPainting() {
Icon icon = menuItem.getIcon();
@@ -412,6 +525,12 @@ debug*/
return pressedIcon;
}
if( isArmedOrSelected( menuItem ) ) {
Icon selectedIcon = menuItem.getSelectedIcon();
if( selectedIcon != null )
return selectedIcon;
}
return icon;
}
@@ -498,6 +617,44 @@ debug*/
shiftGlyph = 0x21E7,
commandGlyph = 0x2318;
/**
* Calculates the maximum width of all menu item icons in the popup.
*/
private int getMaxIconsWidth() {
if( !verticallyAlignText )
return 0;
Container parent = menuItem.getParent();
if( !(parent instanceof JComponent) )
return 0;
int maxWidth = FlatClientProperties.clientPropertyInt( (JComponent) parent, KEY_MAX_ICONS_WIDTH, -1 );
if( maxWidth >= 0 )
return maxWidth;
maxWidth = 0;
for( Component c : parent.getComponents() ) {
if( !(c instanceof JMenuItem) )
continue;
Icon icon = ((JMenuItem)c).getIcon();
if( icon != null )
maxWidth = Math.max( maxWidth, icon.getIconWidth() );
}
((JComponent)parent).putClientProperty( KEY_MAX_ICONS_WIDTH, maxWidth );
return maxWidth;
}
static void clearClientProperties( Component c ) {
if( !(c instanceof JComponent) )
return;
JComponent jc = (JComponent) c;
jc.putClientProperty( FlatMenuItemRenderer.KEY_MAX_ICONS_WIDTH, null );
}
//---- class MinSizeIcon --------------------------------------------------
private class MinSizeIcon
@@ -512,6 +669,7 @@ debug*/
@Override
public int getIconWidth() {
int iconWidth = (delegate != null) ? delegate.getIconWidth() : 0;
iconWidth = Math.max( iconWidth, getMaxIconsWidth() );
return Math.max( iconWidth, scale( minimumIconSize.width ) );
}

View File

@@ -18,11 +18,20 @@ package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}.
@@ -52,15 +61,30 @@ import javax.swing.plaf.basic.BasicMenuItemUI;
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicMenuItemUI.class, key="selectionBackground" )
@StyleableField( cls=BasicMenuItemUI.class, key="selectionForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="disabledForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorSelectionForeground" )
public class FlatMenuItemUI
extends BasicMenuItemUI
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +98,84 @@ public class FlatMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "MenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return applyStyleProperty( menuItem, this, renderer, key, value );
}
static Object applyStyleProperty( JMenuItem menuItem, BasicMenuItemUI ui,
FlatMenuItemRenderer renderer, String key, Object value )
{
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( ui, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return getStyleableInfos( this, renderer );
}
static Map<String, Class<?>> getStyleableInfos( BasicMenuItemUI ui, FlatMenuItemRenderer renderer ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( ui );
infos.putAll( renderer.getStyleableInfos() );
return infos;
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return getStyleableValue( this, renderer, key );
}
static Object getStyleableValue( BasicMenuItemUI ui, FlatMenuItemRenderer renderer, String key ) {
Object value = renderer.getStyleableValue( key );
if( value == null )
value = FlatStylingSupport.getAnnotatedStyleableValue( ui, key );
return value;
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

@@ -20,17 +20,29 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.function.Function;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.MenuBarUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenu}.
@@ -60,26 +72,42 @@ import javax.swing.plaf.basic.BasicMenuUI;
* <!-- FlatMenuUI -->
*
* @uiDefault MenuItem.iconTextGap int
* @uiDefault MenuBar.hoverBackground Color
*
* <!-- FlatMenuRenderer -->
*
* @uiDefault MenuBar.hoverBackground Color
* @uiDefault MenuBar.selectionBackground Color
* @uiDefault MenuBar.selectionForeground Color
* @uiDefault MenuBar.underlineSelectionBackground Color
* @uiDefault MenuBar.underlineSelectionColor Color
* @uiDefault MenuBar.underlineSelectionHeight int
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicMenuItemUI.class, key="selectionBackground" )
@StyleableField( cls=BasicMenuItemUI.class, key="selectionForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="disabledForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorSelectionForeground" )
public class FlatMenuUI
extends BasicMenuUI
implements StyleableUI, StyleableLookupProvider
{
private Color hoverBackground;
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -88,7 +116,6 @@ public class FlatMenuUI
menuItem.setRolloverEnabled( true );
hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
renderer = createRenderer();
}
@@ -96,8 +123,9 @@ public class FlatMenuUI
protected void uninstallDefaults() {
super.uninstallDefaults();
hoverBackground = null;
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
@@ -129,6 +157,50 @@ public class FlatMenuUI
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "Menu" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( this, renderer );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatMenuItemUI.getStyleableValue( this, renderer, key );
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
public Dimension getMinimumSize( JComponent c ) {
// avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare
@@ -153,9 +225,12 @@ public class FlatMenuUI
protected class FlatMenuRenderer
extends FlatMenuItemRenderer
{
protected final Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground );
protected final Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor );
protected final int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight );
protected Color hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
protected Color menuBarSelectionBackground = UIManager.getColor( "MenuBar.selectionBackground" );
protected Color menuBarSelectionForeground = UIManager.getColor( "MenuBar.selectionForeground" );
protected Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground );
protected Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor );
protected int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight );
protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
@@ -165,27 +240,53 @@ public class FlatMenuUI
@Override
protected void paintBackground( Graphics g, Color selectionBackground ) {
if( isUnderlineSelection() && ((JMenu)menuItem).isTopLevelMenu() )
selectionBackground = menuBarUnderlineSelectionBackground;
if( ((JMenu)menuItem).isTopLevelMenu() ) {
if( isUnderlineSelection() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.underlineSelectionBackground, menuBarUnderlineSelectionBackground );
else {
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground,
menuBarSelectionBackground != null ? menuBarSelectionBackground : selectionBackground );
}
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() &&
model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() )
{
g.setColor( deriveBackground( hoverBackground ) );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
} else
super.paintBackground( g, selectionBackground );
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() && model.isEnabled() ) {
g.setColor( deriveBackground( getStyleFromMenuBarUI( ui -> ui.hoverBackground, hoverBackground ) ) );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
return;
}
}
super.paintBackground( g, selectionBackground );
}
@Override
protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) {
if( ((JMenu)menuItem).isTopLevelMenu() && !isUnderlineSelection() ) {
selectionForeground = getStyleFromMenuBarUI( ui -> ui.selectionForeground,
menuBarSelectionForeground != null ? menuBarSelectionForeground : selectionForeground );
}
super.paintText( g, textRect, text, selectionForeground, disabledForeground );
}
@Override
protected void paintUnderlineSelection( Graphics g, Color underlineSelectionColor, int underlineSelectionHeight ) {
if( ((JMenu)menuItem).isTopLevelMenu() ) {
underlineSelectionColor = menuBarUnderlineSelectionColor;
underlineSelectionHeight = menuBarUnderlineSelectionHeight;
underlineSelectionColor = getStyleFromMenuBarUI( ui -> ui.underlineSelectionColor, menuBarUnderlineSelectionColor );
underlineSelectionHeight = getStyleFromMenuBarUI( ui -> (ui.underlineSelectionHeight != -1)
? ui.underlineSelectionHeight : null, menuBarUnderlineSelectionHeight );
}
super.paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight );
}
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( !(ui instanceof FlatMenuBarUI) )
return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.io.File;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.NativeLibrary;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Helper class to load FlatLaf native library (.dll, .so or .dylib),
* if available for current operating system and CPU architecture.
*
* @author Karl Tauber
* @since 2.3
*/
class FlatNativeLibrary
{
private static NativeLibrary nativeLibrary;
/**
* Loads native library (if available) and returns whether loaded successfully.
* Returns {@code false} if no native library is available.
*/
static synchronized boolean isLoaded() {
initialize();
return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false;
}
private static void initialize() {
if( nativeLibrary != null )
return;
String libraryName;
if( SystemInfo.isWindows_10_orLater && (SystemInfo.isX86 || SystemInfo.isX86_64) ) {
// Windows: requires Windows 10/11 (x86 or x86_64)
libraryName = "flatlaf-windows-x86";
if( SystemInfo.isX86_64 )
libraryName += "_64";
// In Java 8, load jawt.dll (part of JRE) explicitly because it
// is not found when running application with <jdk>/bin/java.exe.
// When using <jdk>/jre/bin/java.exe, it is found.
// jawt.dll is located in <jdk>/jre/bin/.
// Java 9 and later do not have this problem,
// but load jawt.dll anyway to be on the safe side.
loadJAWT();
} else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) {
// Linux: requires x86_64
libraryName = "flatlaf-linux-x86_64";
// Load libjawt.so (part of JRE) explicitly because it is not found
// in all Java versions/distributions.
// E.g. not found in Java 13 and later from openjdk.java.net.
// There seems to be also differences between distributions.
// E.g. Adoptium Java 17 does not need this, but Java 17 from openjdk.java.net does.
loadJAWT();
} else
return; // no native library available for current OS or CPU architecture
// load native library
nativeLibrary = createNativeLibrary( libraryName );
}
private static NativeLibrary createNativeLibrary( String libraryName ) {
String libraryPath = System.getProperty( FlatSystemProperties.NATIVE_LIBRARY_PATH );
if( libraryPath != null ) {
if( "system".equals( libraryPath ) ) {
NativeLibrary library = new NativeLibrary( libraryName, true );
if( library.isLoaded() )
return library;
LoggingFacade.INSTANCE.logSevere( "Did not find library " + libraryName + " in java.library.path, using extracted library instead", null );
} else {
File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) );
if( libraryFile.exists() )
return new NativeLibrary( libraryFile, true );
LoggingFacade.INSTANCE.logSevere( "Did not find external library " + libraryFile + ", using extracted library instead", null );
}
}
return new NativeLibrary( "com/formdev/flatlaf/natives/" + libraryName, null, true );
}
private static void loadJAWT() {
try {
System.loadLibrary( "jawt" );
} catch( UnsatisfiedLinkError ex ) {
// log error only if native library jawt.dll not already loaded
String message = ex.getMessage();
if( message == null || !message.contains( "already loaded in another classloader" ) )
LoggingFacade.INSTANCE.logSevere( message, ex );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), ex );
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2022 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.JDialog;
import javax.swing.JFrame;
/**
* Native methods for Linux.
* <p>
* <b>Note</b>: This is private API. Do not use!
*
* @author Karl Tauber
* @since 2.5
*/
class FlatNativeLinuxLibrary
{
static boolean isLoaded() {
return FlatNativeLibrary.isLoaded();
}
// direction for _NET_WM_MOVERESIZE message
// see https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html
static final int MOVE = 8;
private static Boolean isXWindowSystem;
private static boolean isXWindowSystem() {
if( isXWindowSystem == null )
isXWindowSystem = Toolkit.getDefaultToolkit().getClass().getName().endsWith( ".XToolkit" );
return isXWindowSystem;
}
static boolean isWMUtilsSupported( Window window ) {
return hasCustomDecoration( window ) && isXWindowSystem() && isLoaded();
}
static boolean moveOrResizeWindow( Window window, MouseEvent e, int direction ) {
Point pt = scale( window, e.getLocationOnScreen() );
return xMoveOrResizeWindow( window, pt.x, pt.y, direction );
/*
try {
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.linux.X11WmUtils" );
java.lang.reflect.Method m = cls.getMethod( "xMoveOrResizeWindow", Window.class, int.class, int.class, int.class );
return (Boolean) m.invoke( null, window, pt.x, pt.y, direction );
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
*/
}
static boolean showWindowMenu( Window window, MouseEvent e ) {
Point pt = scale( window, e.getLocationOnScreen() );
return xShowWindowMenu( window, pt.x, pt.y );
/*
try {
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.linux.X11WmUtils" );
java.lang.reflect.Method m = cls.getMethod( "xShowWindowMenu", Window.class, int.class, int.class );
return (Boolean) m.invoke( null, window, pt.x, pt.y );
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
*/
}
private static Point scale( Window window, Point pt ) {
AffineTransform transform = window.getGraphicsConfiguration().getDefaultTransform();
int x = (int) Math.round( pt.x * transform.getScaleX() );
int y = (int) Math.round( pt.y * transform.getScaleY() );
return new Point( x, y );
}
// X Window System
private static native boolean xMoveOrResizeWindow( Window window, int x, int y, int direction );
private static native boolean xShowWindowMenu( Window window, int x, int y );
private static boolean hasCustomDecoration( Window window ) {
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
}
}

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Window;
import java.beans.PropertyChangeListener;
@@ -24,9 +25,9 @@ import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
@@ -40,9 +41,24 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeWindowBorder
{
// can use window decorations if:
// - on Windows 10 or later
// - not if system property "sun.java2d.opengl" is true on Windows 10
// - not when running in JetBrains Projector, Webswing or WinPE
// - not disabled via system property
private static final boolean canUseWindowDecorations =
SystemInfo.isWindows_10_orLater &&
(SystemInfo.isWindows_11_orLater || !FlatSystemProperties.getBoolean( "sun.java2d.opengl", false )) &&
!SystemInfo.isProjector &&
!SystemInfo.isWebswing &&
!SystemInfo.isWinPE &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true );
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
private static final boolean canUseJBRCustomDecorations
= SystemInfo.isJetBrainsJVM_11_orLater && SystemInfo.isWindows_10_orLater;
private static final boolean canUseJBRCustomDecorations =
canUseWindowDecorations &&
SystemInfo.isJetBrainsJVM_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false );
private static Boolean supported;
private static Provider nativeProvider;
@@ -62,25 +78,29 @@ public class FlatNativeWindowBorder
if( !isSupported() )
return null;
// Check whether root pane already has a window, which is the case when switching LaF.
// do nothing if root pane has a parent that is not a window (e.g. a JInternalFrame)
Container parent = rootPane.getParent();
if( parent != null && !(parent instanceof Window) )
return null;
// Check whether root pane already has a window, which is the case when
// switching from another LaF to FlatLaf.
// Also check whether the window is displayable, which is required to install
// FlatLaf native window border.
// If the window is not displayable, then it was probably closed/disposed but not yet removed
// from the list of windows that AWT maintains and returns with Window.getWindows().
// It could be also be a window that is currently hidden, but may be shown later.
Window window = SwingUtilities.windowForComponent( rootPane );
if( window != null && window.isDisplayable() ) {
install( window, FlatSystemProperties.USE_WINDOW_DECORATIONS );
return null;
}
if( parent instanceof Window && parent.isDisplayable() )
install( (Window) parent );
// Install FlatLaf native window border, which must be done late,
// when the native window is already created, because it needs access to the window.
// Uninstall FlatLaf native window border when window is disposed (or root pane removed).
// "ancestor" property change event is fired from JComponent.addNotify() and removeNotify().
PropertyChangeListener ancestorListener = e -> {
Object newValue = e.getNewValue();
if( newValue instanceof Window )
install( (Window) newValue, FlatSystemProperties.USE_WINDOW_DECORATIONS );
install( (Window) newValue );
else if( newValue == null && e.getOldValue() instanceof Window )
uninstall( (Window) e.getOldValue() );
};
@@ -88,7 +108,7 @@ public class FlatNativeWindowBorder
return ancestorListener;
}
static void install( Window window, String systemPropertyKey ) {
static void install( Window window ) {
if( hasCustomDecoration( window ) )
return;
@@ -98,13 +118,11 @@ public class FlatNativeWindowBorder
if( window instanceof JFrame ) {
JFrame frame = (JFrame) window;
JRootPane rootPane = frame.getRootPane();
// do not enable native window border if JFrame should use system window decorations
// and if not forced to use FlatLaf/JBR native window decorations
if( !JFrame.isDefaultLookAndFeelDecorated() &&
!UIManager.getBoolean( "TitlePane.useWindowDecorations" ) &&
!FlatSystemProperties.getBoolean( systemPropertyKey, false ) )
return;
// check whether disabled via system property, client property or UI default
if( !useWindowDecorations( rootPane ) )
return;
// do not enable native window border if frame is undecorated
if( frame.isUndecorated() )
@@ -113,18 +131,20 @@ public class FlatNativeWindowBorder
// enable native window border for window
setHasCustomDecoration( frame, true );
// avoid double window title bar if enabling native window border failed
if( !hasCustomDecoration( frame ) )
return;
// enable Swing window decoration
frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
rootPane.setWindowDecorationStyle( JRootPane.FRAME );
} else if( window instanceof JDialog ) {
JDialog dialog = (JDialog) window;
JRootPane rootPane = dialog.getRootPane();
// do not enable native window border if JDialog should use system window decorations
// and if not forced to use FlatLaf/JBR native window decorations
if( !JDialog.isDefaultLookAndFeelDecorated() &&
!UIManager.getBoolean( "TitlePane.useWindowDecorations" ) &&
!FlatSystemProperties.getBoolean( systemPropertyKey, false ) )
return;
// check whether disabled via system property, client property or UI default
if( !useWindowDecorations( rootPane ) )
return;
// do not enable native window border if dialog is undecorated
if( dialog.isUndecorated() )
@@ -133,8 +153,12 @@ public class FlatNativeWindowBorder
// enable native window border for window
setHasCustomDecoration( dialog, true );
// avoid double window title bar if enabling native window border failed
if( !hasCustomDecoration( dialog ) )
return;
// enable Swing window decoration
dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
rootPane.setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
}
}
@@ -144,24 +168,27 @@ public class FlatNativeWindowBorder
return;
}
if( !isSupported() )
return;
// remove listener
if( data instanceof PropertyChangeListener )
rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data );
// uninstall native window border, except when switching to another FlatLaf theme
Window window = SwingUtilities.windowForComponent( rootPane );
if( window != null )
uninstall( window );
// do not uninstall when switching to another FlatLaf theme and if still enabled
if( UIManager.getLookAndFeel() instanceof FlatLaf && useWindowDecorations( rootPane ) )
return;
// uninstall native window border
Container parent = rootPane.getParent();
if( parent instanceof Window )
uninstall( (Window) parent );
}
private static void uninstall( Window window ) {
if( !hasCustomDecoration( window ) )
return;
// do not uninstall when switching to another FlatLaf theme
if( UIManager.getLookAndFeel() instanceof FlatLaf )
return;
// disable native window border for window
setHasCustomDecoration( window, false );
@@ -179,6 +206,14 @@ public class FlatNativeWindowBorder
}
}
private static boolean useWindowDecorations( JRootPane rootPane ) {
return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.USE_WINDOW_DECORATIONS,
FlatClientProperties.USE_WINDOW_DECORATIONS,
"TitlePane.useWindowDecorations",
false );
}
public static boolean hasCustomDecoration( Window window ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.hasCustomDecoration( window );
@@ -202,7 +237,8 @@ public class FlatNativeWindowBorder
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
List<Rectangle> hitTestSpots, Rectangle appIconBounds )
List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
@@ -212,9 +248,15 @@ public class FlatNativeWindowBorder
if( !isSupported() )
return;
nativeProvider.setTitleBarHeight( window, titleBarHeight );
nativeProvider.setTitleBarHitTestSpots( window, hitTestSpots );
nativeProvider.setTitleBarAppIconBounds( window, appIconBounds );
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
static boolean showWindow( Window window, int cmd ) {
if( canUseJBRCustomDecorations || !isSupported() )
return false;
return nativeProvider.showWindow( window, cmd );
}
private static void initialize() {
@@ -222,41 +264,46 @@ public class FlatNativeWindowBorder
return;
supported = false;
// requires Windows 10
if( !SystemInfo.isWindows_10_orLater )
return;
// do not use when running in JetBrains Projector
if( SystemInfo.isProjector )
return;
// check whether disabled via system property
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true ) )
if( !canUseWindowDecorations )
return;
try {
/*
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" );
Method m = cls.getMethod( "getInstance" );
nativeProvider = (Provider) m.invoke( null );
java.lang.reflect.Method m = cls.getMethod( "getInstance" );
setNativeProvider( (Provider) m.invoke( null ) );
*/
nativeProvider = FlatWindowsNativeWindowBorder.getInstance();
supported = (nativeProvider != null);
setNativeProvider( FlatWindowsNativeWindowBorder.getInstance() );
} catch( Exception ex ) {
// ignore
}
}
/** @since 1.1.1 */
public static void setNativeProvider( Provider provider ) {
if( nativeProvider != null )
throw new IllegalStateException();
nativeProvider = provider;
supported = (nativeProvider != null);
}
//---- interface Provider -------------------------------------------------
public interface Provider
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void setTitleBarHeight( Window window, int titleBarHeight );
void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots );
void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds );
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
// commands for showWindow(); values must match Win32 API
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
int SW_MAXIMIZE = 3;
int SW_MINIMIZE = 6;
int SW_RESTORE = 9;
boolean showWindow( Window window, int cmd );
boolean isColorizationColorAffectsBorders();
Color getColorizationColor();
@@ -268,6 +315,10 @@ public class FlatNativeWindowBorder
//---- class WindowTopBorder -------------------------------------------
/**
* Window top border used on Windows 10.
* No longer needed since Windows 11.
*/
static class WindowTopBorder
extends JBRCustomDecorations.JBRWindowTopBorder
{

View File

@@ -19,17 +19,24 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.beans.PropertyChangeListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SwingUtils;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -75,6 +82,7 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatOptionPaneUI -->
*
* @uiDefault OptionPane.showIcon boolean
* @uiDefault OptionPane.iconMessageGap int
* @uiDefault OptionPane.messagePadding int
* @uiDefault OptionPane.maxCharactersPerLine int
@@ -84,10 +92,12 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatOptionPaneUI
extends BasicOptionPaneUI
{
/** @since 2 */ protected boolean showIcon;
protected int iconMessageGap;
protected int messagePadding;
protected int maxCharactersPerLine;
private int focusWidth;
private boolean sameSizeButtons;
public static ComponentUI createUI( JComponent c ) {
return new FlatOptionPaneUI();
@@ -97,10 +107,12 @@ public class FlatOptionPaneUI
protected void installDefaults() {
super.installDefaults();
showIcon = UIManager.getBoolean( "OptionPane.showIcon" );
iconMessageGap = UIManager.getInt( "OptionPane.iconMessageGap" );
messagePadding = UIManager.getInt( "OptionPane.messagePadding" );
maxCharactersPerLine = UIManager.getInt( "OptionPane.maxCharactersPerLine" );
focusWidth = UIManager.getInt( "Component.focusWidth" );
sameSizeButtons = FlatUIUtils.getUIBoolean( "OptionPane.sameSizeButtons", true );
}
@Override
@@ -110,6 +122,24 @@ public class FlatOptionPaneUI
updateChildPanels( optionPane );
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
return e -> {
superListener.propertyChange( e );
// hide window title bar icon
// (only if showIcon is false, otherwise the default behavior is used)
if( !showIcon && "ancestor".equals( e.getPropertyName() ) && e.getNewValue() != null ) {
JRootPane rootPane = SwingUtilities.getRootPane( optionPane );
if( rootPane != null &&
rootPane.getContentPane().getComponentCount() > 0 &&
rootPane.getContentPane().getComponent( 0 ) == optionPane )
rootPane.putClientProperty( FlatClientProperties.TITLE_BAR_SHOW_ICON, false );
}
};
}
@Override
public Dimension getMinimumOptionPaneSize() {
return UIScale.scale( super.getMinimumOptionPaneSize() );
@@ -127,7 +157,7 @@ public class FlatOptionPaneUI
// set icon-message gap
if( iconMessageGap > 0 ) {
Component iconMessageSeparator = findByName( messageArea, "OptionPane.separator" );
Component iconMessageSeparator = SwingUtils.getComponentByName( messageArea, "OptionPane.separator" );
if( iconMessageSeparator != null )
iconMessageSeparator.setPreferredSize( new Dimension( UIScale.scale( iconMessageGap ), 1 ) );
}
@@ -157,15 +187,40 @@ public class FlatOptionPaneUI
cons.insets.bottom = UIScale.scale( messagePadding );
// disable line wrapping for HTML
if( msg instanceof String && BasicHTML.isHTMLString( (String) msg ) )
maxll = Integer.MAX_VALUE;
if( msg != null &&
!(msg instanceof Component) &&
!(msg instanceof Object[]) &&
!(msg instanceof Icon) )
{
msg = msg.toString();
if( BasicHTML.isHTMLString( (String) msg ) )
maxll = Integer.MAX_VALUE;
}
// fix right-to-left alignment if super.addMessageComponents() breaks longer lines
// into multiple labels and puts them into a box that aligns them to the left
if( msg instanceof Box ) {
Box box = (Box) msg;
if( "OptionPane.verticalBox".equals( box.getName() ) &&
box.getLayout() instanceof BoxLayout &&
((BoxLayout)box.getLayout()).getAxis() == BoxLayout.Y_AXIS )
{
box.addPropertyChangeListener( "componentOrientation", e -> {
float alignX = box.getComponentOrientation().isLeftToRight() ? 0 : 1;
for( Component c : box.getComponents() ) {
if( c instanceof JLabel && "OptionPane.label".equals( c.getName() ) )
((JLabel)c).setAlignmentX( alignX );
}
} );
}
}
super.addMessageComponents( container, cons, msg, maxll, internallyCreated );
}
private void updateChildPanels( Container c ) {
for( Component child : c.getComponents() ) {
if( child instanceof JPanel ) {
if( child.getClass() == JPanel.class ) {
JPanel panel = (JPanel)child;
// make sub-panel non-opaque for OptionPane.background
@@ -174,53 +229,16 @@ public class FlatOptionPaneUI
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( new NonUIResourceBorder( border ) );
panel.setBorder( FlatUIUtils.nonUIResource( border ) );
}
if( child instanceof Container ) {
if( child instanceof Container )
updateChildPanels( (Container) child );
}
}
}
private Component findByName( Container c, String name ) {
for( Component child : c.getComponents() ) {
if( name.equals( child.getName() ) )
return child;
if( child instanceof Container ) {
Component c2 = findByName( (Container) child, name );
if( c2 != null )
return c2;
}
}
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();
}
@Override
protected boolean getSizeButtonsToSameWidth() {
return sameSizeButtons;
}
}

View File

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

View File

@@ -17,29 +17,38 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPasswordField;
import javax.swing.JToggleButton;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPasswordFieldUI;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.HiDPIUtils;
import javax.swing.text.PasswordView;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}.
*
* <!-- BasicPasswordFieldUI -->
* <!-- BasicTextFieldUI -->
*
* @uiDefault PasswordField.font Font
* @uiDefault PasswordField.background Color
@@ -52,68 +61,98 @@ import com.formdev.flatlaf.util.HiDPIUtils;
* @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 -->
* <!-- FlatTextFieldUI -->
*
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color
* @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault PasswordField.focusedBackground Color optional
* @uiDefault PasswordField.iconTextGap int optional, default is 4
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* @uiDefault TextComponent.selectAllOnMouseClick boolean
*
* <!-- FlatPasswordFieldUI -->
*
* @uiDefault PasswordField.echoChar character
* @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.showRevealButton boolean
* @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault PasswordField.revealIcon Icon
*
* @author Karl Tauber
*/
public class FlatPasswordFieldUI
extends BasicPasswordFieldUI
extends FlatTextFieldUI
{
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
protected boolean showCapsLock;
protected Icon capsLockIcon;
// used to preserve reveal button state when switching theme
private static final String KEY_REVEAL_SELECTED = "FlatLaf.internal.FlatPasswordFieldUI.revealSelected";
private Character echoChar;
@Styleable protected boolean showCapsLock;
/** @since 2 */ @Styleable protected boolean showRevealButton;
protected Icon capsLockIcon;
/** @since 2 */ protected Icon revealIcon;
private FocusListener focusListener;
private KeyListener capsLockListener;
private boolean capsLockIconShared = true;
private JToggleButton revealButton;
private boolean uninstallEchoChar;
public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI();
}
@Override
protected String getPropertyPrefix() {
return "PasswordField";
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installRevealButton();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallRevealButton();
super.uninstallUI( c );
}
@Override
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
echoChar = (Character) UIManager.get( prefix + ".echoChar" );
if( echoChar != null )
LookAndFeel.installProperty( getComponent(), "echoChar", echoChar );
showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" );
showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
LookAndFeel.installProperty( getComponent(), "opaque", false );
MigLayoutVisualPadding.install( getComponent() );
revealIcon = UIManager.getIcon( "PasswordField.revealIcon" );
capsLockIconShared = true;
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
placeholderForeground = null;
capsLockIcon = null;
MigLayoutVisualPadding.uninstall( getComponent() );
revealIcon = null;
}
@Override
protected void installListeners() {
super.installListeners();
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent() );
// update caps lock indicator
capsLockListener = new KeyAdapter() {
@Override
public void keyPressed( KeyEvent e ) {
@@ -124,12 +163,13 @@ public class FlatPasswordFieldUI
repaint( e );
}
private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK )
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) {
e.getComponent().repaint();
scrollCaretToVisible();
}
}
};
getComponent().addFocusListener( focusListener );
getComponent().addKeyListener( capsLockListener );
}
@@ -137,59 +177,216 @@ public class FlatPasswordFieldUI
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener( focusListener );
getComponent().removeKeyListener( capsLockListener );
focusListener = null;
capsLockListener = null;
}
@Override
protected Caret createCaret() {
return new FlatCaret( UIManager.getString( "TextComponent.selectAllOnFocusPolicy" ),
UIManager.getBoolean( "TextComponent.selectAllOnMouseClick" ) );
protected void installKeyboardActions() {
super.installKeyboardActions();
// map "select-word" action (double-click) to "select-line" action
ActionMap map = SwingUtilities.getUIActionMap( getComponent() );
if( map != null && map.get( DefaultEditorKit.selectWordAction ) != null ) {
Action selectLineAction = map.get( DefaultEditorKit.selectLineAction );
if( selectLineAction != null )
map.put( DefaultEditorKit.selectWordAction, selectLineAction );
}
}
/** @since 2 */
@Override
String getStyleType() {
return "PasswordField";
}
@Override
protected void applyStyle( Object style ) {
boolean oldShowRevealButton = showRevealButton;
super.applyStyle( style );
if( showRevealButton != oldShowRevealButton ) {
uninstallRevealButton();
installRevealButton();
}
}
/** @since 2 */
@Override
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
if( capsLockIconShared ) {
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
capsLockIconShared = false;
}
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
}
return super.applyStyleProperty( key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
infos.put( "capsLockIconColor", Color.class );
return infos;
}
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon )
return ((FlatCapsLockIcon)capsLockIcon).getStyleableValue( key );
return super.getStyleableValue( c, key );
}
@Override
public View create( Element elem ) {
return new PasswordView( elem );
}
/** @since 2 */
@Override
protected void paintIcons( Graphics g, Rectangle r ) {
super.paintIcons( g, r );
if( isCapsLockVisible() )
paintCapsLock( g, r );
}
/** @since 2 */
protected void paintCapsLock( Graphics g, Rectangle r ) {
JTextComponent c = getComponent();
int x = c.getComponentOrientation().isLeftToRight()
? r.x + r.width - capsLockIcon.getIconWidth()
: r.x;
int y = r.y + Math.round( (r.height - capsLockIcon.getIconHeight()) / 2f );
capsLockIcon.paintIcon( c, g, x, y );
}
/** @since 2 */
@Override
protected boolean hasTrailingIcon() {
return super.hasTrailingIcon() || isCapsLockVisible();
}
/** @since 2 */
@Override
protected int getTrailingIconWidth() {
return super.getTrailingIconWidth()
+ (isCapsLockVisible() ? capsLockIcon.getIconWidth() + UIScale.scale( iconTextGap ) : 0);
}
/** @since 1.4 */
protected boolean isCapsLockVisible() {
if( !showCapsLock )
return false;
return FlatUIUtils.isPermanentFocusOwner( getComponent() ) &&
Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK );
}
/** @since 2 */
protected void installRevealButton() {
if( showRevealButton ) {
revealButton = createRevealButton();
updateRevealButton();
installLayout();
getComponent().add( revealButton );
}
}
/** @since 2 */
protected JToggleButton createRevealButton() {
JPasswordField c = (JPasswordField) getComponent();
JToggleButton button = new JToggleButton( revealIcon, !c.echoCharIsSet() );
button.setName( "PasswordField.revealButton" );
prepareLeadingOrTrailingComponent( button );
button.putClientProperty( FlatClientProperties.STYLE_CLASS, "inTextField revealButton" );
if( FlatClientProperties.clientPropertyBoolean( c, KEY_REVEAL_SELECTED, false ) ) {
button.setSelected( true );
updateEchoChar( true );
}
button.addActionListener( e -> {
boolean selected = button.isSelected();
updateEchoChar( selected );
c.putClientProperty( KEY_REVEAL_SELECTED, selected );
} );
return button;
}
/** @since 2.5 */
protected void updateRevealButton() {
if( revealButton == null )
return;
JTextComponent c = getComponent();
boolean visible = c.isEnabled();
if( visible != revealButton.isVisible() ) {
revealButton.setVisible( visible );
c.revalidate();
c.repaint();
if( !visible ) {
revealButton.setSelected( false );
updateEchoChar( false );
getComponent().putClientProperty( KEY_REVEAL_SELECTED, null );
}
}
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatTextFieldUI.propertyChange( getComponent(), e );
switch( e.getPropertyName() ) {
case "enabled":
updateRevealButton();
break;
}
}
@Override
protected void paintSafely( Graphics g ) {
FlatTextFieldUI.paintBackground( g, getComponent(), isIntelliJTheme );
FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground );
paintCapsLock( g );
private void updateEchoChar( boolean selected ) {
char newEchoChar = selected
? 0
: (echoChar != null ? echoChar : '*');
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
}
protected void paintCapsLock( Graphics g ) {
if( !showCapsLock )
JPasswordField c = (JPasswordField) getComponent();
if( newEchoChar == c.getEchoChar() )
return;
JTextComponent c = getComponent();
if( !FlatUIUtils.isPermanentFocusOwner( c ) ||
!Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) )
return;
// set echo char
LookAndFeel.installProperty( c, "echoChar", newEchoChar );
int y = (c.getHeight() - capsLockIcon.getIconHeight()) / 2;
int x = c.getWidth() - capsLockIcon.getIconWidth() - y;
capsLockIcon.paintIcon( c, g, x, y );
// check whether was able to set echo char via LookAndFeel.installProperty()
// if not, then echo char was explicitly changed via JPasswordField.setEchoChar()
char actualEchoChar = c.getEchoChar();
if( actualEchoChar != newEchoChar ) {
if( selected && actualEchoChar != 0 ) {
// use explicitly set echo char
echoChar = actualEchoChar;
uninstallEchoChar = true;
}
c.setEchoChar( newEchoChar );
}
}
/** @since 2 */
protected void uninstallRevealButton() {
if( revealButton != null ) {
if( uninstallEchoChar && revealButton.isSelected() )
((JPasswordField)getComponent()).setEchoChar( echoChar );
getComponent().remove( revealButton );
revealButton = null;
}
}
@Override
protected void paintBackground( Graphics g ) {
// background is painted elsewhere
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return FlatTextFieldUI.applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return FlatTextFieldUI.applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
protected JComponent[] getTrailingComponents() {
return new JComponent[] { trailingComponent, revealButton, clearButton };
}
}

View File

@@ -16,19 +16,29 @@
package com.formdev.flatlaf.ui;
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.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Panel;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.lang.reflect.InvocationTargetException;
import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
@@ -39,6 +49,7 @@ import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
@@ -55,14 +66,14 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatPopupFactory
extends PopupFactory
{
private Method java8getPopupMethod;
private Method java9getPopupMethod;
private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod;
@Override
public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
{
Point pt = fixToolTipLocation( contents, x, y );
Point pt = fixToolTipLocation( owner, contents, x, y );
if( pt != null ) {
x = pt.x;
y = pt.y;
@@ -70,7 +81,7 @@ public class FlatPopupFactory
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector )
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), contents );
// macOS and Linux adds drop shadow to heavy weight popups
@@ -115,6 +126,10 @@ public class FlatPopupFactory
popupWindow.getGraphicsConfiguration() == owner.getGraphicsConfiguration() )
return popup;
// avoid endless loop (should newer happen; PopupFactory cache size is 5)
if( ++count > 10 )
return popup;
// remove contents component from popup window
if( popupWindow instanceof JWindow )
((JWindow)popupWindow).getContentPane().removeAll();
@@ -122,10 +137,6 @@ public class FlatPopupFactory
// dispose unused popup
// (do not invoke popup.hide() because this would cache the popup window)
popupWindow.dispose();
// avoid endless loop (should newer happen; PopupFactory cache size is 5)
if( ++count > 10 )
return popup;
}
}
@@ -134,7 +145,7 @@ public class FlatPopupFactory
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening an dialog on the secondary screen and making combobox popup visible.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
@@ -183,40 +194,46 @@ public class FlatPopupFactory
{
try {
if( SystemInfo.isJava_9_orLater ) {
// Java 9: protected Popup getPopup( Component owner, Component contents, int x, int y, boolean isHeavyWeightPopup )
if( java9getPopupMethod == null ) {
java9getPopupMethod = PopupFactory.class.getDeclaredMethod(
"getPopup", Component.class, Component.class, int.class, int.class, boolean.class );
MethodType mt = MethodType.methodType( Popup.class, Component.class, Component.class, int.class, int.class, boolean.class );
java9getPopupMethod = MethodHandles.lookup().findVirtual( PopupFactory.class, "getPopup", mt );
}
return (Popup) java9getPopupMethod.invoke( this, owner, contents, x, y, true );
} else {
// Java 8
// Java 8: private Popup getPopup( Component owner, Component contents, int ownerX, int ownerY, int popupType )
if( java8getPopupMethod == null ) {
java8getPopupMethod = PopupFactory.class.getDeclaredMethod(
Method m = PopupFactory.class.getDeclaredMethod(
"getPopup", Component.class, Component.class, int.class, int.class, int.class );
java8getPopupMethod.setAccessible( true );
m.setAccessible( true );
java8getPopupMethod = MethodHandles.lookup().unreflect( m );
}
return (Popup) java8getPopupMethod.invoke( this, owner, contents, x, y, /*HEAVY_WEIGHT_POPUP*/ 2 );
}
} catch( NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException ex ) {
// ignore
return null;
} catch( Throwable ex ) {
// fallback
return super.getPopup( owner, contents, x, y );
}
}
/**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen,
* ToolTipManagerthe changes the location so that the entire tooltip fits on screen.
* the ToolTipManager changes the location so that the entire tooltip fits on screen.
* But this can place the tooltip under the mouse location and hide the owner component.
* <p>
* This method checks whether the current mouse location is within tooltip bounds
* and corrects the y-location so that the tooltip is placed above the mouse location.
*/
private Point fixToolTipLocation( Component contents, int x, int y ) {
if( !(contents instanceof JToolTip) || !wasInvokedFromToolTipManager() )
private Point fixToolTipLocation( Component owner, Component contents, int x, int y ) {
if( !(contents instanceof JToolTip) || !wasInvokedFromToolTipManager() || hasTipLocation( owner ) )
return null;
Point mouseLocation = MouseInfo.getPointerInfo().getLocation();
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
if( pointerInfo == null )
return null;
Point mouseLocation = pointerInfo.getLocation();
Dimension tipSize = contents.getPreferredSize();
// check whether mouse location is within tooltip bounds
@@ -224,18 +241,63 @@ public class FlatPopupFactory
if( !tipBounds.contains( mouseLocation ) )
return null;
// place tooltip above mouse location
return new Point( x, mouseLocation.y - tipSize.height - UIScale.scale( 20 ) );
// find GraphicsConfiguration at mouse location (similar to ToolTipManager.getDrawingGC())
GraphicsConfiguration gc = null;
for( GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices() ) {
GraphicsConfiguration dgc = device.getDefaultConfiguration();
if( dgc.getBounds().contains( mouseLocation ) ) {
gc = dgc;
break;
}
}
if( gc == null )
gc = owner.getGraphicsConfiguration();
if( gc == null )
return null;
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
int screenTop = screenBounds.y + screenInsets.top;
// place tooltip above mouse location if there is enough space
int newY = mouseLocation.y - tipSize.height - UIScale.scale( 20 );
if( newY < screenTop )
return null;
return new Point( x, newY );
}
private boolean wasInvokedFromToolTipManager() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for( StackTraceElement stackTraceElement : stackTrace ) {
if( "javax.swing.ToolTipManager".equals( stackTraceElement.getClassName() ) &&
"showTipWindow".equals( stackTraceElement.getMethodName() ) )
return true;
return StackUtils.wasInvokedFrom( ToolTipManager.class.getName(), "showTipWindow", 8 );
}
/**
* Checks whether the owner component returns a tooltip location in
* JComponent.getToolTipLocation(MouseEvent).
*/
private boolean hasTipLocation( Component owner ) {
if( !(owner instanceof JComponent) )
return false;
AWTEvent e = EventQueue.getCurrentEvent();
MouseEvent me;
if( e instanceof MouseEvent )
me = (MouseEvent) e;
else {
// no mouse event available because a timer is used to show the tooltip
// --> create mouse event from current mouse location
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
if( pointerInfo == null )
return false;
Point location = new Point( pointerInfo.getLocation());
SwingUtilities.convertPointFromScreen( location, owner );
me = new MouseEvent( owner, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(),
0, location.x, location.y, 0, false );
}
return false;
return me.getSource() == owner &&
((JComponent)owner).getToolTipLocation( me ) != null;
}
//---- class NonFlashingPopup ---------------------------------------------
@@ -463,6 +525,9 @@ public class FlatPopupFactory
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
layeredPane.add( dropShadowPanel, JLayeredPane.POPUP_LAYER, 0 );
moveMediumWeightDropShadow();
resizeMediumWeightDropShadow();
mediumPanelListener = new ComponentListener() {
@Override
public void componentShown( ComponentEvent e ) {
@@ -478,17 +543,12 @@ public class FlatPopupFactory
@Override
public void componentMoved( ComponentEvent e ) {
if( dropShadowPanel != null && mediumWeightPanel != null ) {
Point location = mediumWeightPanel.getLocation();
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setLocation( location.x - insets.left, location.y - insets.top );
}
moveMediumWeightDropShadow();
}
@Override
public void componentResized( ComponentEvent e ) {
if( dropShadowPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
resizeMediumWeightDropShadow();
}
};
mediumWeightPanel.addComponentListener( mediumPanelListener );
@@ -504,5 +564,18 @@ public class FlatPopupFactory
parent.repaint( bounds.x, bounds.y, bounds.width, bounds.height );
}
}
private void moveMediumWeightDropShadow() {
if( dropShadowPanel != null && mediumWeightPanel != null ) {
Point location = mediumWeightPanel.getLocation();
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setLocation( location.x - insets.left, location.y - insets.top );
}
}
private void resizeMediumWeightDropShadow() {
if( dropShadowPanel != null && mediumWeightPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
}
}
}

View File

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

View File

@@ -24,8 +24,8 @@ import javax.swing.plaf.ComponentUI;
*
* <!-- BasicSeparatorUI -->
*
* @uiDefault PopupMenuSeparator.background Color unused
* @uiDefault PopupMenuSeparator.foreground Color
* @uiDefault Separator.background Color unused
* @uiDefault Separator.foreground Color
*
* <!-- FlatSeparatorUI -->
*
@@ -39,11 +39,24 @@ public class FlatPopupMenuSeparatorUI
extends FlatSeparatorUI
{
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, FlatPopupMenuSeparatorUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, () -> new FlatPopupMenuSeparatorUI( true ) )
: new FlatPopupMenuSeparatorUI( false );
}
/** @since 2 */
protected FlatPopupMenuSeparatorUI( boolean shared ) {
super( shared );
}
@Override
protected String getPropertyPrefix() {
return "PopupMenuSeparator";
}
/** @since 2 */
@Override
String getStyleType() {
return "PopupMenuSeparator";
}
}

View File

@@ -16,9 +16,60 @@
package com.formdev.flatlaf.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.BasicMenuItemUI;
import javax.swing.plaf.basic.BasicPopupMenuUI;
import javax.swing.plaf.basic.DefaultMenuLayout;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}.
@@ -30,12 +81,380 @@ import javax.swing.plaf.basic.BasicPopupMenuUI;
* @uiDefault PopupMenu.foreground Color
* @uiDefault PopupMenu.border Border
*
* <!-- FlatPopupMenuUI -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault PopupMenu.scrollArrowColor Color
* @uiDefault PopupMenu.hoverScrollArrowBackground Color optional
*
* @author Karl Tauber
*/
public class FlatPopupMenuUI
extends BasicPopupMenuUI
implements StyleableUI
{
/** @since 2.1 */ @Styleable protected String arrowType;
/** @since 2.1 */ @Styleable protected Color scrollArrowColor;
/** @since 2.1 */ @Styleable protected Color hoverScrollArrowBackground;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatPopupMenuUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
oldStyleValues = null;
borderShared = null;
}
@Override
public void installDefaults() {
super.installDefaults();
arrowType = UIManager.getString( "Component.arrowType" );
scrollArrowColor = UIManager.getColor( "PopupMenu.scrollArrowColor" );
hoverScrollArrowBackground = UIManager.getColor( "PopupMenu.hoverScrollArrowBackground" );
LayoutManager layout = popupMenu.getLayout();
if( layout == null || layout instanceof UIResource )
popupMenu.setLayout( new FlatPopupMenuLayout( popupMenu, BoxLayout.Y_AXIS ) );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
scrollArrowColor = null;
hoverScrollArrowBackground = null;
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( popupMenu, this::installStyle, null );
popupMenu.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
popupMenu.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( popupMenu, "PopupMenu" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, popupMenu, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, popupMenu.getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, popupMenu.getBorder(), key );
}
@Override
public Popup getPopup( JPopupMenu popup, int x, int y ) {
// do not add scroller to combobox popups or to popups that already have a scroll pane
if( popup instanceof BasicComboPopup ||
(popup.getComponentCount() > 0 && popup.getComponent( 0 ) instanceof JScrollPane) )
return super.getPopup( popup, x, y );
// do not add scroller if popup fits into screen
Dimension prefSize = popup.getPreferredSize();
int screenHeight = getScreenHeightAt( x, y );
if( prefSize.height <= screenHeight )
return super.getPopup( popup, x, y );
// create scroller
FlatPopupScroller scroller = new FlatPopupScroller( popup );
scroller.setPreferredSize( new Dimension( prefSize.width, screenHeight ) );
// create popup
PopupFactory popupFactory = PopupFactory.getSharedInstance();
return popupFactory.getPopup( popup.getInvoker(), scroller, x, y );
}
private int getScreenHeightAt( int x, int y ) {
// find GraphicsConfiguration at popup location (similar to JPopupMenu.getCurrentGraphicsConfiguration())
GraphicsConfiguration gc = null;
for( GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices() ) {
if( device.getType() == GraphicsDevice.TYPE_RASTER_SCREEN ) {
GraphicsConfiguration dgc = device.getDefaultConfiguration();
if( dgc.getBounds().contains( x, y ) ) {
gc = dgc;
break;
}
}
}
if( gc == null && popupMenu.getInvoker() != null )
gc = popupMenu.getInvoker().getGraphicsConfiguration();
// compute screen height
// (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Toolkit toolkit = Toolkit.getDefaultToolkit();
Rectangle screenBounds = (gc != null) ? gc.getBounds() : new Rectangle( toolkit.getScreenSize() );
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
return screenBounds.height - screenInsets.top - screenInsets.bottom;
}
//---- class FlatPopupMenuLayout ------------------------------------------
/**
* @since 2.4
*/
protected static class FlatPopupMenuLayout
extends DefaultMenuLayout
{
public FlatPopupMenuLayout( Container target, int axis ) {
super( target, axis );
}
@Override
public Dimension preferredLayoutSize( Container target ) {
FlatMenuItemRenderer.clearClientProperties( target );
return super.preferredLayoutSize( target );
}
}
//---- class FlatPopupScroller --------------------------------------------
private class FlatPopupScroller
extends JPanel
implements MouseWheelListener, PopupMenuListener, MenuKeyListener
{
private final JPopupMenu popup;
private final JScrollPane scrollPane;
private final JButton scrollUpButton;
private final JButton scrollDownButton;
private int unitIncrement;
FlatPopupScroller( JPopupMenu popup ) {
super( new BorderLayout() );
this.popup = popup;
// this panel is required to avoid that JPopupMenu.setLocation() will be invoked
// while scrolling, because this would call JPopupMenu.showPopup()
JPanel view = new JPanel( new BorderLayout() );
view.add( popup, BorderLayout.CENTER );
scrollPane = new JScrollPane( view, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
scrollPane.setBorder( null );
scrollUpButton = new ArrowButton( SwingConstants.NORTH );
scrollDownButton = new ArrowButton( SwingConstants.SOUTH );
add( scrollPane, BorderLayout.CENTER );
add( scrollUpButton, BorderLayout.NORTH );
add( scrollDownButton, BorderLayout.SOUTH );
setBackground( popup.getBackground() );
setBorder( popup.getBorder() );
popup.setBorder( null );
popup.addPopupMenuListener( this );
popup.addMouseWheelListener( this );
popup.addMenuKeyListener( this );
updateArrowButtons();
}
void scroll( int unitsToScroll ) {
if( unitIncrement == 0 )
unitIncrement = new JMenuItem( "X" ).getPreferredSize().height;
JViewport viewport = scrollPane.getViewport();
Point viewPosition = viewport.getViewPosition();
int newY = viewPosition.y + (unitIncrement * unitsToScroll);
if( newY < 0 )
newY = 0;
else
newY = Math.min( newY, viewport.getViewSize().height - viewport.getExtentSize().height );
viewport.setViewPosition( new Point( viewPosition.x, newY ) );
updateArrowButtons();
}
void updateArrowButtons() {
JViewport viewport = scrollPane.getViewport();
Point viewPosition = viewport.getViewPosition();
scrollUpButton.setVisible( viewPosition.y > 0 );
scrollDownButton.setVisible( viewPosition.y < viewport.getViewSize().height - viewport.getExtentSize().height );
}
//---- interface PopupMenuListener ----
@Override
public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) {
// restore popup border
popup.setBorder( getBorder() );
popup.removePopupMenuListener( this );
popup.removeMouseWheelListener( this );
popup.removeMenuKeyListener( this );
}
@Override public void popupMenuWillBecomeVisible( PopupMenuEvent e ) {}
@Override public void popupMenuCanceled( PopupMenuEvent e ) {}
//---- interface MouseWheelListener ----
/**
* Scroll when user rotates mouse wheel.
*/
@Override
public void mouseWheelMoved( MouseWheelEvent e ) {
// convert mouse location before scrolling
Point mouseLocation = SwingUtilities.convertPoint( (Component) e.getSource(), e.getPoint(), this );
// scroll
scroll( e.getUnitsToScroll() );
// select menu item at mouse location
Component c = SwingUtilities.getDeepestComponentAt( this, mouseLocation.x, mouseLocation.y );
if( c instanceof JMenuItem ) {
ButtonUI ui = ((JMenuItem)c).getUI();
if( ui instanceof BasicMenuItemUI )
MenuSelectionManager.defaultManager().setSelectedPath( ((BasicMenuItemUI)ui).getPath() );
}
// this avoids that the popup is closed when running on Java 8
// https://bugs.openjdk.java.net/browse/JDK-8075063
e.consume();
}
//---- interface MenuKeyListener ----
/**
* Scroll when user presses Up or Down keys.
*/
@Override
public void menuKeyPressed( MenuKeyEvent e ) {
// use invokeLater() because menu selection is not yet updated because
// this listener is invoked before another listener that updates the menu selection
EventQueue.invokeLater( () -> {
if( !isDisplayable() )
return;
MenuElement[] path = MenuSelectionManager.defaultManager().getSelectedPath();
if( path.length == 0 )
return;
// scroll selected menu item to visible area
Component c = path[path.length - 1].getComponent();
JViewport viewport = scrollPane.getViewport();
Point pt = SwingUtilities.convertPoint( c, 0, 0, viewport );
viewport.scrollRectToVisible( new Rectangle( pt, c.getSize() ) );
// update arrow buttons
boolean upVisible = scrollUpButton.isVisible();
updateArrowButtons();
if( !upVisible && scrollUpButton.isVisible() ) {
// if "up" button becomes visible, make sure that bottom menu item stays visible
Point viewPosition = viewport.getViewPosition();
int newY = viewPosition.y + scrollUpButton.getPreferredSize().height;
viewport.setViewPosition( new Point( viewPosition.x, newY ) );
}
} );
}
@Override public void menuKeyTyped( MenuKeyEvent e ) {}
@Override public void menuKeyReleased( MenuKeyEvent e ) {}
//---- class ArrowButton ----------------------------------------------
private class ArrowButton
extends FlatArrowButton
implements MouseListener, ActionListener
{
private Timer timer;
ArrowButton( int direction ) {
super( direction, arrowType, scrollArrowColor, null, null, hoverScrollArrowBackground, null, null );
addMouseListener( this );
}
@Override
public void paint( Graphics g ) {
// always fill background to paint over border on HiDPI screens
g.setColor( popup.getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
super.paint( g );
}
//---- interface MouseListener ----
@Override public void mouseClicked( MouseEvent e ) {}
@Override public void mousePressed( MouseEvent e ) {}
@Override public void mouseReleased( MouseEvent e ) {}
@Override
public void mouseEntered( MouseEvent e ) {
if( timer == null )
timer = new Timer( 50, this );
timer.start();
}
@Override
public void mouseExited( MouseEvent e ) {
if( timer != null )
timer.stop();
}
//---- interface ActionListener ----
@Override
public void actionPerformed( ActionEvent e ) {
if( timer != null && !isDisplayable() ) {
timer.stop();
return;
}
scroll( direction == SwingConstants.NORTH ? -1 : 1 );
}
}
}
}

View File

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

View File

@@ -18,11 +18,19 @@ package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButtonMenuItem}.
@@ -52,15 +60,30 @@ import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicMenuItemUI.class, key="selectionBackground" )
@StyleableField( cls=BasicMenuItemUI.class, key="selectionForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="disabledForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorForeground" )
@StyleableField( cls=BasicMenuItemUI.class, key="acceleratorSelectionForeground" )
public class FlatRadioButtonMenuItemUI
extends BasicRadioButtonMenuItemUI
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatRadioButtonMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +97,59 @@ public class FlatRadioButtonMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "RadioButtonMenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( this, renderer );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatMenuItemUI.getStyleableValue( this, renderer, key );
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

@@ -18,17 +18,34 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
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.Map;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -53,16 +70,52 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatRadioButtonUI
extends BasicRadioButtonUI
implements StyleableUI
{
protected int iconTextGap;
protected Color disabledText;
@Styleable protected Color disabledText;
private Color defaultBackground;
private final boolean shared;
private boolean iconShared = true;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, FlatRadioButtonUI::new );
return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c )
? FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, () -> new FlatRadioButtonUI( true ) )
: new FlatRadioButtonUI( false );
}
/** @since 2 */
protected FlatRadioButtonUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
if( FlatUIUtils.needsLightAWTPeer( c ) )
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) );
else
installUIImpl( c );
}
private void installUIImpl( JComponent c ) {
super.installUI( c );
if( FlatUIUtils.isAWTPeer( c ) )
AWTPeerMouseExitedFix.install( c );
installStyle( (AbstractButton) c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
if( FlatUIUtils.isAWTPeer( c ) )
AWTPeerMouseExitedFix.uninstall( c );
}
@Override
@@ -77,6 +130,7 @@ public class FlatRadioButtonUI
defaultBackground = UIManager.getColor( prefix + "background" );
iconShared = true;
defaults_initialized = true;
}
@@ -90,11 +144,98 @@ public class FlatRadioButtonUI
protected void uninstallDefaults( AbstractButton b ) {
super.uninstallDefaults( b );
oldStyleValues = null;
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
private static Insets tempInsets = new Insets( 0, 0, 0, 0 );
@Override
protected BasicButtonListener createButtonListener( AbstractButton b ) {
return new FlatRadioButtonListener( b );
}
/** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
b.revalidate();
b.repaint();
break;
}
}
/** @since 2 */
protected void installStyle( AbstractButton b ) {
try {
applyStyle( b, FlatStylingSupport.getResolvedStyle( b, getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "RadioButton";
}
/** @since 2 */
protected void applyStyle( AbstractButton b, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( b, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
if( !(icon instanceof FlatCheckBoxIcon) )
return new UnknownStyleException( key );
if( iconShared ) {
icon = FlatStylingSupport.cloneIcon( icon );
iconShared = false;
}
key = key.substring( "icon.".length() );
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, b, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( icon instanceof FlatCheckBoxIcon ) {
for( Map.Entry<String, Class<?>> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() )
infos.put( "icon.".concat( e.getKey() ), e.getValue() );
}
return infos;
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
// style icon
if( key.startsWith( "icon." ) ) {
return (icon instanceof FlatCheckBoxIcon)
? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) )
: null;
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
private static final Insets tempInsets = new Insets( 0, 0, 0, 0 );
@Override
public Dimension getPreferredSize( JComponent c ) {
@@ -107,7 +248,7 @@ public class FlatRadioButtonUI
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.
// 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 );
@@ -120,10 +261,11 @@ public class FlatRadioButtonUI
public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque if
// - contentAreaFilled is true and
// - if background was explicitly set to a non-UIResource color
// - if background color is different to default background color
// (this paints selection if using the component as cell renderer)
if( !c.isOpaque() &&
((AbstractButton)c).isContentAreaFilled() &&
!defaultBackground.equals( c.getBackground() ) )
!Objects.equals( c.getBackground(), getDefaultBackground( c ) ) )
{
g.setColor( c.getBackground() );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
@@ -160,10 +302,111 @@ public class FlatRadioButtonUI
FlatButtonUI.paintText( g, b, textRect, text, b.isEnabled() ? b.getForeground() : disabledText );
}
/**
* Returns the default background color of the component.
* If the component is used as cell renderer (e.g. in JTable),
* then the background color of the renderer container is returned.
*/
private Color getDefaultBackground( JComponent c ) {
Container parent = c.getParent();
return (parent instanceof CellRendererPane && parent.getParent() != null)
? parent.getParent().getBackground()
: defaultBackground;
}
private int getIconFocusWidth( JComponent c ) {
AbstractButton b = (AbstractButton) c;
return (b.getIcon() == null && getDefaultIcon() instanceof FlatCheckBoxIcon)
? UIScale.scale( ((FlatCheckBoxIcon)getDefaultIcon()).focusWidth )
Icon icon = b.getIcon();
if( icon == null )
icon = getDefaultIcon();
return (icon instanceof FlatCheckBoxIcon)
? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) )
: 0;
}
//---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */
protected class FlatRadioButtonListener
extends BasicButtonListener
{
private final AbstractButton b;
protected FlatRadioButtonListener( AbstractButton b ) {
super( b );
this.b = b;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatRadioButtonUI.this.propertyChange( b, e );
}
}
//---- class AWTPeerMouseExitedFix ----------------------------------------
/**
* Hack for missing mouse-exited event for java.awt.Checkbox on macOS (to fix hover effect).
*
* On macOS, AWT components internally use Swing components.
* This is implemented in class sun.lwawt.LWCheckboxPeer, which uses
* a container component CheckboxDelegate that has a JCheckBox and a JRadioButton
* as children. Only one of them is visible.
*
* The reason that mouse-exited event is not sent to the JCheckBox or JRadioButton
* is that sun.lwawt.LWComponentPeer.createDelegateEvent() uses
* SwingUtilities.getDeepestComponentAt() to find the event target,
* which finds the container component CheckboxDelegate,
* which receives the mouse-exited event.
*
* This class adds listeners and forwards the mouse-exited event
* from CheckboxDelegate to JCheckBox or JRadioButton.
*/
private static class AWTPeerMouseExitedFix
extends MouseAdapter
implements PropertyChangeListener
{
private final JComponent button;
static void install( JComponent button ) {
AWTPeerMouseExitedFix l = new AWTPeerMouseExitedFix( button );
button.addPropertyChangeListener( "ancestor", l );
Container parent = button.getParent();
if( parent != null )
parent.addMouseListener( l );
}
static void uninstall( JComponent button ) {
for( PropertyChangeListener l : button.getPropertyChangeListeners( "ancestor" ) ) {
if( l instanceof AWTPeerMouseExitedFix ) {
button.removePropertyChangeListener( "ancestor", l );
Container parent = button.getParent();
if( parent != null )
parent.removeMouseListener( (AWTPeerMouseExitedFix) l );
break;
}
}
}
AWTPeerMouseExitedFix( JComponent button ) {
this.button = button;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( e.getOldValue() instanceof Component )
((Component)e.getOldValue()).removeMouseListener( this );
if( e.getNewValue() instanceof Component ) {
((Component)e.getNewValue()).removeMouseListener( this ); // avoid duplicate listeners
((Component)e.getNewValue()).addMouseListener( this );
}
}
@Override
public void mouseExited( MouseEvent e ) {
button.dispatchEvent( SwingUtilities.convertMouseEvent( e.getComponent(), e, button ) );
}
}
}

View File

@@ -27,7 +27,11 @@ import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.function.Function;
import javax.swing.JComponent;
import javax.swing.JDialog;
@@ -36,6 +40,7 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.BorderUIResource;
@@ -61,6 +66,9 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatWindowResizer -->
*
* @uiDefault RootPane.font Font unused
* @uiDefault RootPane.background Color
* @uiDefault RootPane.foreground Color unused
* @uiDefault RootPane.borderDragThickness int
* @uiDefault RootPane.cornerDragWidth int
* @uiDefault RootPane.honorFrameMinimumSizeOnResize boolean
@@ -79,6 +87,8 @@ public class FlatRootPaneUI
private Object nativeWindowBorderData;
private LayoutManager oldLayout;
private PropertyChangeListener ancestorListener;
private ComponentListener componentListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI();
@@ -95,7 +105,7 @@ public class FlatRootPaneUI
else
installBorder();
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane );
installNativeWindowBorder();
}
protected void installBorder() {
@@ -110,8 +120,7 @@ public class FlatRootPaneUI
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData );
uninstallNativeWindowBorder();
uninstallClientDecorations();
rootPane = null;
}
@@ -120,8 +129,23 @@ public class FlatRootPaneUI
protected void installDefaults( JRootPane c ) {
super.installDefaults( c );
// Give the root pane useful background, foreground and font.
// Background is used for title bar and menu bar if native window decorations
// and unified background are enabled.
// Foreground and font are usually not used, but set for completeness.
// Not using LookAndFeel.installColorsAndFont() here because it will not work
// because the properties are null by default but inherit non-null values from parent.
if( !c.isBackgroundSet() || c.getBackground() instanceof UIResource )
c.setBackground( UIManager.getColor( "RootPane.background" ) );
if( !c.isForegroundSet() || c.getForeground() instanceof UIResource )
c.setForeground( UIManager.getColor( "RootPane.foreground" ) );
if( !c.isFontSet() || c.getFont() instanceof UIResource )
c.setFont( UIManager.getFont( "RootPane.font" ) );
// Update background color of JFrame or JDialog parent to avoid bad border
// on HiDPI screens when switching from light to dark Laf.
// Window background color is also used in native window decorations
// to fill background when window is initially shown or when resizing window.
// The background of JFrame is initialized in JFrame.frameInit() and
// the background of JDialog in JDialog.dialogInit(),
// but it was not updated when switching Laf.
@@ -137,6 +161,96 @@ public class FlatRootPaneUI
c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() );
}
@Override
protected void uninstallDefaults( JRootPane c ) {
super.uninstallDefaults( c );
// uninstall background, foreground and font because not all Lafs set them
if( c.isBackgroundSet() && c.getBackground() instanceof UIResource )
c.setBackground( null );
if( c.isForegroundSet() && c.getForeground() instanceof UIResource )
c.setForeground( null );
if( c.isFontSet() && c.getFont() instanceof UIResource )
c.setFont( null );
}
@Override
protected void installListeners( JRootPane root ) {
super.installListeners( root );
if( SystemInfo.isJava_9_orLater ) {
// On HiDPI screens, where scaling is used, there may be white lines on the
// bottom and on the right side of the window when it is initially shown.
// This is very disturbing in dark themes, but hard to notice in light themes.
// Seems to be a rounding issue when Swing adds dirty region of window
// using RepaintManager.nativeAddDirtyRegion().
//
// Note: Not using a HierarchyListener here, which would be much easier,
// because this causes problems with mouse clicks in heavy-weight popups.
// Instead, add a listener to the root pane that waits until it is added
// to a window, then add a component listener to the window.
// See: https://github.com/JFormDesigner/FlatLaf/issues/371
ancestorListener = e -> {
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if( newValue instanceof Window ) {
if( componentListener == null ) {
componentListener = new ComponentAdapter() {
@Override
public void componentShown( ComponentEvent e ) {
// add whole root pane to dirty regions when window is initially shown
root.getParent().repaint( root.getX(), root.getY(), root.getWidth(), root.getHeight() );
}
};
}
((Window)newValue).addComponentListener( componentListener );
} else if( newValue == null && oldValue instanceof Window ) {
if( componentListener != null )
((Window)oldValue).removeComponentListener( componentListener );
}
};
root.addPropertyChangeListener( "ancestor", ancestorListener );
}
}
@Override
protected void uninstallListeners( JRootPane root ) {
super.uninstallListeners( root );
if( SystemInfo.isJava_9_orLater ) {
if( componentListener != null ) {
Window window = SwingUtilities.windowForComponent( root );
if( window != null )
window.removeComponentListener( componentListener );
componentListener = null;
}
root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
}
}
/** @since 1.1.2 */
protected void installNativeWindowBorder() {
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane );
}
/** @since 1.1.2 */
protected void uninstallNativeWindowBorder() {
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData );
nativeWindowBorderData = null;
}
/** @since 1.1.2 */
public static void updateNativeWindowBorder( JRootPane rootPane ) {
RootPaneUI rui = rootPane.getUI();
if( !(rui instanceof FlatRootPaneUI) )
return;
FlatRootPaneUI ui = (FlatRootPaneUI) rui;
ui.uninstallNativeWindowBorder();
ui.installNativeWindowBorder();
}
protected void installClientDecorations() {
boolean isNativeWindowBorderSupported = FlatNativeWindowBorder.isSupported();
@@ -218,6 +332,10 @@ public class FlatRootPaneUI
installBorder();
break;
case FlatClientProperties.USE_WINDOW_DECORATIONS:
updateNativeWindowBorder( rootPane );
break;
case FlatClientProperties.MENU_BAR_EMBEDDED:
if( titlePane != null ) {
titlePane.menuBarChanged();
@@ -225,6 +343,17 @@ public class FlatRootPaneUI
rootPane.repaint();
}
break;
case FlatClientProperties.TITLE_BAR_SHOW_ICON:
if( titlePane != null )
titlePane.updateIcon();
break;
case FlatClientProperties.TITLE_BAR_BACKGROUND:
case FlatClientProperties.TITLE_BAR_FOREGROUND:
if( titlePane != null )
titlePane.titleBarColorsChanged();
break;
}
}
@@ -235,6 +364,12 @@ public class FlatRootPaneUI
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
}
/** @since 2.4 */
protected static FlatTitlePane getTitlePane( JRootPane rootPane ) {
RootPaneUI ui = rootPane.getUI();
return ui instanceof FlatRootPaneUI ? ((FlatRootPaneUI)ui).titlePane : null;
}
//---- class FlatRootLayout -----------------------------------------------
protected class FlatRootLayout
@@ -269,7 +404,7 @@ public class FlatRootPaneUI
? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize();
int width = Math.max( titlePaneSize.width, contentSize.width );
int width = contentSize.width; // title pane width is not considered here
int height = titlePaneSize.height + contentSize.height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
@@ -381,7 +516,7 @@ public class FlatRootPaneUI
return;
Container parent = c.getParent();
boolean active = parent instanceof Window ? ((Window)parent).isActive() : false;
boolean active = parent instanceof Window && ((Window)parent).isActive();
g.setColor( FlatUIUtils.deriveColor( active ? activeBorderColor : inactiveBorderColor, baseBorderColor ) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
@@ -393,9 +528,7 @@ public class FlatRootPaneUI
protected boolean isWindowMaximized( Component c ) {
Container parent = c.getParent();
return parent instanceof Frame
? (((Frame)parent).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0
: false;
return parent instanceof Frame && (((Frame)parent).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0;
}
}
@@ -431,7 +564,9 @@ public class FlatRootPaneUI
(parent instanceof JFrame &&
(((JFrame)parent).getJMenuBar() == null ||
!((JFrame)parent).getJMenuBar().isVisible())) ||
parent instanceof JDialog;
(parent instanceof JDialog &&
(((JDialog)parent).getJMenuBar() == null ||
!((JDialog)parent).getJMenuBar().isVisible()));
}
}
}

View File

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

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
@@ -24,6 +25,8 @@ import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.Objects;
import javax.swing.InputMap;
import javax.swing.JButton;
@@ -35,6 +38,13 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -43,7 +53,7 @@ import com.formdev.flatlaf.util.UIScale;
* <!-- BasicScrollBarUI -->
*
* @uiDefault ScrollBar.background Color
* @uiDefault ScrollBar.foreground Color
* @uiDefault ScrollBar.foreground Color unused
* @uiDefault ScrollBar.track Color
* @uiDefault ScrollBar.thumb Color
* @uiDefault ScrollBar.width int
@@ -53,6 +63,7 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatScrollBarUI -->
*
* @uiDefault ScrollBar.minimumButtonSize Dimension
* @uiDefault ScrollBar.trackInsets Insets
* @uiDefault ScrollBar.thumbInsets Insets
* @uiDefault ScrollBar.trackArc int
@@ -72,35 +83,56 @@ import com.formdev.flatlaf.util.UIScale;
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicScrollBarUI.class, key="track", fieldName="trackColor" )
@StyleableField( cls=BasicScrollBarUI.class, key="thumb", fieldName="thumbColor" )
@StyleableField( cls=BasicScrollBarUI.class, key="width", fieldName="scrollBarWidth" )
@StyleableField( cls=BasicScrollBarUI.class, key="minimumThumbSize" )
@StyleableField( cls=BasicScrollBarUI.class, key="maximumThumbSize" )
public class FlatScrollBarUI
extends BasicScrollBarUI
implements StyleableUI, StyleableLookupProvider
{
protected Insets trackInsets;
protected Insets thumbInsets;
protected int trackArc;
protected int thumbArc;
protected Color hoverTrackColor;
protected Color hoverThumbColor;
protected boolean hoverThumbWithTrack;
protected Color pressedTrackColor;
protected Color pressedThumbColor;
protected boolean pressedThumbWithTrack;
// overrides BasicScrollBarUI.supportsAbsolutePositioning (which is private)
@Styleable protected boolean allowsAbsolutePositioning;
protected boolean showButtons;
protected String arrowType;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
protected Color hoverButtonBackground;
protected Color pressedButtonBackground;
/** @since 2.1 */ @Styleable protected Dimension minimumButtonSize;
@Styleable protected Insets trackInsets;
@Styleable protected Insets thumbInsets;
@Styleable protected int trackArc;
@Styleable protected int thumbArc;
@Styleable protected Color hoverTrackColor;
@Styleable protected Color hoverThumbColor;
@Styleable protected boolean hoverThumbWithTrack;
@Styleable protected Color pressedTrackColor;
@Styleable protected Color pressedThumbColor;
@Styleable protected boolean pressedThumbWithTrack;
@Styleable protected boolean showButtons;
@Styleable protected String arrowType;
@Styleable protected Color buttonArrowColor;
@Styleable protected Color buttonDisabledArrowColor;
@Styleable protected Color hoverButtonBackground;
@Styleable protected Color pressedButtonBackground;
private MouseAdapter hoverListener;
protected boolean hoverTrack;
protected boolean hoverThumb;
private Map<String, Object> oldStyleValues;
private boolean isAWTPeer;
public static ComponentUI createUI( JComponent c ) {
return new FlatScrollBarUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installListeners() {
super.installListeners();
@@ -123,6 +155,9 @@ public class FlatScrollBarUI
protected void installDefaults() {
super.installDefaults();
allowsAbsolutePositioning = super.getSupportsAbsolutePositioning();
minimumButtonSize = UIManager.getDimension( "ScrollBar.minimumButtonSize" );
trackInsets = UIManager.getInsets( "ScrollBar.trackInsets" );
thumbInsets = UIManager.getInsets( "ScrollBar.thumbInsets" );
trackArc = UIManager.getInt( "ScrollBar.trackArc" );
@@ -152,6 +187,7 @@ public class FlatScrollBarUI
protected void uninstallDefaults() {
super.uninstallDefaults();
minimumButtonSize = null;
trackInsets = null;
thumbInsets = null;
hoverTrackColor = null;
@@ -163,6 +199,8 @@ public class FlatScrollBarUI
buttonDisabledArrowColor = null;
hoverButtonBackground = null;
pressedButtonBackground = null;
oldStyleValues = null;
}
@Override
@@ -177,6 +215,13 @@ public class FlatScrollBarUI
scrollbar.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollbar.revalidate();
scrollbar.repaint();
break;
case "componentOrientation":
// this is missing in BasicScrollBarUI.Handler.propertyChange()
InputMap inputMap = (InputMap) UIManager.get( "ScrollBar.ancestorInputMap" );
@@ -189,10 +234,85 @@ public class FlatScrollBarUI
}
SwingUtilities.replaceUIInputMap( scrollbar, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap );
break;
case "ancestor":
// check whether scroll bar is used as AWT peer on macOS
if( SystemInfo.isMacOS ) {
Container p = scrollbar.getParent();
for( int i = 0; i < 2 && p != null; i++, p = p.getParent() ) {
if( FlatUIUtils.isAWTPeer( p ) ) {
// Used to disable hover, which does not work correctly
// because scroll bars do not receive mouse exited event.
// The scroll pane, including its scroll bars, is not part
// of the component hierarchy and does not receive mouse events
// directly. Instead LWComponentPeer receives mouse events
// and delegates them to peers, but entered/exited events
// are sent only for the whole scroll pane.
// Exited event is only sent when mouse leaves scroll pane.
// If mouse enters/exits scroll bar, no entered/exited events are sent.
isAWTPeer = true;
// if dark theme is active, reinstall using light theme
if( FlatLaf.isLafDark() ) {
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> {
JScrollBar scrollbar = this.scrollbar;
uninstallUI( scrollbar );
installUI( scrollbar );
} );
}
break;
}
}
}
break;
}
};
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( scrollbar, "ScrollBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( incrButton instanceof FlatScrollBarButton )
((FlatScrollBarButton)incrButton).updateStyle();
if( decrButton instanceof FlatScrollBarButton )
((FlatScrollBarButton)decrButton).updateStyle();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, scrollbar, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
/** @since 2.5 */
@Override
public MethodHandles.Lookup getLookupForStyling() {
// MethodHandles.lookup() is caller sensitive and must be invoked in this class,
// otherwise it is not possible to access protected fields in JRE superclass
return MethodHandles.lookup();
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( super.getPreferredSize( c ) );
@@ -209,9 +329,17 @@ public class FlatScrollBarUI
}
protected boolean isShowButtons() {
// check client property on scroll bar
Object showButtons = scrollbar.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollbar.getParent() instanceof JScrollPane )
showButtons = ((JScrollPane)scrollbar.getParent()).getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollbar.getParent() instanceof JScrollPane ) {
JScrollPane scrollPane = (JScrollPane) scrollbar.getParent();
// check client property on scroll pane
showButtons = scrollPane.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
// check styling property on scroll pane
showButtons = ((FlatScrollPaneUI)scrollPane.getUI()).showButtons;
}
}
return (showButtons != null) ? Objects.equals( showButtons, true ) : this.showButtons;
}
@@ -224,6 +352,9 @@ public class FlatScrollBarUI
@Override
protected void paintTrack( Graphics g, JComponent c, Rectangle trackBounds ) {
if( trackBounds.isEmpty() || !scrollbar.isEnabled() )
return;
g.setColor( getTrackColor( c, hoverTrack, isPressed && hoverTrack && !hoverThumb ) );
paintTrackOrThumb( g, c, trackBounds, trackInsets, trackArc );
}
@@ -270,7 +401,7 @@ public class FlatScrollBarUI
Color trackColor = FlatUIUtils.deriveColor( this.trackColor, c.getBackground() );
return (pressed && pressedTrackColor != null)
? FlatUIUtils.deriveColor( pressedTrackColor, trackColor )
: ((hover && hoverTrackColor != null)
: ((hover && hoverTrackColor != null && !isAWTPeer)
? FlatUIUtils.deriveColor( hoverTrackColor, trackColor )
: trackColor);
}
@@ -280,7 +411,7 @@ public class FlatScrollBarUI
Color thumbColor = FlatUIUtils.deriveColor( this.thumbColor, trackColor );
return (pressed && pressedThumbColor != null)
? FlatUIUtils.deriveColor( pressedThumbColor, thumbColor )
: ((hover && hoverThumbColor != null)
: ((hover && hoverThumbColor != null && !isAWTPeer)
? FlatUIUtils.deriveColor( hoverThumbColor, thumbColor )
: thumbColor);
}
@@ -295,6 +426,11 @@ public class FlatScrollBarUI
return UIScale.scale( FlatUIUtils.addInsets( super.getMaximumThumbSize(), thumbInsets ) );
}
@Override
public boolean getSupportsAbsolutePositioning() {
return allowsAbsolutePositioning;
}
//---- class ScrollBarHoverListener ---------------------------------------
// using static field to disabling hover for other scroll bars
@@ -319,18 +455,31 @@ public class FlatScrollBarUI
@Override
public void mousePressed( MouseEvent e ) {
isPressed = true;
repaint();
if( SwingUtilities.isLeftMouseButton( e ) || isAbsolutePositioning( e ) ) {
isPressed = true;
repaint();
// update hover because BasicScrollBarUI.TrackListener.mousePressed()
// moves the track on middle-click (if absolute positioning is enabled)
if( isAbsolutePositioning( e ) )
update( e.getX(), e.getY() );
}
}
@Override
public void mouseReleased( MouseEvent e ) {
isPressed = false;
repaint();
if( SwingUtilities.isLeftMouseButton( e ) || isAbsolutePositioning( e ) ) {
isPressed = false;
repaint();
}
update( e.getX(), e.getY() );
}
private boolean isAbsolutePositioning( MouseEvent e ) {
return getSupportsAbsolutePositioning() && SwingUtilities.isMiddleMouseButton( e );
}
private void update( int x, int y ) {
boolean inTrack = getTrackBounds().contains( x, y );
boolean inThumb = getThumbBounds().contains( x, y );
@@ -363,11 +512,27 @@ public class FlatScrollBarUI
super( direction, type, foreground, disabledForeground,
hoverForeground, hoverBackground, pressedForeground, pressedBackground );
setArrowWidth( FlatArrowButton.DEFAULT_ARROW_WIDTH - 2 );
setFocusable( false );
setRequestFocusEnabled( false );
}
protected void updateStyle() {
updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor,
null, hoverButtonBackground, null, pressedButtonBackground );
}
@Override
public int getArrowWidth() {
// scale arrow size depending on scroll bar width
// (6 is default arrow width; 10 is base scroll bar width)
int arrowWidth = Math.round( 6 * (scrollBarWidth / 10f) );
// compute arrow size that leaves equal space on both sides (arrow is centered)
arrowWidth = scrollBarWidth - (((scrollBarWidth - arrowWidth) / 2) * 2);
return arrowWidth;
}
@Override
protected Color deriveBackground( Color background ) {
return FlatUIUtils.deriveColor( background, scrollbar.getBackground() );
@@ -376,8 +541,9 @@ public class FlatScrollBarUI
@Override
public Dimension getPreferredSize() {
if( isShowButtons() ) {
int w = UIScale.scale( scrollBarWidth );
return new Dimension( w, w );
int w = UIScale.scale( Math.max( scrollBarWidth, (minimumButtonSize != null) ? minimumButtonSize.width : 0 ) );
int h = UIScale.scale( Math.max( scrollBarWidth, (minimumButtonSize != null) ? minimumButtonSize.height : 0 ) );
return new Dimension( w, h );
} else
return new Dimension();
}

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
@@ -28,21 +29,28 @@ import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -63,20 +71,36 @@ import com.formdev.flatlaf.FlatClientProperties;
*/
public class FlatScrollPaneUI
extends BasicScrollPaneUI
implements StyleableUI
{
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected Boolean showButtons;
private Handler handler;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatScrollPaneUI();
}
@Override
public void installUI( JComponent c ) {
if( FlatUIUtils.needsLightAWTPeer( c ) )
FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) );
else
installUIImpl( c );
}
private void installUIImpl( JComponent c ) {
super.installUI( c );
int focusWidth = UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
installStyle();
MigLayoutVisualPadding.install( scrollpane );
}
@@ -85,6 +109,9 @@ public class FlatScrollPaneUI
MigLayoutVisualPadding.uninstall( scrollpane );
super.uninstallUI( c );
oldStyleValues = null;
borderShared = null;
}
@Override
@@ -269,7 +296,18 @@ public class FlatScrollPaneUI
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false );
}
break;
break;
case FlatClientProperties.OUTLINE:
scrollpane.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollpane.revalidate();
scrollpane.repaint();
break;
}
};
}
@@ -280,6 +318,44 @@ public class FlatScrollPaneUI
return handler;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( scrollpane, "ScrollPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "focusWidth" ) ) {
int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 );
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, scrollpane, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, scrollpane.getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, scrollpane.getBorder(), key );
}
@Override
protected void updateViewport( PropertyChangeEvent e ) {
super.updateViewport( e );
@@ -329,6 +405,29 @@ public class FlatScrollPaneUI
paint( g, c );
}
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) {
JViewport viewport = scrollPane.getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view == null )
return false;
// check whether view is focus owner
if( FlatUIUtils.isPermanentFocusOwner( view ) )
return true;
// check whether editor component in JTable or JTree is focus owner
if( (view instanceof JTable && ((JTable)view).isEditing()) ||
(view instanceof JTree && ((JTree)view).isEditing()) )
{
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if( focusOwner != null )
return SwingUtilities.isDescendingFrom( focusOwner, view );
}
return false;
}
//---- class Handler ------------------------------------------------------
/**
@@ -350,11 +449,13 @@ public class FlatScrollPaneUI
@Override
public void focusGained( FocusEvent e ) {
// necessary to update focus border
scrollpane.repaint();
}
@Override
public void focusLost( FocusEvent e ) {
// necessary to update focus border
scrollpane.repaint();
}
}

View File

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

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