Compare commits

..

341 Commits

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
Emmanuel Bourg
6b880af447 MenuItem: paint the selected icon when the item is selected 2021-11-03 18:10:13 +01: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
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
587f431ef4 Typography: added FlatFontsTest to quickly view all available fonts on the current system 2021-10-16 18:56:53 +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
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
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
372 changed files with 64072 additions and 29363 deletions

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,29 +63,18 @@ 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 :flatlaf-theme-editor:build -Dorg.gradle.internal.publish.checksums.insecure=true
@@ -123,25 +103,14 @@ 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 :flatlaf-theme-editor:build -Drelease=true

View File

@@ -9,50 +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
!~/.gradle/caches/modules-2/modules-2.lock
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,7 +1,250 @@
FlatLaf Change Log
==================
## 2.0-SNAPSHOT
## 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
@@ -12,9 +255,34 @@ FlatLaf Change Log
- 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" );`
- TextField, FormattedTextField and PasswordField: Support leading and trailing
icons (set client property `JTextField.leadingIcon` or
`JTextField.trailingIcon` to an `Icon`). (PR #378; issue #368)
- 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
@@ -44,25 +312,96 @@ FlatLaf Change Log
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)
- Extras: Added class `FlatDesktop` for easy integration into macOS screen menu
(About, Preferences and Quit) when using Java 8.
- 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)
- Tree: Fixed editing cell issue with custom cell renderer and cell editor that
use same component for rendering and editing. (issue #385)
- 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

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
@@ -99,10 +99,22 @@ 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
----
@@ -114,6 +126,13 @@ Buzz
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
@@ -141,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
@@ -173,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.6.1"
val developmentVersion = "2.0-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()

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 ) )
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-java9`
`flatlaf-publish`
@@ -29,7 +30,7 @@ dependencies {
testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine" )
// https://github.com/jtulach/netbeans-apitest
sigtest( "org.netbeans.tools:sigtest-maven-plugin:1.4" )
sigtest( "org.netbeans.tools:sigtest-maven-plugin:1.7" )
}
java {
@@ -43,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" )
@@ -71,6 +67,9 @@ tasks {
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" ) {

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 1.6
#Version 2.5
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -11,6 +11,7 @@ fld public final static java.lang.String BUTTON_TYPE_TAB = "tab"
fld public final static java.lang.String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarButton"
fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect"
fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"
fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
@@ -30,7 +31,12 @@ fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY = "JTextFiel
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_NEVER = "never"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ONCE = "once"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE = "JSplitPane.expandableSide"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_LEFT = "left"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_RIGHT = "right"
fld public final static java.lang.String SQUARE_SIZE = "JButton.squareSize"
fld public final static java.lang.String STYLE = "FlatLaf.style"
fld public final static java.lang.String STYLE_CLASS = "FlatLaf.styleClass"
fld public final static java.lang.String TABBED_PANE_ALIGN_CENTER = "center"
fld public final static java.lang.String TABBED_PANE_ALIGN_FILL = "fill"
fld public final static java.lang.String TABBED_PANE_ALIGN_LEADING = "leading"
@@ -59,6 +65,9 @@ fld public final static java.lang.String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JT
fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"
fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"
fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal"
@@ -67,12 +76,21 @@ fld public final static java.lang.String TABBED_PANE_TRAILING_COMPONENT = "JTabb
fld public final static java.lang.String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"
fld public final static java.lang.String TAB_BUTTON_UNDERLINE_COLOR = "JToggleButton.tab.underlineColor"
fld public final static java.lang.String TAB_BUTTON_UNDERLINE_HEIGHT = "JToggleButton.tab.underlineHeight"
fld public final static java.lang.String TAB_BUTTON_UNDERLINE_PLACEMENT = "JToggleButton.tab.underlinePlacement"
fld public final static java.lang.String TEXT_FIELD_CLEAR_CALLBACK = "JTextField.clearCallback"
fld public final static java.lang.String TEXT_FIELD_LEADING_COMPONENT = "JTextField.leadingComponent"
fld public final static java.lang.String TEXT_FIELD_LEADING_ICON = "JTextField.leadingIcon"
fld public final static java.lang.String TEXT_FIELD_PADDING = "JTextField.padding"
fld public final static java.lang.String TEXT_FIELD_SHOW_CLEAR_BUTTON = "JTextField.showClearButton"
fld public final static java.lang.String TEXT_FIELD_TRAILING_COMPONENT = "JTextField.trailingComponent"
fld public final static java.lang.String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon"
fld public final static java.lang.String TITLE_BAR_BACKGROUND = "JRootPane.titleBarBackground"
fld public final static java.lang.String TITLE_BAR_FOREGROUND = "JRootPane.titleBarForeground"
fld public final static java.lang.String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon"
fld public final static java.lang.String TREE_PAINT_SELECTION = "JTree.paintSelection"
fld public final static java.lang.String TREE_WIDE_SELECTION = "JTree.wideSelection"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "JRootPane.useWindowDecorations"
meth public static <%0 extends java.lang.Object> {%%0} clientProperty(javax.swing.JComponent,java.lang.String,{%%0},java.lang.Class<{%%0}>)
meth public static boolean clientPropertyBoolean(javax.swing.JComponent,java.lang.String,boolean)
meth public static boolean clientPropertyEquals(javax.swing.JComponent,java.lang.String,java.lang.Object)
meth public static int clientPropertyInt(javax.swing.JComponent,java.lang.String,int)
@@ -165,8 +183,10 @@ meth public boolean isSupportedLookAndFeel()
meth public final boolean equals(java.lang.Object)
meth public final int hashCode()
meth public java.lang.String getID()
meth public java.util.Map<java.lang.String,java.lang.String> getExtraDefaults()
meth public javax.swing.Icon getDisabledIcon(javax.swing.JComponent,javax.swing.Icon)
meth public javax.swing.UIDefaults getDefaults()
meth public static <%0 extends java.lang.Object> {%%0} getStyleableValue(javax.swing.JComponent,java.lang.String)
meth public static boolean install(javax.swing.LookAndFeel)
anno 0 java.lang.Deprecated()
meth public static boolean isLafDark()
@@ -174,6 +194,9 @@ meth public static boolean isShowMnemonics()
meth public static boolean isUseNativeWindowDecorations()
meth public static boolean setup(javax.swing.LookAndFeel)
meth public static boolean supportsNativeWindowDecorations()
meth public static java.lang.Object parseDefaultsValue(java.lang.String,java.lang.String,java.lang.Class<?>)
meth public static java.util.Map<java.lang.String,java.lang.Class<?>> getStyleableInfos(javax.swing.JComponent)
meth public static java.util.Map<java.lang.String,java.lang.String> getGlobalExtraDefaults()
meth public static javax.swing.UIDefaults$ActiveValue createActiveFontValue(float)
meth public static void hideMnemonics()
meth public static void initIconColors(javax.swing.UIDefaults,boolean)
@@ -181,22 +204,26 @@ meth public static void installLafInfo(java.lang.String,java.lang.Class<? extend
meth public static void registerCustomDefaultsSource(java.io.File)
meth public static void registerCustomDefaultsSource(java.lang.String)
meth public static void registerCustomDefaultsSource(java.lang.String,java.lang.ClassLoader)
meth public static void registerCustomDefaultsSource(java.net.URL)
meth public static void repaintAllFramesAndDialogs()
meth public static void revalidateAndRepaintAllFramesAndDialogs()
meth public static void runWithUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>,java.lang.Runnable)
meth public static void setGlobalExtraDefaults(java.util.Map<java.lang.String,java.lang.String>)
meth public static void setUseNativeWindowDecorations(boolean)
meth public static void showMnemonics(java.awt.Component)
meth public static void unregisterCustomDefaultsSource(java.io.File)
meth public static void unregisterCustomDefaultsSource(java.lang.String)
meth public static void unregisterCustomDefaultsSource(java.lang.String,java.lang.ClassLoader)
meth public static void unregisterCustomDefaultsSource(java.net.URL)
meth public static void updateUI()
meth public static void updateUILater()
meth public void initialize()
meth public void registerUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
meth public void setExtraDefaults(java.util.Map<java.lang.String,java.lang.String>)
meth public void uninitialize()
meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
supr javax.swing.plaf.basic.BasicLookAndFeel
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,mnemonicHandler,oldPopupFactory,postInitialization,uiDefaultsGetters,updateUIPending
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,getUIMethod,getUIMethodInitialized,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,subMenuUsabilityHelperInstalled,uiDefaultsGetters,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -231,9 +258,11 @@ hfds baseTheme,dark,name,properties
CLSS public abstract interface com.formdev.flatlaf.FlatSystemProperties
fld public final static java.lang.String ANIMATION = "flatlaf.animation"
fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded"
fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"
fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale"
fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown"
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange"
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection"
fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"
@@ -330,7 +359,15 @@ innr public static HSLIncreaseDecrease
innr public static Mix
meth public !varargs static java.awt.Color applyFunctions(java.awt.Color,com.formdev.flatlaf.util.ColorFunctions$ColorFunction[])
meth public static float clamp(float)
meth public static float luma(java.awt.Color)
meth public static java.awt.Color darken(java.awt.Color,float)
meth public static java.awt.Color desaturate(java.awt.Color,float)
meth public static java.awt.Color lighten(java.awt.Color,float)
meth public static java.awt.Color mix(java.awt.Color,java.awt.Color,float)
meth public static java.awt.Color saturate(java.awt.Color,float)
meth public static java.awt.Color shade(java.awt.Color,float)
meth public static java.awt.Color spin(java.awt.Color,float)
meth public static java.awt.Color tint(java.awt.Color,float)
supr java.lang.Object
CLSS public abstract interface static com.formdev.flatlaf.util.ColorFunctions$ColorFunction
@@ -566,6 +603,7 @@ meth public static java.util.List<java.awt.Image> getResolutionVariants(java.awt
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.NativeLibrary
cons public init(java.io.File,boolean)
cons public init(java.lang.String,java.lang.ClassLoader,boolean)
meth public boolean isLoaded()
supr java.lang.Object
@@ -589,23 +627,59 @@ meth public void paintIcon(java.awt.Component,java.awt.Graphics,int,int)
supr java.lang.Object
hfds iconHeight,iconWidth,imageIcon,lastImage,lastSystemScaleFactor,lastUserScaleFactor
CLSS public com.formdev.flatlaf.util.SoftCache<%0 extends java.lang.Object, %1 extends java.lang.Object>
cons public init()
cons public init(int)
intf java.util.Map<{com.formdev.flatlaf.util.SoftCache%0},{com.formdev.flatlaf.util.SoftCache%1}>
meth public boolean containsKey(java.lang.Object)
meth public boolean containsValue(java.lang.Object)
meth public boolean isEmpty()
meth public int size()
meth public java.util.Collection<{com.formdev.flatlaf.util.SoftCache%1}> values()
meth public java.util.Set<java.util.Map$Entry<{com.formdev.flatlaf.util.SoftCache%0},{com.formdev.flatlaf.util.SoftCache%1}>> entrySet()
meth public java.util.Set<{com.formdev.flatlaf.util.SoftCache%0}> keySet()
meth public void clear()
meth public void forEach(java.util.function.BiConsumer<? super {com.formdev.flatlaf.util.SoftCache%0},? super {com.formdev.flatlaf.util.SoftCache%1}>)
meth public void putAll(java.util.Map<? extends {com.formdev.flatlaf.util.SoftCache%0},? extends {com.formdev.flatlaf.util.SoftCache%1}>)
meth public void replaceAll(java.util.function.BiFunction<? super {com.formdev.flatlaf.util.SoftCache%0},? super {com.formdev.flatlaf.util.SoftCache%1},? extends {com.formdev.flatlaf.util.SoftCache%1}>)
meth public {com.formdev.flatlaf.util.SoftCache%1} get(java.lang.Object)
meth public {com.formdev.flatlaf.util.SoftCache%1} put({com.formdev.flatlaf.util.SoftCache%0},{com.formdev.flatlaf.util.SoftCache%1})
meth public {com.formdev.flatlaf.util.SoftCache%1} remove(java.lang.Object)
supr java.lang.Object
hfds map,queue
hcls CacheReference
CLSS public com.formdev.flatlaf.util.StringUtils
cons public init()
meth public static boolean isEmpty(java.lang.String)
meth public static boolean isTrimmedEmpty(java.lang.String)
meth public static java.lang.String removeLeading(java.lang.String,java.lang.String)
meth public static java.lang.String removeTrailing(java.lang.String,java.lang.String)
meth public static java.lang.String substringTrimmed(java.lang.String,int)
meth public static java.lang.String substringTrimmed(java.lang.String,int,int)
meth public static java.util.List<java.lang.String> split(java.lang.String,char)
meth public static java.util.List<java.lang.String> split(java.lang.String,char,boolean,boolean)
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.SwingUtils
cons public init()
meth public static <%0 extends java.awt.Component> {%%0} getComponentByName(java.awt.Container,java.lang.String)
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.SystemInfo
cons public init()
fld public final static boolean isAARCH64
fld public final static boolean isJava_11_orLater
fld public final static boolean isJava_12_orLater
fld public final static boolean isJava_15_orLater
fld public final static boolean isJava_17_orLater
fld public final static boolean isJava_18_orLater
fld public final static boolean isJava_9_orLater
fld public final static boolean isJetBrainsJVM
fld public final static boolean isJetBrainsJVM_11_orLater
fld public final static boolean isKDE
fld public final static boolean isLinux
fld public final static boolean isMacFullWindowContentSupported
fld public final static boolean isMacOS
fld public final static boolean isMacOS_10_11_ElCapitan_orLater
fld public final static boolean isMacOS_10_14_Mojave_orLater
@@ -615,6 +689,8 @@ fld public final static boolean isWebswing
fld public final static boolean isWinPE
fld public final static boolean isWindows
fld public final static boolean isWindows_10_orLater
fld public final static boolean isWindows_11_orLater
fld public final static boolean isX86
fld public final static boolean isX86_64
fld public final static long javaVersion
fld public final static long osVersion
@@ -627,6 +703,7 @@ cons public init()
meth public static boolean isSystemScalingEnabled()
meth public static double getSystemScaleFactor(java.awt.Graphics2D)
meth public static double getSystemScaleFactor(java.awt.GraphicsConfiguration)
meth public static float computeFontScaleFactor(java.awt.Font)
meth public static float getUserScaleFactor()
meth public static float scale(float)
meth public static float unscale(float)
@@ -933,6 +1010,34 @@ CLSS public abstract interface !annotation java.lang.annotation.Target
intf java.lang.annotation.Annotation
meth public abstract java.lang.annotation.ElementType[] value()
CLSS public abstract interface java.util.Map<%0 extends java.lang.Object, %1 extends java.lang.Object>
innr public abstract interface static Entry
meth public abstract boolean containsKey(java.lang.Object)
meth public abstract boolean containsValue(java.lang.Object)
meth public abstract boolean equals(java.lang.Object)
meth public abstract boolean isEmpty()
meth public abstract int hashCode()
meth public abstract int size()
meth public abstract java.util.Collection<{java.util.Map%1}> values()
meth public abstract java.util.Set<java.util.Map$Entry<{java.util.Map%0},{java.util.Map%1}>> entrySet()
meth public abstract java.util.Set<{java.util.Map%0}> keySet()
meth public abstract void clear()
meth public abstract void putAll(java.util.Map<? extends {java.util.Map%0},? extends {java.util.Map%1}>)
meth public abstract {java.util.Map%1} get(java.lang.Object)
meth public abstract {java.util.Map%1} put({java.util.Map%0},{java.util.Map%1})
meth public abstract {java.util.Map%1} remove(java.lang.Object)
meth public boolean remove(java.lang.Object,java.lang.Object)
meth public boolean replace({java.util.Map%0},{java.util.Map%1},{java.util.Map%1})
meth public void forEach(java.util.function.BiConsumer<? super {java.util.Map%0},? super {java.util.Map%1}>)
meth public void replaceAll(java.util.function.BiFunction<? super {java.util.Map%0},? super {java.util.Map%1},? extends {java.util.Map%1}>)
meth public {java.util.Map%1} compute({java.util.Map%0},java.util.function.BiFunction<? super {java.util.Map%0},? super {java.util.Map%1},? extends {java.util.Map%1}>)
meth public {java.util.Map%1} computeIfAbsent({java.util.Map%0},java.util.function.Function<? super {java.util.Map%0},? extends {java.util.Map%1}>)
meth public {java.util.Map%1} computeIfPresent({java.util.Map%0},java.util.function.BiFunction<? super {java.util.Map%0},? super {java.util.Map%1},? extends {java.util.Map%1}>)
meth public {java.util.Map%1} getOrDefault(java.lang.Object,{java.util.Map%1})
meth public {java.util.Map%1} merge({java.util.Map%0},{java.util.Map%1},java.util.function.BiFunction<? super {java.util.Map%1},? super {java.util.Map%1},? extends {java.util.Map%1}>)
meth public {java.util.Map%1} putIfAbsent({java.util.Map%0},{java.util.Map%1})
meth public {java.util.Map%1} replace({java.util.Map%0},{java.util.Map%1})
CLSS public abstract interface javax.swing.Icon
meth public abstract int getIconHeight()
meth public abstract int getIconWidth()

View File

@@ -253,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 --------------------------------------------------------------
/**
@@ -331,6 +344,24 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations).
* <p>
* Setting this shows/hides the windows icon
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* This client property has higher priority than UI default {@code TitlePane.showIcon}.
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2
*/
String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon";
/**
* Background color of window title bar (requires enabled window decorations).
* <p>
@@ -373,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>
@@ -716,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>
@@ -729,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>
@@ -816,10 +910,117 @@ public interface FlatClientProperties
*/
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}

View File

@@ -34,6 +34,8 @@ public class 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() );

View File

@@ -33,6 +33,8 @@ 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() );

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

@@ -34,6 +34,8 @@ public class 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() );

View File

@@ -30,7 +30,11 @@ import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
@@ -51,6 +55,7 @@ import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.LookAndFeel;
import javax.swing.PopupFactory;
import javax.swing.RootPaneContainer;
@@ -61,6 +66,7 @@ import javax.swing.UIDefaults.LazyValue;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.IconUIResource;
import javax.swing.plaf.UIResource;
@@ -71,6 +77,7 @@ import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
@@ -101,6 +108,7 @@ public abstract class FlatLaf
private PopupFactory oldPopupFactory;
private MnemonicHandler mnemonicHandler;
private boolean subMenuUsabilityHelperInstalled;
private Consumer<UIDefaults> postInitialization;
private List<Function<Object, Object>> uiDefaultsGetters;
@@ -108,6 +116,8 @@ public abstract class FlatLaf
/**
* Sets the application look and feel to the given LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
*
* @since 1.2
*/
public static boolean setup( LookAndFeel newLookAndFeel ) {
try {
@@ -164,18 +174,19 @@ public abstract class FlatLaf
* Returns whether FlatLaf supports custom window decorations.
* This depends on the operating system and on the used Java runtime.
* <p>
* This method returns {@code true} on Windows 10 (see exception below), {@code false} otherwise.
* This method returns {@code true} on Windows 10/11 (see exception below)
* and on Linux, {@code false} otherwise.
* <p>
* Returns also {@code false} on Windows 10 if:
* Returns also {@code false} on Windows 10/11 if:
* <ul>
* <li>FlatLaf native window border support is available (requires Windows 10)</li>
* <li>FlatLaf native window border support is available (requires Windows 10/11)</li>
* <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
* and JBR supports custom window decorations
* </li>
* </ul>
* In this cases, custom decorations are enabled by the root pane.
* In these cases, custom decorations are enabled by the root pane.
* Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or
* {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary.
*/
@@ -188,7 +199,7 @@ public abstract class FlatLaf
FlatNativeWindowBorder.isSupported() )
return false;
return SystemInfo.isWindows_10_orLater;
return SystemInfo.isWindows_10_orLater || SystemInfo.isLinux;
}
@Override
@@ -228,6 +239,15 @@ public abstract class FlatLaf
@Override
public void initialize() {
// do not initialize if this is not the current look and feel
// This is only necessary for special Laf usage. E.g. in GUI builders,
// which may use multiple Lafs and may invoke this method directly.
// This avoids that listeners and factories are installed multiple times.
// In case of the NetBeans GUI builder, which does not invoke uninitialize(),
// this also avoids that listeners stay registered in the system.
if( UIManager.getLookAndFeel() != this )
return;
if( SystemInfo.isMacOS )
initializeAqua();
@@ -241,6 +261,9 @@ public abstract class FlatLaf
mnemonicHandler = new MnemonicHandler();
mnemonicHandler.install();
// install submenu usability helper
subMenuUsabilityHelperInstalled = SubMenuUsabilityHelper.install();
// listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.isWindows ) {
// Windows 10 allows increasing font size independent of scaling:
@@ -258,6 +281,9 @@ public abstract class FlatLaf
}
if( desktopPropertyName != null ) {
desktopPropertyListener = e -> {
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.UPDATE_UI_ON_SYSTEM_FONT_CHANGE, true ) )
return;
String propertyName = e.getPropertyName();
if( desktopPropertyName.equals( propertyName ) || propertyName.equals( desktopPropertyName2 ) )
reSetLookAndFeel();
@@ -296,6 +322,10 @@ public abstract class FlatLaf
@Override
public void uninitialize() {
// do not uninitialize if this is not the current look and feel
if( UIManager.getLookAndFeel() != this )
return;
// remove desktop property listener
if( desktopPropertyListener != null ) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
@@ -320,6 +350,12 @@ public abstract class FlatLaf
mnemonicHandler = null;
}
// uninstall submenu usability helper
if( subMenuUsabilityHelperInstalled ) {
SubMenuUsabilityHelper.uninstall();
subMenuUsabilityHelperInstalled = false;
}
// restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a, address { color: blue; }" );
postInitialization = null;
@@ -392,6 +428,7 @@ public abstract class FlatLaf
"EditorPane.inactiveBackground",
"FormattedTextField.disabledBackground",
"PasswordField.disabledBackground",
"RootPane.background",
"Spinner.disabledBackground",
"TextArea.disabledBackground",
"TextArea.inactiveBackground",
@@ -410,7 +447,8 @@ public abstract class FlatLaf
"Spinner.disabledForeground",
"ToggleButton.disabledText" );
putDefaults( defaults, defaults.getColor( "textText" ),
"DesktopIcon.foreground" );
"DesktopIcon.foreground",
"RootPane.foreground" );
initFonts( defaults );
initIconColors( defaults, isDark() );
@@ -420,7 +458,7 @@ public abstract class FlatLaf
// (using defaults.remove() to avoid that lazy value is resolved and icon loaded here)
Object icon = defaults.remove( "InternalFrame.icon" );
defaults.put( "InternalFrame.icon", icon );
defaults.put( "TitlePane.icon", icon );
defaults.put( "TitlePane.icon", icon ); // no longer used, but keep for compatibility
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
@@ -533,13 +571,17 @@ public abstract class FlatLaf
// use active value for all fonts to allow changing fonts in all components with:
// UIManager.put( "defaultFont", myFont );
// (this is similar as in Nimbus L&F)
Object activeFont = new ActiveFont( null, -1, 0, 0, 0, 0 );
Object activeFont = new ActiveFont( null, null, -1, 0, 0, 0, 0 );
// override fonts
for( Object key : defaults.keySet() ) {
if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) )
defaults.put( key, activeFont );
}
// add fonts that are not set in BasicLookAndFeel
defaults.put( "RootPane.font", activeFont );
defaults.put( "TitlePane.font", activeFont );
}
private void initDefaultFont( UIDefaults defaults ) {
@@ -598,7 +640,7 @@ public abstract class FlatLaf
uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> {
return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) );
} );
};
}
// increase font size if system property "flatlaf.uiScale" is set
uiFont = UIScale.applyCustomScaleFactor( uiFont );
@@ -617,7 +659,7 @@ public abstract class FlatLaf
/** @since 1.1 */
public static ActiveValue createActiveFontValue( float scaleFactor ) {
return new ActiveFont( null, -1, 0, 0, 0, scaleFactor );
return new ActiveFont( null, null, -1, 0, 0, 0, scaleFactor );
}
/**
@@ -751,6 +793,9 @@ public abstract class FlatLaf
* and can therefore override all UI defaults.
* <p>
* Invoke this method before setting the look and feel.
* <p>
* If using Java modules, the package must be opened in {@code module-info.java}.
* Otherwise, use {@link #registerCustomDefaultsSource(URL)}.
*
* @param packageName a package name (e.g. "com.myapp.resources")
*/
@@ -792,6 +837,32 @@ public abstract class FlatLaf
}
}
/**
* Registers a package where FlatLaf searches for properties files with custom UI defaults.
* <p>
* See {@link #registerCustomDefaultsSource(String)} for details.
* <p>
* This method is useful if using Java modules and the package containing the properties files
* is not opened in {@code module-info.java}.
* E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}.
*
* @param packageUrl a package URL
* @since 2
*/
public static void registerCustomDefaultsSource( URL packageUrl ) {
if( customDefaultsSources == null )
customDefaultsSources = new ArrayList<>();
customDefaultsSources.add( packageUrl );
}
/** @since 2 */
public static void unregisterCustomDefaultsSource( URL packageUrl ) {
if( customDefaultsSources == null )
return;
customDefaultsSources.remove( packageUrl );
}
/**
* Registers a folder where FlatLaf searches for properties files with custom UI defaults.
* <p>
@@ -826,7 +897,7 @@ public abstract class FlatLaf
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p>
* The global extra defaults are useful for smaller additional defaults that may change.
* E.g. accent color. Otherwise FlatLaf properties files should be used.
* E.g. accent color. Otherwise, FlatLaf properties files should be used.
* See {@link #registerCustomDefaultsSource(String)}.
* <p>
* The keys and values are strings in same format as in FlatLaf properties files.
@@ -858,7 +929,7 @@ public abstract class FlatLaf
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p>
* The extra defaults are useful for smaller additional defaults that may change.
* E.g. accent color. Otherwise FlatLaf properties files should be used.
* E.g. accent color. Otherwise, FlatLaf properties files should be used.
* See {@link #registerCustomDefaultsSource(String)}.
* <p>
* The keys and values are strings in same format as in FlatLaf properties files.
@@ -915,7 +986,7 @@ public abstract class FlatLaf
// re-set current LaF
UIManager.setLookAndFeel( lookAndFeel );
// must fire property change events ourself because old and new LaF are the same
// must fire property change events ourselves because old and new LaF are the same
PropertyChangeEvent e = new PropertyChangeEvent( UIManager.class, "lookAndFeel", lookAndFeel, lookAndFeel );
for( PropertyChangeListener l : UIManager.getPropertyChangeListeners() )
l.propertyChange( e );
@@ -959,7 +1030,7 @@ public abstract class FlatLaf
/**
* Returns whether native window decorations are supported on current platform.
* <p>
* This requires Windows 10, but may be disabled if running in special environments
* This requires Windows 10/11, but may be disabled if running in special environments
* (JetBrains Projector, Webswing or WinPE) or if loading native library fails.
* If system property {@link FlatSystemProperties#USE_WINDOW_DECORATIONS} is set to
* {@code false}, then this method also returns {@code false}.
@@ -1001,12 +1072,23 @@ public abstract class FlatLaf
/**
* Revalidate and repaint all displayable frames and dialogs.
* <p>
* Useful to update UI after changing {@code TitlePane.menuBarEmbedded}.
*
* @since 1.1.2
*/
public static void revalidateAndRepaintAllFramesAndDialogs() {
for( Window w : Window.getWindows() ) {
if( isDisplayableFrameOrDialog( w ) ) {
// revalidate menu bar
JMenuBar menuBar = (w instanceof JFrame)
? ((JFrame)w).getJMenuBar()
: (w instanceof JDialog
? ((JDialog)w).getJMenuBar()
: null);
if( menuBar != null )
menuBar.revalidate();
w.revalidate();
w.repaint();
}
@@ -1015,6 +1097,9 @@ public abstract class FlatLaf
/**
* Repaint all displayable frames and dialogs.
* <p>
* Useful to update UI after changing {@code TitlePane.unifiedBackground},
* {@code MenuItem.selectionType} or {@code Component.hideMnemonics}.
*
* @since 1.1.2
*/
@@ -1151,6 +1236,62 @@ public abstract class FlatLaf
*/
public static final Object NULL_VALUE = new Object();
/**
* Returns information about styleable values of a component.
* <p>
* This is equivalent to: {@code ((StyleableUI)c.getUI()).getStyleableInfos(c)}
*
* @since 2.5
*/
public static Map<String, Class<?>> getStyleableInfos( JComponent c ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? ui.getStyleableInfos( c ) : null;
}
/**
* Returns the (styled) value for the given key from the given component.
* <p>
* This is equivalent to: {@code ((StyleableUI)c.getUI()).getStyleableValue(c, key)}
*
* @since 2.5
*/
@SuppressWarnings( "unchecked" )
public static <T> T getStyleableValue( JComponent c, String key ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? (T) ui.getStyleableValue( c, key ) : null;
}
private static StyleableUI getStyleableUI( JComponent c ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
if( SystemInfo.isJava_9_orLater ) {
try {
// JComponent.getUI() is available since Java 9
getUIMethod = MethodHandles.lookup().findVirtual( JComponent.class, "getUI",
MethodType.methodType( ComponentUI.class ) );
} catch( Exception ex ) {
// ignore
}
}
}
try {
Object ui;
if( getUIMethod != null )
ui = getUIMethod.invoke( c );
else
ui = c.getClass().getMethod( "getUI" ).invoke( c );
return (ui instanceof StyleableUI) ? (StyleableUI) ui : null;
} catch( Throwable ex ) {
// ignore
return null;
}
}
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
//---- class FlatUIDefaults -----------------------------------------------
private class FlatUIDefaults
@@ -1173,6 +1314,9 @@ public abstract class FlatLaf
}
private Object getValue( Object key ) {
// use local variable for getters to avoid potential multi-threading issues
List<Function<Object, Object>> uiDefaultsGetters = FlatLaf.this.uiDefaultsGetters;
if( uiDefaultsGetters == null )
return null;
@@ -1191,6 +1335,7 @@ public abstract class FlatLaf
static class ActiveFont
implements ActiveValue
{
private final String baseFontKey;
private final List<String> families;
private final int style;
private final int styleChange;
@@ -1200,7 +1345,9 @@ public abstract class FlatLaf
// cache (scaled/derived) font
private FontUIResource font;
private Font lastDefaultFont;
private Font lastBaseFont;
private boolean inCreateValue;
/**
* @param families list of font families, or {@code null}
@@ -1211,9 +1358,10 @@ public abstract class FlatLaf
* @param relativeSize added to size of base font, or {@code 0}
* @param scaleSize multiply size of base font, or {@code 0}
*/
ActiveFont( List<String> families, int style, int styleChange,
ActiveFont( String baseFontKey, List<String> families, int style, int styleChange,
int absoluteSize, int relativeSize, float scaleSize )
{
this.baseFontKey = baseFontKey;
this.families = families;
this.style = style;
this.styleChange = styleChange;
@@ -1222,18 +1370,33 @@ public abstract class FlatLaf
this.scaleSize = scaleSize;
}
// using synchronized to avoid exception if invoked at the same time on multiple threads
@Override
public Object createValue( UIDefaults table ) {
Font defaultFont = UIManager.getFont( "defaultFont" );
public synchronized Object createValue( UIDefaults table ) {
if( inCreateValue )
throw new IllegalStateException( "FlatLaf: endless recursion in font" );
// fallback (to avoid NPE in case that this is used in another Laf)
if( defaultFont == null )
defaultFont = UIManager.getFont( "Label.font" );
Font baseFont = null;
if( lastDefaultFont != defaultFont ) {
lastDefaultFont = defaultFont;
inCreateValue = true;
try {
if( baseFontKey != null )
baseFont = (Font) UIDefaultsLoader.lazyUIManagerGet( baseFontKey );
font = derive( defaultFont, fontSize -> UIScale.scale( fontSize ) );
if( baseFont == null )
baseFont = UIManager.getFont( "defaultFont" );
// fallback (to avoid NPE in case that this is used in another Laf)
if( baseFont == null )
baseFont = UIManager.getFont( "Label.font" );
} finally {
inCreateValue = false;
}
if( lastBaseFont != baseFont ) {
lastBaseFont = baseFont;
font = derive( baseFont, fontSize -> UIScale.scale( fontSize ) );
}
return font;
@@ -1271,9 +1434,19 @@ public abstract class FlatLaf
}
// derive font
if( newStyle != baseStyle || newSize != baseSize )
if( newStyle != baseStyle || newSize != baseSize ) {
// hack for font "Ubuntu Medium" on Linux, which curiously belongs
// to family "Ubuntu Light" and using deriveFont() would create a light font
if( "Ubuntu Medium".equalsIgnoreCase( baseFont.getName() ) &&
"Ubuntu Light".equalsIgnoreCase( baseFont.getFamily() ) )
{
Font font = createCompositeFont( "Ubuntu Medium", newStyle, newSize );
if( !isFallbackFont( font ) )
return toUIResource( font );
}
return toUIResource( baseFont.deriveFont( newStyle, newSize ) );
else
} else
return toUIResource( baseFont );
}

View File

@@ -33,6 +33,8 @@ 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() );

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -34,7 +35,7 @@ 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 then 100% are allowed.
* 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>
*/
@@ -81,7 +82,7 @@ public interface FlatSystemProperties
* {@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
@@ -92,16 +93,16 @@ 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 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> {@code true}
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*/
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
@@ -116,7 +117,7 @@ public interface FlatSystemProperties
* {@link FlatClientProperties#MENU_BAR_EMBEDDED} and
* UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Window 10)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -139,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

@@ -37,6 +37,7 @@ 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.
@@ -72,6 +73,8 @@ public class IntelliJTheme
*
* The input stream is automatically closed.
* Using a buffered input stream is not necessary.
*
* @since 1.2
*/
public static boolean setup( InputStream in ) {
try {
@@ -162,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" );
@@ -175,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 )
@@ -242,7 +248,19 @@ 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 ) {
@@ -299,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
@@ -534,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
@@ -600,6 +629,11 @@ public class IntelliJTheme
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" );

View File

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

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

@@ -22,13 +22,12 @@ 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.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -56,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;
@@ -80,11 +80,11 @@ class UIDefaultsLoader
private static final String OPTIONAL_PREFIX = "?";
private static final String WILDCARD_PREFIX = "*.";
private static final String KEY_VARIABLES = "FlatLaf.internal.variables";
static final String KEY_VARIABLES = "FlatLaf.internal.variables";
private static int parseColorDepth;
private static final Cache<String, Object> fontCache = new Cache<>();
private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
@@ -158,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;
@@ -338,7 +350,7 @@ class UIDefaultsLoader
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, INTEGERORFLOAT, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static ValueType[] tempResultValueType = new ValueType[1];
private static final ValueType[] tempResultValueType = new ValueType[1];
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
@@ -352,6 +364,12 @@ class UIDefaultsLoader
if( resultValueType == null )
resultValueType = tempResultValueType;
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType[0] = ValueType.STRING;
return value;
}
value = value.trim();
// null
@@ -429,10 +447,7 @@ class UIDefaultsLoader
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( "\"" ) && value.indexOf( '"', 1 ) == value.length() - 1 ) {
valueType = ValueType.STRING;
value = value.substring( 1, value.length() - 1 );
} else if( value.startsWith( TYPE_PREFIX ) ) {
else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
@@ -451,6 +466,10 @@ class UIDefaultsLoader
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 );
@@ -524,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 ) {
@@ -567,17 +592,18 @@ class UIDefaultsLoader
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor[,lineThickness]]
// 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
@@ -758,6 +784,7 @@ class UIDefaultsLoader
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--;
@@ -770,7 +797,7 @@ class UIDefaultsLoader
* 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.
* 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 )
@@ -825,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 ) );
}
@@ -880,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 );
}
@@ -1008,6 +1046,34 @@ class UIDefaultsLoader
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 )
{
@@ -1039,7 +1105,7 @@ class UIDefaultsLoader
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]]
* 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 );
@@ -1052,6 +1118,7 @@ class UIDefaultsLoader
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 ) );
@@ -1101,6 +1168,12 @@ class UIDefaultsLoader
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 )
@@ -1127,7 +1200,7 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
}
font = new FlatLaf.ActiveFont( families, style, styleChange, absoluteSize, relativeSize, scaleSize );
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
fontCache.put( value, font );
return font;
}
@@ -1169,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;
}
@@ -1210,28 +1283,28 @@ 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 );
};
}
@@ -1280,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() );
@@ -1296,43 +1369,4 @@ class UIDefaultsLoader
private static void throwMissingParametersException( String value ) {
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
}
//---- class Cache --------------------------------------------------------
private static class Cache<K,V>
{
private final Map<K, CacheReference<K,V>> map = new HashMap<>();
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
V get( K key ) {
expungeStaleEntries();
CacheReference<K,V> ref = map.get( key );
return (ref != null) ? ref.get() : null;
}
void put( K key, V value ) {
expungeStaleEntries();
map.put( key, new CacheReference<>( key, value, queue ) );
}
@SuppressWarnings( "unchecked" )
void expungeStaleEntries() {
Reference<? extends V> reference;
while( (reference = queue.poll()) != null )
map.remove( ((CacheReference<K,V>)reference).key );
}
//---- class CacheReference ----
private static class CacheReference<K,V>
extends SoftReference<V>
{
final K key;
public CacheReference( K key, V value, ReferenceQueue<? super V> queue ) {
super( value, queue );
this.key = key;
}
}
}
}

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

@@ -51,7 +51,7 @@ public class FlatAscendingSortIcon
boolean chevron = this.chevron;
Color sortIconColor = this.sortIconColor;
// Because this icons are always shared for all table headers,
// 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 ) {

View File

@@ -49,6 +49,14 @@ public class FlatCapsLockIcon
}
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
switch( key ) {
case "capsLockIconColor": return color;
default: return null;
}
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

View File

@@ -172,6 +172,11 @@ public class FlatCheckBoxIcon
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 );

View File

@@ -59,6 +59,11 @@ public class FlatCheckBoxMenuItemIcon
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

@@ -47,8 +47,16 @@ public class FlatClearIcon
@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 */
@@ -61,9 +69,14 @@ public class FlatClearIcon
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 instanceof AbstractButton ) {
if( !ignoreButtonState && c instanceof AbstractButton ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isPressed() || model.isRollover() ) {
/*

View File

@@ -84,6 +84,11 @@ public class FlatHelpButtonIcon
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 ) {
/*
@@ -96,8 +101,8 @@ 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;

View File

@@ -61,9 +61,14 @@ public class FlatMenuArrowIcon
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 ) );
@@ -82,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

@@ -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

@@ -45,8 +45,16 @@ public class FlatSearchIcon
@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 */
@@ -59,6 +67,11 @@ public class FlatSearchIcon
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 ) {
/*
@@ -70,8 +83,10 @@ public class FlatSearchIcon
</svg>
*/
g.setColor( FlatButtonUI.buttonStateColor( c, searchIconColor, searchIconColor,
null, searchIconHoverColor, searchIconPressedColor ) );
g.setColor( ignoreButtonState
? searchIconColor
: FlatButtonUI.buttonStateColor( c, searchIconColor, searchIconColor,
null, searchIconHoverColor, searchIconPressedColor ) );
// paint magnifier
Area area = new Area( new Ellipse2D.Float( 2, 2, 10, 10 ) );

View File

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

View File

@@ -76,6 +76,11 @@ public class FlatTabbedPaneCloseIcon
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background

View File

@@ -76,7 +76,7 @@ public class FlatTreeCollapsedIcon
}
/**
* Because this icons are always shared for all trees,
* 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 ) {

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

@@ -18,8 +18,8 @@ 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 opend in module-info.java.
* Also when using --patch-module (e.g. from an IDE), an error would occur for empty packages.
* 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
*/

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,7 +38,7 @@ public class FlatArrowButton
extends BasicArrowButton
implements UIResource
{
public static final int DEFAULT_ARROW_WIDTH = 8;
public static final int DEFAULT_ARROW_WIDTH = 9;
protected boolean chevron;
protected Color foreground;
@@ -82,14 +83,18 @@ 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();
}
}
} );
}
@@ -211,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

@@ -101,6 +101,12 @@ public class FlatBorder
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();

View File

@@ -159,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;

View File

@@ -38,15 +38,19 @@ 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;
@@ -74,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
@@ -95,8 +106,13 @@ 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
*/
@@ -113,20 +129,27 @@ public class FlatButtonUI
protected Color startBackground;
protected Color endBackground;
@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;
@Styleable(dot=true) protected Color defaultBackground;
protected Color defaultEndBackground;
@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;
@Styleable protected boolean paintShadow;
@@ -135,8 +158,13 @@ public class FlatButtonUI
@Styleable(dot=true) protected Color defaultShadowColor;
@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;
@@ -153,7 +181,7 @@ public class FlatButtonUI
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.canUseSharedUI( c )
return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c )
? FlatUIUtils.createSharedUI( FlatButtonUI.class, () -> new FlatButtonUI( true ) )
: new FlatButtonUI( false );
}
@@ -165,6 +193,13 @@ public class FlatButtonUI
@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 );
@@ -186,20 +221,27 @@ 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" );
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" );
paintShadow = UIManager.getBoolean( "Button.paintShadow" );
@@ -208,8 +250,13 @@ public class FlatButtonUI
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" );
@@ -259,11 +306,15 @@ public class FlatButtonUI
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 applyStyle() from installUI()
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
@@ -322,6 +373,18 @@ public class FlatButtonUI
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();
}
@@ -336,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) )
@@ -420,11 +483,21 @@ 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;
@@ -437,8 +510,15 @@ public class FlatButtonUI
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( paintShadow &&
@@ -470,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 ) )
@@ -490,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;
@@ -503,11 +602,14 @@ public class FlatButtonUI
// selected state
if( ((AbstractButton)c).isSelected() ) {
// in toolbar use same background 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
return buttonStateColor( c,
toolBarButton ? toolbarSelectedBackground : selectedBackground,
toolBarButton ? toolbarSelectedBackground : disabledSelectedBackground,
toolBarButton
? (toolbarDisabledSelectedBackground != null ? toolbarDisabledSelectedBackground : toolbarSelectedBackground)
: disabledSelectedBackground,
null,
null,
toolBarButton ? toolbarPressedBackground : pressedBackground );
@@ -534,6 +636,9 @@ public class FlatButtonUI
}
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 ) )
@@ -549,6 +654,9 @@ public class FlatButtonUI
public static Color buttonStateColor( Component c, Color enabledColor, Color disabledColor,
Color focusedColor, Color hoverColor, Color pressedColor )
{
if( c == null )
return enabledColor;
if( !c.isEnabled() )
return disabledColor;
@@ -569,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 ) || isBorderlessButton( 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;
}
@@ -597,6 +735,9 @@ 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, squareSize ) ) {
@@ -617,6 +758,23 @@ public class FlatButtonUI
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 );

View File

@@ -96,11 +96,14 @@ public class FlatCaret
// if text component is focused, then caret and selection are visible,
// but when switching theme, the component does not yet have
// an highlighter and the selection is not painted
// 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 );
@@ -233,7 +236,7 @@ public class FlatCaret
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 ) ) {
@@ -253,6 +256,9 @@ public class FlatCaret
// select all
if( c instanceof JFormattedTextField ) {
EventQueue.invokeLater( () -> {
if( getComponent() == null )
return; // was deinstalled
select( 0, doc.getLength() );
} );
} else {

View File

@@ -16,18 +16,20 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
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.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -58,9 +60,15 @@ import com.formdev.flatlaf.util.LoggingFacade;
*
* @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
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
@@ -89,6 +97,7 @@ public class FlatCheckBoxMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
@@ -118,29 +127,27 @@ public class FlatCheckBoxMenuItemUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
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

View File

@@ -43,7 +43,7 @@ public class FlatCheckBoxUI
extends FlatRadioButtonUI
{
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.canUseSharedUI( c )
return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c )
? FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, () -> new FlatCheckBoxUI( true ) )
: new FlatCheckBoxUI( false );
}

View File

@@ -42,6 +42,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
@@ -70,6 +71,8 @@ import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;
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;
@@ -86,6 +89,11 @@ import com.formdev.flatlaf.util.SystemInfo;
* @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
@@ -98,8 +106,8 @@ import com.formdev.flatlaf.util.SystemInfo;
* @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
@@ -112,9 +120,11 @@ import com.formdev.flatlaf.util.SystemInfo;
*
* @author Karl Tauber
*/
@StyleableField( cls=BasicComboBoxUI.class, key="padding" )
public class FlatComboBoxUI
extends BasicComboBoxUI
implements StyleableUI
implements StyleableUI, StyleableLookupProvider
{
@Styleable protected int minimumWidth;
@Styleable protected int editorColumns;
@@ -122,6 +132,7 @@ public class FlatComboBoxUI
@Styleable protected String arrowType;
protected boolean isIntelliJTheme;
private Color background;
@Styleable protected Color editableBackground;
@Styleable protected Color focusedBackground;
@Styleable protected Color disabledBackground;
@@ -155,6 +166,13 @@ public class 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();
@@ -217,6 +235,7 @@ public class FlatComboBoxUI
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( "ComboBox.background" );
editableBackground = UIManager.getColor( "ComboBox.editableBackground" );
focusedBackground = UIManager.getColor( "ComboBox.focusedBackground" );
disabledBackground = UIManager.getColor( "ComboBox.disabledBackground" );
@@ -249,6 +268,7 @@ public class FlatComboBoxUI
protected void uninstallDefaults() {
super.uninstallDefaults();
background = null;
editableBackground = null;
focusedBackground = null;
disabledBackground = null;
@@ -281,15 +301,21 @@ public class FlatComboBoxUI
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
if( arrowButton != null ) {
// on macOS, a Swing combo box is used for AWT component java.awt.Choice
// and the font may be (temporary) null
if( arrowButton != null && comboBox.getFont() != null ) {
// limit button width to height of a raw combobox (without insets)
FontMetrics fm = comboBox.getFontMetrics( comboBox.getFont() );
int maxButtonWidth = fm.getHeight() + scale( padding.top ) + scale( padding.bottom );
int minButtonWidth = (maxButtonWidth * 3) / 4;
// make button square (except if width is limited)
Insets insets = getInsets();
int buttonWidth = Math.min( parent.getPreferredSize().height - insets.top - insets.bottom, maxButtonWidth );
int buttonWidth = Math.min( Math.max( parent.getHeight() - insets.top - insets.bottom, minButtonWidth ), maxButtonWidth );
if( buttonWidth != arrowButton.getWidth() ) {
// set width of arrow button to preferred height of combobox
// set width of arrow button
int xOffset = comboBox.getComponentOrientation().isLeftToRight()
? arrowButton.getWidth() - buttonWidth
: 0;
@@ -351,6 +377,7 @@ public class FlatComboBoxUI
break;
case COMPONENT_ROUND_RECT:
case OUTLINE:
comboBox.repaint();
break;
@@ -482,13 +509,6 @@ public class FlatComboBoxUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
// BasicComboBoxUI
if( key.equals( "padding" ) ) {
Object oldValue = padding;
padding = (Insets) value;
return oldValue;
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, comboBox, borderShared );
@@ -497,11 +517,21 @@ public class FlatComboBoxUI
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "padding", Insets.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
FlatStylingSupport.collectStyleableInfos( comboBox.getBorder(), infos );
return infos;
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
@@ -540,24 +570,27 @@ public class FlatComboBoxUI
// paint arrow button background
if( enabled && !isCellRenderer ) {
g2.setColor( paintButton
Color buttonColor = paintButton
? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
: buttonBackground;
if( buttonColor != null ) {
g2.setColor( buttonColor );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
}
// paint vertical line between value and arrow button
if( paintButton ) {
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null ) {
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
@@ -575,22 +608,6 @@ public class FlatComboBoxUI
@Override
@SuppressWarnings( "unchecked" )
public void paintCurrentValue( Graphics g, Rectangle bounds, boolean hasFocus ) {
// apply clipping using rounded rectangle to avoid that renderer paints
// outside of border if combobox uses larger arc for edges
// (e.g. FlatClientProperties.COMPONENT_ROUND_RECT is true)
FlatBorder border = FlatUIUtils.getOutsideFlatBorder( comboBox );
if( border != null ) {
int clipArc = border.getArc( comboBox ) - (border.getLineWidth( comboBox ) * 2);
if( clipArc > 0 ) {
int x = bounds.x;
int width = bounds.width + bounds.height;
if( !comboBox.getComponentOrientation().isLeftToRight() )
x -= bounds.height;
((Graphics2D)g).clip( FlatUIUtils.createComponentRectangle(
x, bounds.y, width, bounds.height, scale( (float) clipArc ) ) );
}
}
paddingBorder.uninstall();
ListCellRenderer<Object> renderer = comboBox.getRenderer();
@@ -604,11 +621,20 @@ public class FlatComboBoxUI
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);
paddingBorder.install( c );
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
@@ -618,6 +644,9 @@ public class FlatComboBoxUI
protected Color getBackground( boolean enabled ) {
if( enabled ) {
if( FlatUIUtils.isAWTPeer( comboBox ) )
return background;
Color background = comboBox.getBackground();
// always use explicitly set color
@@ -677,7 +706,7 @@ public class FlatComboBoxUI
@Override
protected Dimension getSizeForComponent( Component comp ) {
paddingBorder.install( comp );
paddingBorder.install( comp, 0 );
Dimension size = super.getSizeForComponent( comp );
paddingBorder.uninstall();
return size;
@@ -700,7 +729,7 @@ public class FlatComboBoxUI
return true;
Component editorComponent = comboBox.getEditor().getEditorComponent();
return (editorComponent != null) ? FlatUIUtils.isPermanentFocusOwner( editorComponent ) : false;
return editorComponent != null && FlatUIUtils.isPermanentFocusOwner( editorComponent );
} else
return FlatUIUtils.isPermanentFocusOwner( comboBox );
}
@@ -813,9 +842,12 @@ public class FlatComboBoxUI
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
Border border = UIManager.getBorder( "PopupMenu.border" );
if( border != null )
setBorder( border );
setBorder( FlatUIUtils.nonUIResource( border ) );
}
@Override
@@ -829,6 +861,11 @@ public class FlatComboBoxUI
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
@@ -887,7 +924,7 @@ public class FlatComboBoxUI
Component c = renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
c.applyComponentOrientation( comboBox.getComponentOrientation() );
paddingBorder.install( c );
paddingBorder.install( c, Math.round( FlatUIUtils.getBorderFocusWidth( comboBox ) ) );
return c;
}
@@ -910,15 +947,20 @@ public class FlatComboBoxUI
private Insets padding;
private JComponent rendererComponent;
private Border rendererBorder;
private int focusWidth;
CellPaddingBorder( Insets padding ) {
this.padding = padding;
}
void install( Component c ) {
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) )
return;
this.focusWidth = focusWidth;
JComponent jc = (JComponent) c;
Border oldBorder = jc.getBorder();
if( oldBorder == this )
@@ -947,7 +989,7 @@ public class FlatComboBoxUI
* there is no single place to uninstall it.
* This is the reason why this method is called from various places.
*/
void uninstall() {
synchronized void uninstall() {
if( rendererComponent == null )
return;
@@ -958,9 +1000,9 @@ public class FlatComboBoxUI
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
synchronized public Insets getBorderInsets( Component c, Insets insets ) {
Insets padding = scale( this.padding );
if( rendererBorder != null ) {
if( rendererBorder != null && !(rendererBorder instanceof CellPaddingBorder) ) {
Insets insideInsets = rendererBorder.getBorderInsets( c );
insets.top = Math.max( padding.top, insideInsets.top );
insets.left = Math.max( padding.left, insideInsets.left );
@@ -972,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

@@ -107,6 +107,12 @@ public class FlatDropShadowBorder
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )

View File

@@ -44,8 +44,8 @@ import com.formdev.flatlaf.util.LoggingFacade;
* <!-- 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
@@ -209,6 +209,12 @@ public class FlatEditorPaneUI
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,

View File

@@ -56,7 +56,7 @@ public class FlatEmptyBorder
protected static Insets scaleInsets( Component c, Insets insets,
int top, int left, int bottom, int right )
{
boolean leftToRight = left == right || c.getComponentOrientation().isLeftToRight();
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 );
@@ -76,4 +76,9 @@ public class FlatEmptyBorder
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

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

View File

@@ -179,6 +179,32 @@ public class FlatInternalFrameUI
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
@@ -233,6 +259,23 @@ public class FlatInternalFrameUI
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() ) {

View File

@@ -122,7 +122,7 @@ public class FlatLabelUI
JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary
// updateUI() invokes applyStyle() from installUI()
// updateUI() invokes installStyle() from installUI()
label.updateUI();
} else
installStyle( label );
@@ -158,6 +158,12 @@ public class FlatLabelUI
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 );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( getLineThickness() ), 0, null, getLineColor(), null );
0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null );
} finally {
g2.dispose();
}

View File

@@ -37,7 +37,7 @@ import javax.swing.plaf.ListUI;
public class FlatListCellBorder
extends FlatLineBorder
{
protected boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" );
/** @since 2 */ protected boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" );
private Component c;
@@ -72,7 +72,7 @@ public class FlatListCellBorder
}
/**
* Because this borders are always shared for all lists,
* 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 ) {
@@ -113,7 +113,7 @@ 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
@@ -125,7 +125,7 @@ public class FlatListCellBorder
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

@@ -78,9 +78,9 @@ public class FlatListUI
@Styleable protected Color selectionInactiveForeground;
// for FlatListCellBorder
@Styleable protected Insets cellMargins;
@Styleable protected Color cellFocusColor;
@Styleable protected boolean showCellFocusIndicator;
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color cellFocusColor;
/** @since 2 */ @Styleable protected Boolean showCellFocusIndicator;
private Map<String, Object> oldStyleValues;
@@ -90,6 +90,13 @@ public class 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();
@@ -209,10 +216,16 @@ public class FlatListUI
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

@@ -51,8 +51,17 @@ public class FlatMenuBarBorder
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 );
}
@@ -68,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

@@ -17,8 +17,11 @@
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;
@@ -26,6 +29,7 @@ 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;
@@ -39,11 +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}.
@@ -70,6 +76,8 @@ public class FlatMenuBarUI
// 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;
@@ -99,6 +107,10 @@ public class FlatMenuBarUI
super.installDefaults();
LookAndFeel.installProperty( menuBar, "opaque", false );
LayoutManager layout = menuBar.getLayout();
if( layout == null || layout instanceof UIResource )
menuBar.setLayout( new FlatMenuBarLayout( menuBar ) );
}
@Override
@@ -164,6 +176,12 @@ public class FlatMenuBarUI
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 ) {
// paint background
@@ -179,20 +197,22 @@ public class FlatMenuBarUI
protected Color getBackground( JComponent c ) {
Color background = c.getBackground();
// paint background if opaque or if having custom background color
if( c.isOpaque() || !(background instanceof UIResource) )
// paint background if opaque
if( c.isOpaque() )
return background;
// paint background if menu bar is not the "main" menu bar
// do not paint background if non-opaque and having custom background color
if( !(background instanceof UIResource) )
return null;
// paint background if menu bar is not the "main" menu bar (e.g. in internal frame)
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null || !(rootPane.getParent() instanceof Window) || rootPane.getJMenuBar() != c )
return background;
// use parent background for unified title pane
// (not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime)
if( UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
FlatNativeWindowBorder.hasCustomDecoration( (Window) rootPane.getParent() ) )
background = FlatUIUtils.getParentBackground( c );
if( useUnifiedBackground( c ) )
background = FlatUIUtils.getParentBackground( c );
// paint background in full screen mode
if( FlatUIUtils.isFullScreen( rootPane ) )
@@ -202,6 +222,146 @@ public class FlatMenuBarUI
return FlatRootPaneUI.isMenuBarEmbedded( rootPane ) ? null : background;
}
/**@since 2 */
static boolean useUnifiedBackground( Component c ) {
// check whether:
// - TitlePane.unifiedBackground is true and
// - menu bar is the "main" menu bar and
// - window 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

@@ -35,7 +35,7 @@ import javax.swing.plaf.MenuBarUI;
public class FlatMenuItemBorder
extends FlatMarginBorder
{
// only used if parent menubar is not a instance of FlatMenuBarUI
// only used if parent menubar is not an instance of FlatMenuBarUI
private final Insets menuBarItemMargins = UIManager.getInsets( "MenuBar.itemMargins" );
@Override

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;
@@ -32,6 +33,7 @@ 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;
@@ -39,6 +41,7 @@ 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;
@@ -52,12 +55,15 @@ 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
@@ -67,12 +73,15 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatMenuItemRenderer
{
private static final String KEY_MAX_ICONS_WIDTH = "FlatLaf.internal.FlatMenuItemRenderer.maxIconWidth";
protected final JMenuItem menuItem;
protected Icon checkIcon;
protected Icon arrowIcon;
protected final Font acceleratorFont;
@Styleable protected Font acceleratorFont;
protected final String acceleratorDelimiter;
/** @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 );
@@ -88,6 +97,7 @@ public class FlatMenuItemRenderer
@Styleable protected int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" );
private boolean iconsShared = true;
private final Font menuFont = UIManager.getFont( "Menu.font" );
protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
@@ -160,6 +170,19 @@ public class FlatMenuItemRenderer
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;
@@ -171,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() ) );
@@ -273,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() ) );
@@ -383,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 );
}
@@ -405,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,
@@ -438,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());
}
@@ -456,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();
@@ -474,6 +525,12 @@ debug*/
return pressedIcon;
}
if( isArmedOrSelected( menuItem ) ) {
Icon selectedIcon = menuItem.getSelectedIcon();
if( selectedIcon != null )
return selectedIcon;
}
return icon;
}
@@ -560,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
@@ -574,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

@@ -16,16 +16,19 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.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;
@@ -58,9 +61,15 @@ import com.formdev.flatlaf.util.LoggingFacade;
*
* @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
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
@@ -89,6 +98,7 @@ public class FlatMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
@@ -118,42 +128,54 @@ public class FlatMenuItemUI
/** @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
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( ui, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return getStyleableInfos( renderer );
return getStyleableInfos( this, renderer );
}
static Map<String, Class<?>> getStyleableInfos( FlatMenuItemRenderer renderer ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "selectionBackground", Color.class );
infos.put( "selectionForeground", Color.class );
infos.put( "disabledForeground", Color.class );
infos.put( "acceleratorForeground", Color.class );
infos.put( "acceleratorSelectionForeground", Color.class );
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,8 +20,10 @@ 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;
@@ -35,9 +37,11 @@ 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.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -72,15 +76,23 @@ import com.formdev.flatlaf.util.LoggingFacade;
* <!-- 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
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
@@ -111,6 +123,7 @@ public class FlatMenuUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
@@ -165,29 +178,27 @@ public class FlatMenuUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
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
@@ -215,6 +226,8 @@ public class FlatMenuUI
extends FlatMenuItemRenderer
{
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 );
@@ -230,6 +243,10 @@ public class FlatMenuUI
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() ) {
@@ -242,6 +259,16 @@ public class FlatMenuUI
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() ) {

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

@@ -42,11 +42,13 @@ import com.formdev.flatlaf.util.SystemInfo;
public class FlatNativeWindowBorder
{
// can use window decorations if:
// - on Windows 10
// - 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 &&
@@ -56,7 +58,7 @@ public class FlatNativeWindowBorder
private static final boolean canUseJBRCustomDecorations =
canUseWindowDecorations &&
SystemInfo.isJetBrainsJVM_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, true );
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false );
private static Boolean supported;
private static Provider nativeProvider;
@@ -235,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 );
@@ -245,9 +248,8 @@ public class FlatNativeWindowBorder
if( !isSupported() )
return;
nativeProvider.setTitleBarHeight( window, titleBarHeight );
nativeProvider.setTitleBarHitTestSpots( window, hitTestSpots );
nativeProvider.setTitleBarAppIconBounds( window, appIconBounds );
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
static boolean showWindow( Window window, int cmd ) {
@@ -268,7 +270,7 @@ public class FlatNativeWindowBorder
try {
/*
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" );
Method m = cls.getMethod( "getInstance" );
java.lang.reflect.Method m = cls.getMethod( "getInstance" );
setNativeProvider( (Provider) m.invoke( null ) );
*/
setNativeProvider( FlatWindowsNativeWindowBorder.getInstance() );
@@ -292,9 +294,9 @@ public class FlatNativeWindowBorder
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void setTitleBarHeight( Window window, int titleBarHeight );
void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots );
void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds );
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
// commands for showWindow(); values must match Win32 API
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
@@ -313,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,21 +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;
/**
@@ -79,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
@@ -88,6 +92,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatOptionPaneUI
extends BasicOptionPaneUI
{
/** @since 2 */ protected boolean showIcon;
protected int iconMessageGap;
protected int messagePadding;
protected int maxCharactersPerLine;
@@ -102,6 +107,7 @@ public class FlatOptionPaneUI
protected void installDefaults() {
super.installDefaults();
showIcon = UIManager.getBoolean( "OptionPane.showIcon" );
iconMessageGap = UIManager.getInt( "OptionPane.iconMessageGap" );
messagePadding = UIManager.getInt( "OptionPane.messagePadding" );
maxCharactersPerLine = UIManager.getInt( "OptionPane.maxCharactersPerLine" );
@@ -116,6 +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() );
@@ -133,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 ) );
}
@@ -205,7 +229,7 @@ public class FlatOptionPaneUI
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( new NonUIResourceBorder( border ) );
panel.setBorder( FlatUIUtils.nonUIResource( border ) );
}
if( child instanceof Container )
@@ -213,49 +237,8 @@ public class FlatOptionPaneUI
}
}
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;
}
@Override
protected boolean getSizeButtonsToSameWidth() {
return sameSizeButtons;
}
//---- class NonUIResourceBorder ------------------------------------------
private static class NonUIResourceBorder
implements Border
{
private final Border delegate;
NonUIResourceBorder( Border delegate ) {
this.delegate = delegate;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
delegate.paintBorder( c, g, x, y, width, height );
}
@Override
public Insets getBorderInsets( Component c ) {
return delegate.getBorderInsets( c );
}
@Override
public boolean isBorderOpaque() {
return delegate.isBorderOpaque();
}
}
}

View File

@@ -16,9 +16,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

@@ -23,11 +23,14 @@ import java.awt.Toolkit;
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;
@@ -37,6 +40,7 @@ import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.PasswordView;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
@@ -73,18 +77,29 @@ import com.formdev.flatlaf.util.UIScale;
*
* @uiDefault PasswordField.echoChar character
* @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.showRevealButton boolean
* @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault PasswordField.revealIcon Icon
*
* @author Karl Tauber
*/
public class FlatPasswordFieldUI
extends FlatTextFieldUI
{
// 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 KeyListener capsLockListener;
private boolean capsLockIconShared = true;
private JToggleButton revealButton;
private boolean uninstallEchoChar;
public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI();
@@ -95,17 +110,33 @@ public class FlatPasswordFieldUI
return "PasswordField";
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installRevealButton();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallRevealButton();
super.uninstallUI( c );
}
@Override
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
Character echoChar = (Character) UIManager.get( prefix + ".echoChar" );
echoChar = (Character) UIManager.get( prefix + ".echoChar" );
if( echoChar != null )
LookAndFeel.installProperty( getComponent(), "echoChar", echoChar );
showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" );
showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
revealIcon = UIManager.getIcon( "PasswordField.revealIcon" );
capsLockIconShared = true;
}
@@ -114,6 +145,7 @@ public class FlatPasswordFieldUI
super.uninstallDefaults();
capsLockIcon = null;
revealIcon = null;
}
@Override
@@ -168,6 +200,18 @@ public class FlatPasswordFieldUI
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 ) {
@@ -190,6 +234,14 @@ public class FlatPasswordFieldUI
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 );
@@ -232,8 +284,109 @@ public class FlatPasswordFieldUI
if( !showCapsLock )
return false;
JTextComponent c = getComponent();
return FlatUIUtils.isPermanentFocusOwner( c ) &&
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 );
switch( e.getPropertyName() ) {
case "enabled":
updateRevealButton();
break;
}
}
private void updateEchoChar( boolean selected ) {
char newEchoChar = selected
? 0
: (echoChar != null ? echoChar : '*');
JPasswordField c = (JPasswordField) getComponent();
if( newEchoChar == c.getEchoChar() )
return;
// set echo char
LookAndFeel.installProperty( c, "echoChar", newEchoChar );
// 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 JComponent[] getTrailingComponents() {
return new JComponent[] { trailingComponent, revealButton, clearButton };
}
}

View File

@@ -16,10 +16,12 @@
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;
@@ -33,7 +35,10 @@ 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;
@@ -61,8 +66,8 @@ 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 )
@@ -121,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();
@@ -128,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;
}
}
@@ -140,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
*/
@@ -189,23 +194,25 @@ 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 );
}
}
@@ -219,7 +226,7 @@ public class FlatPopupFactory
* and corrects the y-location so that the tooltip is placed above the mouse location.
*/
private Point fixToolTipLocation( Component owner, Component contents, int x, int y ) {
if( !(contents instanceof JToolTip) || !wasInvokedFromToolTipManager() )
if( !(contents instanceof JToolTip) || !wasInvokedFromToolTipManager() || hasTipLocation( owner ) )
return null;
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
@@ -264,6 +271,35 @@ public class FlatPopupFactory
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 me.getSource() == owner &&
((JComponent)owner).getToolTipLocation( me ) != null;
}
//---- class NonFlashingPopup ---------------------------------------------
private class NonFlashingPopup

View File

@@ -66,6 +66,16 @@ public class FlatPopupMenuBorder
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();

View File

@@ -16,12 +16,58 @@
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;
@@ -35,12 +81,22 @@ import com.formdev.flatlaf.util.LoggingFacade;
* @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;
@@ -64,6 +120,27 @@ public class FlatPopupMenuUI
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();
@@ -106,4 +183,278 @@ public class FlatPopupMenuUI
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

@@ -160,6 +160,12 @@ public class FlatProgressBarUI
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 );

View File

@@ -16,18 +16,20 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
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.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -58,9 +60,15 @@ import com.formdev.flatlaf.util.LoggingFacade;
*
* @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
implements StyleableUI, StyleableLookupProvider
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
@@ -89,6 +97,7 @@ public class FlatRadioButtonMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
@@ -118,29 +127,27 @@ public class FlatRadioButtonMenuItemUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
return FlatMenuItemUI.applyStyleProperty( menuItem, this, renderer, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
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

View File

@@ -18,12 +18,16 @@ 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;
@@ -31,6 +35,7 @@ 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;
@@ -78,7 +83,7 @@ public class FlatRadioButtonUI
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.canUseSharedUI( c )
return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c )
? FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, () -> new FlatRadioButtonUI( true ) )
: new FlatRadioButtonUI( false );
}
@@ -90,11 +95,29 @@ public class FlatRadioButtonUI
@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
public void installDefaults( AbstractButton b ) {
super.installDefaults( b );
@@ -139,7 +162,7 @@ public class FlatRadioButtonUI
case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
// unshare component UI if necessary
// updateUI() invokes applyStyle() from installUI()
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
@@ -199,7 +222,20 @@ public class FlatRadioButtonUI
return infos;
}
private static Insets tempInsets = new Insets( 0, 0, 0, 0 );
/** @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 ) {
@@ -212,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 );
@@ -308,4 +344,69 @@ public class FlatRadioButtonUI
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

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

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,7 @@ 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;
@@ -36,9 +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;
/**
@@ -47,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
@@ -57,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
@@ -76,13 +83,20 @@ 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
implements StyleableUI, StyleableLookupProvider
{
// overrides BasicScrollBarUI.supportsAbsolutePositioning (which is private)
@Styleable protected boolean allowsAbsolutePositioning;
/** @since 2.1 */ @Styleable protected Dimension minimumButtonSize;
@Styleable protected Insets trackInsets;
@Styleable protected Insets thumbInsets;
@Styleable protected int trackArc;
@@ -106,6 +120,7 @@ public class FlatScrollBarUI
protected boolean hoverThumb;
private Map<String, Object> oldStyleValues;
private boolean isAWTPeer;
public static ComponentUI createUI( JComponent c ) {
return new FlatScrollBarUI();
@@ -142,6 +157,7 @@ public class FlatScrollBarUI
allowsAbsolutePositioning = super.getSupportsAbsolutePositioning();
minimumButtonSize = UIManager.getDimension( "ScrollBar.minimumButtonSize" );
trackInsets = UIManager.getInsets( "ScrollBar.trackInsets" );
thumbInsets = UIManager.getInsets( "ScrollBar.thumbInsets" );
trackArc = UIManager.getInt( "ScrollBar.trackArc" );
@@ -171,6 +187,7 @@ public class FlatScrollBarUI
protected void uninstallDefaults() {
super.uninstallDefaults();
minimumButtonSize = null;
trackInsets = null;
thumbInsets = null;
hoverTrackColor = null;
@@ -217,6 +234,37 @@ 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;
}
};
}
@@ -242,30 +290,27 @@ public class FlatScrollBarUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
// BasicScrollBarUI
case "track": oldValue = trackColor; trackColor = (Color) value; return oldValue;
case "thumb": oldValue = thumbColor; thumbColor = (Color) value; return oldValue;
case "width": oldValue = scrollBarWidth; scrollBarWidth = (int) value; return oldValue;
case "minimumThumbSize": oldValue = minimumThumbSize; minimumThumbSize = (Dimension) value; return oldValue;
case "maximumThumbSize": oldValue = maximumThumbSize; maximumThumbSize = (Dimension) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, scrollbar, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "track", Color.class );
infos.put( "thumb", Color.class );
infos.put( "width", int.class );
infos.put( "minimumThumbSize", Dimension.class );
infos.put( "maximumThumbSize", Dimension.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
return infos;
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
@@ -307,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 );
}
@@ -353,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);
}
@@ -363,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);
}
@@ -407,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 );
@@ -451,7 +512,6 @@ public class FlatScrollBarUI
super( direction, type, foreground, disabledForeground,
hoverForeground, hoverBackground, pressedForeground, pressedBackground );
setArrowWidth( FlatArrowButton.DEFAULT_ARROW_WIDTH - 2 );
setFocusable( false );
setRequestFocusEnabled( false );
}
@@ -461,6 +521,18 @@ public class FlatScrollBarUI
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() );
@@ -469,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

@@ -87,6 +87,13 @@ public class 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" );
@@ -291,6 +298,10 @@ public class FlatScrollPaneUI
}
break;
case FlatClientProperties.OUTLINE:
scrollpane.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
@@ -339,6 +350,12 @@ public class FlatScrollPaneUI
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 );

View File

@@ -21,6 +21,7 @@ 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;
@@ -28,6 +29,7 @@ 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;
@@ -50,7 +52,7 @@ import com.formdev.flatlaf.util.LoggingFacade;
*/
public class FlatSeparatorUI
extends BasicSeparatorUI
implements StyleableUI
implements StyleableUI, PropertyChangeListener
{
@Styleable protected int height;
@Styleable protected int stripeWidth;
@@ -58,7 +60,6 @@ public class FlatSeparatorUI
private final boolean shared;
private boolean defaults_initialized = false;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
@@ -109,28 +110,33 @@ public class FlatSeparatorUI
protected void installListeners( JSeparator s ) {
super.installListeners( s );
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener(
s, () -> stylePropertyChange( s ), null );
s.addPropertyChangeListener( propertyChangeListener );
s.addPropertyChangeListener( this );
}
@Override
protected void uninstallListeners( JSeparator s ) {
super.uninstallListeners( s );
s.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
s.removePropertyChangeListener( this );
}
private void stylePropertyChange( JSeparator s ) {
if( shared && FlatStylingSupport.hasStyleProperty( s ) ) {
// unshare component UI if necessary
// updateUI() invokes applyStyle() from installUI()
s.updateUI();
} else
installStyle( s );
s.revalidate();
s.repaint();
/** @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 */
@@ -164,6 +170,12 @@ public class FlatSeparatorUI
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();

View File

@@ -222,6 +222,12 @@ public class FlatSliderUI
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
if( c == null )
@@ -234,7 +240,7 @@ public class FlatSliderUI
return -1;
// use default font (instead of slider font) because the slider font size
// may be different to label font size, but we want align the track/thumb with labels
// may be different to label font size, but we want to align the track/thumb with labels
Font font = UIManager.getFont( "defaultFont" );
if( font == null )
font = slider.getFont();

View File

@@ -71,7 +71,7 @@ import com.formdev.flatlaf.util.LoggingFacade;
* @uiDefault Spinner.disabledBackground Color
* @uiDefault Spinner.disabledForeground Color
* @uiDefault Spinner.focusedBackground Color optional
* @uiDefault Spinner.buttonBackground Color
* @uiDefault Spinner.buttonBackground Color optional
* @uiDefault Spinner.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault Spinner.buttonSeparatorColor Color optional
* @uiDefault Spinner.buttonDisabledSeparatorColor Color optional
@@ -223,6 +223,12 @@ public class FlatSpinnerUI
return FlatStylingSupport.getAnnotatedStyleableInfos( this, spinner.getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, spinner.getBorder(), key );
}
@Override
protected JComponent createEditor() {
JComponent editor = super.createEditor();
@@ -293,9 +299,7 @@ public class FlatSpinnerUI
return true;
JTextField textField = getEditorTextField( spinner.getEditor() );
return (textField != null)
? FlatUIUtils.isPermanentFocusOwner( textField )
: false;
return textField != null && FlatUIUtils.isPermanentFocusOwner( textField );
}
protected Color getBackground( boolean enabled ) {
@@ -385,7 +389,7 @@ public class FlatSpinnerUI
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
// paint arrow buttons background
if( enabled ) {
if( enabled && buttonBackground != null ) {
g2.setColor( buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
@@ -398,7 +402,7 @@ public class FlatSpinnerUI
// paint vertical line between value and arrow buttons
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null ) {
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
@@ -447,7 +451,7 @@ public class FlatSpinnerUI
Insets padding = scale( FlatSpinnerUI.this.padding );
Dimension editorSize = (editor != null) ? editor.getPreferredSize() : new Dimension( 0, 0 );
// the arrows width is the same as the inner height so that the arrows area is square
// the arrow buttons width is the same as the inner height so that the arrow buttons area is square
int minimumWidth = FlatUIUtils.minimumWidth( spinner, FlatSpinnerUI.this.minimumWidth );
int innerHeight = editorSize.height + padding.top + padding.bottom;
float focusWidth = FlatUIUtils.getBorderFocusWidth( spinner );
@@ -479,9 +483,10 @@ public class FlatSpinnerUI
// limit buttons width to height of a raw spinner (without insets)
FontMetrics fm = spinner.getFontMetrics( spinner.getFont() );
int maxButtonWidth = fm.getHeight() + scale( padding.top ) + scale( padding.bottom );
int minButtonWidth = (maxButtonWidth * 3) / 4;
// make button area square (if spinner has preferred height)
int buttonsWidth = Math.min( parent.getPreferredSize().height - insets.top - insets.bottom, maxButtonWidth );
// make button area square (except if width is limited)
int buttonsWidth = Math.min( Math.max( buttonsRect.height, minButtonWidth ), maxButtonWidth );
buttonsRect.width = buttonsWidth;
if( parent.getComponentOrientation().isLeftToRight() ) {
@@ -538,6 +543,7 @@ public class FlatSpinnerUI
break;
case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.OUTLINE:
spinner.repaint();
break;

View File

@@ -34,6 +34,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
@@ -52,6 +53,13 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault SplitPaneDivider.border Border
* @uiDefault SplitPaneDivider.draggingColor Color only used if continuousLayout is false
*
* <!-- BasicSplitPaneDivider -->
*
* @uiDefault SplitPane.oneTouchButtonSize int
* @uiDefault SplitPane.oneTouchButtonOffset int
* @uiDefault SplitPane.centerOneTouchButtons boolean
* @uiDefault SplitPane.supportsOneTouchButtons boolean optional; default is true
*
* <!-- JSplitPane -->
*
* @uiDefault SplitPane.continuousLayout boolean
@@ -175,6 +183,17 @@ public class FlatSplitPaneUI
return infos;
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( divider instanceof FlatSplitPaneDivider ) {
Object value = ((FlatSplitPaneDivider)divider).getStyleableValue( key );
if( value != null )
return value;
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
//---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider
@@ -192,20 +211,21 @@ public class FlatSplitPaneUI
setLayout( new FlatDividerLayout() );
}
/**
* @since 2
*/
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/**
* @since 2
*/
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
void updateStyle() {
if( leftButton instanceof FlatOneTouchButton )
((FlatOneTouchButton)leftButton).updateStyle();
@@ -235,7 +255,7 @@ public class FlatSplitPaneUI
switch( e.getPropertyName() ) {
case JSplitPane.DIVIDER_LOCATION_PROPERTY:
// necessary to show/hide one-touch buttons on expand/collapse
revalidate();
doLayout();
break;
}
}
@@ -345,7 +365,7 @@ public class FlatSplitPaneUI
if( leftButton == null || rightButton == null || !splitPane.isOneTouchExpandable() )
return;
// increase side of buttons, which makes them easier to hit by the user
// increase size of buttons, which makes them easier to hit by the user
// and avoids cut arrows at small divider sizes
int extraSize = UIScale.scale( 4 );
if( orientation == JSplitPane.VERTICAL_SPLIT ) {
@@ -360,10 +380,19 @@ public class FlatSplitPaneUI
// hide buttons if not applicable
boolean leftCollapsed = isLeftCollapsed();
if( leftCollapsed )
boolean rightCollapsed = isRightCollapsed();
if( leftCollapsed || rightCollapsed ) {
leftButton.setVisible( !leftCollapsed );
rightButton.setVisible( !rightCollapsed );
} else {
Object expandableSide = splitPane.getClientProperty( FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE );
leftButton.setVisible( expandableSide == null || !FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE_LEFT.equals( expandableSide ) );
rightButton.setVisible( expandableSide == null || !FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE_RIGHT.equals( expandableSide ) );
}
// move right button if left button is hidden
if( !leftButton.isVisible() )
rightButton.setLocation( leftButton.getLocation() );
leftButton.setVisible( !leftCollapsed );
rightButton.setVisible( !isRightCollapsed() );
}
}
}

View File

@@ -18,9 +18,11 @@ package com.formdev.flatlaf.ui;
import java.beans.PropertyChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -63,15 +65,55 @@ public class FlatStylingSupport
Class<?> type() default Void.class;
}
/**
* Indicates that a field in the specified (super) class
* is intended to be used by FlatLaf styling support.
* <p>
* Use this annotation, instead of {@link Styleable}, to style fields
* in superclasses, where it is not possible to use {@link Styleable}.
* <p>
* Classes using this annotation may implement {@link StyleableLookupProvider}
* to give access to protected fields (in JRE) in modular applications.
*
* @since 2.5
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StyleableFields.class)
public @interface StyleableField {
Class<?> cls();
String key();
String fieldName() default "";
}
/**
* Container annotation for {@link StyleableField}.
*
* @since 2.5
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StyleableFields {
StyleableField[] value();
}
/** @since 2 */
public interface StyleableUI {
Map<String, Class<?>> getStyleableInfos( JComponent c );
/** @since 2.5 */ Object getStyleableValue( JComponent c, String key );
}
/** @since 2 */
public interface StyleableBorder {
Object applyStyleProperty( String key, Object value );
Map<String, Class<?>> getStyleableInfos();
/** @since 2.5 */ Object getStyleableValue( String key );
}
/** @since 2.5 */
public interface StyleableLookupProvider {
MethodHandles.Lookup getLookupForStyling();
}
@@ -97,8 +139,7 @@ public class FlatStylingSupport
Object style = getStyle( c );
Object styleClass = getStyleClass( c );
Object styleForClasses = getStyleForClasses( styleClass, type );
Object styleForType = getStyleForType( type );
return joinStyles( joinStyles( styleForType, styleForClasses ), style );
return joinStyles( styleForClasses, style );
}
/**
@@ -163,28 +204,6 @@ public class FlatStylingSupport
UIManager.get( "[style]" + type + '.' + styleClass ) );
}
/**
* Returns the styles for the given type.
* <p>
* The style rules must be defined in UI defaults either as strings (in CSS syntax)
* or as {@link java.util.Map}&lt;String, Object&gt; (with binary values).
* The key must be in syntax: {@code [style]type}.
* E.g. in FlatLaf properties file:
* <pre>{@code
* [style]Button = borderColor: #08f; background: #08f; foreground: #fff
* }</pre>
* or in Java code:
* <pre>{@code
* UIManager.put( "[style]Button", "borderColor: #08f; background: #08f; foreground: #fff" );
* }</pre>
*
* @param type the type of the component
* @return the styles
*/
public static Object getStyleForType( String type ) {
return UIManager.get( "[style]" + type );
}
/**
* Joins two styles. They can be either strings (in CSS syntax)
* or {@link java.util.Map}&lt;String, Object&gt; (with binary values).
@@ -382,27 +401,31 @@ public class FlatStylingSupport
* @param key the name of the field
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws UnknownStyleException if object does not have an annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObject( Object obj, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
String fieldName = key;
int dotIndex = key.indexOf( '.' );
if( dotIndex >= 0 ) {
// remove first dot in key and change subsequent character to uppercase
fieldName = key.substring( 0, dotIndex )
+ Character.toUpperCase( key.charAt( dotIndex + 1 ) )
+ key.substring( dotIndex + 2 );
}
String fieldName = keyToFieldName( key );
return applyToField( obj, fieldName, key, value, field -> {
Styleable styleable = field.getAnnotation( Styleable.class );
return styleable != null && styleable.dot() == (dotIndex >= 0);
return styleable != null && styleable.dot() == (fieldName != key);
} );
}
private static String keyToFieldName( String key ) {
int dotIndex = key.indexOf( '.' );
if( dotIndex < 0 )
return key;
// remove first dot in key and change subsequent character to uppercase
return key.substring( 0, dotIndex )
+ Character.toUpperCase( key.charAt( dotIndex + 1 ) )
+ key.substring( dotIndex + 2 );
}
/**
* Applies the given value to a field of the given object.
*
@@ -428,26 +451,17 @@ public class FlatStylingSupport
for(;;) {
try {
Field f = cls.getDeclaredField( fieldName );
if( predicate == null || predicate.test( f ) ) {
if( !isValidField( f ) )
throw new IllegalArgumentException( "field '" + cls.getName() + "." + fieldName + "' is final or static" );
try {
// necessary to access protected fields in other packages
f.setAccessible( true );
// get old value and set new value
Object oldValue = f.get( obj );
f.set( obj, convertToEnum( value, f.getType() ) );
return oldValue;
} catch( IllegalAccessException ex ) {
throw new IllegalArgumentException( "failed to access field '" + cls.getName() + "." + fieldName + "'", ex );
}
}
if( predicate == null || predicate.test( f ) )
return applyToField( f, obj, value, false );
} catch( NoSuchFieldException ex ) {
// field not found in class --> try superclass
}
for( StyleableField styleableField : cls.getAnnotationsByType( StyleableField.class ) ) {
if( key.equals( styleableField.key() ) )
return applyToField( getStyleableField( styleableField ), obj, value, true );
}
cls = cls.getSuperclass();
if( cls == null )
throw new UnknownStyleException( key );
@@ -460,11 +474,83 @@ public class FlatStylingSupport
}
}
private static Object applyToField( Field f, Object obj, Object value, boolean useMethodHandles ) {
checkValidField( f );
if( useMethodHandles && obj instanceof StyleableLookupProvider ) {
try {
// use method handles to access protected fields in JRE in modular applications
MethodHandles.Lookup lookup = ((StyleableLookupProvider)obj).getLookupForStyling();
// get old value and set new value
Object oldValue = lookup.unreflectGetter( f ).invoke( obj );
lookup.unreflectSetter( f ).invoke( obj, convertToEnum( value, f.getType() ) );
return oldValue;
} catch( Throwable ex ) {
throw newFieldAccessFailed( f, ex );
}
}
try {
// necessary to access protected fields in other packages
f.setAccessible( true );
// get old value and set new value
Object oldValue = f.get( obj );
f.set( obj, convertToEnum( value, f.getType() ) );
return oldValue;
} catch( IllegalAccessException ex ) {
throw newFieldAccessFailed( f, ex );
}
}
private static Object getFieldValue( Field f, Object obj, boolean useMethodHandles ) {
checkValidField( f );
if( useMethodHandles && obj instanceof StyleableLookupProvider ) {
// use method handles to access protected fields in JRE in modular applications
try {
MethodHandles.Lookup lookup = ((StyleableLookupProvider)obj).getLookupForStyling();
return lookup.unreflectGetter( f ).invoke( obj );
} catch( Throwable ex ) {
throw newFieldAccessFailed( f, ex );
}
}
try {
f.setAccessible( true );
return f.get( obj );
} catch( IllegalAccessException ex ) {
throw newFieldAccessFailed( f, ex );
}
}
private static IllegalArgumentException newFieldAccessFailed( Field f, Throwable ex ) {
return new IllegalArgumentException( "failed to access field '" + f.getDeclaringClass().getName() + "." + f.getName() + "'", ex );
}
private static void checkValidField( Field f ) {
if( !isValidField( f ) )
throw new IllegalArgumentException( "field '" + f.getDeclaringClass().getName() + "." + f.getName() + "' is final or static" );
}
private static boolean isValidField( Field f ) {
int modifiers = f.getModifiers();
return (modifiers & (Modifier.FINAL|Modifier.STATIC)) == 0 && !f.isSynthetic();
}
private static Field getStyleableField( StyleableField styleableField ) {
String fieldName = styleableField.fieldName();
if( fieldName.isEmpty() )
fieldName = styleableField.key();
try {
return styleableField.cls().getDeclaredField( fieldName );
} catch( NoSuchFieldException ex ) {
throw new IllegalArgumentException( "field '" + styleableField.cls().getName() + "." + fieldName + "' not found", ex );
}
}
/**
* Applies the given value to a property of the given object.
* Works only for properties that have public getter and setter methods.
@@ -485,34 +571,22 @@ public class FlatStylingSupport
String getterName = buildMethodName( "get", name );
String setterName = buildMethodName( "set", name );
for(;;) {
try {
Method getter;
try {
Method getter;
try {
getter = cls.getMethod( getterName );
} catch( NoSuchMethodException ex ) {
getter = cls.getMethod( buildMethodName( "is", name ) );
}
Method setter = cls.getMethod( setterName, getter.getReturnType() );
Object oldValue = getter.invoke( obj );
setter.invoke( obj, convertToEnum( value, getter.getReturnType() ) );
return oldValue;
getter = cls.getMethod( getterName );
} catch( NoSuchMethodException ex ) {
throw new UnknownStyleException( name );
} catch( Exception ex ) {
if( ex instanceof IllegalAccessException ) {
// this may happen for private subclasses of public Swing classes
// that override public property getter/setter
// e.g. class JSlider.SmartHashtable.LabelUIResource.getForeground()
// --> try again with superclass
cls = cls.getSuperclass();
if( cls != null && cls != Object.class )
continue;
}
throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "."
+ getterName + "()' or '" + setterName + "(...)'", ex );
getter = cls.getMethod( buildMethodName( "is", name ) );
}
Method setter = cls.getMethod( setterName, getter.getReturnType() );
Object oldValue = getter.invoke( obj );
setter.invoke( obj, convertToEnum( value, getter.getReturnType() ) );
return oldValue;
} catch( NoSuchMethodException ex ) {
throw new UnknownStyleException( name );
} catch( Exception ex ) {
throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "."
+ getterName + "()' or '" + setterName + "(...)'", ex );
}
}
@@ -552,7 +626,7 @@ public class FlatStylingSupport
* @param key the name of the field
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws UnknownStyleException if object does not have an annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObjectOrComponent( Object obj, Object comp, String key, Object value )
@@ -663,6 +737,7 @@ public class FlatStylingSupport
Class<?> cls = obj.getClass();
for(;;) {
// find fields annotated with 'Styleable'
for( Field f : cls.getDeclaredFields() ) {
if( !isValidField( f ) )
continue;
@@ -701,6 +776,20 @@ public class FlatStylingSupport
infos.put( name, type );
}
// get fields specified in 'StyleableField' annotation
for( StyleableField styleableField : cls.getAnnotationsByType( StyleableField.class ) ) {
String name = styleableField.key();
// for the case that the same field name is used in a class and in
// one of its superclasses, do not process field in superclass
if( processedFields.contains( name ) )
continue;
processedFields.add( name );
Field f = getStyleableField( styleableField );
infos.put( name, f.getType() );
}
cls = cls.getSuperclass();
if( cls == null )
return;
@@ -721,6 +810,52 @@ public class FlatStylingSupport
infos.put( keyPrefix.concat( e.getKey() ), e.getValue() );
}
public static Object getAnnotatedStyleableValue( Object obj, String key ) {
String fieldName = keyToFieldName( key );
Class<?> cls = obj.getClass();
for(;;) {
try {
// find field annotated with 'Styleable'
Field f = cls.getDeclaredField( fieldName );
Styleable styleable = f.getAnnotation( Styleable.class );
if( styleable != null ) {
if( styleable.dot() != (fieldName != key) )
throw new IllegalArgumentException( "'Styleable.dot' on field '" + fieldName + "' does not match key '" + key + "'" );
if( styleable.type() != Void.class )
throw new IllegalArgumentException( "'Styleable.type' on field '" + fieldName + "' not supported" );
return getFieldValue( f, obj, false );
}
} catch( NoSuchFieldException ex ) {
// field not found in class --> try superclass
}
// find field specified in 'StyleableField' annotation
for( StyleableField styleableField : cls.getAnnotationsByType( StyleableField.class ) ) {
if( key.equals( styleableField.key() ) )
return getFieldValue( getStyleableField( styleableField ), obj, true );
}
cls = cls.getSuperclass();
if( cls == null )
return null;
String superclassName = cls.getName();
if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) )
return null;
}
}
public static Object getAnnotatedStyleableValue( Object obj, Border border, String key ) {
if( border instanceof StyleableBorder ) {
Object value = ((StyleableBorder)border).getStyleableValue( key );
if( value != null )
return value;
}
return getAnnotatedStyleableValue( obj, key );
}
//---- class UnknownStyleException ----------------------------------------
public static class UnknownStyleException

View File

@@ -40,6 +40,8 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
@@ -57,6 +59,7 @@ import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.IntConsumer;
import java.util.function.Predicate;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.Action;
@@ -80,10 +83,12 @@ import javax.swing.event.ChangeListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
@@ -125,6 +130,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.selectedBackground Color optional
* @uiDefault TabbedPane.selectedForeground Color
* @uiDefault TabbedPane.underlineColor Color
* @uiDefault TabbedPane.inactiveUnderlineColor Color
* @uiDefault TabbedPane.disabledUnderlineColor Color
* @uiDefault TabbedPane.hoverColor Color
* @uiDefault TabbedPane.focusColor Color
@@ -134,12 +140,15 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.maximumTabWidth int optional
* @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.cardTabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int
* @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.tabSeparatorsFullHeight boolean
* @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.rotateTabRuns boolean
*
* @uiDefault TabbedPane.tabLayoutPolicy String wrap (default) or scroll
* @uiDefault TabbedPane.tabType String underlined (default) or card
* @uiDefault TabbedPane.tabsPopupPolicy String never or asNeeded (default)
* @uiDefault TabbedPane.scrollButtonsPolicy String never, asNeeded or asNeededSingle (default)
* @uiDefault TabbedPane.scrollButtonsPlacement String both (default) or trailing
@@ -165,6 +174,10 @@ public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
implements StyleableUI
{
// tab type
/** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
/** @since 2 */ protected static final int TAB_TYPE_CARD = 1;
// tabs popup policy / scroll arrows policy
protected static final int NEVER = 0;
// protected static final int ALWAYS = 1;
@@ -189,6 +202,7 @@ public class FlatTabbedPaneUI
@Styleable protected Color selectedBackground;
@Styleable protected Color selectedForeground;
@Styleable protected Color underlineColor;
/** @since 2.2 */ @Styleable protected Color inactiveUnderlineColor;
@Styleable protected Color disabledUnderlineColor;
@Styleable protected Color hoverColor;
@Styleable protected Color focusColor;
@@ -200,12 +214,15 @@ public class FlatTabbedPaneUI
@Styleable protected int maximumTabWidth;
@Styleable protected int tabHeight;
@Styleable protected int tabSelectionHeight;
/** @since 2 */ @Styleable protected int cardTabSelectionHeight;
@Styleable protected int contentSeparatorHeight;
@Styleable protected boolean showTabSeparators;
@Styleable protected boolean tabSeparatorsFullHeight;
@Styleable protected boolean hasFullBorder;
@Styleable protected boolean tabsOpaque = true;
/** @since 2.5 */ @Styleable protected boolean rotateTabRuns = true;
@Styleable(type=String.class) private int tabType;
@Styleable(type=String.class) private int tabsPopupPolicy;
@Styleable(type=String.class) private int scrollButtonsPolicy;
@Styleable(type=String.class) private int scrollButtonsPlacement;
@@ -277,6 +294,7 @@ public class FlatTabbedPaneUI
super.installUI( c );
FlatSelectedTabRepainter.install();
installStyle();
}
@@ -307,6 +325,7 @@ public class FlatTabbedPaneUI
selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" );
selectedForeground = UIManager.getColor( "TabbedPane.selectedForeground" );
underlineColor = UIManager.getColor( "TabbedPane.underlineColor" );
inactiveUnderlineColor = FlatUIUtils.getUIColor( "TabbedPane.inactiveUnderlineColor", underlineColor );
disabledUnderlineColor = UIManager.getColor( "TabbedPane.disabledUnderlineColor" );
hoverColor = UIManager.getColor( "TabbedPane.hoverColor" );
focusColor = UIManager.getColor( "TabbedPane.focusColor" );
@@ -318,12 +337,15 @@ public class FlatTabbedPaneUI
maximumTabWidth = UIManager.getInt( "TabbedPane.maximumTabWidth" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
cardTabSelectionHeight = UIManager.getInt( "TabbedPane.cardTabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
rotateTabRuns = FlatUIUtils.getUIBoolean( "TabbedPane.rotateTabRuns", true );
tabType = parseTabType( UIManager.getString( "TabbedPane.tabType" ) );
tabsPopupPolicy = parseTabsPopupPolicy( UIManager.getString( "TabbedPane.tabsPopupPolicy" ) );
scrollButtonsPolicy = parseScrollButtonsPolicy( UIManager.getString( "TabbedPane.scrollButtonsPolicy" ) );
scrollButtonsPlacement = parseScrollButtonsPlacement( UIManager.getString( "TabbedPane.scrollButtonsPlacement" ) );
@@ -372,6 +394,7 @@ public class FlatTabbedPaneUI
selectedBackground = null;
selectedForeground = null;
underlineColor = null;
inactiveUnderlineColor = null;
disabledUnderlineColor = null;
hoverColor = null;
focusColor = null;
@@ -560,6 +583,13 @@ public class FlatTabbedPaneUI
return handler;
}
@Override
protected FocusListener createFocusListener() {
Handler handler = getHandler();
handler.focusDelegate = super.createFocusListener();
return handler;
}
@Override
protected LayoutManager createLayoutManager() {
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT )
@@ -618,6 +648,7 @@ public class FlatTabbedPaneUI
if( value instanceof String ) {
switch( key ) {
case "tabType": value = parseTabType( (String) value ); break;
case "tabsPopupPolicy": value = parseTabsPopupPolicy( (String) value ); break;
case "scrollButtonsPolicy": value = parseScrollButtonsPolicy( (String) value ); break;
case "scrollButtonsPlacement": value = parseScrollButtonsPlacement( (String) value ); break;
@@ -629,7 +660,7 @@ public class FlatTabbedPaneUI
case "tabIconPlacement": value = parseTabIconPlacement( (String) value ); break;
}
} else {
Object oldValue = null;
Object oldValue;
switch( key ) {
// BasicTabbedPaneUI
case "tabInsets": oldValue = tabInsets; tabInsets = (Insets) value; return oldValue;
@@ -658,6 +689,76 @@ public class FlatTabbedPaneUI
return infos;
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
// close icon
if( key.startsWith( "close" ) ) {
return (closeIcon instanceof FlatTabbedPaneCloseIcon)
? ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableValue( key )
: null;
}
switch( key ) {
// BasicTabbedPaneUI
case "tabInsets": return tabInsets;
case "tabAreaInsets": return tabAreaInsets;
case "textIconGap": return textIconGapUnscaled;
// FlatTabbedPaneUI
case "tabType":
switch( tabType ) {
default:
case TAB_TYPE_UNDERLINED: return TABBED_PANE_TAB_TYPE_UNDERLINED;
case TAB_TYPE_CARD: return TABBED_PANE_TAB_TYPE_CARD;
}
case "tabsPopupPolicy":
switch( tabsPopupPolicy ) {
default:
case AS_NEEDED: return TABBED_PANE_POLICY_AS_NEEDED;
case NEVER: return TABBED_PANE_POLICY_NEVER;
}
case "scrollButtonsPolicy":
switch( scrollButtonsPolicy ) {
default:
case AS_NEEDED_SINGLE: return TABBED_PANE_POLICY_AS_NEEDED_SINGLE;
case AS_NEEDED: return TABBED_PANE_POLICY_AS_NEEDED;
case NEVER: return TABBED_PANE_POLICY_NEVER;
}
case "scrollButtonsPlacement":
switch( scrollButtonsPlacement ) {
default:
case BOTH: return TABBED_PANE_PLACEMENT_BOTH;
case TRAILING: return TABBED_PANE_PLACEMENT_TRAILING;
}
case "tabAreaAlignment": return alignmentToString( tabAreaAlignment, TABBED_PANE_ALIGN_LEADING );
case "tabAlignment": return alignmentToString( tabAlignment, TABBED_PANE_ALIGN_CENTER );
case "tabWidthMode":
switch( tabWidthMode ) {
default:
case WIDTH_MODE_PREFERRED: return TABBED_PANE_TAB_WIDTH_MODE_PREFERRED;
case WIDTH_MODE_EQUAL: return TABBED_PANE_TAB_WIDTH_MODE_EQUAL;
case WIDTH_MODE_COMPACT: return TABBED_PANE_TAB_WIDTH_MODE_COMPACT;
}
case "tabIconPlacement":
switch( tabIconPlacement ) {
default:
case LEADING: return "leading";
case TRAILING: return "trailing";
case TOP: return "top";
case BOTTOM: return "bottom";
}
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
protected void setRolloverTab( int x, int y ) {
setRolloverTab( tabForCoordinate( tabPane, x, y ) );
}
@@ -707,8 +808,24 @@ public class FlatTabbedPaneUI
return;
Rectangle r = getTabBounds( tabPane, tabIndex );
if( r != null )
tabPane.repaint( r );
if( r == null )
return;
// increase size of repaint region to include part of content border
if( contentSeparatorHeight > 0 &&
clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) )
{
int sh = scale( contentSeparatorHeight );
switch( tabPane.getTabPlacement() ) {
default:
case TOP: r.height += sh; break;
case BOTTOM: r.height += sh; r.y -= sh; break;
case LEFT: r.width += sh; break;
case RIGHT: r.width += sh; r.x -= sh; break;
}
}
tabPane.repaint( r );
}
private boolean inCalculateEqual;
@@ -977,7 +1094,7 @@ public class FlatTabbedPaneUI
// paint selection indicator
if( isSelected )
paintTabSelection( g, tabPlacement, x, y, w, h );
paintTabSelection( g, tabPlacement, tabIndex, x, y, w, h );
if( tabPane.getTabComponentAt( tabIndex ) != null )
return;
@@ -1045,16 +1162,21 @@ public class FlatTabbedPaneUI
int x, int y, int w, int h, boolean isSelected )
{
// paint tab background
Color background = getTabBackground( tabPlacement, tabIndex, isSelected );
g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
g.fillRect( x, y, w, h );
}
/** @since 2 */
protected Color getTabBackground( int tabPlacement, int tabIndex, boolean isSelected ) {
boolean enabled = tabPane.isEnabled();
Color background = enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
return enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
? hoverColor
: (enabled && isSelected && FlatUIUtils.isPermanentFocusOwner( tabPane )
? focusColor
: (selectedBackground != null && enabled && isSelected
? selectedBackground
: tabPane.getBackgroundAt( tabIndex )));
g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
g.fillRect( x, y, w, h );
}
@Override
@@ -1064,7 +1186,62 @@ public class FlatTabbedPaneUI
// paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
paintTabSeparator( g, tabPlacement, x, y, w, h );
{
if( getTabType() == TAB_TYPE_CARD ) {
// some separators need to be omitted if selected tab is painted as card
int selectedIndex = tabPane.getSelectedIndex();
if( tabIndex != selectedIndex - 1 && tabIndex != selectedIndex )
paintTabSeparator( g, tabPlacement, x, y, w, h );
} else
paintTabSeparator( g, tabPlacement, x, y, w, h );
}
// paint active tab border
if( isSelected && getTabType() == TAB_TYPE_CARD )
paintCardTabBorder( g, tabPlacement, tabIndex, x, y, w, h );
}
/** @since 2 */
protected void paintCardTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h ) {
Graphics2D g2 = (Graphics2D) g;
float borderWidth = scale( (float) contentSeparatorHeight );
g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor );
switch( tabPlacement ) {
default:
case TOP:
case BOTTOM:
// paint left and right tab border
g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
break;
case LEFT:
case RIGHT:
// paint top and bottom tab border
g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
break;
}
if( cardTabSelectionHeight <= 0 ) {
// if there is no tab selection indicator, paint a top border as well
switch( tabPlacement ) {
default:
case TOP:
g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
break;
case BOTTOM:
g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
break;
case LEFT:
g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
break;
case RIGHT:
g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
break;
}
}
}
protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) {
@@ -1106,34 +1283,62 @@ public class FlatTabbedPaneUI
}
}
protected void paintTabSelection( Graphics g, int tabPlacement, int x, int y, int w, int h ) {
g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor );
protected void paintTabSelection( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h ) {
g.setColor( tabPane.isEnabled()
? (isTabbedPaneOrChildFocused() ? underlineColor : inactiveUnderlineColor)
: disabledUnderlineColor );
// paint underline selection
Insets contentInsets = getContentBorderInsets( tabPlacement );
int tabSelectionHeight = scale( this.tabSelectionHeight );
boolean atBottom = (getTabType() != TAB_TYPE_CARD);
Insets contentInsets = atBottom
? ((!rotateTabRuns && runCount > 1 && !isScrollTabLayout() && getRunForTab( tabPane.getTabCount(), tabIndex ) > 0)
? new Insets( 0, 0, 0, 0 )
: getContentBorderInsets( tabPlacement ))
: null;
int tabSelectionHeight = scale( atBottom ? this.tabSelectionHeight : cardTabSelectionHeight );
int sx, sy;
switch( tabPlacement ) {
case TOP:
default:
int sy = y + h + contentInsets.top - tabSelectionHeight;
sy = atBottom ? (y + h + contentInsets.top - tabSelectionHeight) : y;
g.fillRect( x, sy, w, tabSelectionHeight );
break;
case BOTTOM:
g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight );
sy = atBottom ? (y - contentInsets.bottom) : (y + h - tabSelectionHeight);
g.fillRect( x, sy, w, tabSelectionHeight );
break;
case LEFT:
int sx = x + w + contentInsets.left - tabSelectionHeight;
sx = atBottom ? (x + w + contentInsets.left - tabSelectionHeight) : x;
g.fillRect( sx, y, tabSelectionHeight, h );
break;
case RIGHT:
g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h );
sx = atBottom ? (x - contentInsets.right) : (x + w - tabSelectionHeight);
g.fillRect( sx, y, tabSelectionHeight, h );
break;
}
}
/** @since 2.2 */
@SuppressWarnings( "unchecked" )
protected boolean isTabbedPaneOrChildFocused() {
KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Object value = tabPane.getClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER );
if( value instanceof Predicate ) {
return ((Predicate<JComponent>)value).test( tabPane ) &&
FlatUIUtils.isInActiveWindow( tabPane, keyboardFocusManager.getActiveWindow() );
}
Component focusOwner = keyboardFocusManager.getPermanentFocusOwner();
return focusOwner != null &&
SwingUtilities.isDescendingFrom( focusOwner, tabPane ) &&
FlatUIUtils.isInActiveWindow( focusOwner, keyboardFocusManager.getActiveWindow() );
}
/**
* Actually does nearly the same as super.paintContentBorder() but
* - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly
@@ -1141,6 +1346,7 @@ public class FlatTabbedPaneUI
* - paint full border (if enabled)
* - not invoking paintContentBorder*Edge() methods
* - repaint selection
* - painting active tab border style
*/
@Override
protected void paintContentBorder( Graphics g, int tabPlacement, int selectedIndex ) {
@@ -1189,12 +1395,49 @@ public class FlatTabbedPaneUI
Insets ci = new Insets( 0, 0, 0, 0 );
rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement );
// paint content separator or full border
g.setColor( contentAreaColor );
// create path for content separator or full border
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Rectangle2D.Float( x, y, w, h ), false );
path.append( new Rectangle2D.Float( x + (ci.left / 100f), y + (ci.top / 100f),
w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false );
// add gap for selected tab to path
if( getTabType() == TAB_TYPE_CARD ) {
float csh = scale( (float) contentSeparatorHeight );
Rectangle tabRect = getTabBounds( tabPane, selectedIndex );
Rectangle2D.Float innerTabRect = new Rectangle2D.Float( tabRect.x + csh, tabRect.y + csh,
tabRect.width - (csh * 2), tabRect.height - (csh * 2) );
// Ensure that the separator outside the tabViewport is present (doesn't get cutoff by the active tab)
// If left unsolved the active tab is "visible" in the separator (the gap) even when outside the viewport
if( tabViewport != null )
Rectangle2D.intersect( tabViewport.getBounds(), innerTabRect, innerTabRect );
Rectangle2D.Float gap = null;
if( isHorizontalTabPlacement() ) {
if( innerTabRect.width > 0 ) {
float y2 = (tabPlacement == TOP) ? y : y + h - csh;
gap = new Rectangle2D.Float( innerTabRect.x, y2, innerTabRect.width, csh );
}
} else {
if( innerTabRect.height > 0 ) {
float x2 = (tabPlacement == LEFT) ? x : x + w - csh;
gap = new Rectangle2D.Float( x2, innerTabRect.y, csh, innerTabRect.height );
}
}
if( gap != null ) {
path.append( gap, false );
// fill gap in case that the tab is colored (e.g. focused or hover)
g.setColor( getTabBackground( tabPlacement, selectedIndex, true ) );
((Graphics2D)g).fill( gap );
}
}
// paint content separator or full border
g.setColor( contentAreaColor );
((Graphics2D)g).fill( path );
// repaint selection in scroll-tab-layout because it may be painted before
@@ -1211,7 +1454,7 @@ public class FlatTabbedPaneUI
else
g.clipRect( 0, vr.y, tabPane.getWidth(), vr.height );
paintTabSelection( g, tabPlacement, tabRect.x, tabRect.y, tabRect.width, tabRect.height );
paintTabSelection( g, tabPlacement, selectedIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height );
g.setClip( oldClip );
}
}
@@ -1364,6 +1607,11 @@ public class FlatTabbedPaneUI
super.getTabRunCount( tabPane );
}
@Override
protected boolean shouldRotateTabRuns( int tabPlacement ) {
return rotateTabRuns;
}
private boolean isLastInRun( int tabIndex ) {
int run = getRunForTab( tabPane.getTabCount(), tabIndex );
return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex;
@@ -1399,6 +1647,15 @@ public class FlatTabbedPaneUI
clientPropertyBoolean( tabPane, TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB, hideTabAreaWithOneTab );
}
/** @since 2 */
protected int getTabType() {
Object value = tabPane.getClientProperty( TABBED_PANE_TAB_TYPE );
return (value instanceof String)
? parseTabType( (String) value )
: tabType;
}
protected int getTabsPopupPolicy() {
Object value = tabPane.getClientProperty( TABBED_PANE_TABS_POPUP_POLICY );
@@ -1451,6 +1708,18 @@ public class FlatTabbedPaneUI
: tabWidthMode;
}
/** @since 2 */
protected static int parseTabType( String str ) {
if( str == null )
return TAB_TYPE_UNDERLINED;
switch( str ) {
default:
case TABBED_PANE_TAB_TYPE_UNDERLINED: return TAB_TYPE_UNDERLINED;
case TABBED_PANE_TAB_TYPE_CARD: return TAB_TYPE_CARD;
}
}
protected static int parseTabsPopupPolicy( String str ) {
if( str == null )
return AS_NEEDED;
@@ -1498,6 +1767,16 @@ public class FlatTabbedPaneUI
}
}
private static String alignmentToString( int value, String defaultValue ) {
switch( value ) {
case LEADING: return TABBED_PANE_ALIGN_LEADING;
case TRAILING: return TABBED_PANE_ALIGN_TRAILING;
case CENTER: return TABBED_PANE_ALIGN_CENTER;
case FILL: return TABBED_PANE_ALIGN_FILL;
default: return defaultValue;
}
}
protected static int parseTabWidthMode( String str ) {
if( str == null )
return WIDTH_MODE_PREFERRED;
@@ -1680,7 +1959,7 @@ public class FlatTabbedPaneUI
super( direction, arrowType,
FlatTabbedPaneUI.this.foreground, FlatTabbedPaneUI.this.disabledForeground,
null, buttonHoverBackground, null, buttonPressedBackground );
setArrowWidth( 10 );
setArrowWidth( 11 );
}
protected void updateStyle() {
@@ -1822,7 +2101,7 @@ public class FlatTabbedPaneUI
}
protected JMenuItem createTabMenuItem( int tabIndex ) {
// search for tab name in this places
// search for tab name in these places
// 1. tab title
// 2. text of label or text component in custom tab component (including children)
// 3. accessible name of tab
@@ -1855,7 +2134,7 @@ public class FlatTabbedPaneUI
menuItem.setOpaque( true );
}
if( !tabPane.isEnabledAt( tabIndex ) )
if( !tabPane.isEnabled() || !tabPane.isEnabledAt( tabIndex ) )
menuItem.setEnabled( false );
menuItem.addActionListener( e -> selectTab( tabIndex ) );
@@ -2251,7 +2530,7 @@ public class FlatTabbedPaneUI
if( tabPane == null || tabViewport == null )
return;
if( !scrolled || tabViewport == null )
if( !scrolled )
return;
scrolled = false;
@@ -2264,11 +2543,12 @@ public class FlatTabbedPaneUI
private class Handler
implements MouseListener, MouseMotionListener, PropertyChangeListener,
ChangeListener, ComponentListener, ContainerListener
ChangeListener, ComponentListener, ContainerListener, FocusListener
{
MouseListener mouseDelegate;
PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate;
FocusListener focusDelegate;
private final PropertyChangeListener contentListener = this::contentPropertyChange;
@@ -2309,7 +2589,7 @@ public class FlatTabbedPaneUI
public void mousePressed( MouseEvent e ) {
updateRollover( e );
if( !isPressedTabClose() )
if( !isPressedTabClose() && SwingUtilities.isLeftMouseButton( e ) )
mouseDelegate.mousePressed( e );
}
@@ -2363,10 +2643,8 @@ public class FlatTabbedPaneUI
setRolloverTab( tabIndex );
// check whether mouse hit tab close area
boolean hitClose = isTabClosable( tabIndex )
? getTabCloseHitArea( tabIndex ).contains( x, y )
: false;
if( e.getID() == MouseEvent.MOUSE_PRESSED )
boolean hitClose = isTabClosable( tabIndex ) && getTabCloseHitArea( tabIndex ).contains( x, y );
if( e.getID() == MouseEvent.MOUSE_PRESSED && SwingUtilities.isLeftMouseButton( e ) )
pressedTabIndex = hitClose ? tabIndex : -1;
setRolloverTabClose( hitClose );
setPressedTabClose( hitClose && tabIndex == pressedTabIndex );
@@ -2388,8 +2666,7 @@ public class FlatTabbedPaneUI
if( tabIndex == lastTipTabIndex )
return; // closeTip already set
if( tabIndex != lastTipTabIndex )
restoreTabToolTip();
restoreTabToolTip();
lastTipTabIndex = tabIndex;
lastTip = tabPane.getToolTipTextAt( lastTipTabIndex );
@@ -2438,6 +2715,10 @@ public class FlatTabbedPaneUI
break;
case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_TAB_TYPE:
tabPane.repaint();
break;
case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB:
@@ -2536,6 +2817,20 @@ public class FlatTabbedPaneUI
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( contentListener );
}
//---- interface FocusListener ----
@Override
public void focusGained( FocusEvent e ) {
focusDelegate.focusGained( e );
repaintTab( tabPane.getSelectedIndex() );
}
@Override
public void focusLost( FocusEvent e ) {
focusDelegate.focusLost( e );
repaintTab( tabPane.getSelectedIndex() );
}
}
//---- class FlatTabbedPaneLayout -----------------------------------------
@@ -3164,4 +3459,77 @@ public class FlatTabbedPaneUI
delegate.actionPerformed( e );
}
}
//---- class FlatSelectedTabRepainter -------------------------------------
private static class FlatSelectedTabRepainter
implements PropertyChangeListener//, Runnable
{
private static FlatSelectedTabRepainter instance;
private KeyboardFocusManager keyboardFocusManager;
static void install() {
synchronized( FlatSelectedTabRepainter.class ) {
if( instance != null )
return;
instance = new FlatSelectedTabRepainter();
}
}
FlatSelectedTabRepainter() {
keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
keyboardFocusManager.addPropertyChangeListener( this );
}
private void uninstall() {
synchronized( FlatSelectedTabRepainter.class ) {
if( instance == null )
return;
keyboardFocusManager.removePropertyChangeListener( this );
keyboardFocusManager = null;
instance = null;
}
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
// uninstall if no longer using FlatLaf
if( !(UIManager.getLookAndFeel() instanceof FlatLaf) ) {
uninstall();
return;
}
switch( e.getPropertyName() ) {
case "permanentFocusOwner":
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if( oldValue instanceof Component )
repaintSelectedTabs( (Component) oldValue );
if( newValue instanceof Component )
repaintSelectedTabs( (Component) newValue );
break;
case "activeWindow":
repaintSelectedTabs( keyboardFocusManager.getPermanentFocusOwner() );
break;
}
}
private void repaintSelectedTabs( Component c ) {
if( c instanceof JTabbedPane )
repaintSelectedTab( (JTabbedPane) c );
while( (c = SwingUtilities.getAncestorOfClass( JTabbedPane.class, c )) != null )
repaintSelectedTab( (JTabbedPane) c );
}
private void repaintSelectedTab( JTabbedPane tabbedPane ) {
TabbedPaneUI ui = tabbedPane.getUI();
if( ui instanceof FlatTabbedPaneUI )
((FlatTabbedPaneUI) ui).repaintTab( tabbedPane.getSelectedIndex() );
}
}
}

View File

@@ -37,7 +37,7 @@ import javax.swing.plaf.TableUI;
public class FlatTableCellBorder
extends FlatLineBorder
{
protected boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" );
/** @since 2 */ protected boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" );
private Component c;
@@ -72,7 +72,7 @@ public class FlatTableCellBorder
}
/**
* Because this borders are always shared for all tables,
* Because this border is always shared for all tables,
* get border specific style from FlatTableUI.
*/
static <T> T getStyleFromTableUI( Component c, Function<FlatTableUI, T> f ) {

View File

@@ -141,8 +141,8 @@ public class FlatTableHeaderBorder
protected boolean hideTrailingVerticalLine( JTableHeader header ) {
if( header.getUI() instanceof FlatTableHeaderUI ) {
FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI();
if( ui.showTrailingVerticalLine )
return false;
if( ui.showTrailingVerticalLine != null )
return !ui.showTrailingVerticalLine;
}
if( showTrailingVerticalLine )

View File

@@ -39,7 +39,9 @@ import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
@@ -84,14 +86,14 @@ public class FlatTableHeaderUI
@Styleable(type=String.class) protected int sortIconPosition;
// for FlatTableHeaderBorder
@Styleable protected Insets cellMargins;
@Styleable protected Color separatorColor;
/** @since 2 */ @Styleable protected boolean showTrailingVerticalLine;
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color separatorColor;
/** @since 2 */ @Styleable protected Boolean showTrailingVerticalLine;
// for FlatAscendingSortIcon and FlatDescendingSortIcon
// (needs to be public because icon classes are in another package)
@Styleable public String arrowType;
@Styleable public Color sortIconColor;
/** @since 2 */ @Styleable public String arrowType;
/** @since 2 */ @Styleable public Color sortIconColor;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
@@ -169,6 +171,22 @@ public class FlatTableHeaderUI
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.equals( "sortIconPosition" ) ) {
switch( sortIconPosition ) {
default:
case SwingConstants.RIGHT: return "right";
case SwingConstants.LEFT: return "left";
case SwingConstants.TOP: return "top";
case SwingConstants.BOTTOM: return "bottom";
}
}
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
private static int parseSortIconPosition( String str ) {
if( str == null )
str = "";
@@ -195,6 +213,8 @@ public class FlatTableHeaderUI
@Override
public void paint( Graphics g, JComponent c ) {
fixDraggedAndResizingColumns( header );
TableColumnModel columnModel = header.getColumnModel();
if( columnModel.getColumnCount() <= 0 )
return;
@@ -269,6 +289,32 @@ public class FlatTableHeaderUI
return size;
}
static void fixDraggedAndResizingColumns( JTableHeader header ) {
if( header == null )
return;
// Dragged column may be outdated in the case that the table structure
// was changed from a table header popup menu action. In this case
// the paint methods in BasicTableHeaderUI and BasicTableUI would throw exceptions.
TableColumn draggedColumn = header.getDraggedColumn();
if( draggedColumn != null && !isValidColumn( header.getColumnModel(), draggedColumn ) )
header.setDraggedColumn( null );
// also clear outdated resizing column (although this seems not cause exceptions)
TableColumn resizingColumn = header.getResizingColumn();
if( resizingColumn != null && !isValidColumn( header.getColumnModel(), resizingColumn ) )
header.setResizingColumn( null );
}
private static boolean isValidColumn( TableColumnModel cm, TableColumn column ) {
int count = cm.getColumnCount();
for( int i = 0; i < count; i++ ) {
if( cm.getColumn( i ) == column )
return true;
}
return false;
}
//---- class FlatTableCellHeaderRenderer ----------------------------------
/**

View File

@@ -80,6 +80,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.paintOutsideAlternateRows boolean
*
* <!-- FlatTableCellBorder -->
*
@@ -95,7 +96,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatTableUI
extends BasicTableUI
implements StyleableUI
implements StyleableUI, FlatViewportUI.ViewportPainter
{
protected boolean showHorizontalLines;
protected boolean showVerticalLines;
@@ -108,9 +109,9 @@ public class FlatTableUI
@Styleable protected Color selectionInactiveForeground;
// for FlatTableCellBorder
@Styleable protected Insets cellMargins;
@Styleable protected Color cellFocusColor;
@Styleable protected boolean showCellFocusIndicator;
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color cellFocusColor;
/** @since 2 */ @Styleable protected Boolean showCellFocusIndicator;
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
@@ -285,10 +286,16 @@ public class FlatTableUI
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,
@@ -313,6 +320,8 @@ public class FlatTableUI
@Override
public void paint( Graphics g, JComponent c ) {
FlatTableHeaderUI.fixDraggedAndResizingColumns( table.getTableHeader() );
boolean horizontalLines = table.getShowHorizontalLines();
boolean verticalLines = table.getShowVerticalLines();
if( horizontalLines || verticalLines ) {
@@ -421,4 +430,38 @@ public class FlatTableUI
? (viewport != rowHeader)
: (viewport == rowHeader || rowHeader == null);
}
/** @since 2.3 */
@Override
public void paintViewport( Graphics g, JComponent c, JViewport viewport ) {
int viewportWidth = viewport.getWidth();
int viewportHeight = viewport.getHeight();
// fill viewport background in same color as table background
if( viewport.isOpaque() ) {
g.setColor( table.getBackground() );
g.fillRect( 0, 0, viewportWidth, viewportHeight );
}
// paint alternating empty rows
boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" );
Color alternateColor;
if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) {
g.setColor( alternateColor );
int rowCount = table.getRowCount();
// paint alternating empty rows below the table
int tableHeight = table.getHeight();
if( tableHeight < viewportHeight ) {
int tableWidth = table.getWidth();
int rowHeight = table.getRowHeight();
for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) {
if( row % 2 != 0 )
g.fillRect( 0, y, tableWidth, rowHeight );
}
}
}
}
}

View File

@@ -86,6 +86,13 @@ public class FlatTextAreaUI
@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();
@@ -183,6 +190,12 @@ public class FlatTextAreaUI
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,

View File

@@ -19,29 +19,40 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
@@ -94,6 +105,12 @@ public class FlatTextFieldUI
/** @since 2 */ @Styleable protected Icon leadingIcon;
/** @since 2 */ @Styleable protected Icon trailingIcon;
/** @since 2 */ protected JComponent leadingComponent;
/** @since 2 */ protected JComponent trailingComponent;
/** @since 2 */ protected JComponent clearButton;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected boolean showClearButton;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
@@ -101,6 +118,7 @@ public class FlatTextFieldUI
private Insets defaultMargin;
private FocusListener focusListener;
private DocumentListener documentListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
@@ -110,16 +128,31 @@ public class FlatTextFieldUI
@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 );
leadingIcon = clientProperty( c, TEXT_FIELD_LEADING_ICON, null, Icon.class );
trailingIcon = clientProperty( c, TEXT_FIELD_TRAILING_ICON, null, Icon.class );
installLeadingComponent();
installTrailingComponent();
installClearButton();
installStyle();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallLeadingComponent();
uninstallTrailingComponent();
uninstallClearButton();
super.uninstallUI( c );
leadingIcon = null;
@@ -181,6 +214,11 @@ public class FlatTextFieldUI
getComponent().removeFocusListener( focusListener );
focusListener = null;
if( documentListener != null ) {
getComponent().getDocument().removeDocumentListener( documentListener );
documentListener = null;
}
}
@Override
@@ -201,6 +239,7 @@ public class FlatTextFieldUI
switch( e.getPropertyName() ) {
case PLACEHOLDER_TEXT:
case COMPONENT_ROUND_RECT:
case OUTLINE:
case TEXT_FIELD_PADDING:
c.repaint();
break;
@@ -225,9 +264,61 @@ public class FlatTextFieldUI
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint();
break;
case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent();
installLeadingComponent();
c.revalidate();
c.repaint();
break;
case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent();
installTrailingComponent();
c.revalidate();
c.repaint();
break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton();
installClearButton();
c.revalidate();
c.repaint();
break;
case "enabled":
case "editable":
updateClearButton();
break;
case "document":
if( documentListener != null ) {
if( e.getOldValue() instanceof Document )
((Document)e.getOldValue()).removeDocumentListener( documentListener );
if( e.getNewValue() instanceof Document )
((Document)e.getNewValue()).addDocumentListener( documentListener );
updateClearButton();
}
break;
}
}
/** @since 2 */
protected void installDocumentListener() {
if( documentListener != null )
return;
documentListener = new FlatDocumentListener();
getComponent().getDocument().addDocumentListener( documentListener );
}
/** @since 2 */
protected void documentChanged( DocumentEvent e ) {
if( clearButton != null )
updateClearButton();
}
/** @since 2 */
protected void installStyle() {
try {
@@ -246,10 +337,15 @@ public class FlatTextFieldUI
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
boolean oldShowClearButton = showClearButton;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
if( showClearButton != oldShowClearButton ) {
uninstallClearButton();
installClearButton();
}
}
/** @since 2 */
@@ -265,6 +361,12 @@ public class FlatTextFieldUI
return FlatStylingSupport.getAnnotatedStyleableInfos( this, getComponent().getBorder() );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( JComponent c, String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, getComponent().getBorder(), key );
}
private void updateBackground() {
updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
@@ -280,7 +382,7 @@ public class FlatTextFieldUI
if( !(oldBackground instanceof UIResource) )
return;
// do not update background if it currently has a unknown color (assigned from outside)
// do not update background if it currently has an unknown color (assigned from outside)
if( oldBackground != background &&
oldBackground != disabledBackground &&
oldBackground != inactiveBackground &&
@@ -444,6 +546,16 @@ debug*/
// add width of leading and trailing icons
size.width += getLeadingIconWidth() + getTrailingIconWidth();
// add width of leading and trailing components
for( JComponent comp : getLeadingComponents() ) {
if( comp != null && comp.isVisible() )
size.width += comp.getPreferredSize().width;
}
for( JComponent comp : getTrailingComponents() ) {
if( comp != null && comp.isVisible() )
size.width += comp.getPreferredSize().width;
}
return size;
}
@@ -510,7 +622,8 @@ debug*/
/**
* Returns the rectangle used to paint leading and trailing icons.
* It invokes {@code super.getVisibleEditorRect()} and reduces left and/or
* right margin if the text field has leading or trailing icons.
* right margin if the text field has leading or trailing icons or components.
* Also, the preferred widths of leading and trailing components are removed.
*
* @since 2
*/
@@ -519,10 +632,31 @@ debug*/
if( r == null )
return null;
// if a leading/trailing icon is shown, then the left/right margin is reduced
// to the top margin, which places the icon nicely centered on left/right side
boolean ltr = isLeftToRight();
if( ltr ? hasLeadingIcon() : hasTrailingIcon() ) {
// remove width of leading/trailing components
JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents();
JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents();
boolean leftVisible = false;
boolean rightVisible = false;
for( JComponent leftComponent : leftComponents ) {
if( leftComponent != null && leftComponent.isVisible() ) {
int w = leftComponent.getPreferredSize().width;
r.x += w;
r.width -= w;
leftVisible = true;
}
}
for( JComponent rightComponent : rightComponents ) {
if( rightComponent != null && rightComponent.isVisible() ) {
r.width -= rightComponent.getPreferredSize().width;
rightVisible = true;
}
}
// if a leading/trailing icons (or components) are shown, then the left/right margins are reduced
// to the top margin, which places the icon nicely centered on left/right side
if( leftVisible || (ltr ? hasLeadingIcon() : hasTrailingIcon()) ) {
// reduce left margin
Insets margin = getComponent().getMargin();
int newLeftMargin = Math.min( margin.left, margin.top );
@@ -532,7 +666,7 @@ debug*/
r.width += diff;
}
}
if( ltr ? hasTrailingIcon() : hasLeadingIcon() ) {
if( rightVisible || (ltr ? hasTrailingIcon() : hasLeadingIcon()) ) {
// reduce right margin
Insets margin = getComponent().getMargin();
int newRightMargin = Math.min( margin.right, margin.top );
@@ -540,6 +674,10 @@ debug*/
r.width += scale( margin.right - newRightMargin );
}
// make sure that width and height are not negative
r.width = Math.max( r.width, 0 );
r.height = Math.max( r.height, 0 );
return r;
}
@@ -578,4 +716,266 @@ debug*/
if( caret instanceof FlatCaret )
((FlatCaret)caret).scrollCaretToVisible();
}
/** @since 2 */
protected void installLeadingComponent() {
JTextComponent c = getComponent();
leadingComponent = clientProperty( c, TEXT_FIELD_LEADING_COMPONENT, null, JComponent.class );
if( leadingComponent != null ) {
prepareLeadingOrTrailingComponent( leadingComponent );
installLayout();
c.add( leadingComponent );
}
}
/** @since 2 */
protected void installTrailingComponent() {
JTextComponent c = getComponent();
trailingComponent = clientProperty( c, TEXT_FIELD_TRAILING_COMPONENT, null, JComponent.class );
if( trailingComponent != null ) {
prepareLeadingOrTrailingComponent( trailingComponent );
installLayout();
c.add( trailingComponent );
}
}
/** @since 2 */
protected void uninstallLeadingComponent() {
if( leadingComponent != null ) {
getComponent().remove( leadingComponent );
leadingComponent = null;
}
}
/** @since 2 */
protected void uninstallTrailingComponent() {
if( trailingComponent != null ) {
getComponent().remove( trailingComponent );
trailingComponent = null;
}
}
/** @since 2 */
protected void installClearButton() {
JTextComponent c = getComponent();
if( clientPropertyBoolean( c, TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton ) ) {
clearButton = createClearButton();
updateClearButton();
installDocumentListener();
installLayout();
c.add( clearButton );
}
}
/** @since 2 */
protected void uninstallClearButton() {
if( clearButton != null ) {
getComponent().remove( clearButton );
clearButton = null;
}
}
/** @since 2 */
protected JComponent createClearButton() {
JButton button = new JButton();
button.setName( "TextField.clearButton" );
button.putClientProperty( STYLE_CLASS, "clearButton" );
button.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON );
button.setCursor( Cursor.getDefaultCursor() );
button.addActionListener( e -> clearButtonClicked() );
return button;
}
/** @since 2 */
@SuppressWarnings( "unchecked" )
protected void clearButtonClicked() {
JTextComponent c = getComponent();
Object callback = c.getClientProperty( TEXT_FIELD_CLEAR_CALLBACK );
if( callback instanceof Runnable )
((Runnable)callback).run();
else if( callback instanceof Consumer )
((Consumer<JTextComponent>)callback).accept( c );
else
c.setText( "" );
}
/** @since 2 */
protected void updateClearButton() {
if( clearButton == null )
return;
JTextComponent c = getComponent();
boolean visible = c.isEnabled() && c.isEditable() && c.getDocument().getLength() > 0;
if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible );
c.revalidate();
c.repaint();
}
}
/**
* Returns components placed at the leading side of the text field.
* The returned array may contain {@code null}.
* The default implementation returns {@link #leadingComponent}.
*
* @since 2
*/
protected JComponent[] getLeadingComponents() {
return new JComponent[] { leadingComponent };
}
/**
* Returns components placed at the trailing side of the text field.
* The returned array may contain {@code null}.
* The default implementation returns {@link #trailingComponent} and {@link #clearButton}.
* <p>
* <strong>Note</strong>: The components in the array must be in reverse (visual) order.
*
* @since 2
*/
protected JComponent[] getTrailingComponents() {
return new JComponent[] { trailingComponent, clearButton };
}
/** @since 2 */
protected void prepareLeadingOrTrailingComponent( JComponent c ) {
c.putClientProperty( STYLE_CLASS, "inTextField" );
if( c instanceof JButton || c instanceof JToggleButton ) {
c.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON );
if( !c.isCursorSet() )
c.setCursor( Cursor.getDefaultCursor() );
} else if( c instanceof JToolBar ) {
for( Component child : c.getComponents() ) {
if( child instanceof JComponent )
((JComponent)child).putClientProperty( STYLE_CLASS, "inTextField" );
}
if( !c.isCursorSet() )
c.setCursor( Cursor.getDefaultCursor() );
}
}
/** @since 2 */
protected void installLayout() {
JTextComponent c = getComponent();
LayoutManager oldLayout = c.getLayout();
if( !(oldLayout instanceof FlatTextFieldLayout) )
c.setLayout( new FlatTextFieldLayout( oldLayout ) );
}
//---- class FlatTextFieldLayout ------------------------------------------
private class FlatTextFieldLayout
implements LayoutManager2, UIResource
{
private final LayoutManager delegate;
FlatTextFieldLayout( LayoutManager delegate ) {
this.delegate = delegate;
}
@Override
public void addLayoutComponent( String name, Component comp ) {
if( delegate != null )
delegate.addLayoutComponent( name, comp );
}
@Override
public void removeLayoutComponent( Component comp ) {
if( delegate != null )
delegate.removeLayoutComponent( comp );
}
@Override
public Dimension preferredLayoutSize( Container parent ) {
return (delegate != null) ? delegate.preferredLayoutSize( parent ) : null;
}
@Override
public Dimension minimumLayoutSize( Container parent ) {
return (delegate != null) ? delegate.minimumLayoutSize( parent ) : null;
}
@Override
public void layoutContainer( Container parent ) {
if( delegate != null )
delegate.layoutContainer( parent );
int ow = FlatUIUtils.getBorderFocusAndLineWidth( getComponent() );
int h = parent.getHeight() - ow - ow;
boolean ltr = isLeftToRight();
JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents();
JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents();
// layout left components
int x = ow;
for( JComponent leftComponent : leftComponents ) {
if( leftComponent != null && leftComponent.isVisible() ) {
int cw = leftComponent.getPreferredSize().width;
leftComponent.setBounds( x, ow, cw, h );
x += cw;
}
}
// layout right components
x = parent.getWidth() - ow;
for( JComponent rightComponent : rightComponents ) {
if( rightComponent != null && rightComponent.isVisible() ) {
int cw = rightComponent.getPreferredSize().width;
x -= cw;
rightComponent.setBounds( x, ow, cw, h );
}
}
}
@Override
public void addLayoutComponent( Component comp, Object constraints ) {
if( delegate instanceof LayoutManager2 )
((LayoutManager2)delegate).addLayoutComponent( comp, constraints );
}
@Override
public Dimension maximumLayoutSize( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).maximumLayoutSize( target ) : null;
}
@Override
public float getLayoutAlignmentX( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentX( target ) : 0.5f;
}
@Override
public float getLayoutAlignmentY( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentY( target ) : 0.5f;
}
@Override
public void invalidateLayout( Container target ) {
if( delegate instanceof LayoutManager2 )
((LayoutManager2)delegate).invalidateLayout( target );
}
}
//---- class FlatDocumentListener -----------------------------------------
private class FlatDocumentListener
implements DocumentListener
{
@Override
public void insertUpdate( DocumentEvent e ) {
documentChanged( e );
}
@Override
public void removeUpdate( DocumentEvent e ) {
documentChanged( e );
}
@Override
public void changedUpdate( DocumentEvent e ) {
documentChanged( e );
}
}
}

View File

@@ -191,6 +191,12 @@ public class FlatTextPaneUI
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,

View File

@@ -24,6 +24,8 @@ import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
@@ -31,6 +33,7 @@ import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
@@ -51,14 +54,13 @@ import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
@@ -66,13 +68,13 @@ import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF title bar.
*
* @uiDefault TitlePane.font Font
* @uiDefault TitlePane.background Color
* @uiDefault TitlePane.inactiveBackground Color
* @uiDefault TitlePane.foreground Color
@@ -80,15 +82,21 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.embeddedForeground Color
* @uiDefault TitlePane.borderColor Color optional
* @uiDefault TitlePane.unifiedBackground boolean
* @uiDefault TitlePane.showIcon boolean
* @uiDefault TitlePane.showIconInDialogs boolean
* @uiDefault TitlePane.noIconLeftGap int
* @uiDefault TitlePane.iconSize Dimension
* @uiDefault TitlePane.iconMargins Insets
* @uiDefault TitlePane.titleMargins Insets
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.titleMinimumWidth int
* @uiDefault TitlePane.buttonMinimumWidth int
* @uiDefault TitlePane.buttonMaximizedHeight int
* @uiDefault TitlePane.centerTitle boolean
* @uiDefault TitlePane.centerTitleIfMenuBarEmbedded boolean
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.icon Icon
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -99,6 +107,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane
extends JComponent
{
/** @since 2.5 */ protected final Font titleFont = UIManager.getFont( "TitlePane.font" );
protected final Color activeBackground = UIManager.getColor( "TitlePane.background" );
protected final Color inactiveBackground = UIManager.getColor( "TitlePane.inactiveBackground" );
protected final Color activeForeground = UIManager.getColor( "TitlePane.foreground" );
@@ -106,11 +115,19 @@ public class FlatTitlePane
protected final Color embeddedForeground = UIManager.getColor( "TitlePane.embeddedForeground" );
protected final Color borderColor = UIManager.getColor( "TitlePane.borderColor" );
/** @since 2 */ protected final boolean showIcon = FlatUIUtils.getUIBoolean( "TitlePane.showIcon", true );
/** @since 2.5 */ protected final boolean showIconInDialogs = FlatUIUtils.getUIBoolean( "TitlePane.showIconInDialogs", true );
/** @since 2 */ protected final int noIconLeftGap = FlatUIUtils.getUIInt( "TitlePane.noIconLeftGap", 8 );
protected final Dimension iconSize = UIManager.getDimension( "TitlePane.iconSize" );
/** @since 2.4 */ protected final int titleMinimumWidth = FlatUIUtils.getUIInt( "TitlePane.titleMinimumWidth", 60 );
/** @since 2.4 */ protected final int buttonMinimumWidth = FlatUIUtils.getUIInt( "TitlePane.buttonMinimumWidth", 30 );
protected final int buttonMaximizedHeight = UIManager.getInt( "TitlePane.buttonMaximizedHeight" );
protected final boolean centerTitle = UIManager.getBoolean( "TitlePane.centerTitle" );
protected final boolean centerTitleIfMenuBarEmbedded = FlatUIUtils.getUIBoolean( "TitlePane.centerTitleIfMenuBarEmbedded", true );
protected final int menuBarTitleGap = FlatUIUtils.getUIInt( "TitlePane.menuBarTitleGap", 20 );
/** @since 2.4 */ protected final boolean showIconBesideTitle = UIManager.getBoolean( "TitlePane.showIconBesideTitle" );
protected final int menuBarTitleGap = FlatUIUtils.getUIInt( "TitlePane.menuBarTitleGap", 40 );
/** @since 2.4 */ protected final int menuBarTitleMinimumGap = FlatUIUtils.getUIInt( "TitlePane.menuBarTitleMinimumGap", 12 );
/** @since 2.4 */ protected final int menuBarResizeHeight = FlatUIUtils.getUIInt( "TitlePane.menuBarResizeHeight", 4 );
protected final JRootPane rootPane;
@@ -142,6 +159,8 @@ public class FlatTitlePane
// necessary for closing window with double-click on icon
iconLabel.addMouseListener( handler );
applyComponentOrientation( rootPane.getComponentOrientation() );
}
protected FlatTitlePaneBorder createTitlePaneBorder() {
@@ -163,7 +182,6 @@ public class FlatTitlePane
};
iconLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.iconMargins" ) ) );
titleLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.titleMargins" ) ) );
titleLabel.setHorizontalAlignment( SwingConstants.CENTER );
leftPanel.setLayout( new BoxLayout( leftPanel, BoxLayout.LINE_AXIS ) );
leftPanel.setOpaque( false );
@@ -183,18 +201,52 @@ public class FlatTitlePane
setLayout( new BorderLayout() {
@Override
public void layoutContainer( Container target ) {
super.layoutContainer( target );
// make left panel (with embedded menu bar) smaller if horizontal space is rare
// to avoid that embedded menu bar overlaps button bar
// compute available bounds
Insets insets = target.getInsets();
int width = target.getWidth() - insets.left - insets.right;
if( leftPanel.getWidth() + buttonPanel.getWidth() > width ) {
int oldWidth = leftPanel.getWidth();
int newWidth = Math.max( width - buttonPanel.getWidth(), 0 );
leftPanel.setSize( newWidth, leftPanel.getHeight() );
if( !getComponentOrientation().isLeftToRight() )
leftPanel.setLocation( leftPanel.getX() + (oldWidth - newWidth), leftPanel.getY() );
int x = insets.left;
int y = insets.top;
int w = target.getWidth() - insets.left - insets.right;
int h = target.getHeight() - insets.top - insets.bottom;
// compute widths
int leftWidth = leftPanel.getPreferredSize().width;
int buttonsWidth = buttonPanel.getPreferredSize().width;
int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth );
// increase minimum width if icon is show besides the title
Icon icon = titleLabel.getIcon();
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
int iconTextGap = titleLabel.getComponentOrientation().isLeftToRight() ? iconInsets.right : iconInsets.left;
minTitleWidth += icon.getIconWidth() + iconTextGap;
}
// if title is too small, reduce width of buttons
if( titleWidth < minTitleWidth ) {
buttonsWidth = Math.max( buttonsWidth - (minTitleWidth - titleWidth), buttonPanel.getMinimumSize().width );
titleWidth = w - leftWidth - buttonsWidth;
}
// if title is still too small, reduce width of left panel (icon and embedded menu bar)
if( titleWidth < minTitleWidth ) {
int minLeftWidth = iconLabel.isVisible()
? iconLabel.getWidth() - iconLabel.getInsets().right
: UIScale.scale( noIconLeftGap );
leftWidth = Math.max( leftWidth - (minTitleWidth - titleWidth), minLeftWidth );
titleWidth = w - leftWidth - buttonsWidth;
}
if( target.getComponentOrientation().isLeftToRight() ) {
// left-to-right
leftPanel.setBounds( x, y, leftWidth, h );
titleLabel.setBounds( x + leftWidth, y, titleWidth, h );
buttonPanel.setBounds( x + leftWidth + titleWidth, y, buttonsWidth, h );
} else {
// right-to-left
buttonPanel.setBounds( x, y, buttonsWidth, h );
titleLabel.setBounds( x + buttonsWidth, y, titleWidth, h );
leftPanel.setBounds( x + buttonsWidth + titleWidth, y, leftWidth, h );
}
// If menu bar is embedded and contains a horizontal glue component,
@@ -224,14 +276,16 @@ public class FlatTitlePane
restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
// initially hide buttons that are only supported in frames
iconifyButton.setVisible( false );
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
buttonPanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if( buttonMaximizedHeight > 0 &&
window instanceof Frame &&
(((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
{
if( buttonMaximizedHeight > 0 && isWindowMaximized() && !hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() ) ) {
// make title pane height smaller when frame is maximized
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight ) ) );
}
@@ -243,11 +297,9 @@ public class FlatTitlePane
if( rootPane.getWindowDecorationStyle() == JRootPane.FRAME ) {
// JRootPane.FRAME works only for frames (and not for dialogs)
// but at this time the owner window type is unknown (not yet added)
// so we add the iconify/maximize/restore buttons and they are hidden
// so we add the iconify/maximize/restore buttons, and they are shown
// later in frameStateChanged(), which is invoked from addNotify()
restoreButton.setVisible( false );
buttonPanel.add( iconifyButton );
buttonPanel.add( maximizeButton );
buttonPanel.add( restoreButton );
@@ -256,7 +308,13 @@ public class FlatTitlePane
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
JButton button = new JButton( UIManager.getIcon( iconKey ) );
JButton button = new JButton( UIManager.getIcon( iconKey ) ) {
@Override
public Dimension getMinimumSize() {
// allow the button to shrink if space is rare
return new Dimension( UIScale.scale( buttonMinimumWidth ), super.getMinimumSize().height );
}
};
button.setFocusable( false );
button.setContentAreaFilled( false );
button.setBorder( BorderFactory.createEmptyBorder() );
@@ -306,6 +364,7 @@ public class FlatTitlePane
restoreButton.setVisible( resizable && maximized );
if( maximized &&
!(SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window )) &&
rootPane.getClientProperty( "_flatlaf.maximizedBoundsUpToDate" ) == null )
{
rootPane.putClientProperty( "_flatlaf.maximizedBoundsUpToDate", null );
@@ -336,37 +395,33 @@ public class FlatTitlePane
}
protected void updateIcon() {
boolean defaultShowIcon = showIcon;
if( !showIconInDialogs && rootPane.getParent() instanceof JDialog )
defaultShowIcon = false;
// get window images
List<Image> images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
images = owner.getIconImages();
if( !images.isEmpty() )
break;
List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
images = owner.getIconImages();
if( !images.isEmpty() )
break;
}
}
}
boolean hasIcon = true;
boolean hasIcon = (images != null && !images.isEmpty());
// set icon
if( !images.isEmpty() )
iconLabel.setIcon( new FlatTitlePaneIcon( images, iconSize ) );
else {
// no icon set on window --> use default icon
Icon defaultIcon = UIManager.getIcon( "TitlePane.icon" );
if( defaultIcon != null && (defaultIcon.getIconWidth() == 0 || defaultIcon.getIconHeight() == 0) )
defaultIcon = null;
if( defaultIcon != null ) {
if( defaultIcon instanceof ImageIcon )
defaultIcon = new ScaledImageIcon( (ImageIcon) defaultIcon, iconSize.width, iconSize.height );
iconLabel.setIcon( defaultIcon );
} else
hasIcon = false;
}
iconLabel.setIcon( hasIcon && !showIconBesideTitle ? new FlatTitlePaneIcon( images, iconSize ) : null );
titleLabel.setIcon( hasIcon && showIconBesideTitle ? new FlatTitlePaneIcon( images, iconSize ) : null );
// show/hide icon
iconLabel.setVisible( hasIcon );
iconLabel.setVisible( hasIcon && !showIconBesideTitle );
leftPanel.setBorder( hasIcon && !showIconBesideTitle ? null : FlatUIUtils.nonUIResource( new FlatEmptyBorder( 0, noIconLeftGap, 0, 0 ) ) );
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
@@ -426,7 +481,7 @@ public class FlatTitlePane
}
/**
* Returns whether this title pane currently has an visible and embedded menubar.
* Returns whether this title pane currently has a visible and embedded menubar.
*/
protected boolean hasVisibleEmbeddedMenuBar( JMenuBar menuBar ) {
return menuBar != null && menuBar.isVisible() && isMenuBarEmbedded();
@@ -508,7 +563,7 @@ public class FlatTitlePane
protected void menuBarLayouted() {
updateNativeTitleBarHeightAndHitTestSpotsLater();
revalidate();
doLayout();
}
/*debug
@@ -521,17 +576,23 @@ public class FlatTitlePane
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
}
if( debugHitTestSpots != null ) {
g.setColor( Color.red );
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
for( Rectangle r : debugHitTestSpots )
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
}
if( debugAppIconBounds != null ) {
g.setColor( Color.blue);
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
Rectangle r = debugAppIconBounds;
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void paintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
g.setColor( color );
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
}
debug*/
@@ -567,6 +628,11 @@ debug*/
frame.setExtendedState( frame.getExtendedState() | Frame.ICONIFIED );
}
/** @since 2.4 */
protected boolean isWindowMaximized() {
return window instanceof Frame && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0;
}
/**
* Maximizes the window.
*/
@@ -615,7 +681,7 @@ debug*/
int maximizedWidth = screenBounds.width;
int maximizedHeight = screenBounds.height;
if( !isMaximizedBoundsFixed() ) {
if( SystemInfo.isWindows && !isMaximizedBoundsFixed() ) {
// on Java 8 to 14, maximized x,y are 0,0 based on all screens in a multi-screen environment
maximizedX = 0;
maximizedY = 0;
@@ -683,6 +749,17 @@ debug*/
}
}
private void maximizeOrRestore() {
if( !(window instanceof Frame) || !((Frame)window).isResizable() )
return;
Frame frame = (Frame) window;
if( (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
restore();
else
maximize();
}
/**
* Closes the window.
*/
@@ -702,9 +779,15 @@ debug*/
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter;
protected void updateNativeTitleBarHeightAndHitTestSpotsLater() {
laterCounter++;
EventQueue.invokeLater( () -> {
updateNativeTitleBarHeightAndHitTestSpots();
laterCounter--;
if( laterCounter == 0 )
updateNativeTitleBarHeightAndHitTestSpots();
} );
}
@@ -722,8 +805,9 @@ debug*/
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( iconLabel.isVisible() ) {
// compute real icon size (without insets; 1px wider for easier hitting)
if( !showIconBesideTitle && iconLabel.isVisible() ) {
// compute real icon size (without insets; 1px larger for easier hitting)
Point location = SwingUtilities.convertPoint( iconLabel, 0, 0, window );
Insets iconInsets = iconLabel.getInsets();
Rectangle iconBounds = new Rectangle(
@@ -734,9 +818,7 @@ debug*/
// if frame is maximized, increase icon bounds to upper-left corner
// of window to allow closing window via double-click in upper-left corner
if( window instanceof Frame &&
(((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
{
if( isWindowMaximized() ) {
iconBounds.height += iconBounds.y;
iconBounds.y = 0;
@@ -751,6 +833,38 @@ debug*/
hitTestSpots.add( iconBounds );
else
appIconBounds = iconBounds;
} else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) {
FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI();
// compute real icon bounds
Insets insets = titleLabel.getInsets();
Rectangle viewR = new Rectangle( insets.left, insets.top,
titleLabel.getWidth() - insets.left - insets.right,
titleLabel.getHeight() - insets.top - insets.bottom );
Rectangle iconR = new Rectangle();
Rectangle textR = new Rectangle();
ui.layoutCL( titleLabel, titleLabel.getFontMetrics( titleLabel.getFont() ),
titleLabel.getText(), titleLabel.getIcon(),
viewR, iconR, textR );
// Windows shows the window system menu only in the upper-left corner
if( iconR.x == 0 ) {
// convert icon location to window coordinates
Point location = SwingUtilities.convertPoint( titleLabel, 0, 0, window );
iconR.x += location.x;
iconR.y += location.y;
// make icon bounds 1px larger for easier hitting
iconR.x -= 1;
iconR.y -= 1;
iconR.width += 2;
iconR.height += 2;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconR );
else
appIconBounds = iconR;
}
}
Rectangle r = getNativeHitTestSpot( buttonPanel );
@@ -759,44 +873,73 @@ debug*/
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBarPlaceholder );
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
Component horizontalGlue = findHorizontalGlue( menuBar );
if( horizontalGlue != null ) {
// If menu bar is embedded and contains a horizontal glue component,
// then split the hit test spot into two spots so that
// the glue component area can used to move the window.
// if frame is resizable and not maximized, make menu bar hit test spot smaller at top
// to have a small area above the menu bar to resize the window
if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
// limit to 8, because Windows does not use a larger height
int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
r.y += resizeHeight;
r.height -= resizeHeight;
}
Point glueLocation = SwingUtilities.convertPoint( horizontalGlue, 0, 0, window );
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
int trailingWidth = (r.x + r.width - HIT_TEST_SPOT_GROW) - glueLocation.x;
r.width -= trailingWidth;
r2 = new Rectangle( glueLocation.x + horizontalGlue.getWidth(), r.y, trailingWidth, r.height );
} else {
int leadingWidth = (glueLocation.x + horizontalGlue.getWidth()) - (r.x + HIT_TEST_SPOT_GROW);
r.x += leadingWidth;
r.width -= leadingWidth;
r2 = new Rectangle( glueLocation.x -leadingWidth, r.y, leadingWidth, r.height );
int count = menuBar.getComponentCount();
for( int i = count - 1; i >= 0; i-- ) {
Component c = menuBar.getComponent( i );
if( c instanceof Box.Filler ||
(c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
{
// If menu bar is embedded and contains a horizontal glue or caption component,
// then split the hit test spot so that
// the glue/caption component area can be used to move the window.
Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
int x2 = glueLocation.x + c.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
if( r2.width > 0 )
hitTestSpots.add( r2 );
}
r2.grow( HIT_TEST_SPOT_GROW, HIT_TEST_SPOT_GROW );
hitTestSpots.add( r2 );
}
hitTestSpots.add( r );
}
}
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots, appIconBounds );
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
/*debug
debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
debugCloseButtonBounds = closeButtonBounds;
repaint();
debug*/
}
private Rectangle boundsInWindow( JComponent c ) {
return c.isShowing()
? SwingUtilities.convertRectangle( c.getParent(), c.getBounds(), window )
: null;
}
protected Rectangle getNativeHitTestSpot( JComponent c ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
@@ -804,17 +947,16 @@ debug*/
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
// slightly increase rectangle so that component receives mouseExit events
r.grow( HIT_TEST_SPOT_GROW, HIT_TEST_SPOT_GROW );
return r;
}
private static final int HIT_TEST_SPOT_GROW = 2;
/*debug
private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
private Rectangle debugCloseButtonBounds;
debug*/
//---- class FlatTitlePaneBorder ------------------------------------------
@@ -834,7 +976,7 @@ debug*/
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 );
if( hasNativeCustomDecoration() && !isWindowMaximized( c ) )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets;
@@ -846,14 +988,14 @@ debug*/
Border menuBarBorder = getMenuBarBorder();
if( menuBarBorder != null ) {
// if menu bar is embedded, paint menu bar border
menuBarBorder.paintBorder( c, g, x, y, width, height );
menuBarBorder.paintBorder( rootPane.getJMenuBar(), g, x, y, width, height );
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) {
// paint border between title pane and content if border color is specified
float lineHeight = UIScale.scale( (float) 1 );
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
}
if( hasNativeCustomDecoration() && !isWindowMaximized( c ) )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
}
@@ -861,12 +1003,6 @@ debug*/
JMenuBar menuBar = rootPane.getJMenuBar();
return hasVisibleEmbeddedMenuBar( menuBar ) ? menuBar.getBorder() : null;
}
protected boolean isWindowMaximized( Component c ) {
return window instanceof Frame
? (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0
: false;
}
}
//---- class FlatTitleLabelUI ---------------------------------------------
@@ -880,32 +1016,109 @@ debug*/
}
@Override
protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) {
boolean hasEmbeddedMenuBar = hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() );
int labelWidth = l.getWidth();
int textWidth = labelWidth - (textX * 2);
int gap = UIScale.scale( menuBarTitleGap );
protected void installDefaults( JLabel c ) {
super.installDefaults( c );
// The passed in textX coordinate is always to horizontally center the text within the label bounds.
// Modify textX so that the text is painted either centered within the window bounds or leading aligned.
boolean center = hasEmbeddedMenuBar ? centerTitleIfMenuBarEmbedded : centerTitle;
if( center ) {
// If window is wide enough, center title within window bounds.
// Otherwise leave it centered within free space (label bounds).
int centeredTextX = ((l.getParent().getWidth() - textWidth) / 2) - l.getX();
if( centeredTextX >= gap && centeredTextX + textWidth <= labelWidth - gap )
textX = centeredTextX;
} else {
// leading aligned
boolean leftToRight = getComponentOrientation().isLeftToRight();
Insets insets = l.getInsets();
int leadingInset = hasEmbeddedMenuBar ? gap : (leftToRight ? insets.left : insets.right);
int leadingTextX = leftToRight ? leadingInset : labelWidth - leadingInset - textWidth;
if( leftToRight ? leadingTextX < textX : leadingTextX > textX )
textX = leadingTextX;
if( titleFont != null )
c.setFont( titleFont );
}
@Override
protected String layoutCL( JLabel label, FontMetrics fontMetrics, String text, Icon icon,
Rectangle viewR, Rectangle iconR, Rectangle textR )
{
JMenuBar menuBar = rootPane.getJMenuBar();
boolean hasEmbeddedMenuBar = hasVisibleEmbeddedMenuBar( menuBar );
boolean hasEmbeddedLeadingMenus = hasEmbeddedMenuBar && hasLeadingMenus( menuBar );
boolean leftToRight = getComponentOrientation().isLeftToRight();
if( hasEmbeddedMenuBar ) {
int minGap = UIScale.scale( menuBarTitleMinimumGap );
// apply minimum leading gap (between embedded menu bar and title)
if( hasEmbeddedLeadingMenus ) {
if( leftToRight )
viewR.x += minGap;
viewR.width -= minGap;
}
// apply minimum trailing gap (between title and right aligned components of embedded menu bar)
Component horizontalGlue = findHorizontalGlue( menuBar );
if( horizontalGlue != null && menuBar.getComponent( menuBar.getComponentCount() - 1 ) != horizontalGlue ) {
if( !leftToRight )
viewR.x += minGap;
viewR.width -= minGap;
}
}
super.paintEnabledText( l, g, s, textX, textY );
// compute icon width and gap (if icon is show besides the title)
int iconTextGap = 0;
int iconWidthAndGap = 0;
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
iconTextGap = leftToRight ? iconInsets.right : iconInsets.left;
iconWidthAndGap = icon.getIconWidth() + iconTextGap;
}
// layout title and icon (if show besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
viewR, iconR, textR,
iconTextGap );
// compute text X location
if( !clippedText.equals( text ) ) {
// if text is clipped, align to left (or right)
textR.x = leftToRight
? viewR.x + iconWidthAndGap
: viewR.x + viewR.width - iconWidthAndGap - textR.width;
} else {
int leadingGap = hasEmbeddedLeadingMenus ? UIScale.scale( menuBarTitleGap - menuBarTitleMinimumGap ) : 0;
boolean center = hasEmbeddedLeadingMenus ? centerTitleIfMenuBarEmbedded : centerTitle;
if( center ) {
// If window is wide enough, center title within window bounds.
// Otherwise, center within free space (label bounds).
Container parent = label.getParent();
int centeredTextX = (parent != null) ? ((parent.getWidth() - textR.width - iconWidthAndGap) / 2) + iconWidthAndGap - label.getX() : -1;
textR.x = (centeredTextX >= viewR.x + leadingGap && centeredTextX + textR.width <= viewR.x + viewR.width - leadingGap)
? centeredTextX
: viewR.x + ((viewR.width - textR.width - iconWidthAndGap) / 2) + iconWidthAndGap;
} else {
// leading aligned with leading gap, which is reduced if space is rare
textR.x = leftToRight
? Math.min( viewR.x + leadingGap + iconWidthAndGap, viewR.x + viewR.width - textR.width )
: Math.max( viewR.x + viewR.width - leadingGap - iconWidthAndGap - textR.width, viewR.x );
}
}
// compute icon X location (relative to text X location)
if( icon != null ) {
iconR.x = leftToRight
? textR.x - iconWidthAndGap
: textR.x + textR.width + iconTextGap;
}
return clippedText;
}
private boolean hasLeadingMenus( JMenuBar menuBar ) {
// check whether menu bar is empty
if( menuBar.getComponentCount() == 0 || menuBar.getWidth() == 0 )
return false;
// check whether menu bar has a leading glue component
// (no menus/components at left side)
Component horizontalGlue = findHorizontalGlue( menuBar );
if( horizontalGlue != null ) {
boolean leftToRight = getComponentOrientation().isLeftToRight();
if( (leftToRight && horizontalGlue.getX() == 0) ||
(!leftToRight && horizontalGlue.getX() + horizontalGlue.getWidth() == menuBar.getWidth()) )
return false;
}
return true;
}
}
@@ -946,7 +1159,7 @@ debug*/
activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots();
if( hasNativeCustomDecoration() )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
@@ -957,7 +1170,7 @@ debug*/
activeChanged( false );
updateNativeTitleBarHeightAndHitTestSpots();
if( hasNativeCustomDecoration() )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
@@ -972,23 +1185,30 @@ debug*/
//---- interface MouseListener ----
private Point dragOffset;
private boolean nativeMove;
private long lastSingleClickWhen;
@Override
public void mouseClicked( MouseEvent e ) {
// on Linux, when using native library, the mouse clicked event
// is usually not sent and maximize/restore is done in mouse pressed event
// this check is here for the case that a mouse clicked event comes thru for some reason
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
lastSingleClickWhen = 0;
maximizeOrRestore();
}
return;
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( e.getSource() == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasNativeCustomDecoration() &&
window instanceof Frame &&
((Frame)window).isResizable() )
{
} else if( !hasNativeCustomDecoration() ) {
// maximize/restore on double-click
Frame frame = (Frame) window;
if( (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
restore();
else
maximize();
maximizeOrRestore();
}
}
}
@@ -998,7 +1218,56 @@ debug*/
if( window == null )
return; // should newer occur
// on Linux, show window menu
if( SwingUtilities.isRightMouseButton( e ) &&
SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) )
{
e.consume();
FlatNativeLinuxLibrary.showWindowMenu( window, e );
return;
}
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
nativeMove = false;
// on Linux, move or maximize/restore window
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// The fired Java mouse events, when doing a double-click and the first click
// sends a _NET_WM_MOVERESIZE message, are different for various Linux distributions:
// CentOS 7 (GNOME 3.28.2, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 20.04 (GNOME 3.36.1, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 22.04 (GNOME 42.2, Wayland): PRESSED(clickCount=1) RELEASED(clickCount=1) CLICKED(clickCount=1)
// Kubuntu 22.04 (KDE 5.24.4, X11): PRESSED(clickCount=1) PRESSED(clickCount=1) RELEASED(clickCount=1)
// double-click is not always recognized in Java when using _NET_WM_MOVERESIZE message
int clickCount = e.getClickCount();
if( clickCount == 1 && lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() )
clickCount = 2;
switch( clickCount ) {
case 1:
// move window via _NET_WM_MOVERESIZE message
e.consume();
nativeMove = FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, FlatNativeLinuxLibrary.MOVE );
lastSingleClickWhen = e.getWhen();
break;
case 2:
// maximize/restore on double-click
// also done here because no mouse clicked event is sent when using _NET_WM_MOVERESIZE message
lastSingleClickWhen = 0;
maximizeOrRestore();
break;
}
}
}
private int getMultiClickInterval() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "awt.multiClickInterval" );
return (value instanceof Integer) ? (Integer) value : 500;
}
@Override public void mouseReleased( MouseEvent e ) {}
@@ -1009,9 +1278,15 @@ debug*/
@Override
public void mouseDragged( MouseEvent e ) {
if( window == null )
if( window == null || dragOffset == null )
return; // should newer occur
if( nativeMove )
return;
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
if( hasNativeCustomDecoration() )
return; // do nothing if having native window border

View File

@@ -21,12 +21,8 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import java.util.Iterator;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.UIManager;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
@@ -52,15 +48,29 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ToggleButton.iconTextGap int
* @uiDefault ToggleButton.startBackground Color optional; if set, a gradient paint is used and ToggleButton.background is ignored
* @uiDefault ToggleButton.endBackground Color optional; if set, a gradient paint is used
* @uiDefault ToggleButton.pressedBackground Color
* @uiDefault ToggleButton.focusedBackground Color optional
* @uiDefault ToggleButton.focusedForeground Color optional
* @uiDefault ToggleButton.hoverBackground Color optional
* @uiDefault ToggleButton.hoverForeground Color optional
* @uiDefault ToggleButton.pressedBackground Color optional
* @uiDefault ToggleButton.pressedForeground Color optional
* @uiDefault ToggleButton.selectedBackground Color
* @uiDefault ToggleButton.selectedForeground Color
* @uiDefault ToggleButton.disabledBackground Color optional
* @uiDefault ToggleButton.disabledText Color
* @uiDefault ToggleButton.disabledSelectedBackground Color
* @uiDefault ToggleButton.disabledSelectedForeground Color optional
* @uiDefault Button.paintShadow boolean default is false
* @uiDefault Button.shadowWidth int default is 2
* @uiDefault Button.shadowColor Color optional
* @uiDefault ToggleButton.toolbar.hoverBackground Color
* @uiDefault ToggleButton.toolbar.hoverForeground Color optional
* @uiDefault ToggleButton.toolbar.pressedBackground Color
* @uiDefault ToggleButton.toolbar.pressedForeground Color optional
* @uiDefault ToggleButton.toolbar.selectedBackground Color
* @uiDefault ToggleButton.toolbar.selectedForeground Color optional
* @uiDefault ToggleButton.toolbar.disabledSelectedBackground Color optional
* @uiDefault ToggleButton.toolbar.disabledSelectedForeground Color optional
*
* <!-- FlatToggleButtonUI -->
*
@@ -68,8 +78,11 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ToggleButton.tab.underlineColor Color
* @uiDefault ToggleButton.tab.disabledUnderlineColor Color
* @uiDefault ToggleButton.tab.selectedBackground Color optional
* @uiDefault ToggleButton.tab.selectedForeground Color optional
* @uiDefault ToggleButton.tab.hoverBackground Color
* @uiDefault ToggleButton.tab.hoverForeground Color optional
* @uiDefault ToggleButton.tab.focusBackground Color
* @uiDefault ToggleButton.tab.focusForeground Color optional
*
*
* @author Karl Tauber
@@ -81,8 +94,11 @@ public class FlatToggleButtonUI
@Styleable(dot=true) protected Color tabUnderlineColor;
@Styleable(dot=true) protected Color tabDisabledUnderlineColor;
@Styleable(dot=true) protected Color tabSelectedBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color tabSelectedForeground;
@Styleable(dot=true) protected Color tabHoverBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color tabHoverForeground;
@Styleable(dot=true) protected Color tabFocusBackground;
/** @since 2.3 */ @Styleable(dot=true) protected Color tabFocusForeground;
private boolean defaults_initialized = false;
@@ -115,8 +131,11 @@ public class FlatToggleButtonUI
tabUnderlineColor = UIManager.getColor( "ToggleButton.tab.underlineColor" );
tabDisabledUnderlineColor = UIManager.getColor( "ToggleButton.tab.disabledUnderlineColor" );
tabSelectedBackground = UIManager.getColor( "ToggleButton.tab.selectedBackground" );
tabSelectedForeground = UIManager.getColor( "ToggleButton.tab.selectedForeground" );
tabHoverBackground = UIManager.getColor( "ToggleButton.tab.hoverBackground" );
tabHoverForeground = UIManager.getColor( "ToggleButton.tab.hoverForeground" );
tabFocusBackground = UIManager.getColor( "ToggleButton.tab.focusBackground" );
tabFocusForeground = UIManager.getColor( "ToggleButton.tab.focusForeground" );
defaults_initialized = true;
}
@@ -143,6 +162,7 @@ public class FlatToggleButtonUI
b.repaint();
break;
case TAB_BUTTON_UNDERLINE_PLACEMENT:
case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND:
@@ -164,11 +184,7 @@ public class FlatToggleButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
Iterator<String> it = infos.keySet().iterator();
while( it.hasNext() ) {
if( it.next().startsWith( "help." ) )
it.remove();
}
infos.keySet().removeIf( s -> s.startsWith( "help." ) );
return infos;
}
@@ -201,13 +217,42 @@ public class FlatToggleButtonUI
// paint underline if selected
if( selected ) {
int underlineHeight = UIScale.scale( clientPropertyInt( c, TAB_BUTTON_UNDERLINE_HEIGHT, tabUnderlineHeight ) );
int underlineThickness = UIScale.scale( clientPropertyInt( c, TAB_BUTTON_UNDERLINE_HEIGHT, tabUnderlineHeight ) );
g.setColor( c.isEnabled()
? clientPropertyColor( c, TAB_BUTTON_UNDERLINE_COLOR, tabUnderlineColor )
: tabDisabledUnderlineColor );
g.fillRect( 0, height - underlineHeight, width, underlineHeight );
int placement = clientPropertyInt( c, TAB_BUTTON_UNDERLINE_PLACEMENT, SwingConstants.BOTTOM );
switch (placement) {
case SwingConstants.TOP:
g.fillRect( 0, 0, width, underlineThickness );
break;
case SwingConstants.LEFT:
g.fillRect( 0, 0, underlineThickness, height );
break;
case SwingConstants.RIGHT:
g.fillRect( width - underlineThickness, 0, underlineThickness, height );
break;
case SwingConstants.BOTTOM:
default:
g.fillRect( 0, height - underlineThickness, width, underlineThickness );
}
}
} else
super.paintBackground( g, c );
}
@Override
protected Color getForeground( JComponent c ) {
if( isTabButton( c ) ) {
if( !c.isEnabled() )
return disabledText;
if( tabSelectedForeground != null && ((AbstractButton)c).isSelected() )
return tabSelectedForeground;
return buttonStateColor( c, c.getForeground(), disabledText,
tabFocusForeground, tabHoverForeground, null );
} else
return super.getForeground( c );
}
}

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