Merge pull request #1 from JFormDesigner/master

update fork
This commit is contained in:
basix86
2020-11-04 09:48:40 +01:00
committed by GitHub
562 changed files with 50686 additions and 8785 deletions

View File

@@ -5,7 +5,8 @@ jdk:
- openjdk8 - openjdk8
- openjdk9 - openjdk9
- openjdk11 - openjdk11
- openjdk13 - openjdk14
- openjdk15
before_cache: before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock

View File

@@ -1,7 +1,369 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## Unreleased ## 0.44-SNAPSHOT
#### New features and improvements
- TabbedPane: Replaced forward/backward scrolling arrow buttons with "Show
Hidden Tabs" button. If pressed, it shows a popup menu that contains (partly)
hidden tabs and selecting one activates that tab. If you prefer
forward/backward buttons, use `UIManager.put(
"TabbedPane.hiddenTabsNavigation", "arrowButtons" )`. (PR #190; issue #40)
- TabbedPane: Support scrolling tabs with mouse wheel (if `tabLayoutPolicy` is
`SCROLL_TAB_LAYOUT`). (PR #187; issue #40)
- TabbedPane: Repeat scrolling as long as arrow buttons are pressed. (PR #187;
issue #40)
- TabbedPane: Support adding custom components to left and right sides of tabs
area. (set client property `JTabbedPane.leadingComponent` or
`JTabbedPane.trailingComponent` to a `java.awt.Component`) (PR #192; issue
#40)
- TabbedPane: Support closable tabs. (PR #193; issues #31 and #40)
- TabbedPane: Support minimum or maximum tab widths. (set client property
`JTabbedPane.minimumTabWidth` or `JTabbedPane.maximumTabWidth` to an integer)
(PR #199)
- TabbedPane: Support alignment of tab area. (set client property
`JTabbedPane.tabAreaAlignment` to `"leading"`, `"trailing"`, `"center"` or
`"fill"`) (PR #199)
- TabbedPane: Support equal and compact tab width modes. (set client property
`JTabbedPane.tabWidthMode` to `"preferred"`, `"equal"` or `"compact"`) (PR
#199)
- TabbedPane: Support left, right, top and bottom tab icon placement. (set
client property `JTabbedPane.tabIconPlacement` to `SwingConstants.LEADING`,
`SwingConstants.TRAILING`, `SwingConstants.TOP` or `SwingConstants.BOTTOM`)
(PR #199)
- Support painting separator line between window title and content (use UI value
`TitlePane.borderColor`). (issue #184)
- Extras: `FlatSVGIcon` now allows specifying icon width and height in
constructors. (issue #196)
- SplitPane: Hide not applicable expand/collapse buttons. Added tooltips to
expand/collapse buttons. (issue #198)
#### Fixed bugs
- Custom window decorations: Not visible menu bar is now ignored in layout.
- Popups using `JToolTip` components did not respect their location. (issue
#188; regression in 0.42 in fix for #164)
## 0.43
#### New features and improvements
- TabbedPane: Made tabs separator color lighter in dark themes so that it is
easier to recognize the tabbed pane.
- TabbedPane: Added top and bottom tab insets to avoid that large tab icons are
painted over active tab underline.
- TabbedPane: Support hiding separator between tabs and content area (set client
property `JTabbedPane.showContentSeparator` to `false`).
- CheckBoxMenuItem and RadioButtonMenuItem: Improved checkmark background colors
of selected menu items that have also an icon. This makes it is easier to
recognize selected menu items.
- Windows: Made scaling compatible with Windows OS scaling, which distinguish
between "screen scaling" and "text scaling". (issue #175)
#### Fixed bugs
- ComboBox: If using own `JTextField` as editor, default text field border is
now removed to avoid duplicate border.
- ComboBox: Limit popup width to screen width for very long items. (issue #182)
- FileChooser: Fixed localizing special Windows folders (e.g. "Documents") and
enabled hiding known file extensions (if enabled in Windows Explorer). (issue
#178)
- Spinner: Fixed `NullPointerException` in case that arrow buttons were removed
to create button-less spinner. (issue #181)
## 0.42
#### New features and improvements
- Demo: Improved "SplitPane & Tabs" and "Data Components" tabs.
- Demo: Menu items "File > Open" and "File > Save As" now show file choosers.
- InternalFrame: Support draggable border for resizing frame inside of the
visible frame border. (issue #121)
- `FlatUIDefaultsInspector` added (see [FlatLaf Extras](flatlaf-extras)). A
simple UI defaults inspector that shows a window with all UI defaults used in
current theme (look and feel).
- Made disabled text color slightly lighter in dark themes for better
readability. (issue #174)
- PasswordField: Support disabling Caps Lock warning icon. (issue #172)
#### Fixed bugs
- TextComponents: Fixed text color of disabled text components in dark themes.
- Custom window decorations: Fixed wrong window placement when moving window to
another screen with different scaling factor. (issue #166)
- Custom window decorations: Fixed wrong window bounds when resizing window to
another screen with different scaling factor. (issue #166)
- Fixed occasional wrong positioning of heavy weight popups when using multiple
screens with different scaling factors. (issue #166)
- ToolTip: Avoid that tooltip hides owner component. (issue #164)
## 0.41
#### New features and improvements
- Added API to register packages or folders where FlatLaf searches for
application specific properties files with custom UI defaults (see
`FlatLaf.registerCustomDefaultsSource(...)` methods).
- Demo: Show hint popups to guide users to some features of the FlatLaf Demo
application.
- Extras: `FlatSVGIcon` now allows specifying `ClassLoader` that is used to load
SVG file. (issue #163)
- Smoother transition from old to new theme, independent of UI complexity, when
using animated theme change (see [FlatLaf Extras](flatlaf-extras)).
#### Fixed bugs
- Button: "selected" state was not shown. (issue #161)
- TextArea: Update background color property if enabled or editable state
changes in the same way as Swing does it for all other text components. (issue
#147)
- Demo: Fixed restoring last used theme on startup. (regression in 0.39)
- Custom window decorations: Fixed iconify, maximize and close icon colors if
window is inactive.
- Custom window decorations: Fixed title pane background color in IntelliJ
themes if window is inactive.
- Fixed sub-pixel text rendering in animated theme change (see
[FlatLaf Extras](flatlaf-extras)).
#### Other Changes
- Extras: Updated dependency
[svgSalamander](https://github.com/JFormDesigner/svgSalamander) to version
1.1.2.3.
## 0.40
#### New features
- Table: Detect whether component is used in cell editor and automatically
disable round border style and reduce cell editor outer border width (used for
focus indicator) to zero. (issue #148)
- ComboBox, Spinner and TextField: Support disabling round border style per
component, if globally enabled (set client property `JComponent.roundRect` to
`false`). (issue #148)
#### Fixed bugs
- Custom window decorations: Embedded menu bar did not always respond to mouse
events after adding menus and when running in JetBrains Runtime. (issue #151)
- IntelliJ Themes: Fixed NPE in Solarized themes on scroll bar hover.
## 0.39
#### New features
- Animated theme change (see [FlatLaf Extras](flatlaf-extras)). Used in Demo.
- Demo: Added combo box above themes list to show only light or dark themes.
- IntelliJ Themes:
- Added "Arc Dark", "Arc Dark - Orange", "Carbon" and "Cobalt 2" themes.
- Replaced "Solarized" themes with much better ones from 4lex4.
- Updated "Arc", "One Dark" and "Vuesion" themes.
- ScrollPane: Enable/disable smooth scrolling per component if client property
"JScrollPane.smoothScrolling" is set to a `Boolean` on `JScrollPane`.
- ScrollBar: Increased minimum thumb size on macOS and Linux from 8 to 18
pixels. On Windows, it is now 10 pixels. (issue #131)
- Button: Support specifying button border width.
- ComboBox: Changed maximum row count of popup list to 15 (was 20). Set UI value
`ComboBox.maximumRowCount` to any integer to use a different value.
#### Fixed bugs
- Custom window decorations: Fixed maximized window bounds when programmatically
maximizing window. E.g. restoring window state at startup. (issue #129)
- InternalFrame: Title pane height was too small when iconify, maximize and
close buttons are hidden. (issue #132)
- ToolTip: Do not show empty tooltip component if tooltip text is an empty
string. (issue #134)
- ToolTip: Fixed truncated text in HTML formatted tooltip on HiDPI displays.
(issue #142)
- ComboBox: Fixed width of popup, which was too small if popup is wider than
combo box and vertical scroll bar is visible. (issue #137)
- MenuItem on macOS: Removed plus characters from accelerator text and made
modifier key order conform with macOS standard. (issue #141)
- FileChooser: Fixed too small text field when renaming a file/directory in Flat
IntelliJ/Darcula themes. (issue #143)
- IntelliJ Themes: Fixed text colors in ProgressBar. (issue #138)
## 0.38
- Hide focus indicator when window is inactive.
- Custom window decorations: Improved/fixed window border color in dark themes.
- Custom window decorations: Hide window border if window is maximized.
- Custom window decorations: Center title if menu bar is embedded.
- Custom window decorations: Cursor of components (e.g. TextField) was not
changed. (issue #125)
- CheckBox: Fixed colors in light IntelliJ themes. (issue #126; regression in
0.37)
- InternalFrame: Use default icon in internal frames. (issue #122)
## 0.37
- Custom window decorations (Windows 10 only; PR #108; issues #47 and #82)
support:
- dark window title panes
- embedding menu bar into window title pane
- native Windows 10 borders and behavior when running in
[JetBrains Runtime 11](https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime)
or later (the JRE that IntelliJ IDEA uses)
- CheckBox and RadioButton: Support changing selected icon style from outline to
filled (as in FlatLaf IntelliJ theme) with `UIManager.put(
"CheckBox.icon.style", "filled" );`.
- Button and ToggleButton: Support disabled background color (use UI values
`Button.disabledBackground` and `ToggleButton.disabledBackground`). (issue
#112)
- Button and ToggleButton: Support making buttons square (set client property
`JButton.squareSize` to `true`). (issue #118)
- ScrollBar: Support pressed track, thumb and button colors (use UI values
`ScrollBar.pressedTrackColor`, `ScrollBar.pressedThumbColor` and
`ScrollBar.pressedButtonBackground`). (issue #115)
- ComboBox: Support changing arrow button style (set UI value
`ComboBox.buttonStyle` to `auto` (default), `button` or `none`). (issue #114)
- Spinner: Support changing arrows button style (set UI value
`Spinner.buttonStyle` to `button` (default) or `none`).
- TableHeader: Support top/bottom/left positioned sort arrow when using
[Glazed Lists](https://github.com/glazedlists/glazedlists). (issue #113)
- Button, CheckBox, RadioButton and ToggleButton: Do not paint focus indicator
if `AbstractButton.isFocusPainted()` returns `false`.
- ComboBox: Increase maximum row count of popup list to 20 (was 8). Set UI value
`ComboBox.maximumRowCount` to any integer to use a different value.
- Fixed/improved vertical position of text when scaled on HiDPI screens on
Windows.
- IntelliJ Themes: Updated Gradianto Themes.
- IntelliJ Themes: Fixed menu bar and menu item margins in all Material UI Lite
themes.
## 0.36
- ScrollBar: Made styling more flexible by supporting insets and arc for track
and thumb. (issue #103)
- ScrollBar: Use round thumb on macOS and Linux to make it look similar to
native platform scroll bars. (issue #103)
- ComboBox: Minimum width is now 72 pixels (was ~50 for non-editable and ~130
for editable comboboxes).
- ComboBox: Support custom borders in combobox editors. (issue #102)
- Button: Support non-square icon-only buttons. (issue #110)
- Ubuntu Linux: Fixed poorly rendered font. (issue #105)
- macOS Catalina: Use Helvetica Neue font.
- `FlatInspector` added (see [FlatLaf Extras](flatlaf-extras)).
## 0.35
- Added drop shadows to popup menus, combobox popups, tooltips and internal
frames. (issue #94)
- Support different component border colors to indicate errors, warnings or
custom state (set client property `JComponent.outline` to `error`, `warning`
or any `java.awt.Color`).
- Button and ToggleButton: Support round button style (set client property
`JButton.buttonType` to `roundRect`).
- ComboBox, Spinner and TextField: Support round border style (set client
property `JComponent.roundRect` to `true`).
- Paint nicely rounded buttons, comboboxes, spinners and text fields when
setting `Button.arc`, `Component.arc` or `TextComponent.arc` to a large value
(e.g. 1000).
- Added Java 9 module descriptor to `flatlaf-extras-<version>.jar` and
`flatlaf-swingx-<version>.jar`.
- CheckBox and RadioButton: Flag `opaque` is no longer ignored when checkbox or
radio button is used as table cell renderer. (issue #77)
- FileChooser: Use system icons. (issue #100)
- FileChooser: Fixed missing labels in file chooser when running on Java 9 or
later. (issue #98)
- PasswordField: Do not apply minimum width if `columns` property is greater
than zero.
## 0.34
- Menus: New menu item renderer brings stable left margins, right aligned
accelerators and larger gap between text and accelerator. This makes menus
look more modern and more similar to native platform menus.
- New underline menu selection style that displays selected menu items similar
to tabs (to enable use `UIManager.put( "MenuItem.selectionType", "underline"
);`).
- Menus: Fixed text color of selected menu items that use HTML. (issue #87)
- Menus: On Windows, pressing <kbd>F10</kbd> now activates the menu bar without
showing a menu popup (as usual on Windows platform). On other platforms the
first menu popup is shown.
- Menus: On Windows, releasing <kbd>Alt</kbd> key now activates the menu bar (as
usual on Windows platform). (issue #43)
- Menus: Fixed inconsistent left padding in menu items. (issue #3)
- Menus: Fixed: Setting `iconTextGap` property on a menu item did increase left
and right margins. (issue #54)
- Hide mnemonics if window is deactivated (e.g. <kbd>Alt+Tab</kbd> to another
window). (issue #43)
- macOS: Enabled drop shadows for popup menus and combobox popups. (issue #94)
- macOS: Fixed NPE if using `JMenuBar` in `JInternalFrame` and macOS screen menu
bar is enabled (with `-Dapple.laf.useScreenMenuBar=true`). (issue #90)
## 0.33
- Improved creation of disabled grayscale icons used in disabled buttons, labels
and tabs. They now have more contrast and are lighter in light themes and
darker in dark themes. (issue #70)
- IntelliJ Themes: Fixed ComboBox size and Spinner border in all Material UI
Lite themes and limit tree row height in all Material UI Lite themes and some
other themes.
- IntelliJ Themes: Material UI Lite themes did not work when using
[IntelliJ Themes Pack](flatlaf-intellij-themes) addon. (PR #88, issue #89)
- IntelliJ Themes: Added Java 9 module descriptor to
`flatlaf-intellij-themes-<version>.jar`.
## 0.32
- New [IntelliJ Themes Pack](flatlaf-intellij-themes) addon bundles many popular
open-source 3rd party themes from JetBrains Plugins Repository into a JAR and
provides Java classes to use them.
- IntelliJ Themes: Fixed button and toggle button colors. (issue #86)
- Updated IntelliJ Themes in demo to the latest versions.
- ToggleButton: Compute selected background color based on current component
background. (issue #32)
## 0.31
- Focus indication border (or background) no longer hidden when temporary
loosing focus (e.g. showing a popup menu).
- List, Table and Tree: Item selection color of focused components no longer
change from blue to gray when temporary loosing focus (e.g. showing a popup
menu).
## 0.30
- Windows: Fixed rendering of Unicode characters. Previously not all Unicode
characters were rendered on Windows. (issue #81)
## 0.29
- Linux: Fixed scaling if `GDK_SCALE` environment variable is set or if running
on JetBrains Runtime. (issue #69)
- Tree: Fixed repainting wide selection on focus gained/lost.
- ComboBox: No longer ignore `JComboBox.prototypeDisplayValue` when computing
popup width. (issue #80)
- Support changing default font used for all components with automatic scaling
UI if using larger font. Use `UIManager.put( "defaultFont", myFont );`
- No longer use system property `sun.java2d.uiScale`. (Java 8 only)
- Support specifying custom scale factor in system property `flatlaf.uiScale`
also for Java 9 and later.
- Demo: Support using own FlatLaf themes (`.properties` files) that are located
in working directory of Demo application. Shown in the "Themes" list under
category "Current Directory".
## 0.28
- PasswordField: Warn about enabled Caps Lock. - PasswordField: Warn about enabled Caps Lock.
- TabbedPane: Support <kbd>Ctrl+TAB</kbd> / <kbd>Ctrl+Shift+TAB</kbd> to switch - TabbedPane: Support <kbd>Ctrl+TAB</kbd> / <kbd>Ctrl+Shift+TAB</kbd> to switch
@@ -11,6 +373,10 @@ FlatLaf Change Log
- IntelliJ Themes: Added Gradianto themes to demo. - IntelliJ Themes: Added Gradianto themes to demo.
- Button, CheckBox and RadioButton: Fixed NPE when button has children. (PR #68) - Button, CheckBox and RadioButton: Fixed NPE when button has children. (PR #68)
- ScrollBar: Improved colors. - ScrollBar: Improved colors.
- Reviewed (and tested) all key bindings on Windows and macOS. Linux key
bindings are equal to Windows key bindings. macOS key bindings are slightly
different for platform specific behavior.
- UI default values are no longer based on Metal/Aqua UI defaults.
## 0.27 ## 0.27

View File

@@ -11,19 +11,18 @@ scales on **HiDPI** displays and runs on Java 8 or newer.
The look is heavily inspired by **Darcula** and **IntelliJ** themes from The look is heavily inspired by **Darcula** and **IntelliJ** themes from
IntelliJ IDEA 2019.2+ and uses almost the same colors and icons. IntelliJ IDEA 2019.2+ and uses almost the same colors and icons.
![Flat Light Demo](images/FlatLightDemo.png) ![Flat Light](images/flat_light.png)
![Flat Dark Demo](images/FlatDarkDemo.png) ![Flat Dark](images/flat_dark.png)
IntelliJ Platform Themes IntelliJ Platform Themes
------------------------ ------------------------
FlatLaf can use 3rd party themes created for IntelliJ Platform: FlatLaf can use 3rd party themes created for IntelliJ Platform (see
[IntelliJ Themes Pack](flatlaf-intellij-themes)):
![Cyan Light Demo](images/CyanLightDemo.png) ![IntelliJ Platform Themes](images/intellij_platform_themes.png)
![Dark Purple Demo](images/DarkPurpleDemo.png)
Demo Demo
@@ -68,6 +67,8 @@ docs).
Addons Addons
------ ------
- [IntelliJ Themes Pack](flatlaf-intellij-themes)
- [Extras](flatlaf-extras)
- [SwingX](flatlaf-swingx) - [SwingX](flatlaf-swingx)
- [JIDE Common Layer](flatlaf-jide-oss) - [JIDE Common Layer](flatlaf-jide-oss)
@@ -77,7 +78,16 @@ Projects using FlatLaf
- [NetBeans](https://netbeans.apache.org/) 11.3 - [NetBeans](https://netbeans.apache.org/) 11.3
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5 - [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
- [KeyStore Explorer](https://keystore-explorer.org/) 5.4.3
- [OWASP Zed Attack Proxy (ZAP)](https://www.zaproxy.org/) (in weekly releases)
- ![New](images/new.svg) [jAlbum](https://jalbum.net/) 21 (commercial)
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (commercial)
- [Total Validator](https://www.totalvalidator.com/) 15 (commercial)
- [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org)
- [MegaMek](https://github.com/MegaMek/megamek) v0.47.4 and
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder)
0.13.b024
- [Rest Suite](https://github.com/supanadit/restsuite) - [Rest Suite](https://github.com/supanadit/restsuite)
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy) - [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy)
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - [SpringRemote](https://github.com/HaleyWang/SpringRemote)
@@ -87,6 +97,15 @@ Projects using FlatLaf
[mendelson AS2](https://mendelson-e-c.com/as2/), [mendelson AS2](https://mendelson-e-c.com/as2/),
[AS4](https://mendelson-e-c.com/as4/) and [AS4](https://mendelson-e-c.com/as4/) and
[OFTP2](https://mendelson-e-c.com/oftp2) (commercial) [OFTP2](https://mendelson-e-c.com/oftp2) (commercial)
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) 2.2
- [lsfusion platform](https://github.com/lsfusion/platform)
- [Jes - Die Java-EÜR](https://www.jes-eur.de)
- [Mapton](https://mapton.org/) 2.0
([source code](https://github.com/trixon/mapton)) based on NetBeans platform
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE)
- [Sound Analysis](https://github.com/tomasz-herman/SoundAnalysis)
- [RemoteLight](https://github.com/Drumber/RemoteLight) - Multifunctional LED
Control Software
- and more... - and more...

View File

@@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
val releaseVersion = "0.27" val releaseVersion = "0.43"
val developmentVersion = "0.28-SNAPSHOT" val developmentVersion = "0.44-SNAPSHOT"
version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion

34
buildSrc/build.gradle.kts Normal file
View File

@@ -0,0 +1,34 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
`kotlin-dsl`
}
// required for kotlin-dsl or embedded-kotlin plugins
repositories {
jcenter()
}
dependencies {
// NOTE: keep plugin versions in sync with settings.gradle.kts
// "com.jfrog.bintray" plugin
implementation( "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4" )
// "com.jfrog.artifactory" plugin
implementation( "org.jfrog.buildinfo:build-info-extractor-gradle:4.13.0" )
}

View File

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

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "java9" ) {
java {
setSrcDirs( listOf( "src/main/java9" ) )
}
}
}
tasks {
named<JavaCompile>( "compileJava9Java" ) {
sourceCompatibility = "9"
targetCompatibility = "9"
}
jar {
manifest.attributes( "Multi-Release" to "true" )
into( "META-INF/versions/9" ) {
from( sourceSets["java9"].output )
}
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
open class ModuleInfoExtension {
var paths: ArrayList<String> = ArrayList()
fun dependsOn( vararg paths: String ) {
this.paths.addAll( paths )
}
}
val extension = project.extensions.create<ModuleInfoExtension>( "flatlafModuleInfo" )
plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "module-info" ) {
java {
// include "src/main/java" here to get compile errors if classes are
// used from other modules that are not specified in module dependencies
setSrcDirs( listOf( "src/main/module-info", "src/main/java" ) )
}
}
}
tasks {
named<JavaCompile>( "compileModuleInfoJava" ) {
sourceCompatibility = "9"
targetCompatibility = "9"
dependsOn( extension.paths )
options.compilerArgs.add( "--module-path" )
options.compilerArgs.add( configurations.runtimeClasspath.get().asPath )
}
jar {
manifest.attributes( "Multi-Release" to "true" )
into( "META-INF/versions/9" ) {
from( sourceSets["module-info"].output ) {
include( "module-info.class" )
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
open class PublishExtension {
var artifactId: String? = null
var name: String? = null
var description: String? = null
}
val extension = project.extensions.create<PublishExtension>( "flatlafPublish" )
plugins {
`maven-publish`
id( "com.jfrog.bintray" )
id( "com.jfrog.artifactory" )
}
publishing {
publications {
create<MavenPublication>( "maven" ) {
afterEvaluate {
artifactId = extension.artifactId
}
groupId = "com.formdev"
from( components["java"] )
pom {
afterEvaluate {
this@pom.name.set( extension.name )
this@pom.description.set( extension.description )
}
url.set( "https://github.com/JFormDesigner/FlatLaf" )
licenses {
license {
name.set( "The Apache License, Version 2.0" )
url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" )
}
}
developers {
developer {
name.set( "Karl Tauber" )
organization.set( "FormDev Software GmbH" )
organizationUrl.set( "https://www.formdev.com/" )
}
}
scm {
url.set( "https://github.com/JFormDesigner/FlatLaf" )
}
}
}
}
}
bintray {
user = rootProject.extra["bintray.user"] as String?
key = rootProject.extra["bintray.key"] as String?
setPublications( "maven" )
with( pkg ) {
repo = "flatlaf"
afterEvaluate {
this@with.name = extension.artifactId
}
setLicenses( "Apache-2.0" )
vcsUrl = "https://github.com/JFormDesigner/FlatLaf"
with( version ) {
name = project.version.toString()
}
publish = rootProject.extra["bintray.publish"] as Boolean
dryRun = rootProject.extra["bintray.dryRun"] as Boolean
}
}
artifactory {
setContextUrl( "https://oss.jfrog.org" )
publish( closureOf<org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig> {
repository( delegateClosureOf<groovy.lang.GroovyObject> {
setProperty( "repoKey", "oss-snapshot-local" )
setProperty( "username", rootProject.extra["bintray.user"] as String? )
setProperty( "password", rootProject.extra["bintray.key"] as String? )
} )
defaults( delegateClosureOf<groovy.lang.GroovyObject> {
invokeMethod( "publications", "maven" )
setProperty( "publishArtifacts", true )
setProperty( "publishPom", true )
} )
} )
resolve( delegateClosureOf<org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig> {
setProperty( "repoKey", "jcenter" )
} )
}

View File

@@ -0,0 +1,377 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
org.eclipse.jdt.core.formatter.align_with_spaces=false
org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=48
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_record_components=16
org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=33
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=33
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=33
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=33
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=32
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=32
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=0
org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=next_line_on_wrap
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line_on_wrap
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line_on_wrap
org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line_on_wrap
org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line
org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=true
org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=1
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.lineSplit=120
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=tab
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.text_block_indentation=0
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
org.eclipse.jdt.core.formatter.wrap_before_logical_operator=false
org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter

View File

@@ -0,0 +1,3 @@
eclipse.preferences.version=1
formatter_profile=_FlatLaf
formatter_settings_version=19

View File

@@ -16,150 +16,46 @@
plugins { plugins {
`java-library` `java-library`
`maven-publish` `flatlaf-module-info`
id( "com.jfrog.bintray" ) `flatlaf-java9`
id( "com.jfrog.artifactory" ) `flatlaf-publish`
} }
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "module-info" ) {
java { java {
// include "src/main/java" here to get compile errors if classes are withSourcesJar()
// used from other modules that are not specified in module dependencies withJavadocJar()
setSrcDirs( listOf( "src/main/module-info", "src/main/java" ) )
}
}
}
} }
tasks { tasks {
assemble {
dependsOn(
"sourcesJar",
"javadocJar"
)
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
named<JavaCompile>( "compileModuleInfoJava" ) {
sourceCompatibility = "9"
targetCompatibility = "9"
}
}
jar { jar {
archiveBaseName.set( "flatlaf" ) archiveBaseName.set( "flatlaf" )
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) { doLast {
from( sourceSets["module-info"].output ) { ReorderJarEntries.reorderJarEntries( outputs.files.singleFile );
include( "module-info.class" )
}
} }
} }
javadoc { javadoc {
options { options {
this as StandardJavadocDocletOptions this as StandardJavadocDocletOptions
use( true )
tags = listOf( "uiDefault", "clientProperty" ) tags = listOf( "uiDefault", "clientProperty" )
addStringOption( "Xdoclint:all,-missing", "-Xdoclint:all,-missing" ) addStringOption( "Xdoclint:all,-missing", "-Xdoclint:all,-missing" )
} }
isFailOnError = false isFailOnError = false
} }
register( "sourcesJar", Jar::class ) { named<Jar>("sourcesJar" ) {
archiveBaseName.set( "flatlaf" ) archiveBaseName.set( "flatlaf" )
archiveClassifier.set( "sources" )
from( sourceSets.main.get().allJava )
} }
register( "javadocJar", Jar::class ) { named<Jar>("javadocJar" ) {
archiveBaseName.set( "flatlaf" ) archiveBaseName.set( "flatlaf" )
archiveClassifier.set( "javadoc" )
from( javadoc )
} }
} }
publishing { flatlafPublish {
publications {
create<MavenPublication>( "maven" ) {
artifactId = "flatlaf" artifactId = "flatlaf"
groupId = "com.formdev" name = "FlatLaf"
description = "Flat Look and Feel"
from( components["java"] )
artifact( tasks["sourcesJar"] )
artifact( tasks["javadocJar"] )
pom {
name.set( "FlatLaf" )
description.set( "Flat Look and Feel" )
url.set( "https://github.com/JFormDesigner/FlatLaf" )
licenses {
license {
name.set( "The Apache License, Version 2.0" )
url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" )
}
}
developers {
developer {
name.set( "Karl Tauber" )
organization.set( "FormDev Software GmbH" )
organizationUrl.set( "https://www.formdev.com/" )
}
}
scm {
url.set( "https://github.com/JFormDesigner/FlatLaf" )
}
}
}
}
}
bintray {
user = rootProject.extra["bintray.user"] as String?
key = rootProject.extra["bintray.key"] as String?
setPublications( "maven" )
with( pkg ) {
repo = "flatlaf"
name = "flatlaf"
setLicenses( "Apache-2.0" )
vcsUrl = "https://github.com/JFormDesigner/FlatLaf"
with( version ) {
name = project.version.toString()
}
publish = rootProject.extra["bintray.publish"] as Boolean
dryRun = rootProject.extra["bintray.dryRun"] as Boolean
}
}
artifactory {
setContextUrl( "https://oss.jfrog.org" )
publish( closureOf<org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig> {
repository( delegateClosureOf<groovy.lang.GroovyObject> {
setProperty( "repoKey", "oss-snapshot-local" )
setProperty( "username", rootProject.extra["bintray.user"] as String? )
setProperty( "password", rootProject.extra["bintray.key"] as String? )
} )
defaults( delegateClosureOf<groovy.lang.GroovyObject> {
invokeMethod( "publications", "maven" )
setProperty( "publishArtifacts", true )
setProperty( "publishPom", true )
} )
} )
resolve( delegateClosureOf<org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig> {
setProperty( "repoKey", "jcenter" )
} )
} }

View File

@@ -19,18 +19,22 @@ package com.formdev.flatlaf;
import java.awt.Color; import java.awt.Color;
import java.util.Objects; import java.util.Objects;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.SwingConstants;
/** /**
* @author Karl Tauber * @author Karl Tauber
*/ */
public interface FlatClientProperties public interface FlatClientProperties
{ {
//---- JButton ------------------------------------------------------------
/** /**
* Specifies type of a button. * Specifies type of a button.
* <p> * <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br> * <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.String}<br> * <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong> {@link #BUTTON_TYPE_SQUARE} and {@link #BUTTON_TYPE_HELP} * <strong>Allowed Values</strong> {@link #BUTTON_TYPE_SQUARE}, {@link #BUTTON_TYPE_ROUND_RECT},
* {@link #BUTTON_TYPE_TAB}, {@link #BUTTON_TYPE_HELP} and {@link BUTTON_TYPE_TOOLBAR_BUTTON}
*/ */
String BUTTON_TYPE = "JButton.buttonType"; String BUTTON_TYPE = "JButton.buttonType";
@@ -43,6 +47,15 @@ public interface FlatClientProperties
*/ */
String BUTTON_TYPE_SQUARE = "square"; String BUTTON_TYPE_SQUARE = "square";
/**
* Paint the button with round edges.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
*
* @see #BUTTON_TYPE
*/
String BUTTON_TYPE_ROUND_RECT = "roundRect";
/** /**
* Paint the toggle button in tab style. * Paint the toggle button in tab style.
* <p> * <p>
@@ -61,6 +74,15 @@ public interface FlatClientProperties
*/ */
String BUTTON_TYPE_HELP = "help"; String BUTTON_TYPE_HELP = "help";
/**
* Paint the button in toolbar style.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
*
* @see #BUTTON_TYPE
*/
String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarButton";
/** /**
* Specifies selected state of a checkbox. * Specifies selected state of a checkbox.
* <p> * <p>
@@ -77,11 +99,22 @@ public interface FlatClientProperties
*/ */
String SELECTED_STATE_INDETERMINATE = "indeterminate"; String SELECTED_STATE_INDETERMINATE = "indeterminate";
/**
* Specifies whether the button preferred size will be made square (quadratically).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JComponent ---------------------------------------------------------
/** /**
* Specifies minimum width of a component. * Specifies minimum width of a component.
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JButton}, {@link javax.swing.JToggleButton} and {@link javax.swing.text.JTextComponent}<br> * <strong>Component</strong> {@link javax.swing.JButton}, {@link javax.swing.JToggleButton},
* <strong>Value type</strong> {@link java.lang.Integer}<br> * {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner} and {@link javax.swing.text.JTextComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/ */
String MINIMUM_WIDTH = "JComponent.minimumWidth"; String MINIMUM_WIDTH = "JComponent.minimumWidth";
@@ -89,10 +122,60 @@ public interface FlatClientProperties
* Specifies minimum height of a component. * Specifies minimum height of a component.
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br> * <strong>Component</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br> * <strong>Value type</strong> {@link java.lang.Integer}
*/ */
String MINIMUM_HEIGHT = "JComponent.minimumHeight"; String MINIMUM_HEIGHT = "JComponent.minimumHeight";
/**
* Specifies the outline color of the component border.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton}, {@link javax.swing.JComboBox},
* {@link javax.swing.JFormattedTextField}, {@link javax.swing.JPasswordField},
* {@link javax.swing.JScrollPane}, {@link javax.swing.JSpinner},
* {@link javax.swing.JTextField} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.String} or {@link java.awt.Color} or {@link java.awt.Color}[2]<br>
* <strong>Allowed Values</strong> {@link #OUTLINE_ERROR}, {@link #OUTLINE_WARNING},
* any color (type {@link java.awt.Color}) or an array of two colors (type {@link java.awt.Color}[2])
* where the first color is for focused state and the second for unfocused state
*/
String OUTLINE = "JComponent.outline";
/**
* Paint the component border in another color (usually reddish) to indicate an error.
*
* @see #OUTLINE
*/
String OUTLINE_ERROR = "error";
/**
* Paint the component border in another color (usually yellowish) to indicate a warning.
*
* @see #OUTLINE
*/
String OUTLINE_WARNING = "warning";
/**
* Paint the component with round edges.
* <p>
* <strong>Components</strong> {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner},
* {@link javax.swing.JTextField}, {@link javax.swing.JFormattedTextField} and {@link javax.swing.JPasswordField}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String COMPONENT_ROUND_RECT = "JComponent.roundRect";
//---- Popup --------------------------------------------------------------
/**
* Specifies whether a drop shadow is painted if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup.
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted";
//---- JProgressBar -------------------------------------------------------
/** /**
* Specifies whether the progress bar has always the larger height even if no string is painted. * Specifies whether the progress bar has always the larger height even if no string is painted.
* <p> * <p>
@@ -109,6 +192,19 @@ public interface FlatClientProperties
*/ */
String PROGRESS_BAR_SQUARE = "JProgressBar.square"; String PROGRESS_BAR_SQUARE = "JProgressBar.square";
//---- JRootPane ----------------------------------------------------------
/**
* Specifies whether the menu bar is embedded into the title pane if custom
* window decorations are enabled. Default is {@code true}.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
//---- JScrollBar ---------------------------------------------------------
/** /**
* Specifies whether the decrease/increase arrow buttons of a scrollbar are shown. * Specifies whether the decrease/increase arrow buttons of a scrollbar are shown.
* <p> * <p>
@@ -117,6 +213,16 @@ public interface FlatClientProperties
*/ */
String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons"; String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons";
/**
* Specifies whether the scroll pane uses smooth scrolling.
* <p>
* <strong>Component</strong> {{@link javax.swing.JScrollPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling";
//---- JTabbedPane --------------------------------------------------------
/** /**
* Specifies whether separators are shown between tabs. * Specifies whether separators are shown between tabs.
* <p> * <p>
@@ -125,6 +231,14 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_SHOW_TAB_SEPARATORS = "JTabbedPane.showTabSeparators"; String TABBED_PANE_SHOW_TAB_SEPARATORS = "JTabbedPane.showTabSeparators";
/**
* Specifies whether the separator between tabs area and content area should be shown.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String TABBED_PANE_SHOW_CONTENT_SEPARATOR = "JTabbedPane.showContentSeparator";
/** /**
* Specifies whether a full border is painted around a tabbed pane. * Specifies whether a full border is painted around a tabbed pane.
* <p> * <p>
@@ -133,6 +247,27 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder"; String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder";
/**
* Specifies the minimum width of a tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TABBED_PANE_MINIMUM_TAB_WIDTH = "JTabbedPane.minimumTabWidth";
/**
* Specifies the maximum width of a tab.
* <p>
* Applied only if tab does not have a custom tab component
* (see {@link javax.swing.JTabbedPane#setTabComponentAt(int, java.awt.Component)}).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TABBED_PANE_MAXIMUM_TAB_WIDTH = "JTabbedPane.maximumTabWidth";
/** /**
* Specifies the height of a tab. * Specifies the height of a tab.
* <p> * <p>
@@ -141,6 +276,220 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"; String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight";
/**
* Specifies the insets of a tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.awt.Insets}
*/
String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets";
/**
* Specifies the insets of the tab area.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.awt.Insets}
*/
String TABBED_PANE_TAB_AREA_INSETS = "JTabbedPane.tabAreaInsets";
/**
* Specifies whether tabs are closable.
* If set to {@code true} on a tabbed pane component, all tabs in that tabbed pane are closable.
* To make individual tabs closable, set it to {@code true} on a tab content component.
* <p>
* Note that you have to specify a callback (see {@link #TABBED_PANE_TAB_CLOSABLE})
* that is invoked when the user clicks a tab close button.
* The callback is responsible for closing the tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @see #TABBED_PANE_TAB_CLOSE_CALLBACK
*/
String TABBED_PANE_TAB_CLOSABLE = "JTabbedPane.tabClosable";
/**
* Specifies the tooltip text used for tab close buttons.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.lang.String}
*/
String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JTabbedPane.tabCloseToolTipText";
/**
* Specifies the callback that is invoked when a tab close button is clicked.
* The callback is responsible for closing the tab.
* <p>
* Either use a {@link java.util.function.IntConsumer} that received the tab index as parameter:
* <pre>{@code
* myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
* (IntConsumer) tabIndex -> {
* // close tab here
* } );
* }</pre>
* Or use a {@link java.util.function.BiConsumer}&lt;javax.swing.JTabbedPane, Integer&gt;
* that received the tabbed pane and the tab index as parameters:
* <pre>{@code
* myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
* (BiConsumer<JTabbedPane, Integer>) (tabbedPane, tabIndex) -> {
* // close tab here
* } );
* }</pre>
* If you need to check whether a modifier key (e.g. Alt or Shift) was pressed
* while the user clicked the tab close button, use {@link java.awt.EventQueue#getCurrentEvent}
* to get current event, check whether it is a {@link java.awt.event.MouseEvent}
* and invoke its methods. E.g.
* <pre>{@code
* AWTEvent e = EventQueue.getCurrentEvent();
* boolean shift = (e instanceof MouseEvent) ? ((MouseEvent)e).isShiftDown() : false;
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})<br>
* <strong>Value type</strong> {@link java.util.function.IntConsumer}
* or {@link java.util.function.BiConsumer}&lt;javax.swing.JTabbedPane, Integer&gt;
*
* @see #TABBED_PANE_TAB_CLOSABLE
*/
String TABBED_PANE_TAB_CLOSE_CALLBACK = "JTabbedPane.tabCloseCallback";
/**
* Specifies how to navigate to hidden tabs.
* <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_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON}
* or {@link #TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS}
*/
String TABBED_PANE_HIDDEN_TABS_NAVIGATION = "JTabbedPane.hiddenTabsNavigation";
/**
* Use "more tabs" button for navigation to hidden tabs.
*
* @see #TABBED_PANE_HIDDEN_TABS_NAVIGATION
*/
String TABBED_PANE_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON = "moreTabsButton";
/**
* Use forward/backward buttons for navigation to hidden tabs.
*
* @see #TABBED_PANE_HIDDEN_TABS_NAVIGATION
*/
String TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS = "arrowButtons";
/**
* Specifies the alignment of the tab area.
* <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_AREA_ALIGN_LEADING} (default),
* {@link #TABBED_PANE_TAB_AREA_ALIGN_TRAILING}, {@link #TABBED_PANE_TAB_AREA_ALIGN_CENTER}
* or {@link #TABBED_PANE_TAB_AREA_ALIGN_FILL}
*/
String TABBED_PANE_TAB_AREA_ALIGNMENT = "JTabbedPane.tabAreaAlignment";
/**
* Align the tab area to the leading edge.
*
* @see #TABBED_PANE_TAB_AREA_ALIGNMENT
*/
String TABBED_PANE_TAB_AREA_ALIGN_LEADING = "leading";
/**
* Align the tab area to the trailing edge.
*
* @see #TABBED_PANE_TAB_AREA_ALIGNMENT
*/
String TABBED_PANE_TAB_AREA_ALIGN_TRAILING = "trailing";
/**
* Align the tab area to center.
*
* @see #TABBED_PANE_TAB_AREA_ALIGNMENT
*/
String TABBED_PANE_TAB_AREA_ALIGN_CENTER = "center";
/**
* Stretch tabs to fill all available space.
*
* @see #TABBED_PANE_TAB_AREA_ALIGNMENT
*/
String TABBED_PANE_TAB_AREA_ALIGN_FILL = "fill";
/**
* Specifies how the tabs should be sized.
* <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_WIDTH_MODE_PREFERRED} (default),
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
*/
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
/**
* Tab width is adjusted to tab icon and title.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_PREFERRED = "preferred";
/**
* All tabs in a tabbed pane has same width.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal";
/**
* Unselected tabs are smaller because they show only the tab icon, but no tab title.
* Selected tabs show both.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
*/
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
/**
* Specifies the tab icon placement (relative to tab title).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
* <strong>Allowed Values</strong> {@link SwingConstants#LEADING} (default),
* {@link SwingConstants#TRAILING}, {@link SwingConstants#TOP}
* or {@link SwingConstants#BOTTOM}
*/
String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement";
/**
* 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
* the preferred component width and the tab area height.<br>
* For left and right tab placement, the layed out component size will be
* the tab area width and the preferred component height.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.awt.Component}
*/
String TABBED_PANE_LEADING_COMPONENT = "JTabbedPane.leadingComponent";
/**
* 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
* 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
* the tab area width and the available vertical space (minimum is preferred component height).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.awt.Component}
*/
String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent";
//---- JTextField ---------------------------------------------------------
/** /**
* Specifies whether all text is selected when the text component gains focus. * Specifies whether all text is selected when the text component gains focus.
* <p> * <p>
@@ -183,6 +532,8 @@ public interface FlatClientProperties
*/ */
String PLACEHOLDER_TEXT = "JTextField.placeholderText"; String PLACEHOLDER_TEXT = "JTextField.placeholderText";
//---- JToggleButton ------------------------------------------------------
/** /**
* Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}. * Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p> * <p>
@@ -207,6 +558,8 @@ public interface FlatClientProperties
*/ */
String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"; String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground";
//---- helper methods -----------------------------------------------------
/** /**
* Checks whether a client property of a component has the given value. * Checks whether a client property of a component has the given value.
*/ */
@@ -223,6 +576,15 @@ public interface FlatClientProperties
return (value instanceof Boolean) ? (boolean) value : defaultValue; return (value instanceof Boolean) ? (boolean) value : defaultValue;
} }
/**
* Checks whether a client property of a component is a {@link Boolean} and returns its value.
* If the client property is not set, or not a {@link Boolean}, defaultValue is returned.
*/
static Boolean clientPropertyBooleanStrict( JComponent c, String key, Boolean defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Boolean) ? (Boolean) value : defaultValue;
}
/** /**
* Checks whether a client property of a component is an integer and returns its value. * Checks whether a client property of a component is an integer and returns its value.
* If the client property is not set, or not an integer, defaultValue is returned. * If the client property is not set, or not an integer, defaultValue is returned.
@@ -240,4 +602,14 @@ public interface FlatClientProperties
Object value = c.getClientProperty( key ); Object value = c.getClientProperty( key );
return (value instanceof Color) ? (Color) value : defaultValue; return (value instanceof Color) ? (Color) value : defaultValue;
} }
static int clientPropertyChoice( JComponent c, String key, String... choices ) {
Object value = c.getClientProperty( key );
for( int i = 0; i < choices.length; i++ ) {
if( choices[i].equals( value ) )
return i;
}
return -1;
}
} }

View File

@@ -32,11 +32,11 @@ public class FlatDarculaLaf
@Override @Override
public String getName() { public String getName() {
return "Flat Darcula"; return "FlatLaf Darcula";
} }
@Override @Override
public String getDescription() { public String getDescription() {
return "Flat Darcula Look and Feel"; return "FlatLaf Darcula Look and Feel";
} }
} }

View File

@@ -32,12 +32,12 @@ public class FlatDarkLaf
@Override @Override
public String getName() { public String getName() {
return "Flat Dark"; return "FlatLaf Dark";
} }
@Override @Override
public String getDescription() { public String getDescription() {
return "Flat Dark Look and Feel"; return "FlatLaf Dark Look and Feel";
} }
@Override @Override

View File

@@ -17,6 +17,8 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import java.io.InputStream; import java.io.InputStream;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
/** /**
* Addon for FlatLaf UI defaults. * Addon for FlatLaf UI defaults.
@@ -50,6 +52,13 @@ public abstract class FlatDefaultsAddon
return addonClass.getResourceAsStream( propertiesName ); return addonClass.getResourceAsStream( propertiesName );
} }
/**
* Allows modifying UI defaults after loading UI defaults.
* The default implementation does nothing.
*/
public void afterDefaultsLoading( LookAndFeel laf, UIDefaults defaults ) {
}
/** /**
* Returns the priority used to sort addon loading. * 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 overwrite UI defaults of other addons.

View File

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

View File

@@ -22,9 +22,11 @@ import javax.swing.KeyStroke;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIDefaults.LazyValue; import javax.swing.UIDefaults.LazyValue;
import javax.swing.UIManager;
import javax.swing.plaf.InputMapUIResource; import javax.swing.plaf.InputMapUIResource;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import static javax.swing.text.DefaultEditorKit.*; import static javax.swing.text.DefaultEditorKit.*;
import java.util.function.BooleanSupplier;
/** /**
* @author Karl Tauber * @author Karl Tauber
@@ -35,7 +37,7 @@ class FlatInputMaps
initBasicInputMaps( defaults ); initBasicInputMaps( defaults );
initTextComponentInputMaps( defaults ); initTextComponentInputMaps( defaults );
if( SystemInfo.IS_MAC ) if( SystemInfo.isMacOS )
initMacInputMaps( defaults ); initMacInputMaps( defaults );
} }
@@ -48,10 +50,10 @@ class FlatInputMaps
modifyInputMap( defaults, "ComboBox.ancestorInputMap", modifyInputMap( defaults, "ComboBox.ancestorInputMap",
"SPACE", "spacePopup", "SPACE", "spacePopup",
"UP", "selectPrevious2", "UP", mac( "selectPrevious2", "selectPrevious" ),
"DOWN", "selectNext2", "DOWN", mac( "selectNext2", "selectNext" ),
"KP_UP", "selectPrevious2", "KP_UP", mac( "selectPrevious2", "selectPrevious" ),
"KP_DOWN", "selectNext2", "KP_DOWN", mac( "selectNext2", "selectNext" ),
mac( "alt UP", null ), "togglePopup", mac( "alt UP", null ), "togglePopup",
mac( "alt DOWN", null ), "togglePopup", mac( "alt DOWN", null ), "togglePopup",
@@ -59,7 +61,7 @@ class FlatInputMaps
mac( "alt KP_DOWN", null ), "togglePopup" mac( "alt KP_DOWN", null ), "togglePopup"
); );
if( !SystemInfo.IS_MAC ) { if( !SystemInfo.isMacOS ) {
modifyInputMap( defaults, "FileChooser.ancestorInputMap", modifyInputMap( defaults, "FileChooser.ancestorInputMap",
"F2", "editFileName", "F2", "editFileName",
"BACK_SPACE", "Go Up" "BACK_SPACE", "Go Up"
@@ -81,15 +83,22 @@ class FlatInputMaps
"shift ctrl TAB", "navigatePrevious" "shift ctrl TAB", "navigatePrevious"
); );
modifyInputMap( defaults, "Table.ancestorInputMap", // swap Home/End with Ctrl+Home/End to make it consistent with List and Tree
// swap to make it consistent with List and Tree modifyInputMap( () -> {
return UIManager.getBoolean( "Table.consistentHomeEndKeyBehavior" );
},
defaults, "Table.ancestorInputMap",
"HOME", "selectFirstRow", "HOME", "selectFirstRow",
"END", "selectLastRow", "END", "selectLastRow",
"shift HOME", "selectFirstRowExtendSelection",
"shift END", "selectLastRowExtendSelection",
mac( "ctrl HOME", null ), "selectFirstColumn", mac( "ctrl HOME", null ), "selectFirstColumn",
mac( "ctrl END", null ), "selectLastColumn" mac( "ctrl END", null ), "selectLastColumn",
mac( "shift ctrl HOME", null ), "selectFirstColumnExtendSelection",
mac( "shift ctrl END", null ), "selectLastColumnExtendSelection"
); );
if( !SystemInfo.IS_MAC ) { if( !SystemInfo.isMacOS ) {
modifyInputMap( defaults, "Tree.focusInputMap", modifyInputMap( defaults, "Tree.focusInputMap",
"ADD", "expand", "ADD", "expand",
"SUBTRACT", "collapse" "SUBTRACT", "collapse"
@@ -160,7 +169,7 @@ class FlatInputMaps
"control shift O", "toggle-componentOrientation", // DefaultEditorKit.toggleComponentOrientation "control shift O", "toggle-componentOrientation", // DefaultEditorKit.toggleComponentOrientation
}; };
Object[] macCommonTextComponentBindings = SystemInfo.IS_MAC ? new Object[] { Object[] macCommonTextComponentBindings = SystemInfo.isMacOS ? new Object[] {
// move caret one character (without selecting text) // move caret one character (without selecting text)
"ctrl B", backwardAction, "ctrl B", backwardAction,
"ctrl F", forwardAction, "ctrl F", forwardAction,
@@ -182,7 +191,7 @@ class FlatInputMaps
"ctrl A", beginLineAction, "ctrl A", beginLineAction,
"ctrl E", endLineAction, "ctrl E", endLineAction,
// move caret to document begin/end (without selecting text) // move caret to document begin/end and select text
"shift meta UP", selectionBeginAction, "shift meta UP", selectionBeginAction,
"shift meta DOWN", selectionEndAction, "shift meta DOWN", selectionEndAction,
"shift meta KP_UP", selectionBeginAction, "shift meta KP_UP", selectionBeginAction,
@@ -198,16 +207,6 @@ class FlatInputMaps
"shift KP_UP", selectionBeginLineAction, "shift KP_UP", selectionBeginLineAction,
"shift KP_DOWN", selectionEndLineAction, "shift KP_DOWN", selectionEndLineAction,
// move caret one page (without selecting text)
"PAGE_UP", pageUpAction,
"PAGE_DOWN", pageDownAction,
// move caret one page and select text
"shift PAGE_UP", "selection-page-up", // DefaultEditorKit.selectionPageUpAction
"shift PAGE_DOWN", "selection-page-down", // DefaultEditorKit.selectionPageDownAction
"shift meta PAGE_UP", "selection-page-left", // DefaultEditorKit.selectionPageLeftAction
"shift meta PAGE_DOWN", "selection-page-right", // DefaultEditorKit.selectionPageRightAction
// delete previous/next word // delete previous/next word
"ctrl W", deletePrevWordAction, "ctrl W", deletePrevWordAction,
"ctrl D", deleteNextCharAction, "ctrl D", deleteNextCharAction,
@@ -217,7 +216,7 @@ class FlatInputMaps
"ENTER", JTextField.notifyAction, "ENTER", JTextField.notifyAction,
}; };
Object[] macSingleLineTextComponentBindings = SystemInfo.IS_MAC ? new Object[] { Object[] macSingleLineTextComponentBindings = SystemInfo.isMacOS ? new Object[] {
// move caret to line begin/end (without selecting text) // move caret to line begin/end (without selecting text)
"UP", beginLineAction, "UP", beginLineAction,
"DOWN", endLineAction, "DOWN", endLineAction,
@@ -295,7 +294,7 @@ class FlatInputMaps
mac( "ctrl SPACE", "meta SPACE" ), "activate-link-action", mac( "ctrl SPACE", "meta SPACE" ), "activate-link-action",
}; };
Object[] macMultiLineTextComponentBindings = SystemInfo.IS_MAC ? new Object[] { Object[] macMultiLineTextComponentBindings = SystemInfo.isMacOS ? new Object[] {
// move caret one line (without selecting text) // move caret one line (without selecting text)
"ctrl N", downAction, "ctrl N", downAction,
"ctrl P", upAction, "ctrl P", upAction,
@@ -350,6 +349,12 @@ class FlatInputMaps
"meta V", "paste", "meta V", "paste",
"meta X", "cut", "meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl A", null, "ctrl A", null,
"ctrl BACK_SLASH", null, "ctrl BACK_SLASH", null,
"ctrl C", null, "ctrl C", null,
@@ -370,8 +375,6 @@ class FlatInputMaps
"ctrl UP", null, "ctrl UP", null,
"ctrl V", null, "ctrl V", null,
"ctrl X", null, "ctrl X", null,
"PAGE_DOWN", null,
"PAGE_UP", null,
"SPACE", null, "SPACE", null,
"shift ctrl DOWN", null, "shift ctrl DOWN", null,
"shift ctrl END", null, "shift ctrl END", null,
@@ -390,10 +393,16 @@ class FlatInputMaps
"shift INSERT", null, "shift INSERT", null,
"shift SPACE", null "shift SPACE", null
); );
modifyInputMap( defaults, "List.focusInputMap.RightToLeft",
// scrollbar "ctrl KP_LEFT", null,
copyInputMap( defaults, "ScrollBar.ancestorInputMap", "ScrollBar.focusInputMap" ); "ctrl KP_RIGHT", null,
copyInputMap( defaults, "ScrollBar.ancestorInputMap.RightToLeft", "ScrollBar.focusInputMap.RightToLeft" ); "ctrl LEFT", null,
"ctrl RIGHT", null,
"shift ctrl KP_LEFT", null,
"shift ctrl KP_RIGHT", null,
"shift ctrl LEFT", null,
"shift ctrl RIGHT", null
);
// scrollpane // scrollpane
modifyInputMap( defaults, "ScrollPane.ancestorInputMap", modifyInputMap( defaults, "ScrollPane.ancestorInputMap",
@@ -410,6 +419,16 @@ class FlatInputMaps
"ctrl PAGE_UP", null "ctrl PAGE_UP", null
); );
// tabbedpane
modifyInputMap( defaults, "TabbedPane.ancestorInputMap",
"ctrl UP", null,
"ctrl KP_UP", null
);
modifyInputMap( defaults, "TabbedPane.focusInputMap",
"ctrl DOWN", null,
"ctrl KP_DOWN", null
);
// table // table
modifyInputMap( defaults, "Table.ancestorInputMap", modifyInputMap( defaults, "Table.ancestorInputMap",
"alt TAB", "focusHeader", "alt TAB", "focusHeader",
@@ -419,6 +438,12 @@ class FlatInputMaps
"meta V", "paste", "meta V", "paste",
"meta X", "cut", "meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl A", null, "ctrl A", null,
"ctrl BACK_SLASH", null, "ctrl BACK_SLASH", null,
"ctrl C", null, "ctrl C", null,
@@ -476,27 +501,34 @@ class FlatInputMaps
"RIGHT", "selectChild", "RIGHT", "selectChild",
"KP_LEFT", "selectParent", "KP_LEFT", "selectParent",
"KP_RIGHT", "selectChild", "KP_RIGHT", "selectChild",
"shift LEFT", "selectParent",
"shift RIGHT", "selectChild",
"shift KP_LEFT", "selectParent",
"shift KP_RIGHT", "selectChild",
"alt LEFT", "selectParent",
"alt RIGHT", "selectChild",
"alt KP_LEFT", "selectParent",
"alt KP_RIGHT", "selectChild",
"shift HOME", "selectFirstExtendSelection",
"shift END", "selectLastExtendSelection",
"meta A", "selectAll", "meta A", "selectAll",
"meta C", "copy", "meta C", "copy",
"meta V", "paste", "meta V", "paste",
"meta X", "cut", "meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl LEFT", null, "ctrl LEFT", null,
"ctrl RIGHT", null, "ctrl RIGHT", null,
"ctrl KP_LEFT", null, "ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null, "ctrl KP_RIGHT", null,
"shift LEFT", null,
"shift RIGHT", null,
"shift KP_LEFT", null,
"shift KP_RIGHT", null,
"alt LEFT", null,
"alt RIGHT", null,
"alt KP_LEFT", null,
"alt KP_RIGHT", null,
"ctrl A", null, "ctrl A", null,
"ctrl BACK_SLASH", null, "ctrl BACK_SLASH", null,
"ctrl C", null, "ctrl C", null,
@@ -513,11 +545,7 @@ class FlatInputMaps
"ctrl UP", null, "ctrl UP", null,
"ctrl V", null, "ctrl V", null,
"ctrl X", null, "ctrl X", null,
"END", null,
"F2", null, "F2", null,
"HOME", null,
"PAGE_DOWN", null,
"PAGE_UP", null,
"SPACE", null, "SPACE", null,
"shift ctrl DOWN", null, "shift ctrl DOWN", null,
"shift ctrl END", null, "shift ctrl END", null,
@@ -529,8 +557,6 @@ class FlatInputMaps
"shift ctrl SPACE", null, "shift ctrl SPACE", null,
"shift ctrl UP", null, "shift ctrl UP", null,
"shift DELETE", null, "shift DELETE", null,
"shift END", null,
"shift HOME", null,
"shift INSERT", null, "shift INSERT", null,
"shift PAGE_DOWN", null, "shift PAGE_DOWN", null,
"shift PAGE_UP", null, "shift PAGE_UP", null,
@@ -540,24 +566,29 @@ class FlatInputMaps
"LEFT", "selectChild", "LEFT", "selectChild",
"RIGHT", "selectParent", "RIGHT", "selectParent",
"KP_LEFT", "selectChild", "KP_LEFT", "selectChild",
"KP_RIGHT", "selectParent" "KP_RIGHT", "selectParent",
"shift LEFT", "selectChild",
"shift RIGHT", "selectParent",
"shift KP_LEFT", "selectChild",
"shift KP_RIGHT", "selectParent",
"alt LEFT", "selectChild",
"alt RIGHT", "selectParent",
"alt KP_LEFT", "selectChild",
"alt KP_RIGHT", "selectParent"
} ) ); } ) );
} }
private static void copyInputMap( UIDefaults defaults, String srcKey, String destKey ) { private static void modifyInputMap( UIDefaults defaults, String key, Object... bindings ) {
// Note: not using `defaults.get(key)` here because this would resolve the lazy value modifyInputMap( null, defaults, key, bindings );
Object inputMap = defaults.remove( srcKey );
defaults.put( srcKey, inputMap );
defaults.put( destKey, inputMap );
} }
private static void modifyInputMap( UIDefaults defaults, String key, Object... bindings ) { private static void modifyInputMap( BooleanSupplier condition, UIDefaults defaults, String key, Object... bindings ) {
// Note: not using `defaults.get(key)` here because this would resolve the lazy value // Note: not using `defaults.get(key)` here because this would resolve a lazy value
defaults.put( key, new LazyModifyInputMap( defaults.remove( key ), bindings ) ); defaults.put( key, new LazyModifyInputMap( condition, defaults.remove( key ), bindings ) );
} }
private static <T> T mac( T value, T macValue ) { private static <T> T mac( T value, T macValue ) {
return SystemInfo.IS_MAC ? macValue : value; return SystemInfo.isMacOS ? macValue : value;
} }
//---- class LazyInputMapEx ----------------------------------------------- //---- class LazyInputMapEx -----------------------------------------------
@@ -592,10 +623,12 @@ class FlatInputMaps
private static class LazyModifyInputMap private static class LazyModifyInputMap
implements LazyValue implements LazyValue
{ {
private final BooleanSupplier condition;
private final Object baseInputMap; private final Object baseInputMap;
private final Object[] bindings; private final Object[] bindings;
LazyModifyInputMap( Object baseInputMap, Object[] bindings ) { LazyModifyInputMap( BooleanSupplier condition, Object baseInputMap, Object[] bindings ) {
this.condition = condition;
this.baseInputMap = baseInputMap; this.baseInputMap = baseInputMap;
this.bindings = bindings; this.bindings = bindings;
} }
@@ -607,6 +640,9 @@ class FlatInputMaps
? (InputMap) ((LazyValue)baseInputMap).createValue( table ) ? (InputMap) ((LazyValue)baseInputMap).createValue( table )
: (InputMap) baseInputMap; : (InputMap) baseInputMap;
if( condition != null && !condition.getAsBoolean() )
return inputMap;
// modify input map (replace or remove) // modify input map (replace or remove)
for( int i = 0; i < bindings.length; i += 2 ) { for( int i = 0; i < bindings.length; i += 2 ) {
KeyStroke keyStroke = KeyStroke.getKeyStroke( (String) bindings[i] ); KeyStroke keyStroke = KeyStroke.getKeyStroke( (String) bindings[i] );

View File

@@ -32,11 +32,11 @@ public class FlatIntelliJLaf
@Override @Override
public String getName() { public String getName() {
return "Flat IntelliJ"; return "FlatLaf IntelliJ";
} }
@Override @Override
public String getDescription() { public String getDescription() {
return "Flat IntelliJ Look and Feel"; return "FlatLaf IntelliJ Look and Feel";
} }
} }

View File

@@ -18,38 +18,51 @@ package com.formdev.flatlaf;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Font; import java.awt.Font;
import java.awt.KeyEventPostProcessor; import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.KeyEvent; import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference; import java.io.File;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.swing.AbstractButton; import javax.swing.BorderFactory;
import javax.swing.JLabel; import javax.swing.Icon;
import javax.swing.JRootPane; import javax.swing.ImageIcon;
import javax.swing.JTabbedPane; import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.PopupFactory; import javax.swing.PopupFactory;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException; import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource; import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.text.StyleContext;
import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit;
import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.JBRCustomDecorations;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -64,18 +77,23 @@ public abstract class FlatLaf
static final Logger LOG = Logger.getLogger( FlatLaf.class.getName() ); static final Logger LOG = Logger.getLogger( FlatLaf.class.getName() );
private static final String DESKTOPFONTHINTS = "awt.font.desktophints"; private static final String DESKTOPFONTHINTS = "awt.font.desktophints";
private static List<Object> customDefaultsSources;
private String desktopPropertyName; private String desktopPropertyName;
private String desktopPropertyName2;
private PropertyChangeListener desktopPropertyListener; private PropertyChangeListener desktopPropertyListener;
private static boolean aquaLoaded; private static boolean aquaLoaded;
private static boolean updateUIPending; private static boolean updateUIPending;
private KeyEventPostProcessor mnemonicListener; private PopupFactory oldPopupFactory;
private static boolean showMnemonics; private MnemonicHandler mnemonicHandler;
private static WeakReference<Window> lastShowMnemonicWindow;
private Consumer<UIDefaults> postInitialization; private Consumer<UIDefaults> postInitialization;
private Boolean oldFrameWindowDecorated;
private Boolean oldDialogWindowDecorated;
public static boolean install( LookAndFeel newLookAndFeel ) { public static boolean install( LookAndFeel newLookAndFeel ) {
try { try {
UIManager.setLookAndFeel( newLookAndFeel ); UIManager.setLookAndFeel( newLookAndFeel );
@@ -101,6 +119,45 @@ public abstract class FlatLaf
public abstract boolean isDark(); public abstract boolean isDark();
/**
* Checks whether the current look and feel is dark.
*/
public static boolean isLafDark() {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
return lookAndFeel instanceof FlatLaf && ((FlatLaf)lookAndFeel).isDark();
}
/**
* Returns whether FlatLaf supports custom window decorations.
* This depends on the operating system and on the used Java runtime.
* <p>
* To use custom window decorations in your application, enable them with
* following code (before creating any frames or dialogs). Then custom window
* decorations are only enabled if this method returns {@code true}.
* <pre>
* JFrame.setDefaultLookAndFeelDecorated( true );
* JDialog.setDefaultLookAndFeelDecorated( true );
* </pre>
* <p>
* Returns {@code true} on Windows 10, {@code false} otherwise.
* <p>
* Return also {@code false} if running on Windows 10 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. In this case, JBR custom decorations
* are enabled if {@link JFrame#isDefaultLookAndFeelDecorated()} or
* {@link JDialog#isDefaultLookAndFeelDecorated()} return {@code true}.
*/
@Override
public boolean getSupportsWindowDecorations() {
if( SystemInfo.isJetBrainsJVM_11_orLater &&
SystemInfo.isWindows_10_orLater &&
JBRCustomDecorations.isSupported() )
return false;
return SystemInfo.isWindows_10_orLater;
}
@Override @Override
public boolean isNativeLookAndFeel() { public boolean isNativeLookAndFeel() {
return false; return false;
@@ -111,35 +168,60 @@ public abstract class FlatLaf
return true; return true;
} }
@Override
public Icon getDisabledIcon( JComponent component, Icon icon ) {
if( icon instanceof ImageIcon ) {
Object grayFilter = UIManager.get( "Component.grayFilter" );
ImageFilter filter = (grayFilter instanceof ImageFilter)
? (ImageFilter) grayFilter
: GrayFilter.createDisabledIconFilter( isDark() ); // fallback
Function<Image, Image> mapper = img -> {
ImageProducer producer = new FilteredImageSource( img.getSource(), filter );
return Toolkit.getDefaultToolkit().createImage( producer );
};
Image image = ((ImageIcon)icon).getImage();
return new ImageIconUIResource( MultiResolutionImageSupport.map( image, mapper ) );
}
return null;
}
@Override @Override
public void initialize() { public void initialize() {
if( SystemInfo.IS_MAC ) if( SystemInfo.isMacOS )
initializeAqua(); initializeAqua();
super.initialize(); super.initialize();
// add mnemonic listener // install popup factory
mnemonicListener = e -> { oldPopupFactory = PopupFactory.getSharedInstance();
checkShowMnemonics( e ); PopupFactory.setSharedInstance( new FlatPopupFactory() );
return false;
}; // install mnemonic handler
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( mnemonicListener ); mnemonicHandler = new MnemonicHandler();
mnemonicHandler.install();
// listen to desktop property changes to update UI if system font or scaling changes // listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.IS_WINDOWS ) { if( SystemInfo.isWindows ) {
// Windows 10 allows increasing font size independent of scaling: // Windows 10 allows increasing font size independent of scaling:
// Settings > Ease of Access > Display > Make text bigger (100% - 225%) // Settings > Ease of Access > Display > Make text bigger (100% - 225%)
desktopPropertyName = "win.messagebox.font"; desktopPropertyName = "win.messagebox.font";
} else if( SystemInfo.IS_LINUX ) { } else if( SystemInfo.isLinux ) {
// Linux/Gnome allows changing font in "Tweaks" app
desktopPropertyName = "gnome.Gtk/FontName";
// Linux/Gnome allows extra scaling and larger text: // Linux/Gnome allows extra scaling and larger text:
// Settings > Devices > Displays > Scale (100% or 200%) // Settings > Devices > Displays > Scale (100% or 200%)
// Settings > Universal access > Large Text (off or on, 125%) // Settings > Universal access > Large Text (off or on, 125%)
desktopPropertyName = "gnome.Xft/DPI"; // "Tweaks" app > Fonts > Scaling Factor (0,5 - 3)
desktopPropertyName2 = "gnome.Xft/DPI";
} }
if( desktopPropertyName != null ) { if( desktopPropertyName != null ) {
desktopPropertyListener = e -> { desktopPropertyListener = e -> {
String propertyName = e.getPropertyName(); String propertyName = e.getPropertyName();
if( desktopPropertyName.equals( propertyName ) ) if( desktopPropertyName.equals( propertyName ) || propertyName.equals( desktopPropertyName2 ) )
reSetLookAndFeel(); reSetLookAndFeel();
else if( DESKTOPFONTHINTS.equals( propertyName ) ) { else if( DESKTOPFONTHINTS.equals( propertyName ) ) {
if( UIManager.getLookAndFeel() instanceof FlatLaf ) { if( UIManager.getLookAndFeel() instanceof FlatLaf ) {
@@ -150,6 +232,8 @@ public abstract class FlatLaf
}; };
Toolkit toolkit = Toolkit.getDefaultToolkit(); Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( desktopPropertyName, desktopPropertyListener ); toolkit.addPropertyChangeListener( desktopPropertyName, desktopPropertyListener );
if( desktopPropertyName2 != null )
toolkit.addPropertyChangeListener( desktopPropertyName2, desktopPropertyListener );
toolkit.addPropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener ); toolkit.addPropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener );
} }
@@ -164,6 +248,16 @@ public abstract class FlatLaf
String.format( "a { color: #%06x; }", linkColor.getRGB() & 0xffffff ) ); String.format( "a { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
} }
}; };
// enable/disable window decorations, but only if system property is either
// "true" or "false"; in other cases it is not changed
Boolean useWindowDecorations = FlatSystemProperties.getBooleanStrict( FlatSystemProperties.USE_WINDOW_DECORATIONS, null );
if( useWindowDecorations != null ) {
oldFrameWindowDecorated = JFrame.isDefaultLookAndFeelDecorated();
oldDialogWindowDecorated = JDialog.isDefaultLookAndFeelDecorated();
JFrame.setDefaultLookAndFeelDecorated( useWindowDecorations );
JDialog.setDefaultLookAndFeelDecorated( useWindowDecorations );
}
} }
@Override @Override
@@ -172,21 +266,38 @@ public abstract class FlatLaf
if( desktopPropertyListener != null ) { if( desktopPropertyListener != null ) {
Toolkit toolkit = Toolkit.getDefaultToolkit(); Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.removePropertyChangeListener( desktopPropertyName, desktopPropertyListener ); toolkit.removePropertyChangeListener( desktopPropertyName, desktopPropertyListener );
if( desktopPropertyName2 != null )
toolkit.removePropertyChangeListener( desktopPropertyName2, desktopPropertyListener );
toolkit.removePropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener ); toolkit.removePropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener );
desktopPropertyName = null; desktopPropertyName = null;
desktopPropertyName2 = null;
desktopPropertyListener = null; desktopPropertyListener = null;
} }
// remove mnemonic listener // uninstall popup factory
if( mnemonicListener != null ) { if( oldPopupFactory != null ) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor( mnemonicListener ); PopupFactory.setSharedInstance( oldPopupFactory );
mnemonicListener = null; oldPopupFactory = null;
}
// uninstall mnemonic handler
if( mnemonicHandler != null ) {
mnemonicHandler.uninstall();
mnemonicHandler = null;
} }
// restore default link color // restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" ); new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" );
postInitialization = null; postInitialization = null;
// restore enable/disable window decorations
if( oldFrameWindowDecorated != null ) {
JFrame.setDefaultLookAndFeelDecorated( oldFrameWindowDecorated );
JDialog.setDefaultLookAndFeelDecorated( oldDialogWindowDecorated );
oldFrameWindowDecorated = null;
oldDialogWindowDecorated = null;
}
super.uninitialize(); super.uninitialize();
} }
@@ -207,7 +318,7 @@ public abstract class FlatLaf
String aquaLafClassName = "com.apple.laf.AquaLookAndFeel"; String aquaLafClassName = "com.apple.laf.AquaLookAndFeel";
BasicLookAndFeel aquaLaf; BasicLookAndFeel aquaLaf;
try { try {
if( SystemInfo.IS_JAVA_9_OR_LATER ) { if( SystemInfo.isJava_9_orLater ) {
Method m = UIManager.class.getMethod( "createLookAndFeel", String.class ); Method m = UIManager.class.getMethod( "createLookAndFeel", String.class );
aquaLaf = (BasicLookAndFeel) m.invoke( null, "Mac OS X" ); aquaLaf = (BasicLookAndFeel) m.invoke( null, "Mac OS X" );
} else } else
@@ -233,12 +344,17 @@ public abstract class FlatLaf
public UIDefaults getDefaults() { public UIDefaults getDefaults() {
UIDefaults defaults = super.getDefaults(); UIDefaults defaults = super.getDefaults();
// add Metal resource bundle, which is required for FlatFileChooserUI // add flag that indicates whether the LaF is light or dark
defaults.addResourceBundle( "com.sun.swing.internal.plaf.metal.resources.metal" ); // (can be queried without using FlatLaf API)
defaults.put( "laf.dark", isDark() );
// add resource bundle for localized texts
defaults.addResourceBundle( "com.formdev.flatlaf.resources.Bundle" );
// initialize some defaults (for overriding) that are used in UI delegates, // initialize some defaults (for overriding) that are used in UI delegates,
// but are not set in BasicLookAndFeel // but are not set in BasicLookAndFeel
putDefaults( defaults, defaults.getColor( "control" ), putDefaults( defaults, defaults.getColor( "control" ),
"Button.disabledBackground",
"EditorPane.disabledBackground", "EditorPane.disabledBackground",
"EditorPane.inactiveBackground", "EditorPane.inactiveBackground",
"FormattedTextField.disabledBackground", "FormattedTextField.disabledBackground",
@@ -248,7 +364,8 @@ public abstract class FlatLaf
"TextArea.inactiveBackground", "TextArea.inactiveBackground",
"TextField.disabledBackground", "TextField.disabledBackground",
"TextPane.disabledBackground", "TextPane.disabledBackground",
"TextPane.inactiveBackground" ); "TextPane.inactiveBackground",
"ToggleButton.disabledBackground" );
putDefaults( defaults, defaults.getColor( "textInactiveText" ), putDefaults( defaults, defaults.getColor( "textInactiveText" ),
"Button.disabledText", "Button.disabledText",
"CheckBox.disabledText", "CheckBox.disabledText",
@@ -266,73 +383,121 @@ public abstract class FlatLaf
initIconColors( defaults, isDark() ); initIconColors( defaults, isDark() );
FlatInputMaps.initInputMaps( defaults ); FlatInputMaps.initInputMaps( defaults );
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
List<FlatDefaultsAddon> addons = new ArrayList<>();
for( FlatDefaultsAddon addon : addonLoader )
addons.add( addon );
addons.sort( (addon1, addon2) -> addon1.getPriority() - addon2.getPriority() );
// load defaults from properties // load defaults from properties
List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading(); List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading();
if( lafClassesForDefaultsLoading != null ) if( lafClassesForDefaultsLoading != null )
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, defaults ); UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, getAdditionalDefaults(), isDark(), defaults );
else else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), defaults ); UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), isDark(), defaults );
// use Aqua MenuBarUI if Mac screen menubar is enabled // use Aqua MenuBarUI if Mac screen menubar is enabled
if( SystemInfo.IS_MAC && Boolean.getBoolean( "apple.laf.useScreenMenuBar" ) ) if( SystemInfo.isMacOS && Boolean.getBoolean( "apple.laf.useScreenMenuBar" ) ) {
defaults.put( "MenuBarUI", "com.apple.laf.AquaMenuBarUI" ); defaults.put( "MenuBarUI", "com.apple.laf.AquaMenuBarUI" );
// add defaults necessary for AquaMenuBarUI
defaults.put( "MenuBar.backgroundPainter", BorderFactory.createEmptyBorder() );
}
// initialize text antialiasing // initialize text antialiasing
putAATextInfo( defaults ); putAATextInfo( defaults );
invokePostInitialization( defaults ); // apply additional defaults (e.g. from IntelliJ themes)
applyAdditionalDefaults( defaults );
return defaults; // allow addons modifying UI defaults
} for( FlatDefaultsAddon addon : addons )
addon.afterDefaultsLoading( this, defaults );
// add user scale factor to allow layout managers (e.g. MigLayout) to use it
defaults.put( "laf.scaleFactor", (ActiveValue) t -> {
return UIScale.getUserScaleFactor();
} );
void invokePostInitialization( UIDefaults defaults ) {
if( postInitialization != null ) { if( postInitialization != null ) {
postInitialization.accept( defaults ); postInitialization.accept( defaults );
postInitialization = null; postInitialization = null;
} }
return defaults;
} }
List<Class<?>> getLafClassesForDefaultsLoading() { void applyAdditionalDefaults( UIDefaults defaults ) {
}
protected List<Class<?>> getLafClassesForDefaultsLoading() {
return null;
}
protected Properties getAdditionalDefaults() {
return null; return null;
} }
private void initFonts( UIDefaults defaults ) { private void initFonts( UIDefaults defaults ) {
FontUIResource uiFont = null; FontUIResource uiFont = null;
if( SystemInfo.IS_WINDOWS ) { if( SystemInfo.isWindows ) {
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" ); Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" );
if( winFont != null ) if( winFont != null )
uiFont = new FontUIResource( winFont ); uiFont = createCompositeFont( winFont.getFamily(), winFont.getStyle(), winFont.getSize() );
} else if( SystemInfo.IS_MAC ) { } else if( SystemInfo.isMacOS ) {
String fontName; String fontName;
if( SystemInfo.IS_MAC_OS_10_11_EL_CAPITAN_OR_LATER ) { if( SystemInfo.isMacOS_10_15_Catalina_orLater ) {
// use Helvetica Neue font
fontName = "Helvetica Neue";
} else if( SystemInfo.isMacOS_10_11_ElCapitan_orLater ) {
// use San Francisco Text font // use San Francisco Text font
fontName = ".SF NS Text"; fontName = ".SF NS Text";
} else { } else {
// default font on older systems (see com.apple.laf.AquaFonts) // default font on older systems (see com.apple.laf.AquaFonts)
fontName = "Lucida Grande"; fontName = "Lucida Grande";
} }
uiFont = new FontUIResource( fontName, Font.PLAIN, 13 );
} else if( SystemInfo.IS_LINUX ) { uiFont = createCompositeFont( fontName, Font.PLAIN, 13 );
} else if( SystemInfo.isLinux ) {
Font font = LinuxFontPolicy.getFont(); Font font = LinuxFontPolicy.getFont();
uiFont = (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font ); uiFont = (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
} }
// fallback
if( uiFont == null ) if( uiFont == null )
return; uiFont = createCompositeFont( Font.SANS_SERIF, Font.PLAIN, 12 );
// increase font size if system property "flatlaf.uiScale" is set
uiFont = UIScale.applyCustomScaleFactor( uiFont ); uiFont = UIScale.applyCustomScaleFactor( uiFont );
// use active value for all fonts to allow changing fonts in all components
// (similar as in Nimbus L&F) with:
// UIManager.put( "defaultFont", myFont );
Object activeFont = new ActiveFont( 1 );
// override fonts // override fonts
for( Object key : defaults.keySet() ) { for( Object key : defaults.keySet() ) {
if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) ) if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) )
defaults.put( key, uiFont ); defaults.put( key, activeFont );
} }
// use smaller font for progress bar // use smaller font for progress bar
defaults.put( "ProgressBar.font", UIScale.scaleFont( uiFont, 0.85f ) ); defaults.put( "ProgressBar.font", new ActiveFont( 0.85f ) );
// set default font
defaults.put( "defaultFont", uiFont );
}
static FontUIResource createCompositeFont( String family, int style, int size ) {
// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
// and creates a composite font that is able to display all Unicode characters
Font font = StyleContext.getDefaultStyleContext().getFont( family, style, size );
return (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
} }
/** /**
@@ -348,34 +513,18 @@ public abstract class FlatLaf
* <a href="https://jetbrains.design/intellij/principles/icons/#action-icons">Action icons</a> * <a href="https://jetbrains.design/intellij/principles/icons/#action-icons">Action icons</a>
* and * and
* <a href="https://jetbrains.design/intellij/principles/icons/#noun-icons">Noun icons</a> * <a href="https://jetbrains.design/intellij/principles/icons/#noun-icons">Noun icons</a>
* <p>
* These colors may be changed by IntelliJ Platform themes.
*/ */
public static void initIconColors( UIDefaults defaults, boolean dark ) { public static void initIconColors( UIDefaults defaults, boolean dark ) {
// colors for action icons for( FlatIconColors c : FlatIconColors.values() ) {
// see https://jetbrains.design/intellij/principles/icons/#action-icons if( c.light == !dark || c.dark == dark )
defaults.put( "Actions.Red", new ColorUIResource( !dark ? 0xDB5860 : 0xC75450 ) ); defaults.put( c.key, new ColorUIResource( c.rgb ) );
defaults.put( "Actions.Yellow", new ColorUIResource( !dark ? 0xEDA200 : 0xF0A732 ) ); }
defaults.put( "Actions.Green", new ColorUIResource( !dark ? 0x59A869 : 0x499C54 ) );
defaults.put( "Actions.Blue", new ColorUIResource( !dark ? 0x389FD6 : 0x3592C4 ) );
defaults.put( "Actions.Grey", new ColorUIResource( !dark ? 0x6E6E6E : 0xAFB1B3 ) );
defaults.put( "Actions.GreyInline", new ColorUIResource( !dark ? 0x7F8B91 : 0x7F8B91 ) );
// colors for object icons
// see https://jetbrains.design/intellij/principles/icons/#noun-icons
defaults.put( "Objects.Grey", new ColorUIResource( 0x9AA7B0 ) );
defaults.put( "Objects.Blue", new ColorUIResource( 0x40B6E0 ) );
defaults.put( "Objects.Green", new ColorUIResource( 0x62B543 ) );
defaults.put( "Objects.Yellow", new ColorUIResource( 0xF4AF3D ) );
defaults.put( "Objects.YellowDark", new ColorUIResource( 0xD9A343 ) );
defaults.put( "Objects.Purple", new ColorUIResource( 0xB99BF8 ) );
defaults.put( "Objects.Pink", new ColorUIResource( 0xF98B9E ) );
defaults.put( "Objects.Red", new ColorUIResource( 0xF26522 ) );
defaults.put( "Objects.RedStatus", new ColorUIResource( 0xE05555 ) );
defaults.put( "Objects.GreenAndroid", new ColorUIResource( 0xA4C639 ) );
defaults.put( "Objects.BlackText", new ColorUIResource( 0x231F20 ) );
} }
private void putAATextInfo( UIDefaults defaults ) { private void putAATextInfo( UIDefaults defaults ) {
if( SystemInfo.IS_JAVA_9_OR_LATER ) { if( SystemInfo.isJava_9_orLater ) {
Object desktopHints = Toolkit.getDefaultToolkit().getDesktopProperty( DESKTOPFONTHINTS ); Object desktopHints = Toolkit.getDefaultToolkit().getDesktopProperty( DESKTOPFONTHINTS );
if( desktopHints instanceof Map ) { if( desktopHints instanceof Map ) {
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
@@ -412,6 +561,87 @@ public abstract class FlatLaf
defaults.put( key, value ); defaults.put( key, value );
} }
static List<Object> getCustomDefaultsSources() {
return customDefaultsSources;
}
/**
* Registers a package where FlatLaf searches for properties files with custom UI defaults.
* <p>
* This can be used to specify application specific UI defaults that override UI values
* of existing themes or to define own UI values used in custom controls.
* <p>
* There may be multiple properties files in that package for multiple themes.
* The properties file name must match the used theme class names.
* E.g. {@code FlatLightLaf.properties} for class {@link FlatLightLaf}
* or {@code FlatDarkLaf.properties} for class {@link FlatDarkLaf}.
* {@code FlatLaf.properties} is loaded first for all themes.
* <p>
* These properties files are loaded after theme and addon properties files
* and can therefore override all UI defaults.
* <p>
* Invoke this method before setting the look and feel.
*
* @param packageName a package name (e.g. "com.myapp.resources")
*/
public static void registerCustomDefaultsSource( String packageName ) {
registerCustomDefaultsSource( packageName, null );
}
public static void unregisterCustomDefaultsSource( String packageName ) {
unregisterCustomDefaultsSource( packageName, null );
}
/**
* Registers a package where FlatLaf searches for properties files with custom UI defaults.
* <p>
* See {@link #registerCustomDefaultsSource(String)} for details.
*
* @param packageName a package name (e.g. "com.myapp.resources")
* @param classLoader a class loader used to find resources, or {@code null}
*/
public static void registerCustomDefaultsSource( String packageName, ClassLoader classLoader ) {
if( customDefaultsSources == null )
customDefaultsSources = new ArrayList<>();
customDefaultsSources.add( packageName );
customDefaultsSources.add( classLoader );
}
public static void unregisterCustomDefaultsSource( String packageName, ClassLoader classLoader ) {
if( customDefaultsSources == null )
return;
int size = customDefaultsSources.size();
for( int i = 0; i < size - 1; i++ ) {
Object source = customDefaultsSources.get( i );
if( packageName.equals( source ) && customDefaultsSources.get( i + 1 ) == classLoader ) {
customDefaultsSources.remove( i + 1 );
customDefaultsSources.remove( i );
break;
}
}
}
/**
* Registers a folder where FlatLaf searches for properties files with custom UI defaults.
* <p>
* See {@link #registerCustomDefaultsSource(String)} for details.
*
* @param folder a folder
*/
public static void registerCustomDefaultsSource( File folder ) {
if( customDefaultsSources == null )
customDefaultsSources = new ArrayList<>();
customDefaultsSources.add( folder );
}
public static void unregisterCustomDefaultsSource( File folder ) {
if( customDefaultsSources == null )
return;
customDefaultsSources.remove( folder );
}
private static void reSetLookAndFeel() { private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> { EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
@@ -461,85 +691,75 @@ public abstract class FlatLaf
} }
public static boolean isShowMnemonics() { public static boolean isShowMnemonics() {
return showMnemonics || !UIManager.getBoolean( "Component.hideMnemonics" ); return MnemonicHandler.isShowMnemonics();
} }
private static void checkShowMnemonics( KeyEvent e ) { public static void showMnemonics( Component c ) {
int keyCode = e.getKeyCode(); MnemonicHandler.showMnemonics( true, c );
if( SystemInfo.IS_MAC ) { }
// Ctrl+Alt keys must be pressed on Mac
if( keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT ) public static void hideMnemonics() {
showMnemonics( e.getID() == KeyEvent.KEY_PRESSED && e.isControlDown() && e.isAltDown(), e.getComponent() ); MnemonicHandler.showMnemonics( false, null );
}
// do not allow overriding to avoid issues in FlatUIUtils.createSharedUI()
@Override
public final boolean equals( Object obj ) {
return super.equals( obj );
}
// do not allow overriding to avoid issues in FlatUIUtils.createSharedUI()
@Override
public final int hashCode() {
return super.hashCode();
}
//---- class ActiveFont ---------------------------------------------------
private static class ActiveFont
implements ActiveValue
{
private final float scaleFactor;
// cache (scaled) font
private Font font;
private Font lastDefaultFont;
ActiveFont( float scaleFactor ) {
this.scaleFactor = scaleFactor;
}
@Override
public Object createValue( UIDefaults table ) {
Font defaultFont = UIManager.getFont( "defaultFont" );
if( lastDefaultFont != defaultFont ) {
lastDefaultFont = defaultFont;
if( scaleFactor != 1 ) {
// scale font
int newFontSize = Math.round( defaultFont.getSize() * scaleFactor );
font = new FontUIResource( defaultFont.deriveFont( (float) newFontSize ) );
} else { } else {
// Alt key must be pressed on Windows and Linux // make sure that font is a UIResource for LaF switching
if( keyCode == KeyEvent.VK_ALT ) font = (defaultFont instanceof UIResource)
showMnemonics( e.getID() == KeyEvent.KEY_PRESSED, e.getComponent() ); ? defaultFont
: new FontUIResource( defaultFont );
} }
} }
private static void showMnemonics( boolean show, Component c ) { return font;
if( show == showMnemonics )
return;
showMnemonics = show;
// check whether it is necessary to repaint
if( !UIManager.getBoolean( "Component.hideMnemonics" ) )
return;
if( show ) {
// get root pane
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( rootPane );
if( window == null )
return;
// repaint components with mnemonics in focused window
repaintMnemonics( window );
lastShowMnemonicWindow = new WeakReference<>( window );
} else if( lastShowMnemonicWindow != null ) {
Window window = lastShowMnemonicWindow.get();
if( window != null )
repaintMnemonics( window );
lastShowMnemonicWindow = null;
} }
} }
private static void repaintMnemonics( Container container ) { //---- class ImageIconUIResource ------------------------------------------
for( Component c : container.getComponents() ) {
if( !c.isVisible() )
continue;
if( hasMnemonic( c ) ) private static class ImageIconUIResource
c.repaint(); extends ImageIcon
implements UIResource
if( c instanceof Container ) {
repaintMnemonics( (Container) c ); ImageIconUIResource( Image image ) {
super( image );
} }
} }
private static boolean hasMnemonic( Component c ) {
if( c instanceof JLabel && ((JLabel)c).getDisplayedMnemonicIndex() >= 0 )
return true;
if( c instanceof AbstractButton && ((AbstractButton)c).getDisplayedMnemonicIndex() >= 0 )
return true;
if( c instanceof JTabbedPane ) {
JTabbedPane tabPane = (JTabbedPane) c;
int tabCount = tabPane.getTabCount();
for( int i = 0; i < tabCount; i++ ) {
if( tabPane.getDisplayedMnemonicIndexAt( i ) >= 0 )
return true;
}
}
return false;
}
} }

View File

@@ -32,12 +32,12 @@ public class FlatLightLaf
@Override @Override
public String getName() { public String getName() {
return "Flat Light"; return "FlatLaf Light";
} }
@Override @Override
public String getDescription() { public String getDescription() {
return "Flat Light Look and Feel"; return "FlatLaf Light Look and Feel";
} }
@Override @Override

View File

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

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import javax.swing.JDialog;
import javax.swing.JFrame;
/**
* Defines/documents own system properties used in FlatLaf.
*
* @author Karl Tauber
*/
public interface FlatSystemProperties
{
/**
* Specifies a custom scale factor used to scale the UI.
* <p>
* If Java runtime scales (Java 9 or later), this scale factor is applied on top
* of the Java system scale factor. Java 8 does not scale and this scale factor
* replaces the user scale factor that FlatLaf computes based on the font.
* To replace the Java 9+ system scale factor, use system property "sun.java2d.uiScale",
* which has the same syntax as this one.
* <p>
* <strong>Allowed Values</strong> e.g. {@code 1.5}, {@code 1.5x}, {@code 150%} or {@code 144dpi} (96dpi is 100%)<br>
*/
String UI_SCALE = "flatlaf.uiScale";
/**
* Specifies whether user scaling mode is enabled.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled";
/**
* Specifies whether Ubuntu font should be used on Ubuntu Linux.
* By default, if not running in a JetBrains Runtime, the Liberation Sans font
* is used because there are rendering issues (in Java) with Ubuntu fonts.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false}
*/
String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont";
/**
* Specifies whether custom look and feel window decorations should be used
* when creating {@code JFrame} or {@code JDialog}.
* <p>
* If this system property is set, FlatLaf invokes {@link JFrame#setDefaultLookAndFeelDecorated(boolean)}
* and {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} on LaF initialization.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
*/
String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations";
/**
* 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>
* (based on OpenJDK).
* <p>
* Setting this to {@code true} forces using JetBrains Runtime custom window decorations
* even if they are not enabled by the application.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/**
* Specifies whether menubar is embedded into custom window decorations.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded";
/**
* Specifies whether animations are enabled.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String ANIMATION = "flatlaf.animation";
/**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection";
/**
* Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.
* If the system property is not set, {@code defaultValue} is returned.
*/
static boolean getBoolean( String key, boolean defaultValue ) {
String value = System.getProperty( key );
return (value != null) ? Boolean.parseBoolean( value ) : defaultValue;
}
/**
* Checks whether a system property is set and returns {@code Boolean.TRUE} if its value
* is {@code "true"} (case-insensitive) or returns {@code Boolean.FALSE} if its value
* is {@code "false"} (case-insensitive). Otherwise {@code defaultValue} is returned.
*/
static Boolean getBooleanStrict( String key, Boolean defaultValue ) {
String value = System.getProperty( key );
if( "true".equalsIgnoreCase( value ) )
return Boolean.TRUE;
if( "false".equalsIgnoreCase( value ) )
return Boolean.FALSE;
return defaultValue;
}
}

View File

@@ -86,7 +86,7 @@ public class IntelliJTheme
* Using a buffered input stream is not necessary. * Using a buffered input stream is not necessary.
*/ */
public static FlatLaf createLaf( InputStream in ) public static FlatLaf createLaf( InputStream in )
throws IOException, ParseException throws IOException
{ {
return createLaf( new IntelliJTheme( in ) ); return createLaf( new IntelliJTheme( in ) );
} }
@@ -106,11 +106,13 @@ public class IntelliJTheme
*/ */
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public IntelliJTheme( InputStream in ) public IntelliJTheme( InputStream in )
throws IOException, ParseException throws IOException
{ {
Map<String, Object> json; Map<String, Object> json;
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) { try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) {
json = (Map<String, Object>) Json.parse( reader ); json = (Map<String, Object>) Json.parse( reader );
} catch( ParseException ex ) {
throw new IOException( ex.getMessage(), ex );
} }
name = (String) json.get( "name" ); name = (String) json.get( "name" );
@@ -132,6 +134,8 @@ public class IntelliJTheme
defaults.put( "Button.paintShadow", true ); defaults.put( "Button.paintShadow", true );
defaults.put( "Button.shadowWidth", dark ? 2 : 1 ); defaults.put( "Button.shadowWidth", dark ? 2 : 1 );
Map<Object, Object> themeSpecificDefaults = removeThemeSpecificDefaults( defaults );
loadNamedColors( defaults ); loadNamedColors( defaults );
// convert Json "ui" structure to UI defaults // convert Json "ui" structure to UI defaults
@@ -143,6 +147,15 @@ public class IntelliJTheme
applyColorPalette( defaults ); applyColorPalette( defaults );
applyCheckBoxColors( defaults ); applyCheckBoxColors( defaults );
// copy values
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() )
defaults.put( e.getKey(), defaults.get( e.getValue() ) );
// IDEA does not paint button background if disabled, but FlatLaf does
Object panelBackground = defaults.get( "Panel.background" );
defaults.put( "Button.disabledBackground", panelBackground );
defaults.put( "ToggleButton.disabledBackground", panelBackground );
// IDEA uses a SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground // IDEA uses a SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
Object helpButtonBackground = defaults.get( "Button.startBackground" ); Object helpButtonBackground = defaults.get( "Button.startBackground" );
Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" ); Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" );
@@ -152,7 +165,7 @@ public class IntelliJTheme
helpButtonBorderColor = defaults.get( "Button.borderColor" ); helpButtonBorderColor = defaults.get( "Button.borderColor" );
defaults.put( "HelpButton.background", helpButtonBackground ); defaults.put( "HelpButton.background", helpButtonBackground );
defaults.put( "HelpButton.borderColor", helpButtonBorderColor ); defaults.put( "HelpButton.borderColor", helpButtonBorderColor );
defaults.put( "HelpButton.disabledBackground", defaults.get( "Panel.background" ) ); defaults.put( "HelpButton.disabledBackground", panelBackground );
defaults.put( "HelpButton.disabledBorderColor", defaults.get( "Button.disabledBorderColor" ) ); defaults.put( "HelpButton.disabledBorderColor", defaults.get( "Button.disabledBorderColor" ) );
defaults.put( "HelpButton.focusedBorderColor", defaults.get( "Button.focusedBorderColor" ) ); defaults.put( "HelpButton.focusedBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.focusedBackground", defaults.get( "Button.focusedBackground" ) ); defaults.put( "HelpButton.focusedBackground", defaults.get( "Button.focusedBackground" ) );
@@ -183,6 +196,42 @@ public class IntelliJTheme
if( !uiKeys.contains( "Spinner.background" ) ) if( !uiKeys.contains( "Spinner.background" ) )
defaults.put( "Spinner.background", textFieldBackground ); defaults.put( "Spinner.background", textFieldBackground );
} }
// fix ToggleButton
if( !uiKeys.contains( "ToggleButton.startBackground" ) && !uiKeys.contains( "*.startBackground" ) )
defaults.put( "ToggleButton.startBackground", defaults.get( "Button.startBackground" ) );
if( !uiKeys.contains( "ToggleButton.endBackground" ) && !uiKeys.contains( "*.endBackground" ) )
defaults.put( "ToggleButton.endBackground", defaults.get( "Button.endBackground" ) );
if( !uiKeys.contains( "ToggleButton.foreground" ) && uiKeys.contains( "Button.foreground" ) )
defaults.put( "ToggleButton.foreground", defaults.get( "Button.foreground" ) );
// limit tree row height
int rowHeight = defaults.getInt( "Tree.rowHeight" );
if( rowHeight > 22 )
defaults.put( "Tree.rowHeight", 22 );
// apply theme specific UI defaults at the end to allow overwriting
defaults.putAll( themeSpecificDefaults );
}
private Map<Object, Object> removeThemeSpecificDefaults( UIDefaults defaults ) {
// search for theme specific UI defaults keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) )
themeSpecificKeys.add( (String) key );
}
// remove theme specific UI defaults and remember only those for current theme
Map<Object, Object> themeSpecificDefaults = new HashMap<>();
String currentThemePrefix = '[' + name.replace( ' ', '_' ) + ']';
for( String key : themeSpecificKeys ) {
Object value = defaults.remove( key );
if( key.startsWith( currentThemePrefix ) )
themeSpecificDefaults.put( key.substring( currentThemePrefix.length() ), value );
}
return themeSpecificDefaults;
} }
/** /**
@@ -200,7 +249,7 @@ public class IntelliJTheme
if( color != null ) { if( color != null ) {
String key = e.getKey(); String key = e.getKey();
namedColors.put( key, color ); namedColors.put( key, color );
defaults.put( "ColorPalette." + e.getKey(), color ); defaults.put( "ColorPalette." + key, color );
} }
} }
} }
@@ -214,8 +263,16 @@ public class IntelliJTheme
for( Map.Entry<String, Object> e : ((Map<String, Object>)value).entrySet() ) for( Map.Entry<String, Object> e : ((Map<String, Object>)value).entrySet() )
apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys ); apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
} else { } else {
if( "".equals( value ) )
return; // ignore empty value
uiKeys.add( key ); uiKeys.add( key );
// fix ComboBox size and Spinner border in all Material UI Lite themes
boolean isMaterialUILite = author.equals( "Mallowigi" );
if( isMaterialUILite && (key.equals( "ComboBox.padding" ) || key.equals( "Spinner.border" )) )
return; // ignore
// map keys // map keys
key = uiKeyMapping.getOrDefault( key, key ); key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() ) if( key.isEmpty() )
@@ -270,7 +327,7 @@ public class IntelliJTheme
// (e.g. set ComboBox.buttonEditableBackground to *.background // (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background) // because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, (String) k ); String km = uiKeyInverseMapping.getOrDefault( k, (String) k );
if( km.endsWith( tail ) && !noWildcardReplace.contains( k ) && !((String)k).startsWith( "CheckBox.icon." ) ) if( km.endsWith( tail ) && !((String)k).startsWith( "CheckBox.icon." ) )
defaults.put( k, uiValue ); defaults.put( k, uiValue );
} }
} }
@@ -362,6 +419,10 @@ public class IntelliJTheme
String newKey = checkboxKeyMapping.get( key ); String newKey = checkboxKeyMapping.get( key );
if( newKey != null ) { if( newKey != null ) {
String checkBoxIconPrefix = "CheckBox.icon.";
if( !dark && newKey.startsWith( checkBoxIconPrefix ) )
newKey = "CheckBox.icon[filled].".concat( newKey.substring( checkBoxIconPrefix.length() ) );
ColorUIResource color = toColor( (String) value ); ColorUIResource color = toColor( (String) value );
if( color != null ) { if( color != null ) {
defaults.put( newKey, color ); defaults.put( newKey, color );
@@ -394,17 +455,24 @@ public class IntelliJTheme
// remove hover and pressed colors // remove hover and pressed colors
if( checkboxModified ) { if( checkboxModified ) {
defaults.remove( "CheckBox.icon.focusWidth" );
defaults.remove( "CheckBox.icon.hoverBorderColor" ); defaults.remove( "CheckBox.icon.hoverBorderColor" );
defaults.remove( "CheckBox.icon.focusedBackground" ); defaults.remove( "CheckBox.icon.focusedBackground" );
defaults.remove( "CheckBox.icon.hoverBackground" ); defaults.remove( "CheckBox.icon.hoverBackground" );
defaults.remove( "CheckBox.icon.pressedBackground" ); defaults.remove( "CheckBox.icon.pressedBackground" );
defaults.remove( "CheckBox.icon.selectedFocusedBackground" );
defaults.remove( "CheckBox.icon.selectedHoverBackground" ); defaults.remove( "CheckBox.icon.selectedHoverBackground" );
defaults.remove( "CheckBox.icon.selectedPressedBackground" ); defaults.remove( "CheckBox.icon.selectedPressedBackground" );
}
// copy values defaults.remove( "CheckBox.icon[filled].focusWidth" );
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() ) defaults.remove( "CheckBox.icon[filled].hoverBorderColor" );
defaults.put( e.getKey(), defaults.get( e.getValue() ) ); defaults.remove( "CheckBox.icon[filled].focusedBackground" );
defaults.remove( "CheckBox.icon[filled].hoverBackground" );
defaults.remove( "CheckBox.icon[filled].pressedBackground" );
defaults.remove( "CheckBox.icon[filled].selectedFocusedBackground" );
defaults.remove( "CheckBox.icon[filled].selectedHoverBackground" );
defaults.remove( "CheckBox.icon[filled].selectedPressedBackground" );
}
} }
private static Map<String, String> uiKeyMapping = new HashMap<>(); private static Map<String, String> uiKeyMapping = new HashMap<>();
@@ -412,7 +480,6 @@ public class IntelliJTheme
private static Map<String, String> uiKeyInverseMapping = new HashMap<>(); private static Map<String, String> uiKeyInverseMapping = new HashMap<>();
private static Map<String, String> checkboxKeyMapping = new HashMap<>(); private static Map<String, String> checkboxKeyMapping = new HashMap<>();
private static Map<String, String> checkboxDuplicateColors = new HashMap<>(); private static Map<String, String> checkboxDuplicateColors = new HashMap<>();
private static Set<String> noWildcardReplace = new HashSet<>();
static { static {
// ComboBox // ComboBox
@@ -423,14 +490,35 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" ); uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" ); uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
// Component
uiKeyMapping.put( "Component.inactiveErrorFocusColor", "Component.error.borderColor" );
uiKeyMapping.put( "Component.errorFocusColor", "Component.error.focusedBorderColor" );
uiKeyMapping.put( "Component.inactiveWarningFocusColor", "Component.warning.borderColor" );
uiKeyMapping.put( "Component.warningFocusColor", "Component.warning.focusedBorderColor" );
// Link // Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" ); uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
// Menu
uiKeyMapping.put( "Menu.border", "Menu.margin" );
uiKeyMapping.put( "MenuItem.border", "MenuItem.margin" );
uiKeyCopying.put( "CheckBoxMenuItem.margin", "MenuItem.margin" );
uiKeyCopying.put( "RadioButtonMenuItem.margin", "MenuItem.margin" );
uiKeyMapping.put( "PopupMenu.border", "PopupMenu.borderInsets" );
// IDEA uses List.selectionBackground also for menu selection
uiKeyCopying.put( "Menu.selectionBackground", "List.selectionBackground" );
uiKeyCopying.put( "MenuItem.selectionBackground", "List.selectionBackground" );
uiKeyCopying.put( "CheckBoxMenuItem.selectionBackground", "List.selectionBackground" );
uiKeyCopying.put( "RadioButtonMenuItem.selectionBackground", "List.selectionBackground" );
// ProgressBar // ProgressBar
uiKeyMapping.put( "ProgressBar.background", "" ); // ignore uiKeyMapping.put( "ProgressBar.background", "" ); // ignore
uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" ); uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" ); uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
uiKeyCopying.put( "ProgressBar.selectionForeground", "ProgressBar.background" );
uiKeyCopying.put( "ProgressBar.selectionBackground", "ProgressBar.foreground" );
// ScrollBar // ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" ); uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
@@ -442,6 +530,11 @@ public class IntelliJTheme
// Slider // Slider
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite) uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
// TitlePane
uiKeyCopying.put( "TitlePane.inactiveBackground", "TitlePane.background" );
uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
uiKeyMapping.put( "TitlePane.inactiveInfoForeground", "TitlePane.inactiveForeground" );
for( Map.Entry<String, String> e : uiKeyMapping.entrySet() ) for( Map.Entry<String, String> e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() ); uiKeyInverseMapping.put( e.getValue(), e.getKey() );
@@ -456,7 +549,7 @@ public class IntelliJTheme
checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" ); checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" );
checkboxKeyMapping.put( "Checkbox.Border.Disabled", "CheckBox.icon.disabledBorderColor" ); checkboxKeyMapping.put( "Checkbox.Border.Disabled", "CheckBox.icon.disabledBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Default", "CheckBox.icon.focusedBorderColor" ); checkboxKeyMapping.put( "Checkbox.Focus.Thin.Default", "CheckBox.icon.focusedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Wide", "CheckBox.icon.focusedColor" ); checkboxKeyMapping.put( "Checkbox.Focus.Wide", "CheckBox.icon.focusColor" );
checkboxKeyMapping.put( "Checkbox.Foreground.Disabled", "CheckBox.icon.disabledCheckmarkColor" ); checkboxKeyMapping.put( "Checkbox.Foreground.Disabled", "CheckBox.icon.disabledCheckmarkColor" );
checkboxKeyMapping.put( "Checkbox.Background.Selected", "CheckBox.icon.selectedBackground" ); checkboxKeyMapping.put( "Checkbox.Background.Selected", "CheckBox.icon.selectedBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Selected", "CheckBox.icon.selectedBorderColor" ); checkboxKeyMapping.put( "Checkbox.Border.Selected", "CheckBox.icon.selectedBorderColor" );
@@ -470,16 +563,6 @@ public class IntelliJTheme
Map.Entry<String, String>[] entries = checkboxDuplicateColors.entrySet().toArray( new Map.Entry[checkboxDuplicateColors.size()] ); Map.Entry<String, String>[] entries = checkboxDuplicateColors.entrySet().toArray( new Map.Entry[checkboxDuplicateColors.size()] );
for( Map.Entry<String, String> e : entries ) for( Map.Entry<String, String> e : entries )
checkboxDuplicateColors.put( e.getValue(), e.getKey() ); checkboxDuplicateColors.put( e.getValue(), e.getKey() );
// because FlatLaf uses Button.background and Button.borderColor,
// but IDEA uses Button.startBackground and Button.startBorderColor,
// our default button background and border colors may be replaced by
// wildcard *.background and *.borderColor colors
noWildcardReplace.add( "Button.background" );
noWildcardReplace.add( "Button.borderColor" );
noWildcardReplace.add( "Button.default.background" );
noWildcardReplace.add( "Button.default.borderColor" );
noWildcardReplace.add( "ToggleButton.background" );
} }
//---- class ThemeLaf ----------------------------------------------------- //---- class ThemeLaf -----------------------------------------------------
@@ -513,19 +596,12 @@ public class IntelliJTheme
} }
@Override @Override
public UIDefaults getDefaults() { void applyAdditionalDefaults( UIDefaults defaults ) {
UIDefaults defaults = super.getDefaults();
theme.applyProperties( defaults ); theme.applyProperties( defaults );
super.invokePostInitialization( defaults );
return defaults;
} }
@Override @Override
void invokePostInitialization( UIDefaults defaults ) { protected ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
}
@Override
ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = new ArrayList<>(); ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class ); lafClasses.add( FlatLaf.class );
lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class ); lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import java.awt.Font; import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -28,9 +29,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/** /**
* @author Karl Tauber * @author Karl Tauber
@@ -38,7 +39,7 @@ import com.formdev.flatlaf.util.SystemInfo;
class LinuxFontPolicy class LinuxFontPolicy
{ {
static Font getFont() { static Font getFont() {
return SystemInfo.IS_KDE ? getKDEFont() : getGnomeFont(); return SystemInfo.isKDE ? getKDEFont() : getGnomeFont();
} }
/** /**
@@ -73,6 +74,13 @@ class LinuxFontPolicy
family = family.isEmpty() ? word : (family + ' ' + word); family = family.isEmpty() ? word : (family + ' ' + word);
} }
// Ubuntu font is rendered poorly (except if running in JetBrains VM)
// --> use Liberation Sans font
if( family.startsWith( "Ubuntu" ) &&
!SystemInfo.isJetBrainsJVM &&
!FlatSystemProperties.getBoolean( FlatSystemProperties.USE_UBUNTU_FONT, false ) )
family = "Liberation Sans";
// scale font size // scale font size
double dsize = size * getGnomeFontScale(); double dsize = size * getGnomeFontScale();
size = (int) (dsize + 0.5); size = (int) (dsize + 0.5);
@@ -88,9 +96,7 @@ class LinuxFontPolicy
} }
private static Font createFont( String family, int style, int size, double dsize ) { private static Font createFont( String family, int style, int size, double dsize ) {
// using StyleContext.getFont() here because it uses Font font = FlatLaf.createCompositeFont( family, style, size );
// sun.font.FontUtilities.getCompositeFontUIResource()
Font font = new StyleContext().getFont( family, style, size );
// set font size in floating points // set font size in floating points
font = font.deriveFont( style, (float) dsize ); font = font.deriveFont( style, (float) dsize );
@@ -99,6 +105,10 @@ class LinuxFontPolicy
} }
private static double getGnomeFontScale() { private static double getGnomeFontScale() {
// do not scale font here if JRE scales
if( isSystemScaling() )
return 96. / 72.;
// see class com.sun.java.swing.plaf.gtk.PangoFonts background information // see class com.sun.java.swing.plaf.gtk.PangoFonts background information
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" ); Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" );
@@ -168,7 +178,7 @@ class LinuxFontPolicy
// font dpi // font dpi
int dpi = 96; int dpi = 96;
if( forceFontDPI != null ) { if( forceFontDPI != null && !isSystemScaling() ) {
try { try {
dpi = Integer.parseInt( forceFontDPI ); dpi = Integer.parseInt( forceFontDPI );
if( dpi <= 0 ) if( dpi <= 0 )
@@ -247,4 +257,15 @@ class LinuxFontPolicy
} }
return null; return null;
} }
/**
* Returns true if the JRE scales, which is the case if:
* - environment variable GDK_SCALE is set and running on Java 9 or later
* - running on JetBrains Runtime 11 or later and scaling is enabled in system Settings
*/
private static boolean isSystemScaling() {
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
return UIScale.getSystemScaleFactor( gc ) > 1;
}
} }

View File

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

View File

@@ -19,16 +19,19 @@ package com.formdev.flatlaf;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Insets; import java.awt.Insets;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
@@ -41,7 +44,9 @@ import javax.swing.plaf.InsetsUIResource;
import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.ColorFunctions.ColorFunction;
import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.HSLColor; import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -63,13 +68,13 @@ class UIDefaultsLoader
private static final String TYPE_PREFIX = "{"; private static final String TYPE_PREFIX = "{";
private static final String TYPE_PREFIX_END = "}"; private static final String TYPE_PREFIX_END = "}";
private static final String VARIABLE_PREFIX = "@"; private static final String VARIABLE_PREFIX = "@";
@Deprecated
private static final String REF_PREFIX = VARIABLE_PREFIX + "@";
private static final String PROPERTY_PREFIX = "$"; private static final String PROPERTY_PREFIX = "$";
private static final String OPTIONAL_PREFIX = "?"; private static final String OPTIONAL_PREFIX = "?";
private static final String GLOBAL_PREFIX = "*."; private static final String GLOBAL_PREFIX = "*.";
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, UIDefaults defaults ) { static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
// determine classes in class hierarchy in reverse order // determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>(); ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = lookAndFeelClass; for( Class<?> lafClass = lookAndFeelClass;
@@ -79,10 +84,12 @@ class UIDefaultsLoader
lafClasses.add( 0, lafClass ); lafClasses.add( 0, lafClass );
} }
loadDefaultsFromProperties( lafClasses, defaults ); loadDefaultsFromProperties( lafClasses, addons, additionalDefaults, dark, defaults );
} }
static void loadDefaultsFromProperties( List<Class<?>> lafClasses, UIDefaults defaults ) { static void loadDefaultsFromProperties( List<Class<?>> lafClasses, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
try { try {
// load core properties files // load core properties files
Properties properties = new Properties(); Properties properties = new Properties();
@@ -94,15 +101,8 @@ class UIDefaultsLoader
} }
} }
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
List<FlatDefaultsAddon> addonList = new ArrayList<>();
for( FlatDefaultsAddon addon : addonLoader )
addonList.add( addon );
addonList.sort( (addon1, addon2) -> addon1.getPriority() - addon2.getPriority() );
// load properties from addons // load properties from addons
for( FlatDefaultsAddon addon : addonList ) { for( FlatDefaultsAddon addon : addons ) {
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
try( InputStream in = addon.getDefaults( lafClass ) ) { try( InputStream in = addon.getDefaults( lafClass ) ) {
if( in != null ) if( in != null )
@@ -113,26 +113,84 @@ class UIDefaultsLoader
// collect addon class loaders // collect addon class loaders
List<ClassLoader> addonClassLoaders = new ArrayList<>(); List<ClassLoader> addonClassLoaders = new ArrayList<>();
for( FlatDefaultsAddon addon : addonList ) { for( FlatDefaultsAddon addon : addons ) {
ClassLoader addonClassLoader = addon.getClass().getClassLoader(); ClassLoader addonClassLoader = addon.getClass().getClassLoader();
if( !addonClassLoaders.contains( addonClassLoader ) ) if( !addonClassLoaders.contains( addonClassLoader ) )
addonClassLoaders.add( addonClassLoader ); addonClassLoaders.add( addonClassLoader );
} }
// load custom properties files (usually provides by applications)
List<Object> customDefaultsSources = FlatLaf.getCustomDefaultsSources();
int size = (customDefaultsSources != null) ? customDefaultsSources.size() : 0;
for( int i = 0; i < size; i++ ) {
Object source = customDefaultsSources.get( i );
if( source instanceof String && i + 1 < size ) {
// load from package in classloader
String packageName = (String) source;
ClassLoader classLoader = (ClassLoader) customDefaultsSources.get( ++i );
// use class loader also for instantiating classes specified in values
if( classLoader != null && !addonClassLoaders.contains( classLoader ) )
addonClassLoaders.add( classLoader );
packageName = packageName.replace( '.', '/' );
if( classLoader == null )
classLoader = FlatLaf.class.getClassLoader();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + lafClass.getSimpleName() + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null )
properties.load( in );
}
}
} else if( source instanceof File ) {
// load from folder
File folder = (File) source;
for( Class<?> lafClass : lafClasses ) {
File propertiesFile = new File( folder, lafClass.getSimpleName() + ".properties" );
if( !propertiesFile.isFile() )
continue;
try( InputStream in = new FileInputStream( propertiesFile ) ) {
properties.load( in );
}
}
}
}
// add additional defaults
if( additionalDefaults != null )
properties.putAll( additionalDefaults );
// collect all platform specific keys (but do not modify properties) // collect all platform specific keys (but do not modify properties)
ArrayList<String> platformSpecificKeys = new ArrayList<>(); ArrayList<String> platformSpecificKeys = new ArrayList<>();
for( Object key : properties.keySet() ) { for( Object okey : properties.keySet() ) {
if( ((String)key).startsWith( "[" ) ) String key = (String) okey;
platformSpecificKeys.add( (String) key ); if( key.startsWith( "[" ) &&
(key.startsWith( "[win]" ) ||
key.startsWith( "[mac]" ) ||
key.startsWith( "[linux]" ) ||
key.startsWith( "[light]" ) ||
key.startsWith( "[dark]" )) )
platformSpecificKeys.add( key );
} }
// remove platform specific properties and re-add only properties // remove platform specific properties and re-add only properties
// for current platform, but with platform prefix removed // for current platform, but with platform prefix removed
if( !platformSpecificKeys.isEmpty() ) { if( !platformSpecificKeys.isEmpty() ) {
// handle light/dark specific properties
String lightOrDarkPrefix = dark ? "[dark]" : "[light]";
for( String key : platformSpecificKeys ) {
if( key.startsWith( lightOrDarkPrefix ) )
properties.put( key.substring( lightOrDarkPrefix.length() ), properties.remove( key ) );
}
// handle platform specific properties
String platformPrefix = String platformPrefix =
SystemInfo.IS_WINDOWS ? "[win]" : SystemInfo.isWindows ? "[win]" :
SystemInfo.IS_MAC ? "[mac]" : SystemInfo.isMacOS ? "[mac]" :
SystemInfo.IS_LINUX ? "[linux]" : "[unknown]"; SystemInfo.isLinux ? "[linux]" : "[unknown]";
for( String key : platformSpecificKeys ) { for( String key : platformSpecificKeys ) {
Object value = properties.remove( key ); Object value = properties.remove( key );
if( key.startsWith( platformPrefix ) ) if( key.startsWith( platformPrefix ) )
@@ -140,45 +198,45 @@ class UIDefaultsLoader
} }
} }
Function<String, String> resolver = value -> { // get (and remove) globals, which override all other defaults that end with same suffix
return resolveValue( properties, value ); HashMap<String, String> globals = new HashMap<>();
}; Iterator<Entry<Object, Object>> it = properties.entrySet().iterator();
while( it.hasNext() ) {
// get globals, which override all other defaults that end with same suffix Entry<Object, Object> e = it.next();
HashMap<String, Object> globals = new HashMap<>();
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey(); String key = (String) e.getKey();
if( !key.startsWith( GLOBAL_PREFIX ) ) if( key.startsWith( GLOBAL_PREFIX ) ) {
continue; globals.put( key.substring( GLOBAL_PREFIX.length() ), (String) e.getValue() );
it.remove();
String value = resolveValue( properties, (String) e.getValue() );
try {
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) {
logParseError( Level.SEVERE, key, value, ex );
} }
} }
// override UI defaults with globals // override UI defaults with globals
for( Object key : defaults.keySet() ) { for( Object okey : defaults.keySet() ) {
if( key instanceof String && ((String)key).contains( "." ) ) { if( okey instanceof String && ((String)okey).contains( "." ) ) {
String skey = (String) key; String key = (String) okey;
String globalKey = skey.substring( skey.lastIndexOf( '.' ) + 1 ); String globalKey = key.substring( key.lastIndexOf( '.' ) + 1 );
Object globalValue = globals.get( globalKey ); String globalValue = globals.get( globalKey );
if( globalValue != null ) if( globalValue != null && !properties.containsKey( key ) )
defaults.put( key, globalValue ); properties.put( key, globalValue );
} }
} }
// add non-global properties to UI defaults Function<String, String> propertiesGetter = key -> {
return properties.getProperty( key );
};
Function<String, String> resolver = value -> {
return resolveValue( value, propertiesGetter );
};
// parse and add properties to UI defaults
for( Map.Entry<Object, Object> e : properties.entrySet() ) { for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey(); String key = (String) e.getKey();
if( key.startsWith( VARIABLE_PREFIX ) || key.startsWith( GLOBAL_PREFIX ) ) if( key.startsWith( VARIABLE_PREFIX ) )
continue; continue;
String value = resolveValue( properties, (String) e.getValue() ); String value = resolveValue( (String) e.getValue(), propertiesGetter );
try { try {
defaults.put( key, parseValue( key, value, resolver, addonClassLoaders ) ); defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
logParseError( Level.SEVERE, key, value, ex ); logParseError( Level.SEVERE, key, value, ex );
} }
@@ -192,25 +250,22 @@ class UIDefaultsLoader
FlatLaf.LOG.log( level, "FlatLaf: Failed to parse: '" + key + '=' + value + '\'', ex ); FlatLaf.LOG.log( level, "FlatLaf: Failed to parse: '" + key + '=' + value + '\'', ex );
} }
private static String resolveValue( Properties properties, String value ) { static String resolveValue( String value, Function<String, String> propertiesGetter ) {
value = value.trim();
String value0 = value;
if( value.startsWith( PROPERTY_PREFIX ) ) if( value.startsWith( PROPERTY_PREFIX ) )
value = value.substring( PROPERTY_PREFIX.length() ); value = value.substring( PROPERTY_PREFIX.length() );
else if( !value.startsWith( VARIABLE_PREFIX ) ) else if( !value.startsWith( VARIABLE_PREFIX ) )
return value; return value;
// for compatibility
if( value.startsWith( REF_PREFIX ) ) {
FlatLaf.LOG.log( Level.WARNING, "FlatLaf: Usage of '@@' in .properties files is deprecated. Use '$' instead." );
value = value.substring( REF_PREFIX.length() );
}
boolean optional = false; boolean optional = false;
if( value.startsWith( OPTIONAL_PREFIX ) ) { if( value.startsWith( OPTIONAL_PREFIX ) ) {
value = value.substring( OPTIONAL_PREFIX.length() ); value = value.substring( OPTIONAL_PREFIX.length() );
optional = true; optional = true;
} }
String newValue = properties.getProperty( value ); String newValue = propertiesGetter.apply( value );
if( newValue == null ) { if( newValue == null ) {
if( optional ) if( optional )
return "null"; return "null";
@@ -218,29 +273,40 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "variable or property '" + value + "' not found" ); throw new IllegalArgumentException( "variable or property '" + value + "' not found" );
} }
return resolveValue( properties, newValue ); if( newValue.equals( value0 ) )
throw new IllegalArgumentException( "endless recursion in variable or property '" + value + "'" );
return resolveValue( newValue, propertiesGetter );
} }
private enum ValueType { UNKNOWN, STRING, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS } SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static ValueType[] tempResultValueType = new ValueType[1];
static Object parseValue( String key, String value ) { static Object parseValue( String key, String value ) {
return parseValue( key, value, v -> v, Collections.emptyList() ); return parseValue( key, value, null, v -> v, Collections.emptyList() );
} }
private static Object parseValue( String key, String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) { static Object parseValue( String key, String value, ValueType[] resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
{
if( resultValueType == null )
resultValueType = tempResultValueType;
value = value.trim(); value = value.trim();
// null, false, true // null, false, true
switch( value ) { switch( value ) {
case "null": return null; case "null": resultValueType[0] = ValueType.NULL; return null;
case "false": return false; case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": return true; case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
} }
// check for function "lazy" // check for function "lazy"
// Syntax: lazy(uiKey) // Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
String uiKey = value.substring( 5, value.length() - 1 ).trim(); String uiKey = value.substring( 5, value.length() - 1 ).trim();
return (LazyValue) t -> { return (LazyValue) t -> {
return lazyUIManagerGet( uiKey ); return lazyUIManagerGet( uiKey );
@@ -289,8 +355,12 @@ class UIDefaultsLoader
valueType = ValueType.CHARACTER; valueType = ValueType.CHARACTER;
else if( key.endsWith( "UI" ) ) else if( key.endsWith( "UI" ) )
valueType = ValueType.STRING; valueType = ValueType.STRING;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
} }
resultValueType[0] = valueType;
// parse value // parse value
switch( valueType ) { switch( valueType ) {
case STRING: return value; case STRING: return value;
@@ -308,40 +378,49 @@ class UIDefaultsLoader
case SCALEDDIMENSION:return parseScaledDimension( value ); case SCALEDDIMENSION:return parseScaledDimension( value );
case INSTANCE: return parseInstance( value, addonClassLoaders ); case INSTANCE: return parseInstance( value, addonClassLoaders );
case CLASS: return parseClass( value, addonClassLoaders ); case CLASS: return parseClass( value, addonClassLoaders );
case GRAYFILTER: return parseGrayFilter( value );
case UNKNOWN: case UNKNOWN:
default: default:
// colors // colors
Object color = parseColorOrFunction( value, resolver, false ); Object color = parseColorOrFunction( value, resolver, false );
if( color != null ) if( color != null ) {
resultValueType[0] = ValueType.COLOR;
return color; return color;
}
// integer // integer
Integer integer = parseInteger( value, false ); Integer integer = parseInteger( value, false );
if( integer != null ) if( integer != null ) {
resultValueType[0] = ValueType.INTEGER;
return integer; return integer;
}
// float // float
Float f = parseFloat( value, false ); Float f = parseFloat( value, false );
if( f != null ) if( f != null ) {
resultValueType[0] = ValueType.FLOAT;
return f; return f;
}
// string // string
resultValueType[0] = ValueType.STRING;
return value; return value;
} }
} }
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) { private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) { if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor] // top,left,bottom,right[,lineColor[,lineThickness]]
List<String> parts = split( value, ',' ); List<String> parts = split( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() == 5) ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null; : null;
float lineThickness = (parts.size() >= 6) ? parseFloat( parts.get( 5 ), true ) : 1f;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null)
? new FlatLineBorder( insets, lineColor ) ? new FlatLineBorder( insets, lineColor, lineThickness )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };
} else } else
@@ -496,29 +575,42 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" ); throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
switch( function ) { switch( function ) {
case "rgb": return parseColorRgbOrRgba( false, params ); case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError );
case "rgba": return parseColorRgbOrRgba( true, params ); case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError );
case "hsl": return parseColorHslOrHsla( false, params ); case "hsl": return parseColorHslOrHsla( false, params );
case "hsla": return parseColorHslOrHsla( true, params ); case "hsla": return parseColorHslOrHsla( true, params );
case "lighten": return parseColorLightenOrDarken( true, params, resolver, reportError ); case "lighten": return parseColorHSLIncreaseDecrease( 2, true, params, resolver, reportError );
case "darken": return parseColorLightenOrDarken( false, params, resolver, reportError ); case "darken": return parseColorHSLIncreaseDecrease( 2, false, params, resolver, reportError );
case "saturate": return parseColorHSLIncreaseDecrease( 1, true, params, resolver, reportError );
case "desaturate": return parseColorHSLIncreaseDecrease( 1, false, params, resolver, reportError );
} }
throw new IllegalArgumentException( "unknown color function '" + value + "'" ); throw new IllegalArgumentException( "unknown color function '" + value + "'" );
} }
/** /**
* Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha) * Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha) or rgba(color,alpha)
* - red: an integer 0-255 * - red: an integer 0-255 or a percentage 0-100%
* - green: an integer 0-255 * - green: an integer 0-255 or a percentage 0-100%
* - blue: an integer 0-255 * - blue: an integer 0-255 or a percentage 0-100%
* - alpha: an integer 0-255 * - alpha: an integer 0-255 or a percentage 0-100%
*/ */
private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params ) { private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params,
int red = parseInteger( params.get( 0 ), 0, 255 ); Function<String, String> resolver, boolean reportError )
int green = parseInteger( params.get( 1 ), 0, 255 ); {
int blue = parseInteger( params.get( 2 ), 0, 255 ); if( hasAlpha && params.size() == 2 ) {
int alpha = hasAlpha ? parseInteger( params.get( 3 ), 0, 255 ) : 255; // syntax rgba(color,alpha), which allows adding alpha to any color
String colorStr = params.get( 0 );
int alpha = parseInteger( params.get( 1 ), 0, 255, true );
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
return new ColorUIResource( new Color( ((alpha & 0xff) << 24) | (color.getRGB() & 0xffffff), true ) );
}
int red = parseInteger( params.get( 0 ), 0, 255, true );
int green = parseInteger( params.get( 1 ), 0, 255, true );
int blue = parseInteger( params.get( 2 ), 0, 255, true );
int alpha = hasAlpha ? parseInteger( params.get( 3 ), 0, 255, true ) : 255;
return hasAlpha return hasAlpha
? new ColorUIResource( new Color( red, green, blue, alpha ) ) ? new ColorUIResource( new Color( red, green, blue, alpha ) )
@@ -533,7 +625,7 @@ class UIDefaultsLoader
* - alpha: a percentage 0-100% * - alpha: a percentage 0-100%
*/ */
private static ColorUIResource parseColorHslOrHsla( boolean hasAlpha, List<String> params ) { private static ColorUIResource parseColorHslOrHsla( boolean hasAlpha, List<String> params ) {
int hue = parseInteger( params.get( 0 ), 0, 360 ); int hue = parseInteger( params.get( 0 ), 0, 360, false );
int saturation = parsePercentage( params.get( 1 ) ); int saturation = parsePercentage( params.get( 1 ) );
int lightness = parsePercentage( params.get( 2 ) ); int lightness = parsePercentage( params.get( 2 ) );
int alpha = hasAlpha ? parsePercentage( params.get( 3 ) ) : 100; int alpha = hasAlpha ? parsePercentage( params.get( 3 ) ) : 100;
@@ -543,35 +635,37 @@ class UIDefaultsLoader
} }
/** /**
* Syntax: lighten([color,]amount[,options]) or darken([color,]amount[,options]) * Syntax: lighten(color,amount[,options]) or darken(color,amount[,options]) or
* saturate(color,amount[,options]) or desaturate(color,amount[,options])
* - color: a color (e.g. #f00) or a color function * - color: a color (e.g. #f00) or a color function
* - amount: percentage 0-100% * - amount: percentage 0-100%
* - options: [relative] [autoInverse] [lazy] * - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived]
*/ */
private static Object parseColorLightenOrDarken( boolean lighten, List<String> params, private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase,
Function<String, String> resolver, boolean reportError ) List<String> params, Function<String, String> resolver, boolean reportError )
{ {
boolean isDerived = params.get( 0 ).endsWith( "%" ); String colorStr = params.get( 0 );
String colorStr = isDerived ? null : params.get( 0 ); int amount = parsePercentage( params.get( 1 ) );
int nextParam = isDerived ? 0 : 1;
int amount = parsePercentage( params.get( nextParam++ ) );
boolean relative = false; boolean relative = false;
boolean autoInverse = false; boolean autoInverse = false;
boolean lazy = false; boolean lazy = false;
boolean derived = false;
if( params.size() > nextParam ) { if( params.size() > 2 ) {
String options = params.get( nextParam++ ); String options = params.get( 2 );
relative = options.contains( "relative" ); relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" ); autoInverse = options.contains( "autoInverse" );
lazy = options.contains( "lazy" ); lazy = options.contains( "lazy" );
derived = options.contains( "derived" );
// use autoInverse by default for derived colors, except if noAutoInverse is set
if( derived && !options.contains( "noAutoInverse" ) )
autoInverse = true;
} }
ColorFunctions.ColorFunction function = lighten // create function
? new ColorFunctions.Lighten( amount, relative, autoInverse ) ColorFunction function = new ColorFunctions.HSLIncreaseDecrease(
: new ColorFunctions.Darken( amount, relative, autoInverse ); hslIndex, increase, amount, relative, autoInverse );
if( isDerived )
return new DerivedColor( function );
if( lazy ) { if( lazy ) {
return (LazyValue) t -> { return (LazyValue) t -> {
@@ -582,8 +676,31 @@ class UIDefaultsLoader
}; };
} }
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError ); // parse base color
return new ColorUIResource( ColorFunctions.applyFunctions( color, function ) ); String resolvedColorStr = resolver.apply( colorStr );
ColorUIResource baseColor = (ColorUIResource) parseColorOrFunction( resolvedColorStr, resolver, reportError );
if( baseColor == null )
return null;
// apply this function to base color
Color newColor = ColorFunctions.applyFunctions( baseColor, function );
if( derived ) {
ColorFunction[] functions;
if( baseColor instanceof DerivedColor && resolvedColorStr == colorStr ) {
// if the base color is also derived, join the color functions
// but only if base color function is specified directly in this function
ColorFunction[] baseFunctions = ((DerivedColor)baseColor).getFunctions();
functions = new ColorFunction[baseFunctions.length + 1];
System.arraycopy( baseFunctions, 0, functions, 0, baseFunctions.length );
functions[baseFunctions.length] = function;
} else
functions = new ColorFunction[] { function };
return new DerivedColor( newColor, functions );
}
return new ColorUIResource( newColor );
} }
private static int parsePercentage( String value ) { private static int parsePercentage( String value ) {
@@ -608,7 +725,12 @@ class UIDefaultsLoader
return value.charAt( 0 ); return value.charAt( 0 );
} }
private static Integer parseInteger( String value, int min, int max ) { private static Integer parseInteger( String value, int min, int max, boolean allowPercentage ) {
if( allowPercentage && value.endsWith( "%" ) ) {
int percent = parsePercentage( value );
return (max * percent) / 100;
}
Integer integer = parseInteger( value, true ); Integer integer = parseInteger( value, true );
if( integer.intValue() < min || integer.intValue() > max ) if( integer.intValue() < min || integer.intValue() > max )
throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' ); throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' );
@@ -663,6 +785,21 @@ class UIDefaultsLoader
}; };
} }
private static Object parseGrayFilter( String value ) {
List<String> numbers = split( value, ',' );
try {
int brightness = Integer.parseInt( numbers.get( 0 ) );
int contrast = Integer.parseInt( numbers.get( 1 ) );
int alpha = Integer.parseInt( numbers.get( 2 ) );
return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha );
};
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
}
}
/** /**
* Split string and trim parts. * Split string and trim parts.
*/ */

View File

@@ -36,25 +36,28 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* is painted outside of the icon bounds. Make sure that the checkbox * is painted outside of the icon bounds. Make sure that the checkbox
* has margins, which are equal or greater than focusWidth. * has margins, which are equal or greater than focusWidth.
* *
* @uiDefault CheckBox.icon.style String optional; "outline"/null (default) or "filled"
* @uiDefault Component.focusWidth int * @uiDefault Component.focusWidth int
* @uiDefault Component.focusColor Color * @uiDefault Component.focusColor Color
* @uiDefault CheckBox.icon.focusedColor Color optional; defaults to Component.focusColor * @uiDefault CheckBox.icon.focusWidth int optional; defaults to Component.focusWidth
* @uiDefault CheckBox.icon.focusColor Color optional; defaults to Component.focusColor
* @uiDefault CheckBox.icon.borderColor Color * @uiDefault CheckBox.icon.borderColor Color
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.selectedBorderColor Color
* @uiDefault CheckBox.icon.focusedBorderColor Color
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.selectedFocusedBorderColor Color optional
* @uiDefault CheckBox.icon.background Color * @uiDefault CheckBox.icon.background Color
* @uiDefault CheckBox.icon.disabledBackground Color * @uiDefault CheckBox.icon.selectedBorderColor Color
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.selectedBackground Color * @uiDefault CheckBox.icon.selectedBackground Color
* @uiDefault CheckBox.icon.selectedHoverBackground Color optional
* @uiDefault CheckBox.icon.selectedPressedBackground Color optional
* @uiDefault CheckBox.icon.checkmarkColor Color * @uiDefault CheckBox.icon.checkmarkColor Color
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.disabledBackground Color
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color * @uiDefault CheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBox.icon.focusedBorderColor Color
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.selectedFocusedBorderColor Color optional
* @uiDefault CheckBox.icon.selectedFocusedBackground Color optional
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.selectedHoverBackground Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.selectedPressedBackground Color optional
* @uiDefault CheckBox.arc int * @uiDefault CheckBox.arc int
* *
* @author Karl Tauber * @author Karl Tauber
@@ -62,27 +65,62 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatCheckBoxIcon public class FlatCheckBoxIcon
extends FlatAbstractIcon extends FlatAbstractIcon
{ {
public final int focusWidth = UIManager.getInt( "Component.focusWidth" ); protected final String style = UIManager.getString( "CheckBox.icon.style" );
protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusedColor", public final int focusWidth = getUIInt( "CheckBox.icon.focusWidth",
UIManager.getInt( "Component.focusWidth" ), style );
protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusColor",
UIManager.getColor( "Component.focusColor" ) ); UIManager.getColor( "Component.focusColor" ) );
protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 ); protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
protected final Color borderColor = UIManager.getColor( "CheckBox.icon.borderColor" ); // enabled
protected final Color disabledBorderColor = UIManager.getColor( "CheckBox.icon.disabledBorderColor" ); protected final Color borderColor = getUIColor( "CheckBox.icon.borderColor", style );
protected final Color selectedBorderColor = UIManager.getColor( "CheckBox.icon.selectedBorderColor" ); protected final Color background = getUIColor( "CheckBox.icon.background", style );
protected final Color focusedBorderColor = UIManager.getColor( "CheckBox.icon.focusedBorderColor" ); protected final Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style );
protected final Color hoverBorderColor = UIManager.getColor( "CheckBox.icon.hoverBorderColor" ); protected final Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style );
protected final Color selectedFocusedBorderColor = UIManager.getColor( "CheckBox.icon.selectedFocusedBorderColor" ); protected final Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style );
protected final Color background = UIManager.getColor( "CheckBox.icon.background" );
protected final Color disabledBackground = UIManager.getColor( "CheckBox.icon.disabledBackground" ); // disabled
protected final Color focusedBackground = UIManager.getColor( "CheckBox.icon.focusedBackground" ); protected final Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style );
protected final Color hoverBackground = UIManager.getColor( "CheckBox.icon.hoverBackground" ); protected final Color disabledBackground = getUIColor( "CheckBox.icon.disabledBackground", style );
protected final Color pressedBackground = UIManager.getColor( "CheckBox.icon.pressedBackground" ); protected final Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style );
protected final Color selectedBackground = UIManager.getColor( "CheckBox.icon.selectedBackground" );
protected final Color selectedHoverBackground = UIManager.getColor( "CheckBox.icon.selectedHoverBackground" ); // focused
protected final Color selectedPressedBackground = UIManager.getColor( "CheckBox.icon.selectedPressedBackground" ); protected final Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style );
protected final Color checkmarkColor = UIManager.getColor( "CheckBox.icon.checkmarkColor" ); protected final Color focusedBackground = getUIColor( "CheckBox.icon.focusedBackground", style );
protected final Color disabledCheckmarkColor = UIManager.getColor( "CheckBox.icon.disabledCheckmarkColor" ); protected final Color selectedFocusedBorderColor = getUIColor( "CheckBox.icon.selectedFocusedBorderColor", style );
protected final Color selectedFocusedBackground = getUIColor( "CheckBox.icon.selectedFocusedBackground", style );
protected final Color selectedFocusedCheckmarkColor = getUIColor( "CheckBox.icon.selectedFocusedCheckmarkColor", style );
// hover
protected final Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style );
protected final Color hoverBackground = getUIColor( "CheckBox.icon.hoverBackground", style );
protected final Color selectedHoverBackground = getUIColor( "CheckBox.icon.selectedHoverBackground", style );
// pressed
protected final Color pressedBackground = getUIColor( "CheckBox.icon.pressedBackground", style );
protected final Color selectedPressedBackground = getUIColor( "CheckBox.icon.selectedPressedBackground", style );
protected static Color getUIColor( String key, String style ) {
if( style != null ) {
Color color = UIManager.getColor( styleKey( key, style ) );
if( color != null )
return color;
}
return UIManager.getColor( key );
}
protected static int getUIInt( String key, int defaultValue, String style ) {
if( style != null ) {
Object value = UIManager.get( styleKey( key, style ) );
if( value instanceof Integer )
return (Integer) value;
}
return FlatUIUtils.getUIInt( key, defaultValue );
}
private static String styleKey( String key, String style ) {
return key.replace( ".icon.", ".icon[" + style + "]." );
}
static final int ICON_SIZE = 15; static final int ICON_SIZE = 15;
@@ -94,9 +132,10 @@ public class FlatCheckBoxIcon
protected void paintIcon( Component c, Graphics2D g2 ) { protected void paintIcon( Component c, Graphics2D g2 ) {
boolean indeterminate = c instanceof JComponent && clientPropertyEquals( (JComponent) c, SELECTED_STATE, SELECTED_STATE_INDETERMINATE ); boolean indeterminate = c instanceof JComponent && clientPropertyEquals( (JComponent) c, SELECTED_STATE, SELECTED_STATE_INDETERMINATE );
boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected()); boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
boolean isFocused = FlatUIUtils.isPermanentFocusOwner( c );
// paint focused border // paint focused border
if( c.hasFocus() && focusWidth > 0 ) { if( isFocused && focusWidth > 0 && FlatButtonUI.isFocusPainted( c ) ) {
g2.setColor( focusColor ); g2.setColor( focusColor );
paintFocusBorder( g2 ); paintFocusBorder( g2 );
} }
@@ -111,18 +150,22 @@ public class FlatCheckBoxIcon
paintBorder( g2 ); paintBorder( g2 );
// paint background // paint background
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c, g2.setColor( FlatUIUtils.deriveColor( FlatButtonUI.buttonStateColor( c,
selected ? selectedBackground : background, selected ? selectedBackground : background,
disabledBackground, disabledBackground,
focusedBackground, (selected && selectedFocusedBackground != null) ? selectedFocusedBackground : focusedBackground,
selected && selectedHoverBackground != null ? selectedHoverBackground : hoverBackground, (selected && selectedHoverBackground != null) ? selectedHoverBackground : hoverBackground,
selected && selectedPressedBackground != null ? selectedPressedBackground : pressedBackground ), (selected && selectedPressedBackground != null) ? selectedPressedBackground : pressedBackground ),
background ); background ) );
paintBackground( g2 ); paintBackground( g2 );
// paint checkmark // paint checkmark
if( selected || indeterminate ) { if( selected || indeterminate ) {
g2.setColor( c.isEnabled() ? checkmarkColor : disabledCheckmarkColor ); g2.setColor( c.isEnabled()
? ((selected && isFocused && selectedFocusedCheckmarkColor != null)
? selectedFocusedCheckmarkColor
: checkmarkColor)
: disabledCheckmarkColor );
if( indeterminate ) if( indeterminate )
paintIndeterminate( g2 ); paintIndeterminate( g2 );
else else

View File

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

View File

@@ -82,10 +82,10 @@ public class FlatHelpButtonIcon
*/ */
boolean enabled = c.isEnabled(); boolean enabled = c.isEnabled();
boolean focused = c.hasFocus(); boolean focused = FlatUIUtils.isPermanentFocusOwner( c );
// paint focused border // paint focused border
if( focused ) { if( focused && FlatButtonUI.isFocusPainted( c ) ) {
g2.setColor( focusColor ); g2.setColor( focusColor );
g2.fill( new Ellipse2D.Float( 0.5f, 0.5f, iconSize - 1, iconSize - 1 ) ); g2.fill( new Ellipse2D.Float( 0.5f, 0.5f, iconSize - 1, iconSize - 1 ) );
} }
@@ -100,12 +100,12 @@ public class FlatHelpButtonIcon
g2.fill( new Ellipse2D.Float( focusWidth + 0.5f, focusWidth + 0.5f, 21, 21 ) ); g2.fill( new Ellipse2D.Float( focusWidth + 0.5f, focusWidth + 0.5f, 21, 21 ) );
// paint background // paint background
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c, g2.setColor( FlatUIUtils.deriveColor( FlatButtonUI.buttonStateColor( c,
background, background,
disabledBackground, disabledBackground,
focusedBackground, focusedBackground,
hoverBackground, hoverBackground,
pressedBackground ), background ); pressedBackground ), background ) );
g2.fill( new Ellipse2D.Float( focusWidth + 1.5f, focusWidth + 1.5f, 19, 19 ) ); g2.fill( new Ellipse2D.Float( focusWidth + 1.5f, focusWidth + 1.5f, 19, 19 ) );
// paint question mark // paint question mark

View File

@@ -27,6 +27,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/** /**
* Base class for internal frame icons. * Base class for internal frame icons.
* *
* @uiDefault InternalFrame.buttonSize Dimension
* @uiDefault InternalFrame.buttonHoverBackground Color * @uiDefault InternalFrame.buttonHoverBackground Color
* @uiDefault InternalFrame.buttonPressedBackground Color * @uiDefault InternalFrame.buttonPressedBackground Color
* *
@@ -53,7 +54,7 @@ public abstract class FlatInternalFrameAbstractIcon
protected void paintBackground( Component c, Graphics2D g ) { protected void paintBackground( Component c, Graphics2D g ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground ); Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) { if( background != null ) {
FlatUIUtils.setColor( g, background, c.getBackground() ); g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
g.fillRect( 0, 0, width, height ); g.fillRect( 0, 0, width, height );
} }
} }

View File

@@ -28,8 +28,11 @@ import com.formdev.flatlaf.ui.FlatButtonUI;
/** /**
* "close" icon for {@link javax.swing.JInternalFrame}. * "close" icon for {@link javax.swing.JInternalFrame}.
* *
* @uiDefault InternalFrame.buttonHoverBackground Color * @uiDefault InternalFrame.buttonSize Dimension
* @uiDefault InternalFrame.buttonPressedBackground Color * @uiDefault InternalFrame.closeHoverBackground Color
* @uiDefault InternalFrame.closePressedBackground Color
* @uiDefault InternalFrame.closeHoverForeground Color
* @uiDefault InternalFrame.closePressedForeground Color
* *
* @author Karl Tauber * @author Karl Tauber
*/ */

View File

@@ -24,14 +24,14 @@ import java.awt.geom.Rectangle2D;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
/** /**
* "minimize" (actually "restore") icon for {@link javax.swing.JInternalFrame}. * "restore" (or "minimize") icon for {@link javax.swing.JInternalFrame}.
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatInternalFrameMinimizeIcon public class FlatInternalFrameRestoreIcon
extends FlatInternalFrameAbstractIcon extends FlatInternalFrameAbstractIcon
{ {
public FlatInternalFrameMinimizeIcon() { public FlatInternalFrameRestoreIcon() {
} }
@Override @Override

View File

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

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.icons;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/** /**
* Icon for {@link javax.swing.JRadioButton}. * Icon for {@link javax.swing.JRadioButton}.
@@ -34,7 +33,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatRadioButtonIcon public class FlatRadioButtonIcon
extends FlatCheckBoxIcon extends FlatCheckBoxIcon
{ {
protected final int centerDiameter = FlatUIUtils.getUIInt( "RadioButton.icon.centerDiameter", 8 ); protected final int centerDiameter = getUIInt( "RadioButton.icon.centerDiameter", 8, style );
@Override @Override
protected void paintFocusBorder( Graphics2D g2 ) { protected void paintFocusBorder( Graphics2D g2 ) {

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "close" icon for closable tabs in {@link javax.swing.JTabbedPane}.
*
* @uiDefault TabbedPane.closeSize Dimension
* @uiDefault TabbedPane.closeArc int
* @uiDefault TabbedPane.closeCrossPlainSize float
* @uiDefault TabbedPane.closeCrossFilledSize float
* @uiDefault TabbedPane.closeCrossLineWidth float
* @uiDefault TabbedPane.closeBackground Color
* @uiDefault TabbedPane.closeForeground Color
* @uiDefault TabbedPane.closeHoverBackground Color
* @uiDefault TabbedPane.closeHoverForeground Color
* @uiDefault TabbedPane.closePressedBackground Color
* @uiDefault TabbedPane.closePressedForeground Color
*
* @author Karl Tauber
*/
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
{
protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" );
protected final int arc = UIManager.getInt( "TabbedPane.closeArc" );
protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize );
protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" );
protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" );
protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
public FlatTabbedPaneCloseIcon() {
super( 16, 16, null );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background
Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground );
if( bg != null ) {
g.setColor( bg );
g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2,
size.width, size.height, arc, arc );
}
// set cross color
g.setColor( FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground ) );
float mx = width / 2;
float my = height / 2;
float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2;
// paint cross
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( mx - r, my - r, mx + r, my + r ), false );
path.append( new Line2D.Float( mx - r, my + r, mx + r, my - r ), false );
g.setStroke( new BasicStroke( closeCrossLineWidth ) );
g.draw( path );
}
}

View File

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

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
/**
* "close" icon for windows (frames and dialogs).
*
* @uiDefault TitlePane.closeHoverBackground Color
* @uiDefault TitlePane.closePressedBackground Color
* @uiDefault TitlePane.closeHoverForeground Color
* @uiDefault TitlePane.closePressedForeground Color
*
* @author Karl Tauber
*/
public class FlatWindowCloseIcon
extends FlatWindowAbstractIcon
{
private final Color hoverForeground = UIManager.getColor( "TitlePane.closeHoverForeground" );
private final Color pressedForeground = UIManager.getColor( "TitlePane.closePressedForeground" );
public FlatWindowCloseIcon() {
super( UIManager.getDimension( "TitlePane.buttonSize" ),
UIManager.getColor( "TitlePane.closeHoverBackground" ),
UIManager.getColor( "TitlePane.closePressedBackground" ) );
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (10 * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int ix2 = ix + iwh - 1;
int iy2 = iy + iwh - 1;
int thickness = (int) scaleFactor;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( ix, iy, ix2, iy2 ), false );
path.append( new Line2D.Float( ix, iy2, ix2, iy ), false );
g.setStroke( new BasicStroke( thickness ) );
g.draw( path );
}
@Override
protected Color getForeground( Component c ) {
return FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground );
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Graphics2D;
/**
* "iconify" icon for windows (frames and dialogs).
*
* @author Karl Tauber
*/
public class FlatWindowIconifyIcon
extends FlatWindowAbstractIcon
{
public FlatWindowIconifyIcon() {
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iw = (int) (10 * scaleFactor);
int ih = (int) scaleFactor;
int ix = x + ((width - iw) / 2);
int iy = y + ((height - ih) / 2);
g.fillRect( ix, iy, iw, ih );
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Graphics2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "maximize" icon for windows (frames and dialogs).
*
* @author Karl Tauber
*/
public class FlatWindowMaximizeIcon
extends FlatWindowAbstractIcon
{
public FlatWindowMaximizeIcon() {
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (10 * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int thickness = (int) scaleFactor;
g.fill( FlatUIUtils.createRectangle( ix, iy, iwh, iwh, thickness ) );
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "restore" icon for windows (frames and dialogs).
*
* @author Karl Tauber
*/
public class FlatWindowRestoreIcon
extends FlatWindowAbstractIcon
{
public FlatWindowRestoreIcon() {
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (10 * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int thickness = (int) 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 );
Area area = new Area( r1 );
area.subtract( new Area( new Rectangle2D.Float( ix, iy + ro2, rwh, rwh ) ) );
g.fill( area );
g.fill( r2 );
}
}

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.BasicStroke; import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@@ -26,6 +27,7 @@ import java.awt.Shape;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import javax.swing.JComponent;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.plaf.basic.BasicArrowButton;
@@ -45,15 +47,23 @@ public class FlatArrowButton
private final Color disabledForeground; private final Color disabledForeground;
private final Color hoverForeground; private final Color hoverForeground;
private final Color hoverBackground; private final Color hoverBackground;
private final Color pressedBackground;
private int arrowWidth = DEFAULT_ARROW_WIDTH; private int arrowWidth = DEFAULT_ARROW_WIDTH;
private int xOffset = 0; private int xOffset = 0;
private int yOffset = 0; private int yOffset = 0;
private boolean hover; private boolean hover;
private boolean pressed;
public FlatArrowButton( int direction, String type, Color foreground, Color disabledForeground, public FlatArrowButton( int direction, String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground ) Color hoverForeground, Color hoverBackground )
{
this( direction, type, foreground, disabledForeground, hoverForeground, hoverBackground, null );
}
public FlatArrowButton( int direction, String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground, Color pressedBackground )
{ {
super( direction, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE ); super( direction, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE );
@@ -62,11 +72,12 @@ public class FlatArrowButton
this.disabledForeground = disabledForeground; this.disabledForeground = disabledForeground;
this.hoverForeground = hoverForeground; this.hoverForeground = hoverForeground;
this.hoverBackground = hoverBackground; this.hoverBackground = hoverBackground;
this.pressedBackground = pressedBackground;
setOpaque( false ); setOpaque( false );
setBorder( null ); setBorder( null );
if( hoverForeground != null || hoverBackground != null ) { if( hoverForeground != null || hoverBackground != null || pressedBackground != null ) {
addMouseListener( new MouseAdapter() { addMouseListener( new MouseAdapter() {
@Override @Override
public void mouseEntered( MouseEvent e ) { public void mouseEntered( MouseEvent e ) {
@@ -79,6 +90,18 @@ public class FlatArrowButton
hover = false; hover = false;
repaint(); repaint();
} }
@Override
public void mousePressed( MouseEvent e ) {
pressed = true;
repaint();
}
@Override
public void mouseReleased( MouseEvent e ) {
pressed = false;
repaint();
}
} ); } );
} }
} }
@@ -95,6 +118,10 @@ public class FlatArrowButton
return hover; return hover;
} }
protected boolean isPressed() {
return pressed;
}
public int getXOffset() { public int getXOffset() {
return xOffset; return xOffset;
} }
@@ -111,6 +138,10 @@ public class FlatArrowButton
this.yOffset = yOffset; this.yOffset = yOffset;
} }
protected Color deriveBackground( Color background ) {
return background;
}
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
return scale( super.getPreferredSize() ); return scale( super.getPreferredSize() );
@@ -130,33 +161,54 @@ public class FlatArrowButton
int height = getHeight(); int height = getHeight();
boolean enabled = isEnabled(); boolean enabled = isEnabled();
// paint hover background // paint hover or pressed background
if( enabled && isHover() && hoverBackground != null ) { if( enabled ) {
g.setColor( hoverBackground ); Color background = (pressedBackground != null && isPressed())
? deriveBackground( pressedBackground )
: ((hoverBackground != null && isHover())
? deriveBackground( hoverBackground )
: null);
if( background != null ) {
g.setColor( background );
g.fillRect( 0, 0, width, height ); g.fillRect( 0, 0, width, height );
} }
}
int direction = getDirection(); int direction = getDirection();
boolean vert = (direction == NORTH || direction == SOUTH); boolean vert = (direction == NORTH || direction == SOUTH);
// compute width/height
int w = scale( arrowWidth + (chevron ? 0 : 1) ); int w = scale( arrowWidth + (chevron ? 0 : 1) );
int h = scale( (arrowWidth / 2) + (chevron ? 0 : 1) ); int h = scale( (arrowWidth / 2) + (chevron ? 0 : 1) );
// rotate width/height
int rw = vert ? w : h; int rw = vert ? w : h;
int rh = vert ? h : w; int rh = vert ? h : w;
// chevron lines end 1px outside of width/height
if( chevron ) {
// add 1px to width/height for position calculation only
rw++;
rh++;
}
int x = Math.round( (width - rw) / 2f + scale( (float) xOffset ) ); int x = Math.round( (width - rw) / 2f + scale( (float) xOffset ) );
int y = Math.round( (height - rh) / 2f + scale( (float) yOffset ) ); int y = Math.round( (height - rh) / 2f + scale( (float) yOffset ) );
// optimization for small chevron arrows (e.g. OneTouchButtons in SplitPane) // move arrow for round borders
if( x + rw >= width && x > 0 ) Container parent = getParent();
x--; if( vert && parent instanceof JComponent && FlatUIUtils.hasRoundBorder( (JComponent) parent ) )
if( y + rh >= height && y > 0 ) x -= scale( parent.getComponentOrientation().isLeftToRight() ? 1 : -1 );
y--;
// paint arrow // paint arrow
g.setColor( enabled g.setColor( enabled
? (isHover() && hoverForeground != null ? hoverForeground : foreground) ? (isHover() && hoverForeground != null ? hoverForeground : foreground)
: disabledForeground ); : disabledForeground );
g.translate( x, y ); g.translate( x, y );
/*debug
debugPaint( g2, vert, rw, rh );
debug*/
Shape arrowShape = createArrowShape( direction, chevron, w, h ); Shape arrowShape = createArrowShape( direction, chevron, w, h );
if( chevron ) { if( chevron ) {
g2.setStroke( new BasicStroke( scale( 1f ) ) ); g2.setStroke( new BasicStroke( scale( 1f ) ) );
@@ -177,4 +229,22 @@ public class FlatArrowButton
default: return new Path2D.Float(); default: return new Path2D.Float();
} }
} }
/*debug
private void debugPaint( Graphics g, boolean vert, int w, int h ) {
Color oldColor = g.getColor();
g.setColor( Color.red );
g.drawRect( 0, 0, w - 1, h - 1 );
int xy1 = -2;
int xy2 = h + 1;
for( int i = 0; i < 20; i++ ) {
g.drawRect( vert ? 0 : xy1, vert ? xy1 : 0, 0, 0 );
g.drawRect( vert ? 0 : xy2, vert ? xy2 : 0, 0, 0 );
xy1 -= 2;
xy2 += 2;
}
g.setColor( oldColor );
}
debug*/
} }

View File

@@ -36,14 +36,16 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders; import javax.swing.plaf.basic.BasicBorders;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.DerivedColor;
/** /**
* Border for various components (e.g. {@link javax.swing.JTextField}). * Border for various components (e.g. {@link javax.swing.JTextField}).
* *
* There is empty space around the component border, if Component.focusWidth is greater than zero, * There is empty space around the component border, if Component.focusWidth is greater than zero,
* which is used to paint focus border. * which is used to paint outer focus border.
* *
* Because there is empty space (if focus border is not painted), * Because there is empty space (if outer focus border is not painted),
* UI delegates that use this border (or subclasses) must invoke * UI delegates that use this border (or subclasses) must invoke
* {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly. * {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly.
* *
@@ -54,6 +56,12 @@ import javax.swing.text.JTextComponent;
* @uiDefault Component.disabledBorderColor Color * @uiDefault Component.disabledBorderColor Color
* @uiDefault Component.focusedBorderColor Color * @uiDefault Component.focusedBorderColor Color
* *
* @uiDefault Component.error.borderColor Color
* @uiDefault Component.error.focusedBorderColor Color
* @uiDefault Component.warning.borderColor Color
* @uiDefault Component.warning.focusedBorderColor Color
* @uiDefault Component.custom.borderColor Color
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatBorder public class FlatBorder
@@ -61,37 +69,77 @@ public class FlatBorder
{ {
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ); protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
protected final float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" ); protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final Color borderColor = UIManager.getColor( "Component.borderColor" ); protected final Color borderColor = UIManager.getColor( "Component.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" ); protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" );
protected final Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" );
protected final Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
protected final Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
protected final Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
protected final Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
boolean isCellEditor = isTableCellEditor( c ); float focusWidth = scale( (float) getFocusWidth( c ) );
float focusWidth = isCellEditor ? 0 : getFocusWidth( c ); float borderWidth = scale( (float) getBorderWidth( c ) );
float borderWidth = getBorderWidth( c ); float arc = scale( (float) getArc( c ) );
float arc = isCellEditor ? 0 : getArc( c ); Color outlineColor = getOutlineColor( c );
if( isFocused( c ) ) { // paint outer border
float innerFocusWidth = !(c instanceof JScrollPane) ? this.innerFocusWidth : 0; if( outlineColor != null || isFocused( c ) ) {
float innerWidth = !isCellEditor( c ) && !(c instanceof JScrollPane)
? (outlineColor != null ? innerOutlineWidth : innerFocusWidth)
: 0;
g2.setColor( getFocusColor( c ) ); g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height, focusWidth, FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height,
getLineWidth( c ) + scale( innerFocusWidth ), arc ); focusWidth, borderWidth + scale( innerWidth ), arc );
} }
g2.setPaint( getBorderColor( c ) ); // paint border
g2.setPaint( (outlineColor != null) ? outlineColor : getBorderColor( c ) );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc ); FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
} }
/**
* Returns the outline color of the component border specified in client property
* {@link FlatClientProperties#OUTLINE}.
*/
protected Color getOutlineColor( Component c ) {
if( !(c instanceof JComponent) )
return null;
Object outline = ((JComponent)c).getClientProperty( FlatClientProperties.OUTLINE );
if( outline instanceof String ) {
switch( (String) outline ) {
case FlatClientProperties.OUTLINE_ERROR:
return isFocused( c ) ? errorFocusedBorderColor : errorBorderColor;
case FlatClientProperties.OUTLINE_WARNING:
return isFocused( c ) ? warningFocusedBorderColor : warningBorderColor;
}
} else if( outline instanceof Color ) {
Color color = (Color) outline;
// use color functions to compute color for unfocused state
if( !isFocused( c ) && customBorderColor instanceof DerivedColor )
color = ((DerivedColor)customBorderColor).derive( color );
return color;
} else if( outline instanceof Color[] && ((Color[])outline).length >= 2 )
return ((Color[])outline)[isFocused( c ) ? 0 : 1];
return null;
}
protected Color getFocusColor( Component c ) { protected Color getFocusColor( Component c ) {
return focusColor; return focusColor;
} }
@@ -119,7 +167,7 @@ public class FlatBorder
JViewport viewport = ((JScrollPane)c).getViewport(); JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null; Component view = (viewport != null) ? viewport.getView() : null;
if( view != null ) { if( view != null ) {
if( view.hasFocus() ) if( FlatUIUtils.isPermanentFocusOwner( view ) )
return true; return true;
if( (view instanceof JTable && ((JTable)view).isEditing()) || if( (view instanceof JTable && ((JTable)view).isEditing()) ||
@@ -133,49 +181,81 @@ public class FlatBorder
return false; return false;
} else if( c instanceof JComboBox && ((JComboBox<?>)c).isEditable() ) { } else if( c instanceof JComboBox && ((JComboBox<?>)c).isEditable() ) {
Component editorComponent = ((JComboBox<?>)c).getEditor().getEditorComponent(); Component editorComponent = ((JComboBox<?>)c).getEditor().getEditorComponent();
return (editorComponent != null) ? editorComponent.hasFocus() : false; return (editorComponent != null) ? FlatUIUtils.isPermanentFocusOwner( editorComponent ) : false;
} else if( c instanceof JSpinner ) { } else if( c instanceof JSpinner ) {
if( FlatUIUtils.isPermanentFocusOwner( c ) )
return true;
JComponent editor = ((JSpinner)c).getEditor(); JComponent editor = ((JSpinner)c).getEditor();
if( editor instanceof JSpinner.DefaultEditor ) { if( editor instanceof JSpinner.DefaultEditor ) {
JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField(); JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField();
if( textField != null ) if( textField != null )
return textField.hasFocus(); return FlatUIUtils.isPermanentFocusOwner( textField );
} }
return false; return false;
} else } else
return c.hasFocus(); return FlatUIUtils.isPermanentFocusOwner( c );
} }
protected boolean isTableCellEditor( Component c ) { protected boolean isCellEditor( Component c ) {
return FlatUIUtils.isTableCellEditor( c ); return FlatUIUtils.isCellEditor( c );
} }
@Override @Override
public Insets getBorderInsets( Component c, Insets insets ) { public Insets getBorderInsets( Component c, Insets insets ) {
boolean isCellEditor = isTableCellEditor( c ); float focusWidth = scale( (float) getFocusWidth( c ) );
float ow = (isCellEditor ? 0 : getFocusWidth( c )) + getLineWidth( c ); float ow = focusWidth + scale( (float) getLineWidth( c ) );
insets = super.getBorderInsets( c, insets ); insets = super.getBorderInsets( c, insets );
insets.top = Math.round( scale( (float) insets.top ) + ow ); insets.top = Math.round( scale( (float) insets.top ) + ow );
insets.left = Math.round( scale( (float) insets.left ) + ow ); insets.left = Math.round( scale( (float) insets.left ) + ow );
insets.bottom = Math.round( scale( (float) insets.bottom ) + ow ); insets.bottom = Math.round( scale( (float) insets.bottom ) + ow );
insets.right = Math.round( scale( (float) insets.right ) + ow ); insets.right = Math.round( scale( (float) insets.right ) + ow );
if( isCellEditor( c ) ) {
// remove top and bottom insets if used as cell editor
insets.top = insets.bottom = 0;
// remove right/left insets to avoid that text is truncated (e.g. in file chooser)
if( c.getComponentOrientation().isLeftToRight() )
insets.right = 0;
else
insets.left = 0;
}
return insets; return insets;
} }
protected float getFocusWidth( Component c ) { /**
return scale( (float) focusWidth ); * Returns the (unscaled) thickness of the outer focus border.
*/
protected int getFocusWidth( Component c ) {
if( isCellEditor( c ) )
return 0;
return focusWidth;
} }
protected float getLineWidth( Component c ) { /**
return scale( 1f ); * Returns the (unscaled) line thickness used to compute the border insets.
* This may be different to {@link #getBorderWidth}.
*/
protected int getLineWidth( Component c ) {
return 1;
} }
protected float getBorderWidth( Component c ) { /**
* Returns the (unscaled) line thickness used to paint the border.
* This may be different to {@link #getLineWidth}.
*/
protected int getBorderWidth( Component c ) {
return getLineWidth( c ); return getLineWidth( c );
} }
protected float getArc( Component c ) { /**
* Returns the (unscaled) arc diameter of the border.
*/
protected int getArc( Component c ) {
return 0; return 0;
} }
} }

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.GradientPaint; import java.awt.GradientPaint;
@@ -43,6 +42,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.default.hoverBorderColor Color optional * @uiDefault Button.default.hoverBorderColor Color optional
* @uiDefault Button.default.focusedBorderColor Color * @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color * @uiDefault Button.default.focusColor Color
* @uiDefault Button.borderWidth int
* @uiDefault Button.default.borderWidth int * @uiDefault Button.default.borderWidth int
* @uiDefault Button.toolbar.margin Insets * @uiDefault Button.toolbar.margin Insets
* @uiDefault Button.toolbar.spacingInsets Insets * @uiDefault Button.toolbar.spacingInsets Insets
@@ -63,6 +63,7 @@ public class FlatButtonBorder
protected final Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" ); protected final Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
protected final int borderWidth = UIManager.getInt( "Button.borderWidth" );
protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" ); protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" );
protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" ); protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" ); protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
@@ -82,6 +83,11 @@ public class FlatButtonBorder
return FlatButtonUI.isDefaultButton( c ) ? defaultFocusColor : super.getFocusColor( c ); return FlatButtonUI.isDefaultButton( c ) ? defaultFocusColor : super.getFocusColor( c );
} }
@Override
protected boolean isFocused( Component c ) {
return FlatButtonUI.isFocusPainted( c ) && super.isFocused( c );
}
@Override @Override
protected Paint getBorderColor( Component c ) { protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c ); boolean def = FlatButtonUI.isDefaultButton( c );
@@ -115,8 +121,8 @@ public class FlatButtonBorder
} else { } else {
insets = super.getBorderInsets( c, insets ); insets = super.getBorderInsets( c, insets );
// use smaller left and right insets for icon-only buttons (so that they are square) // use smaller left and right insets for icon-only or single-character buttons (so that they are square)
if( FlatButtonUI.isIconOnlyButton( c ) && ((AbstractButton)c).getMargin() instanceof UIResource ) if( FlatButtonUI.isIconOnlyOrSingleCharacterButton( c ) && ((AbstractButton)c).getMargin() instanceof UIResource )
insets.left = insets.right = Math.min( insets.top, insets.bottom ); insets.left = insets.right = Math.min( insets.top, insets.bottom );
} }
@@ -124,17 +130,24 @@ public class FlatButtonBorder
} }
@Override @Override
protected float getFocusWidth( Component c ) { protected int getFocusWidth( Component c ) {
return FlatToggleButtonUI.isTabButton( c ) ? 0 : super.getFocusWidth( c ); return FlatToggleButtonUI.isTabButton( c ) ? 0 : super.getFocusWidth( c );
} }
@Override @Override
protected float getBorderWidth( Component c ) { protected int getBorderWidth( Component c ) {
return FlatButtonUI.isDefaultButton( c ) ? scale( (float) defaultBorderWidth ) : super.getBorderWidth( c ); return FlatButtonUI.isDefaultButton( c ) ? defaultBorderWidth : borderWidth;
} }
@Override @Override
protected float getArc( Component c ) { protected int getArc( Component c ) {
return FlatButtonUI.isSquareButton( c ) ? 0 : scale( (float) arc ); if( isCellEditor( c ) )
return 0;
switch( FlatButtonUI.getButtonType( c ) ) {
case FlatButtonUI.TYPE_SQUARE: return 0;
case FlatButtonUI.TYPE_ROUND_RECT: return Short.MAX_VALUE;
default: return arc;
}
} }
} }

View File

@@ -39,7 +39,6 @@ import javax.swing.JToggleButton;
import javax.swing.JToolBar; import javax.swing.JToolBar;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonListener;
@@ -61,8 +60,6 @@ import com.formdev.flatlaf.util.UIScale;
* *
* <!-- FlatButtonUI --> * <!-- FlatButtonUI -->
* *
* @uiDefault Component.focusWidth int
* @uiDefault Button.arc int
* @uiDefault Button.minimumWidth int * @uiDefault Button.minimumWidth int
* @uiDefault Button.iconTextGap int * @uiDefault Button.iconTextGap int
* @uiDefault Button.startBackground Color optional; if set, a gradient paint is used and Button.background is ignored * @uiDefault Button.startBackground Color optional; if set, a gradient paint is used and Button.background is ignored
@@ -70,7 +67,11 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.focusedBackground Color optional * @uiDefault Button.focusedBackground Color optional
* @uiDefault Button.hoverBackground Color optional * @uiDefault Button.hoverBackground Color optional
* @uiDefault Button.pressedBackground Color optional * @uiDefault Button.pressedBackground Color optional
* @uiDefault Button.selectedBackground Color
* @uiDefault Button.selectedForeground Color
* @uiDefault Button.disabledBackground Color optional
* @uiDefault Button.disabledText Color * @uiDefault Button.disabledText Color
* @uiDefault Button.disabledSelectedBackground Color
* @uiDefault Button.default.background Color * @uiDefault Button.default.background Color
* @uiDefault Button.default.startBackground Color optional; if set, a gradient paint is used and Button.default.background is ignored * @uiDefault Button.default.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.endBackground Color optional; if set, a gradient paint is used
@@ -86,23 +87,29 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.toolbar.spacingInsets Insets * @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.toolbar.hoverBackground Color * @uiDefault Button.toolbar.hoverBackground Color
* @uiDefault Button.toolbar.pressedBackground Color * @uiDefault Button.toolbar.pressedBackground Color
* @uiDefault Button.toolbar.selectedBackground Color
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatButtonUI public class FlatButtonUI
extends BasicButtonUI extends BasicButtonUI
{ {
protected int focusWidth;
protected int arc;
protected int minimumWidth; protected int minimumWidth;
protected int iconTextGap; protected int iconTextGap;
protected Color background;
protected Color foreground;
protected Color startBackground; protected Color startBackground;
protected Color endBackground; protected Color endBackground;
protected Color focusedBackground; protected Color focusedBackground;
protected Color hoverBackground; protected Color hoverBackground;
protected Color pressedBackground; protected Color pressedBackground;
protected Color selectedBackground;
protected Color selectedForeground;
protected Color disabledBackground;
protected Color disabledText; protected Color disabledText;
protected Color disabledSelectedBackground;
protected Color defaultBackground; protected Color defaultBackground;
protected Color defaultEndBackground; protected Color defaultEndBackground;
@@ -119,17 +126,14 @@ public class FlatButtonUI
protected Insets toolbarSpacingInsets; protected Insets toolbarSpacingInsets;
protected Color toolbarHoverBackground; protected Color toolbarHoverBackground;
protected Color toolbarPressedBackground; protected Color toolbarPressedBackground;
protected Color toolbarSelectedBackground;
private Icon helpButtonIcon; private Icon helpButtonIcon;
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatButtonUI.class, FlatButtonUI::new );
instance = new FlatButtonUI();
return instance;
} }
@Override @Override
@@ -139,17 +143,22 @@ public class FlatButtonUI
if( !defaults_initialized ) { if( !defaults_initialized ) {
String prefix = getPropertyPrefix(); String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( "Button.arc" );
minimumWidth = UIManager.getInt( prefix + "minimumWidth" ); minimumWidth = UIManager.getInt( prefix + "minimumWidth" );
iconTextGap = FlatUIUtils.getUIInt( prefix + "iconTextGap", 4 ); iconTextGap = FlatUIUtils.getUIInt( prefix + "iconTextGap", 4 );
background = UIManager.getColor( prefix + "background" );
foreground = UIManager.getColor( prefix + "foreground" );
startBackground = UIManager.getColor( prefix + "startBackground" ); startBackground = UIManager.getColor( prefix + "startBackground" );
endBackground = UIManager.getColor( prefix + "endBackground" ); endBackground = UIManager.getColor( prefix + "endBackground" );
focusedBackground = UIManager.getColor( prefix + "focusedBackground" ); focusedBackground = UIManager.getColor( prefix + "focusedBackground" );
hoverBackground = UIManager.getColor( prefix + "hoverBackground" ); hoverBackground = UIManager.getColor( prefix + "hoverBackground" );
pressedBackground = UIManager.getColor( prefix + "pressedBackground" ); pressedBackground = UIManager.getColor( prefix + "pressedBackground" );
selectedBackground = UIManager.getColor( prefix + "selectedBackground" );
selectedForeground = UIManager.getColor( prefix + "selectedForeground" );
disabledBackground = UIManager.getColor( prefix + "disabledBackground" );
disabledText = UIManager.getColor( prefix + "disabledText" ); disabledText = UIManager.getColor( prefix + "disabledText" );
disabledSelectedBackground = UIManager.getColor( prefix + "disabledSelectedBackground" );
if( UIManager.getBoolean( "Button.paintShadow" ) ) { if( UIManager.getBoolean( "Button.paintShadow" ) ) {
shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 ); shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 );
@@ -172,6 +181,7 @@ public class FlatButtonUI
toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" ); toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" ); toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" );
toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" ); toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" );
toolbarSelectedBackground = UIManager.getColor( prefix + "toolbar.selectedBackground" );
helpButtonIcon = UIManager.getIcon( "HelpButton.icon" ); helpButtonIcon = UIManager.getIcon( "HelpButton.icon" );
@@ -187,7 +197,7 @@ public class FlatButtonUI
LookAndFeel.installProperty( b, "opaque", false ); LookAndFeel.installProperty( b, "opaque", false );
LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) ); LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b, getFocusWidth( b ) ); MigLayoutVisualPadding.install( b );
} }
@Override @Override
@@ -200,21 +210,21 @@ public class FlatButtonUI
@Override @Override
protected BasicButtonListener createButtonListener( AbstractButton b ) { protected BasicButtonListener createButtonListener( AbstractButton b ) {
return new BasicButtonListener( b ) { return new FlatButtonListener( b );
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatButtonUI.this.propertyChange( b, e );
}
};
} }
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case SQUARE_SIZE:
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
case MINIMUM_HEIGHT: case MINIMUM_HEIGHT:
b.revalidate(); b.revalidate();
break; break;
case BUTTON_TYPE:
b.revalidate();
b.repaint();
break;
} }
} }
@@ -222,11 +232,19 @@ public class FlatButtonUI
return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled(); return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled();
} }
public static boolean isFocusPainted( Component c ) {
return !(c instanceof AbstractButton) || ((AbstractButton)c).isFocusPainted();
}
static boolean isDefaultButton( Component c ) { static boolean isDefaultButton( Component c ) {
return c instanceof JButton && ((JButton)c).isDefaultButton(); return c instanceof JButton && ((JButton)c).isDefaultButton();
} }
static boolean isIconOnlyButton( Component c ) { /**
* 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.
*/
static boolean isIconOnlyOrSingleCharacterButton( Component c ) {
if( !(c instanceof JButton) && !(c instanceof JToggleButton) ) if( !(c instanceof JButton) && !(c instanceof JToggleButton) )
return false; return false;
@@ -236,8 +254,15 @@ public class FlatButtonUI
(icon == null && text != null && ("...".equals( text ) || text.length() == 1)); (icon == null && text != null && ("...".equals( text ) || text.length() == 1));
} }
static boolean isSquareButton( Component c ) { // same indices as in parameters to clientPropertyChoice()
return c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_SQUARE ); static final int TYPE_OTHER = -1;
static final int TYPE_SQUARE = 0;
static final int TYPE_ROUND_RECT = 1;
static int getButtonType( Component c ) {
return (c instanceof AbstractButton)
? clientPropertyChoice( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_SQUARE, BUTTON_TYPE_ROUND_RECT )
: TYPE_OTHER;
} }
static boolean isHelpButton( Component c ) { static boolean isHelpButton( Component c ) {
@@ -245,7 +270,8 @@ public class FlatButtonUI
} }
static boolean isToolBarButton( Component c ) { static boolean isToolBarButton( Component c ) {
return c.getParent() instanceof JToolBar; return c.getParent() instanceof JToolBar ||
(c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ));
} }
@Override @Override
@@ -267,16 +293,17 @@ public class FlatButtonUI
protected void paintBackground( Graphics g, JComponent c ) { protected void paintBackground( Graphics g, JComponent c ) {
Color background = getBackground( c ); Color background = getBackground( c );
if( background != null ) { if( background == null )
return;
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
Border border = c.getBorder();
boolean isToolBarButton = isToolBarButton( c ); boolean isToolBarButton = isToolBarButton( c );
float focusWidth = (border instanceof FlatBorder && !isToolBarButton) ? scale( (float) getFocusWidth( c ) ) : 0; float focusWidth = isToolBarButton ? 0 : FlatUIUtils.getBorderFocusWidth( c );
float arc = ((border instanceof FlatButtonBorder && !isSquareButton( c )) || isToolBarButton) float arc = FlatUIUtils.getBorderArc( c );
? scale( (float) this.arc ) : 0;
boolean def = isDefaultButton( c ); boolean def = isDefaultButton( c );
int x = 0; int x = 0;
@@ -294,7 +321,9 @@ public class FlatButtonUI
// paint shadow // paint shadow
Color shadowColor = def ? defaultShadowColor : this.shadowColor; Color shadowColor = def ? defaultShadowColor : this.shadowColor;
if( !isToolBarButton && shadowColor != null && shadowWidth > 0 && focusWidth > 0 && !c.hasFocus() && c.isEnabled() ) { if( !isToolBarButton && shadowColor != null && shadowWidth > 0 && focusWidth > 0 &&
!(isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c )) && c.isEnabled() )
{
g2.setColor( shadowColor ); g2.setColor( shadowColor );
g2.fill( new RoundRectangle2D.Float( focusWidth, focusWidth + UIScale.scale( (float) shadowWidth ), g2.fill( new RoundRectangle2D.Float( focusWidth, focusWidth + UIScale.scale( (float) shadowWidth ),
width - focusWidth * 2, height - focusWidth * 2, arc, arc ) ); width - focusWidth * 2, height - focusWidth * 2, arc, arc ) );
@@ -306,13 +335,17 @@ public class FlatButtonUI
if( background == startBg && endBg != null && !startBg.equals( endBg ) ) if( background == startBg && endBg != null && !startBg.equals( endBg ) )
g2.setPaint( new GradientPaint( 0, 0, startBg, 0, height, endBg ) ); g2.setPaint( new GradientPaint( 0, 0, startBg, 0, height, endBg ) );
else else
FlatUIUtils.setColor( g2, background, def ? defaultBackground : c.getBackground() ); g2.setColor( FlatUIUtils.deriveColor( background, getBackgroundBase( c, def ) ) );
FlatUIUtils.paintComponentBackground( g2, x, y, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, x, y, width, height, focusWidth, arc );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
} }
@Override
public void paint( Graphics g, JComponent c ) {
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
} }
@Override @Override
@@ -331,7 +364,7 @@ public class FlatButtonUI
} }
} }
paintText( g, b, textRect, text, b.isEnabled() ? getForeground( b ) : disabledText ); paintText( g, b, textRect, text, getForeground( b ) );
} }
public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) { public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
@@ -344,8 +377,19 @@ public class FlatButtonUI
} }
protected Color getBackground( JComponent c ) { protected Color getBackground( JComponent c ) {
if( ((AbstractButton)c).isSelected() ) {
// in toolbar use same colors for disabled and enabled because
// we assume that toolbar icon is shown disabled
boolean toolBarButton = isToolBarButton( c );
return buttonStateColor( c,
toolBarButton ? toolbarSelectedBackground : selectedBackground,
toolBarButton ? toolbarSelectedBackground : disabledSelectedBackground,
null, null,
toolBarButton ? toolbarPressedBackground : pressedBackground );
}
if( !c.isEnabled() ) if( !c.isEnabled() )
return null; return disabledBackground;
// toolbar button // toolbar button
if( isToolBarButton( c ) ) { if( isToolBarButton( c ) ) {
@@ -361,13 +405,26 @@ public class FlatButtonUI
boolean def = isDefaultButton( c ); boolean def = isDefaultButton( c );
return buttonStateColor( c, return buttonStateColor( c,
def ? defaultBackground : c.getBackground(), getBackgroundBase( c, def ),
null, null,
def ? defaultFocusedBackground : focusedBackground, isCustomBackground( c.getBackground() ) ? null : (def ? defaultFocusedBackground : focusedBackground),
def ? defaultHoverBackground : hoverBackground, def ? defaultHoverBackground : hoverBackground,
def ? defaultPressedBackground : pressedBackground ); def ? defaultPressedBackground : pressedBackground );
} }
protected Color getBackgroundBase( JComponent c, boolean def ) {
// use component background if explicitly set
Color bg = c.getBackground();
if( isCustomBackground( bg ) )
return bg;
return def ? defaultBackground : bg;
}
protected boolean isCustomBackground( Color bg ) {
return bg != background && (startBackground == null || bg != startBackground);
}
public static Color buttonStateColor( Component c, Color enabledColor, Color disabledColor, public static Color buttonStateColor( Component c, Color enabledColor, Color disabledColor,
Color focusedColor, Color hoverColor, Color pressedColor ) Color focusedColor, Color hoverColor, Color pressedColor )
{ {
@@ -382,15 +439,30 @@ public class FlatButtonUI
if( hoverColor != null && b != null && b.getModel().isRollover() ) if( hoverColor != null && b != null && b.getModel().isRollover() )
return hoverColor; return hoverColor;
if( focusedColor != null && c.hasFocus() ) if( focusedColor != null && isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c ) )
return focusedColor; return focusedColor;
return enabledColor; return enabledColor;
} }
protected Color getForeground( JComponent c ) { protected Color getForeground( JComponent c ) {
if( !c.isEnabled() )
return disabledText;
if( ((AbstractButton)c).isSelected() && !isToolBarButton( c ) )
return selectedForeground;
// use component foreground if explicitly set
Color fg = c.getForeground();
if( isCustomForeground( fg ) )
return fg;
boolean def = isDefaultButton( c ); boolean def = isDefaultButton( c );
return def ? defaultForeground : c.getForeground(); return def ? defaultForeground : fg;
}
protected boolean isCustomForeground( Color fg ) {
return fg != foreground;
} }
@Override @Override
@@ -402,20 +474,40 @@ public class FlatButtonUI
if( prefSize == null ) if( prefSize == null )
return null; return null;
// make button square if it is a icon-only button // make square or apply minimum width/height
// or apply minimum width, if not in toolbar and not a icon-only button boolean isIconOnlyOrSingleCharacter = isIconOnlyOrSingleCharacterButton( c );
if( isIconOnlyButton( c ) ) if( clientPropertyBoolean( c, SQUARE_SIZE, false ) ) {
// make button square (increase width or height so that they are equal)
prefSize.width = prefSize.height = Math.max( prefSize.width, prefSize.height );
} else if( isIconOnlyOrSingleCharacter && ((AbstractButton)c).getIcon() == null ) {
// make single-character-no-icon button square (increase width)
prefSize.width = Math.max( prefSize.width, prefSize.height ); prefSize.width = Math.max( prefSize.width, prefSize.height );
else if( !isToolBarButton( c ) && c.getBorder() instanceof FlatButtonBorder ) { } else if( !isIconOnlyOrSingleCharacter && !isToolBarButton( c ) && c.getBorder() instanceof FlatButtonBorder ) {
int focusWidth = getFocusWidth( c ); // apply minimum width/height
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) + (focusWidth * 2) ) ); float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) + (focusWidth * 2) ) ); prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + Math.round( focusWidth * 2 ) );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) ) + Math.round( focusWidth * 2 ) );
} }
return prefSize; return prefSize;
} }
protected int getFocusWidth( JComponent c ) { //---- class FlatButtonListener -------------------------------------------
return focusWidth;
protected class FlatButtonListener
extends BasicButtonListener
{
private final AbstractButton b;
protected FlatButtonListener( AbstractButton b ) {
super( b );
this.b = b;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatButtonUI.this.propertyChange( b, e );
}
} }
} }

View File

@@ -31,7 +31,7 @@ import javax.swing.text.JTextComponent;
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
class FlatCaret public class FlatCaret
extends DefaultCaret extends DefaultCaret
implements UIResource implements UIResource
{ {
@@ -41,7 +41,7 @@ class FlatCaret
private boolean wasTemporaryLost; private boolean wasTemporaryLost;
private boolean isMousePressed; private boolean isMousePressed;
FlatCaret( String selectAllOnFocusPolicy ) { public FlatCaret( String selectAllOnFocusPolicy ) {
this.selectAllOnFocusPolicy = selectAllOnFocusPolicy; this.selectAllOnFocusPolicy = selectAllOnFocusPolicy;
} }
@@ -87,7 +87,7 @@ class FlatCaret
super.mouseReleased( e ); super.mouseReleased( e );
} }
private void selectAllOnFocusGained() { protected void selectAllOnFocusGained() {
JTextComponent c = getComponent(); JTextComponent c = getComponent();
Document doc = c.getDocument(); Document doc = c.getDocument();
if( doc == null || !c.isEnabled() || !c.isEditable() ) if( doc == null || !c.isEnabled() || !c.isEditable() )

View File

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

View File

@@ -42,12 +42,8 @@ import javax.swing.plaf.ComponentUI;
public class FlatCheckBoxUI public class FlatCheckBoxUI
extends FlatRadioButtonUI extends FlatRadioButtonUI
{ {
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, FlatCheckBoxUI::new );
instance = new FlatCheckBoxUI();
return instance;
} }
@Override @Override

View File

@@ -24,10 +24,12 @@ import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Insets; import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
@@ -39,6 +41,7 @@ import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.ComboBoxEditor;
import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListCellRenderer;
import javax.swing.InputMap; import javax.swing.InputMap;
import javax.swing.JButton; import javax.swing.JButton;
@@ -46,6 +49,8 @@ import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTextField;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer; import javax.swing.ListCellRenderer;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
@@ -76,8 +81,10 @@ import com.formdev.flatlaf.util.UIScale;
* *
* <!-- FlatComboBoxUI --> * <!-- FlatComboBoxUI -->
* *
* @uiDefault Component.focusWidth int * @uiDefault ComboBox.minimumWidth int
* @uiDefault Component.arc int * @uiDefault ComboBox.editorColumns int
* @uiDefault ComboBox.maximumRowCount int
* @uiDefault ComboBox.buttonStyle String auto (default), button or none
* @uiDefault Component.arrowType String triangle (default) or chevron * @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color * @uiDefault Component.borderColor Color
@@ -96,8 +103,9 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatComboBoxUI public class FlatComboBoxUI
extends BasicComboBoxUI extends BasicComboBoxUI
{ {
protected int focusWidth; protected int minimumWidth;
protected int arc; protected int editorColumns;
protected String buttonStyle;
protected String arrowType; protected String arrowType;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color borderColor; protected Color borderColor;
@@ -114,7 +122,7 @@ public class FlatComboBoxUI
protected Color buttonHoverArrowColor; protected Color buttonHoverArrowColor;
private MouseListener hoverListener; private MouseListener hoverListener;
private boolean hover; protected boolean hover;
private WeakReference<Component> lastRendererComponent; private WeakReference<Component> lastRendererComponent;
@@ -150,8 +158,9 @@ public class FlatComboBoxUI
LookAndFeel.installProperty( comboBox, "opaque", false ); LookAndFeel.installProperty( comboBox, "opaque", false );
focusWidth = UIManager.getInt( "Component.focusWidth" ); minimumWidth = UIManager.getInt( "ComboBox.minimumWidth" );
arc = UIManager.getInt( "Component.arc" ); editorColumns = UIManager.getInt( "ComboBox.editorColumns" );
buttonStyle = UIManager.getString( "ComboBox.buttonStyle" );
arrowType = UIManager.getString( "Component.arrowType" ); arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" ); borderColor = UIManager.getColor( "Component.borderColor" );
@@ -167,10 +176,15 @@ public class FlatComboBoxUI
buttonDisabledArrowColor = UIManager.getColor( "ComboBox.buttonDisabledArrowColor" ); buttonDisabledArrowColor = UIManager.getColor( "ComboBox.buttonDisabledArrowColor" );
buttonHoverArrowColor = UIManager.getColor( "ComboBox.buttonHoverArrowColor" ); buttonHoverArrowColor = UIManager.getColor( "ComboBox.buttonHoverArrowColor" );
// set maximumRowCount
int maximumRowCount = UIManager.getInt( "ComboBox.maximumRowCount" );
if( maximumRowCount > 0 && maximumRowCount != 8 && comboBox.getMaximumRowCount() == 8 )
comboBox.setMaximumRowCount( maximumRowCount );
// scale // scale
padding = UIScale.scale( padding ); padding = UIScale.scale( padding );
MigLayoutVisualPadding.install( comboBox, focusWidth ); MigLayoutVisualPadding.install( comboBox );
} }
@Override @Override
@@ -249,6 +263,10 @@ public class FlatComboBoxUI
editor.applyComponentOrientation( o ); editor.applyComponentOrientation( o );
} else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) ) } else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint(); editor.repaint();
else if( FlatClientProperties.COMPONENT_ROUND_RECT.equals( propertyName ) )
comboBox.repaint();
else if( FlatClientProperties.MINIMUM_WIDTH.equals( propertyName ) )
comboBox.revalidate();
} }
}; };
} }
@@ -259,16 +277,32 @@ public class FlatComboBoxUI
} }
@Override @Override
protected void configureEditor() { protected ComboBoxEditor createEditor() {
super.configureEditor(); ComboBoxEditor comboBoxEditor = super.createEditor();
// assign a non-javax.swing.plaf.UIResource border to the text field, Component editor = comboBoxEditor.getEditorComponent();
if( editor instanceof JTextField ) {
JTextField textField = (JTextField) editor;
textField.setColumns( editorColumns );
// assign a non-null and non-javax.swing.plaf.UIResource border to the text field,
// otherwise it is replaced with default text field border when switching LaF // otherwise it is replaced with default text field border when switching LaF
// because javax.swing.plaf.basic.BasicComboBoxEditor.BorderlessTextField.setBorder() // because javax.swing.plaf.basic.BasicComboBoxEditor.BorderlessTextField.setBorder()
// uses "border instanceof javax.swing.plaf.basic.BasicComboBoxEditor.UIResource" // uses "border instanceof javax.swing.plaf.basic.BasicComboBoxEditor.UIResource"
// instead of "border instanceof javax.swing.plaf.UIResource" // instead of "border instanceof javax.swing.plaf.UIResource"
if( editor instanceof JTextComponent ) textField.setBorder( BorderFactory.createEmptyBorder() );
((JTextComponent)editor).setBorder( BorderFactory.createEmptyBorder() ); }
return comboBoxEditor;
}
@Override
protected void configureEditor() {
super.configureEditor();
// remove default text field border from editor
if( editor instanceof JTextField && ((JTextField)editor).getBorder() instanceof FlatTextBorder )
((JTextField)editor).setBorder( BorderFactory.createEmptyBorder() );
// explicitly make non-opaque // explicitly make non-opaque
if( editor instanceof JComponent ) if( editor instanceof JComponent )
@@ -279,15 +313,16 @@ public class FlatComboBoxUI
updateEditorColors(); updateEditorColors();
// macOS // macOS
if( SystemInfo.IS_MAC && editor instanceof JTextComponent ) { if( SystemInfo.isMacOS && editor instanceof JTextComponent ) {
// delegate actions from editor text field to combobox, which is necessary // delegate actions from editor text field to combobox, which is necessary
// because text field on macOS (based on Aqua LaF UI defaults) // because text field on macOS already handle those keys
// already handle those keys
InputMap inputMap = ((JTextComponent)editor).getInputMap(); InputMap inputMap = ((JTextComponent)editor).getInputMap();
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "UP" ) ); new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "UP" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_UP" ) ); new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_UP" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "DOWN" ) ); new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "DOWN" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_DOWN" ) ); new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_DOWN" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "HOME" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "END" ) );
} }
} }
@@ -295,30 +330,25 @@ public class FlatComboBoxUI
// use non-UIResource colors because when SwingUtilities.updateComponentTreeUI() // use non-UIResource colors because when SwingUtilities.updateComponentTreeUI()
// is used, then the editor is updated after the combobox and the // is used, then the editor is updated after the combobox and the
// colors are again replaced with default colors // colors are again replaced with default colors
boolean enabled = editor.isEnabled(); boolean isTextComponent = editor instanceof JTextComponent;
editor.setForeground( FlatUIUtils.nonUIResource( (enabled || editor instanceof JTextComponent) editor.setForeground( FlatUIUtils.nonUIResource( getForeground( isTextComponent || editor.isEnabled() ) ) );
? comboBox.getForeground()
: disabledForeground ) ); if( isTextComponent )
if( editor instanceof JTextComponent ) ((JTextComponent)editor).setDisabledTextColor( FlatUIUtils.nonUIResource( getForeground( false ) ) );
((JTextComponent)editor).setDisabledTextColor( FlatUIUtils.nonUIResource( disabledForeground ) );
} }
@Override @Override
protected JButton createArrowButton() { protected JButton createArrowButton() {
return new FlatArrowButton( SwingConstants.SOUTH, arrowType, buttonArrowColor, return new FlatComboBoxButton();
buttonDisabledArrowColor, buttonHoverArrowColor, null )
{
@Override
protected boolean isHover() {
return super.isHover() || (!comboBox.isEditable() ? hover : false);
}
};
} }
@Override @Override
public void update( Graphics g, JComponent c ) { public void update( Graphics g, JComponent c ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
float arc = FlatUIUtils.getBorderArc( c );
// fill background if opaque to avoid garbage if user sets opaque to true // fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && (focusWidth > 0 || arc != 0) ) if( c.isOpaque() && (focusWidth > 0 || arc > 0) )
FlatUIUtils.paintParentBackground( g, c ); FlatUIUtils.paintParentBackground( g, c );
Graphics2D g2 = (Graphics2D) g; Graphics2D g2 = (Graphics2D) g;
@@ -326,22 +356,19 @@ public class FlatComboBoxUI
int width = c.getWidth(); int width = c.getWidth();
int height = c.getHeight(); int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
int arrowX = arrowButton.getX(); int arrowX = arrowButton.getX();
int arrowWidth = arrowButton.getWidth(); int arrowWidth = arrowButton.getWidth();
boolean paintButton = (comboBox.isEditable() || "button".equals( buttonStyle )) && !"none".equals( buttonStyle );
boolean enabled = comboBox.isEnabled(); boolean enabled = comboBox.isEnabled();
boolean isLeftToRight = comboBox.getComponentOrientation().isLeftToRight(); boolean isLeftToRight = comboBox.getComponentOrientation().isLeftToRight();
// paint background // paint background
g2.setColor( enabled g2.setColor( getBackground( enabled ) );
? (editableBackground != null && comboBox.isEditable() ? editableBackground : c.getBackground())
: getDisabledBackground( comboBox ) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background // paint arrow button background
if( enabled ) { if( enabled ) {
g2.setColor( comboBox.isEditable() ? buttonEditableBackground : buttonBackground ); g2.setColor( paintButton ? buttonEditableBackground : buttonBackground );
Shape oldClip = g2.getClip(); Shape oldClip = g2.getClip();
if( isLeftToRight ) if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height ); g2.clipRect( arrowX, 0, width - arrowX, height );
@@ -352,7 +379,7 @@ public class FlatComboBoxUI
} }
// paint vertical line between value and arrow button // paint vertical line between value and arrow button
if( comboBox.isEditable() ) { if( paintButton ) {
g2.setColor( enabled ? borderColor : disabledBorderColor ); g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f ); float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
@@ -375,8 +402,8 @@ public class FlatComboBoxUI
uninstallCellPaddingBorder( c ); uninstallCellPaddingBorder( c );
boolean enabled = comboBox.isEnabled(); boolean enabled = comboBox.isEnabled();
c.setForeground( enabled ? comboBox.getForeground() : disabledForeground ); c.setBackground( getBackground( enabled ) );
c.setBackground( enabled ? comboBox.getBackground() : getDisabledBackground( comboBox ) ); c.setForeground( getForeground( enabled ) );
boolean shouldValidate = (c instanceof JPanel); boolean shouldValidate = (c instanceof JPanel);
if( padding != null ) if( padding != null )
@@ -393,12 +420,24 @@ public class FlatComboBoxUI
@Override @Override
public void paintCurrentValueBackground( Graphics g, Rectangle bounds, boolean hasFocus ) { public void paintCurrentValueBackground( Graphics g, Rectangle bounds, boolean hasFocus ) {
g.setColor( comboBox.isEnabled() ? comboBox.getBackground() : getDisabledBackground( comboBox ) ); // not necessary because already painted in update()
g.fillRect( bounds.x, bounds.y, bounds.width, bounds.height );
} }
private Color getDisabledBackground( JComponent c ) { protected Color getBackground( boolean enabled ) {
return isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground; return enabled
? (editableBackground != null && comboBox.isEditable() ? editableBackground : comboBox.getBackground())
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( comboBox ) : disabledBackground);
}
protected Color getForeground( boolean enabled ) {
return enabled ? comboBox.getForeground() : disabledForeground;
}
@Override
public Dimension getMinimumSize( JComponent c ) {
Dimension minimumSize = super.getMinimumSize( c );
minimumSize.width = Math.max( minimumSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) );
return minimumSize;
} }
@Override @Override
@@ -421,6 +460,18 @@ public class FlatComboBoxUI
Dimension displaySize = super.getDisplaySize(); Dimension displaySize = super.getDisplaySize();
// recalculate width without hardcoded 100 under special conditions
if( displaySize.width == 100 + padding.left + padding.right &&
comboBox.isEditable() &&
comboBox.getItemCount() == 0 &&
comboBox.getPrototypeDisplayValue() == null )
{
int width = getDefaultSize().width;
width = Math.max( width, editor.getPreferredSize().width );
width += padding.left + padding.right;
displaySize = new Dimension( width, displaySize.height );
}
uninstallCellPaddingBorder( renderer ); uninstallCellPaddingBorder( renderer );
return displaySize; return displaySize;
} }
@@ -456,15 +507,36 @@ public class FlatComboBoxUI
} }
} }
//---- class FlatComboBoxButton -------------------------------------------
protected class FlatComboBoxButton
extends FlatArrowButton
{
protected FlatComboBoxButton() {
this( SwingConstants.SOUTH, arrowType, buttonArrowColor, buttonDisabledArrowColor, buttonHoverArrowColor, null, null );
}
protected FlatComboBoxButton( int direction, String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground, Color pressedBackground )
{
super( direction, type, foreground, disabledForeground, hoverForeground, hoverBackground, pressedBackground );
}
@Override
protected boolean isHover() {
return super.isHover() || (!comboBox.isEditable() ? hover : false);
}
}
//---- class FlatComboPopup ----------------------------------------------- //---- class FlatComboPopup -----------------------------------------------
@SuppressWarnings( { "rawtypes", "unchecked" } ) @SuppressWarnings( { "rawtypes", "unchecked" } )
private class FlatComboPopup protected class FlatComboPopup
extends BasicComboPopup extends BasicComboPopup
{ {
private CellPaddingBorder paddingBorder; private CellPaddingBorder paddingBorder;
FlatComboPopup( JComboBox combo ) { protected FlatComboPopup( JComboBox combo ) {
super( combo ); super( combo );
// BasicComboPopup listens to JComboBox.componentOrientation and updates // BasicComboPopup listens to JComboBox.componentOrientation and updates
@@ -479,18 +551,37 @@ public class FlatComboBoxUI
@Override @Override
protected Rectangle computePopupBounds( int px, int py, int pw, int ph ) { protected Rectangle computePopupBounds( int px, int py, int pw, int ph ) {
// get maximum display size of all items, ignoring prototype value // get maximum display width of all items
Object prototype = comboBox.getPrototypeDisplayValue(); int displayWidth = getDisplaySize().width;
if( prototype != null )
comboBox.setPrototypeDisplayValue( null ); // add border insets
Dimension displaySize = getDisplaySize(); for( Border border : new Border[] { scroller.getViewportBorder(), scroller.getBorder() } ) {
if( prototype != null ) if( border != null ) {
comboBox.setPrototypeDisplayValue( prototype ); Insets borderInsets = border.getBorderInsets( null );
displayWidth += borderInsets.left + borderInsets.right;
}
}
// add width of vertical scroll bar
JScrollBar verticalScrollBar = scroller.getVerticalScrollBar();
if( verticalScrollBar != null )
displayWidth += verticalScrollBar.getPreferredSize().width;
// make popup wider if necessary // make popup wider if necessary
if( displaySize.width > pw ) { if( displayWidth > pw ) {
int diff = displaySize.width - pw; // limit popup width to screen width
pw = displaySize.width; GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
if( gc != null ) {
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
} else {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
displayWidth = Math.min( displayWidth, screenSize.width );
}
int diff = displayWidth - pw;
pw = displayWidth;
if( !comboBox.getComponentOrientation().isLeftToRight() ) if( !comboBox.getComponentOrientation().isLeftToRight() )
px -= diff; px -= diff;

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
* Paints a drop shadow border around the component.
* Supports 1-sided, 2-side, 3-sided or 4-sided drop shadows.
* <p>
* The shadow insets allow specifying drop shadow thickness for each side.
* A zero or negative value hides the drop shadow on that side.
* A negative value can be used to indent the drop shadow on corners.
* E.g. -4 on left indents drop shadow at top-left and bottom-left corners by 4 pixels.
*
* @author Karl Tauber
*/
public class FlatDropShadowBorder
extends FlatEmptyBorder
{
private final Color shadowColor;
private final Insets shadowInsets;
private final float shadowOpacity;
private final int shadowSize;
private Image shadowImage;
private Color lastShadowColor;
private double lastSystemScaleFactor;
private float lastUserScaleFactor;
public FlatDropShadowBorder() {
this( null );
}
public FlatDropShadowBorder( Color shadowColor ) {
this( shadowColor, 4, 0.5f );
}
public FlatDropShadowBorder( Color shadowColor, int shadowSize, float shadowOpacity ) {
this( shadowColor, new Insets( -shadowSize, -shadowSize, shadowSize, shadowSize ), shadowOpacity );
}
public FlatDropShadowBorder( Color shadowColor, Insets shadowInsets, float shadowOpacity ) {
super( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ),
Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) );
this.shadowColor = shadowColor;
this.shadowInsets = shadowInsets;
this.shadowOpacity = shadowOpacity;
shadowSize = Math.max(
Math.max( shadowInsets.left, shadowInsets.right ),
Math.max( shadowInsets.top, shadowInsets.bottom ) );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )
return;
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
Color shadowColor = (this.shadowColor != null) ? this.shadowColor : g.getColor();
int shadowSize = scale( this.shadowSize, scaleFactor );
// create and cache shadow image
float userScaleFactor = UIScale.getUserScaleFactor();
if( shadowImage == null ||
!shadowColor.equals( lastShadowColor ) ||
lastSystemScaleFactor != scaleFactor ||
lastUserScaleFactor != userScaleFactor )
{
shadowImage = createShadowImage( shadowColor, shadowSize, shadowOpacity,
(float) (scaleFactor * userScaleFactor) );
lastShadowColor = shadowColor;
lastSystemScaleFactor = scaleFactor;
lastUserScaleFactor = userScaleFactor;
}
/*debug
int m = shadowImage.getWidth( null );
Color oldColor = g.getColor();
g.setColor( Color.lightGray );
g.drawRect( x - m - 1, y - m - 1, m + 1, m + 1 );
g.setColor( Color.white );
g.fillRect( x - m, y - m, m, m );
g.drawImage( shadowImage, x - m, y - m, null );
g.setColor( oldColor );
debug*/
int left = scale( shadowInsets.left, scaleFactor );
int right = scale( shadowInsets.right, scaleFactor );
int top = scale( shadowInsets.top, scaleFactor );
int bottom = scale( shadowInsets.bottom, scaleFactor );
// shadow outer coordinates
int x1o = x - Math.min( left, 0 );
int y1o = y - Math.min( top, 0 );
int x2o = x + width + Math.min( right, 0 );
int y2o = y + height + Math.min( bottom, 0 );
// shadow inner coordinates
int x1i = x1o + shadowSize;
int y1i = y1o + shadowSize;
int x2i = x2o - shadowSize;
int y2i = y2o - shadowSize;
int wh = (shadowSize * 2) - 1;
int center = shadowSize - 1;
// left-top edge
if( left > 0 || top > 0 ) {
g.drawImage( shadowImage, x1o, y1o, x1i, y1i,
0, 0, center, center, null );
}
// top shadow
if( top > 0 ) {
g.drawImage( shadowImage, x1i, y1o, x2i, y1i,
center, 0, center + 1, center, null );
}
// right-top edge
if( right > 0 || top > 0 ) {
g.drawImage( shadowImage, x2i, y1o, x2o, y1i,
center, 0, wh, center, null );
}
// left shadow
if( left > 0 ) {
g.drawImage( shadowImage, x1o, y1i, x1i, y2i,
0, center, center, center + 1, null );
}
// right shadow
if( right > 0 ) {
g.drawImage( shadowImage, x2i, y1i, x2o, y2i,
center, center, wh, center + 1, null );
}
// left-bottom edge
if( left > 0 || bottom > 0 ) {
g.drawImage( shadowImage, x1o, y2i, x1i, y2o,
0, center, center, wh, null );
}
// bottom shadow
if( bottom > 0 ) {
g.drawImage( shadowImage, x1i, y2i, x2i, y2o,
center, center, center + 1, wh, null );
}
// right-bottom edge
if( right > 0 || bottom > 0 ) {
g.drawImage( shadowImage, x2i, y2i, x2o, y2o,
center, center, wh, wh, null );
}
}
private int scale( int value, double scaleFactor ) {
return (int) Math.ceil( UIScale.scale( value ) * scaleFactor );
}
private static BufferedImage createShadowImage( Color shadowColor, int shadowSize,
float shadowOpacity, float scaleFactor )
{
int shadowRGB = shadowColor.getRGB() & 0xffffff;
int shadowAlpha = (int) (255 * shadowOpacity);
Color startColor = new Color( shadowRGB | ((shadowAlpha & 0xff) << 24), true );
Color midColor = new Color( shadowRGB | (((shadowAlpha / 2) & 0xff) << 24), true );
Color endColor = new Color( shadowRGB, true );
/*debug
startColor = Color.red;
midColor = Color.green;
endColor = Color.blue;
debug*/
int wh = (shadowSize * 2) - 1;
int center = shadowSize - 1;
RadialGradientPaint p = new RadialGradientPaint( center, center,
shadowSize - (0.75f * scaleFactor),
new float[] { 0, 0.35f, 1 },
new Color[] { startColor, midColor, endColor } );
BufferedImage image = new BufferedImage( wh, wh, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = image.createGraphics();
try {
g.setPaint( p );
g.fillRect( 0, 0, wh, wh );
} finally {
g.dispose();
}
return image;
}
}

View File

@@ -19,6 +19,8 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JEditorPane; import javax.swing.JEditorPane;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -26,6 +28,8 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicEditorPaneUI; import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.HiDPIUtils;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}.
@@ -83,26 +87,45 @@ public class FlatEditorPaneUI
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties ); getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
} }
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
propertyChange( getComponent(), e );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case FlatClientProperties.MINIMUM_WIDTH:
c.revalidate();
break;
}
}
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ) ); return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
} }
@Override @Override
public Dimension getMinimumSize( JComponent c ) { public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ) ); return applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
} }
private Dimension applyMinimumWidth( Dimension size ) { static Dimension applyMinimumWidth( JComponent c, Dimension size, int minimumWidth ) {
// Assume that text area is in a scroll pane (that displays the border) // Assume that text area is in a scroll pane (that displays the border)
// and subtract 1px border line width. // and subtract 1px border line width.
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding // Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3. // issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth ); minimumWidth = FlatUIUtils.minimumWidth( c, minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) ); size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size; return size;
} }
@Override
protected void paintSafely( Graphics g ) {
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
}
@Override @Override
protected void paintBackground( Graphics g ) { protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent(); JTextComponent c = getComponent();

View File

@@ -57,4 +57,8 @@ public class FlatEmptyBorder
insets.bottom = scale( bottom ); insets.bottom = scale( bottom );
return insets; return insets;
} }
public Insets getUnscaledBorderInsets() {
return super.getBorderInsets();
}
} }

View File

@@ -16,11 +16,28 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Insets;
import java.io.File;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.UIManager;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalFileChooserUI; import javax.swing.plaf.metal.MetalFileChooserUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -86,6 +103,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault FileChooser.folderNameLabelText String * @uiDefault FileChooser.folderNameLabelText String
* @uiDefault FileChooser.filesOfTypeLabelMnemonic String * @uiDefault FileChooser.filesOfTypeLabelMnemonic String
* @uiDefault FileChooser.filesOfTypeLabelText String * @uiDefault FileChooser.filesOfTypeLabelText String
*
* @uiDefault FileChooser.upFolderToolTipText String * @uiDefault FileChooser.upFolderToolTipText String
* @uiDefault FileChooser.upFolderAccessibleName String * @uiDefault FileChooser.upFolderAccessibleName String
* @uiDefault FileChooser.homeFolderToolTipText String * @uiDefault FileChooser.homeFolderToolTipText String
@@ -97,11 +115,27 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault FileChooser.detailsViewButtonToolTipText String * @uiDefault FileChooser.detailsViewButtonToolTipText String
* @uiDefault FileChooser.detailsViewButtonAccessibleName String * @uiDefault FileChooser.detailsViewButtonAccessibleName String
* *
* <!-- FilePane -->
*
* @uiDefault FileChooser.fileNameHeaderText String
* @uiDefault FileChooser.fileSizeHeaderText String
* @uiDefault FileChooser.fileTypeHeaderText String
* @uiDefault FileChooser.fileDateHeaderText String
* @uiDefault FileChooser.fileAttrHeaderText String
*
* @uiDefault FileChooser.viewMenuLabelText String
* @uiDefault FileChooser.refreshActionLabelText String
* @uiDefault FileChooser.newFolderActionLabelText String
* @uiDefault FileChooser.listViewActionLabelText String
* @uiDefault FileChooser.detailsViewActionLabelText String
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatFileChooserUI public class FlatFileChooserUI
extends MetalFileChooserUI extends MetalFileChooserUI
{ {
private final FlatFileView fileView = new FlatFileView();
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c ); return new FlatFileChooserUI( (JFileChooser) c );
} }
@@ -110,6 +144,52 @@ public class FlatFileChooserUI
super( filechooser ); super( filechooser );
} }
@Override
public void installComponents( JFileChooser fc ) {
super.installComponents( fc );
patchUI( fc );
}
private void patchUI( JFileChooser fc ) {
// turn top-right buttons into toolbar buttons
Component topPanel = fc.getComponent( 0 );
if( (topPanel instanceof JPanel) &&
(((JPanel)topPanel).getLayout() instanceof BorderLayout) )
{
Component topButtonPanel = ((JPanel)topPanel).getComponent( 0 );
if( (topButtonPanel instanceof JPanel) &&
(((JPanel)topButtonPanel).getLayout() instanceof BoxLayout) )
{
Insets margin = UIManager.getInsets( "Button.margin" );
Component[] comps = ((JPanel)topButtonPanel).getComponents();
for( int i = comps.length - 1; i >= 0; i-- ) {
Component c = comps[i];
if( c instanceof JButton || c instanceof JToggleButton ) {
AbstractButton b = (AbstractButton)c;
b.putClientProperty( FlatClientProperties.BUTTON_TYPE,
FlatClientProperties.BUTTON_TYPE_TOOLBAR_BUTTON );
b.setMargin( margin );
b.setFocusable( false );
} else if( c instanceof Box.Filler )
((JPanel)topButtonPanel).remove( i );
}
}
}
// increase maximum row count of directory combo box popup list
try {
Component directoryComboBox = ((JPanel)topPanel).getComponent( 2 );
if( directoryComboBox instanceof JComboBox ) {
int maximumRowCount = UIManager.getInt( "ComboBox.maximumRowCount" );
if( maximumRowCount > 0 )
((JComboBox<?>)directoryComboBox).setMaximumRowCount( maximumRowCount );
}
} catch( ArrayIndexOutOfBoundsException ex ) {
// ignore
}
}
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( super.getPreferredSize( c ) ); return UIScale.scale( super.getPreferredSize( c ) );
@@ -119,4 +199,50 @@ public class FlatFileChooserUI
public Dimension getMinimumSize( JComponent c ) { public Dimension getMinimumSize( JComponent c ) {
return UIScale.scale( super.getMinimumSize( c ) ); return UIScale.scale( super.getMinimumSize( c ) );
} }
@Override
public FileView getFileView( JFileChooser fc ) {
return fileView;
}
@Override
public void clearIconCache() {
fileView.clearIconCache();
}
//---- class FlatFileView -------------------------------------------------
private class FlatFileView
extends BasicFileView
{
@Override
public Icon getIcon( File f ) {
// get cached icon
Icon icon = getCachedIcon( f );
if( icon != null )
return icon;
// get system icon
if( f != null ) {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
if( icon != null ) {
if( icon instanceof ImageIcon )
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
return icon;
}
}
// get default icon
icon = super.getIcon( f );
if( icon instanceof ImageIcon ) {
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
}
return icon;
}
}
} }

View File

@@ -26,13 +26,14 @@ import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JInternalFrame; import javax.swing.JInternalFrame;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane; import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -91,7 +92,21 @@ public class FlatInternalFrameTitlePane
updateFrameIcon(); updateFrameIcon();
updateColors(); updateColors();
buttonPanel = new JPanel(); buttonPanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int height = size.height;
// use height of invisible buttons to always have same title pane height
if( !iconButton.isVisible() )
height = Math.max( height, iconButton.getPreferredSize().height );
if( !maxButton.isVisible() )
height = Math.max( height, maxButton.getPreferredSize().height );
if( !closeButton.isVisible() )
height = Math.max( height, closeButton.getPreferredSize().height );
return new Dimension( size.width, height );
}
};
buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) ); buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
buttonPanel.setOpaque( false ); buttonPanel.setOpaque( false );
@@ -103,14 +118,16 @@ public class FlatInternalFrameTitlePane
add( buttonPanel, BorderLayout.LINE_END ); add( buttonPanel, BorderLayout.LINE_END );
} }
private void updateFrameIcon() { protected void updateFrameIcon() {
Icon frameIcon = frame.getFrameIcon(); Icon frameIcon = frame.getFrameIcon();
if( frameIcon == UIManager.getIcon( "InternalFrame.icon" ) ) if( frameIcon != null && (frameIcon.getIconWidth() == 0 || frameIcon.getIconHeight() == 0) )
frameIcon = null; frameIcon = null;
else if( frameIcon instanceof ImageIcon )
frameIcon = new ScaledImageIcon( (ImageIcon) frameIcon );
titleLabel.setIcon( frameIcon ); titleLabel.setIcon( frameIcon );
} }
private void updateColors() { protected void updateColors() {
Color background = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTitleColor : notSelectedTitleColor ); Color background = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTitleColor : notSelectedTitleColor );
Color foreground = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTextColor : notSelectedTextColor ); Color foreground = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTextColor : notSelectedTextColor );
@@ -123,7 +140,7 @@ public class FlatInternalFrameTitlePane
closeButton.setForeground( foreground ); closeButton.setForeground( foreground );
} }
private void updateButtonsVisibility() { protected void updateButtonsVisibility() {
iconButton.setVisible( frame.isIconifiable() ); iconButton.setVisible( frame.isIconifiable() );
maxButton.setVisible( frame.isMaximizable() ); maxButton.setVisible( frame.isMaximizable() );
closeButton.setVisible( frame.isClosable() ); closeButton.setVisible( frame.isClosable() );
@@ -150,7 +167,7 @@ public class FlatInternalFrameTitlePane
//---- class FlatPropertyChangeHandler ------------------------------------ //---- class FlatPropertyChangeHandler ------------------------------------
private class FlatPropertyChangeHandler protected class FlatPropertyChangeHandler
extends PropertyChangeHandler extends PropertyChangeHandler
{ {
@Override @Override

View File

@@ -84,6 +84,8 @@ import javax.swing.plaf.basic.BasicInternalFrameUI;
public class FlatInternalFrameUI public class FlatInternalFrameUI
extends BasicInternalFrameUI extends BasicInternalFrameUI
{ {
protected FlatWindowResizer windowResizer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatInternalFrameUI( (JInternalFrame) c ); return new FlatInternalFrameUI( (JInternalFrame) c );
} }
@@ -97,6 +99,18 @@ public class FlatInternalFrameUI
super.installUI( c ); super.installUI( c );
LookAndFeel.installProperty( frame, "opaque", false ); LookAndFeel.installProperty( frame, "opaque", false );
windowResizer = createWindowResizer();
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
if( windowResizer != null ) {
windowResizer.uninstall();
windowResizer = null;
}
} }
@Override @Override
@@ -104,6 +118,10 @@ public class FlatInternalFrameUI
return new FlatInternalFrameTitlePane( w ); return new FlatInternalFrameTitlePane( w );
} }
protected FlatWindowResizer createWindowResizer() {
return new FlatWindowResizer.InternalFrameResizer( frame, this::getDesktopManager );
}
//---- class FlatInternalFrameBorder -------------------------------------- //---- class FlatInternalFrameBorder --------------------------------------
public static class FlatInternalFrameBorder public static class FlatInternalFrameBorder
@@ -112,6 +130,16 @@ public class FlatInternalFrameUI
private final Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" ); private final Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
private final Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" ); private final Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );
private final int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 ); private final int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 );
private final boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" );
private final FlatDropShadowBorder activeDropShadowBorder = new FlatDropShadowBorder(
UIManager.getColor( "InternalFrame.activeDropShadowColor" ),
UIManager.getInsets( "InternalFrame.activeDropShadowInsets" ),
FlatUIUtils.getUIFloat( "InternalFrame.activeDropShadowOpacity", 0.5f ) );
private final FlatDropShadowBorder inactiveDropShadowBorder = new FlatDropShadowBorder(
UIManager.getColor( "InternalFrame.inactiveDropShadowColor" ),
UIManager.getInsets( "InternalFrame.inactiveDropShadowInsets" ),
FlatUIUtils.getUIFloat( "InternalFrame.inactiveDropShadowOpacity", 0.5f ) );
public FlatInternalFrameBorder() { public FlatInternalFrameBorder() {
super( UIManager.getInsets( "InternalFrame.borderMargins" ) ); super( UIManager.getInsets( "InternalFrame.borderMargins" ) );
@@ -137,16 +165,31 @@ public class FlatInternalFrameUI
Insets insets = getBorderInsets( c ); Insets insets = getBorderInsets( c );
float lineWidth = scale( (float) borderLineWidth ); float lineWidth = scale( (float) borderLineWidth );
float rx = x + insets.left - lineWidth;
float ry = y + insets.top - lineWidth;
float rwidth = width - insets.left - insets.right + (lineWidth * 2);
float rheight = height - insets.top - insets.bottom + (lineWidth * 2);
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
g2.setColor( f.isSelected() ? activeBorderColor : inactiveBorderColor ); g2.setColor( f.isSelected() ? activeBorderColor : inactiveBorderColor );
g2.fill( FlatUIUtils.createRectangle(
x + insets.left - lineWidth, // paint drop shadow
y + insets.top - lineWidth, if( dropShadowPainted ) {
width - insets.left - insets.right + (lineWidth * 2), FlatDropShadowBorder dropShadowBorder = f.isSelected()
height - insets.top - insets.bottom + (lineWidth * 2), ? activeDropShadowBorder : inactiveDropShadowBorder;
lineWidth ) );
Insets dropShadowInsets = dropShadowBorder.getBorderInsets();
dropShadowBorder.paintBorder( c, g2,
(int) rx - dropShadowInsets.left,
(int) ry - dropShadowInsets.top,
(int) rwidth + dropShadowInsets.left + dropShadowInsets.right,
(int) rheight + dropShadowInsets.top + dropShadowInsets.bottom );
}
// paint border
g2.fill( FlatUIUtils.createRectangle( rx, ry, rwidth, rheight, lineWidth ) );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import javax.swing.Icon; import javax.swing.Icon;
@@ -30,6 +31,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicLabelUI; import javax.swing.plaf.basic.BasicLabelUI;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -54,12 +56,8 @@ public class FlatLabelUI
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatLabelUI.class, FlatLabelUI::new );
instance = new FlatLabelUI();
return instance;
} }
@Override @Override
@@ -124,6 +122,17 @@ public class FlatLabelUI
BasicHTML.updateRenderer( c, text ); BasicHTML.updateRenderer( c, text );
} }
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {
return (c.getClientProperty( BasicHTML.propertyKey ) != null)
? HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g )
: g;
}
@Override
public void paint( Graphics g, JComponent c ) {
super.paint( createGraphicsHTMLTextYCorrection( g, c ), c );
}
@Override @Override
protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) { protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) {
int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1; int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1;

View File

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

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import javax.swing.JComponent; import javax.swing.JComponent;
@@ -81,7 +82,7 @@ public class FlatListUI
selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" );
toggleSelectionColors( list.hasFocus() ); toggleSelectionColors();
} }
@Override @Override
@@ -100,13 +101,17 @@ public class FlatListUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
toggleSelectionColors( true ); toggleSelectionColors();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
toggleSelectionColors( false );
// use invokeLater for the case that the window is deactivated
EventQueue.invokeLater( () -> {
toggleSelectionColors();
} );
} }
}; };
} }
@@ -120,8 +125,11 @@ public class FlatListUI
* already used in applications. Then either the inactive colors are not used, * already used in applications. Then either the inactive colors are not used,
* or the application has to be changed to extend a FlatLaf renderer. * or the application has to be changed to extend a FlatLaf renderer.
*/ */
private void toggleSelectionColors( boolean focused ) { private void toggleSelectionColors() {
if( focused ) { if( list == null )
return;
if( FlatUIUtils.isPermanentFocusOwner( list ) ) {
if( list.getSelectionBackground() == selectionInactiveBackground ) if( list.getSelectionBackground() == selectionInactiveBackground )
list.setSelectionBackground( selectionBackground ); list.setSelectionBackground( selectionBackground );
if( list.getSelectionForeground() == selectionInactiveForeground ) if( list.getSelectionForeground() == selectionInactiveForeground )

View File

@@ -20,9 +20,7 @@ import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.geom.Rectangle2D;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -40,16 +38,8 @@ public class FlatMenuBarBorder
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
float lineHeight = scale( (float) 1 ); float lineHeight = scale( (float) 1 );
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( borderColor );
g2.fill( new Rectangle2D.Float( x, y + height - lineHeight, width, lineHeight ) );
} finally {
g2.dispose();
}
} }
@Override @Override

View File

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

View File

@@ -40,7 +40,7 @@ public class FlatMenuItemBorder
if( c.getParent() instanceof JMenuBar ) { if( c.getParent() instanceof JMenuBar ) {
insets.top = scale( menuBarItemMargins.top ); insets.top = scale( menuBarItemMargins.top );
insets.left = scale( menuBarItemMargins.left ); insets.left = scale( menuBarItemMargins.left );
insets.bottom = scale( menuBarItemMargins.bottom + 1 ); insets.bottom = scale( menuBarItemMargins.bottom );
insets.right = scale( menuBarItemMargins.right ); insets.right = scale( menuBarItemMargins.right );
return insets; return insets;
} else } else

View File

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

View File

@@ -16,19 +16,13 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Dimension;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Rectangle; import javax.swing.Icon;
import java.beans.PropertyChangeListener;
import javax.swing.ButtonModel;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu; import javax.swing.LookAndFeel;
import javax.swing.JMenuItem;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicMenuItemUI;
import com.formdev.flatlaf.FlatLaf;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}. * Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}.
@@ -51,13 +45,18 @@ import com.formdev.flatlaf.FlatLaf;
* @uiDefault MenuItem.arrowIcon Icon * @uiDefault MenuItem.arrowIcon Icon
* @uiDefault MenuItem.checkIcon Icon * @uiDefault MenuItem.checkIcon Icon
* @uiDefault MenuItem.opaque boolean * @uiDefault MenuItem.opaque boolean
* @uiDefault MenuItem.evenHeight boolean *
* <!-- FlatMenuItemUI -->
*
* @uiDefault MenuItem.iconTextGap int
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatMenuItemUI public class FlatMenuItemUI
extends BasicMenuItemUI extends BasicMenuItemUI
{ {
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatMenuItemUI(); return new FlatMenuItemUI();
} }
@@ -66,42 +65,30 @@ public class FlatMenuItemUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// scale LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
defaultTextIconGap = scale( defaultTextIconGap );
}
/** renderer = createRenderer();
* Scale defaultTextIconGap again if iconTextGap property has changed.
*/
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
PropertyChangeListener superListener = super.createPropertyChangeListener( c );
return e -> {
superListener.propertyChange( e );
if( e.getPropertyName() == "iconTextGap" )
defaultTextIconGap = scale( defaultTextIconGap );
};
} }
@Override @Override
protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { protected void uninstallDefaults() {
paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); super.uninstallDefaults();
renderer = null;
} }
public static void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, protected FlatMenuItemRenderer createRenderer() {
String text, Color disabledForeground, Color selectionForeground ) return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
{ }
FontMetrics fm = menuItem.getFontMetrics( menuItem.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? menuItem.getDisplayedMnemonicIndex() : -1;
ButtonModel model = menuItem.getModel(); @Override
g.setColor( !model.isEnabled() protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
? disabledForeground return renderer.getPreferredMenuItemSize();
: (model.isArmed() || (menuItem instanceof JMenu && model.isSelected()) }
? selectionForeground
: menuItem.getForeground()) );
FlatUIUtils.drawStringUnderlineCharAt( menuItem, g, text, mnemonicIndex, @Override
textRect.x, textRect.y + fm.getAscent() ); public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
} }
} }

View File

@@ -16,16 +16,17 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import javax.swing.ButtonModel; import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.MouseInputListener; import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
@@ -52,13 +53,13 @@ import javax.swing.plaf.basic.BasicMenuUI;
* @uiDefault Menu.arrowIcon Icon * @uiDefault Menu.arrowIcon Icon
* @uiDefault Menu.checkIcon Icon * @uiDefault Menu.checkIcon Icon
* @uiDefault Menu.opaque boolean * @uiDefault Menu.opaque boolean
* @uiDefault Menu.evenHeight boolean
* @uiDefault Menu.crossMenuMnemonic boolean default is false * @uiDefault Menu.crossMenuMnemonic boolean default is false
* @uiDefault Menu.useMenuBarBackgroundForTopLevel boolean default is false * @uiDefault Menu.useMenuBarBackgroundForTopLevel boolean default is false
* @uiDefault MenuBar.background Color used if Menu.useMenuBarBackgroundForTopLevel is true * @uiDefault MenuBar.background Color used if Menu.useMenuBarBackgroundForTopLevel is true
* *
* <!-- FlatMenuUI --> * <!-- FlatMenuUI -->
* *
* @uiDefault MenuItem.iconTextGap int
* @uiDefault MenuBar.hoverBackground Color * @uiDefault MenuBar.hoverBackground Color
* *
* @author Karl Tauber * @author Karl Tauber
@@ -67,6 +68,7 @@ public class FlatMenuUI
extends BasicMenuUI extends BasicMenuUI
{ {
private Color hoverBackground; private Color hoverBackground;
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatMenuUI(); return new FlatMenuUI();
@@ -76,12 +78,12 @@ public class FlatMenuUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
menuItem.setRolloverEnabled( true ); menuItem.setRolloverEnabled( true );
hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" ); hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
renderer = createRenderer();
// scale
defaultTextIconGap = scale( defaultTextIconGap );
} }
@Override @Override
@@ -89,19 +91,11 @@ public class FlatMenuUI
super.uninstallDefaults(); super.uninstallDefaults();
hoverBackground = null; hoverBackground = null;
renderer = null;
} }
/** protected FlatMenuItemRenderer createRenderer() {
* Scale defaultTextIconGap again if iconTextGap property has changed. return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
*/
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
PropertyChangeListener superListener = super.createPropertyChangeListener( c );
return e -> {
superListener.propertyChange( e );
if( e.getPropertyName() == "iconTextGap" )
defaultTextIconGap = scale( defaultTextIconGap );
};
} }
@Override @Override
@@ -130,19 +124,45 @@ public class FlatMenuUI
} }
@Override @Override
protected void paintBackground( Graphics g, JMenuItem menuItem, Color bgColor ) { public Dimension getMinimumSize( JComponent c ) {
ButtonModel model = menuItem.getModel(); // avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare
if( model.isArmed() || model.isSelected() ) { // same code is in BasicMenuUI since Java 10
super.paintBackground( g, menuItem, bgColor ); // see https://bugs.openjdk.java.net/browse/JDK-8178430
} else if( model.isRollover() && model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() ) { return ((JMenu)menuItem).isTopLevelMenu() ? c.getPreferredSize() : null;
FlatUIUtils.setColor( g, hoverBackground, menuItem.getBackground() );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
} else
super.paintBackground( g, menuItem, bgColor );
} }
@Override @Override
protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
FlatMenuItemUI.paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); return renderer.getPreferredMenuItemSize();
}
@Override
public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
}
//---- class FlatMenuRenderer ---------------------------------------------
protected class FlatMenuRenderer
extends FlatMenuItemRenderer
{
protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
{
super( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected void paintBackground( Graphics g, Color selectionBackground ) {
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() &&
model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() )
{
g.setColor( deriveBackground( hoverBackground ) );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
} else
super.paintBackground( g, selectionBackground );
}
} }
} }

View File

@@ -35,11 +35,7 @@ import javax.swing.plaf.basic.BasicPanelUI;
public class FlatPanelUI public class FlatPanelUI
extends BasicPanelUI extends BasicPanelUI
{ {
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatPanelUI.class, FlatPanelUI::new );
instance = new FlatPanelUI();
return instance;
} }
} }

View File

@@ -16,10 +16,10 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
@@ -34,7 +34,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPasswordFieldUI; import javax.swing.plaf.basic.BasicPasswordFieldUI;
import javax.swing.text.Caret; import javax.swing.text.Caret;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.util.HiDPIUtils;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}. * Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}.
@@ -57,11 +57,10 @@ import com.formdev.flatlaf.FlatClientProperties;
* *
* <!-- FlatPasswordFieldUI --> * <!-- FlatPasswordFieldUI -->
* *
* @uiDefault TextComponent.arc int
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int * @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color * @uiDefault PasswordField.placeholderForeground Color
* @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.capsLockIcon Icon * @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always * @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* *
@@ -70,11 +69,10 @@ import com.formdev.flatlaf.FlatClientProperties;
public class FlatPasswordFieldUI public class FlatPasswordFieldUI
extends BasicPasswordFieldUI extends BasicPasswordFieldUI
{ {
protected int arc;
protected int focusWidth;
protected int minimumWidth; protected int minimumWidth;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color placeholderForeground; protected Color placeholderForeground;
protected boolean showCapsLock;
protected Icon capsLockIcon; protected Icon capsLockIcon;
private FocusListener focusListener; private FocusListener focusListener;
@@ -89,16 +87,15 @@ public class FlatPasswordFieldUI
super.installDefaults(); super.installDefaults();
String prefix = getPropertyPrefix(); String prefix = getPropertyPrefix();
arc = UIManager.getInt( "TextComponent.arc" );
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" ); placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" ); capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 ); LookAndFeel.installProperty( getComponent(), "opaque", false );
MigLayoutVisualPadding.install( getComponent(), focusWidth ); MigLayoutVisualPadding.install( getComponent() );
} }
@Override @Override
@@ -153,22 +150,24 @@ public class FlatPasswordFieldUI
@Override @Override
protected void propertyChange( PropertyChangeEvent e ) { protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e ); super.propertyChange( e );
FlatTextFieldUI.propertyChange( getComponent(), e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
} }
@Override @Override
protected void paintSafely( Graphics g ) { protected void paintSafely( Graphics g ) {
FlatTextFieldUI.paintBackground( g, getComponent(), focusWidth, arc, isIntelliJTheme ); FlatTextFieldUI.paintBackground( g, getComponent(), isIntelliJTheme );
FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground ); FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground );
paintCapsLock( g ); paintCapsLock( g );
super.paintSafely( g );
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
} }
protected void paintCapsLock( Graphics g ) { protected void paintCapsLock( Graphics g ) {
if( !showCapsLock )
return;
JTextComponent c = getComponent(); JTextComponent c = getComponent();
if( !c.isFocusOwner() || if( !FlatUIUtils.isPermanentFocusOwner( c ) ||
!Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) ) !Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) )
return; return;
@@ -184,18 +183,11 @@ public class FlatPasswordFieldUI
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c ); return FlatTextFieldUI.applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
} }
@Override @Override
public Dimension getMinimumSize( JComponent c ) { public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ), c ); return FlatTextFieldUI.applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
}
private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0;
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) );
return size;
} }
} }

View File

@@ -0,0 +1,512 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.JWindow;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
* A popup factory that adds drop shadows to popups on Windows.
* On macOS and Linux, heavy weight popups (without drop shadow) are produced and the
* operating system automatically adds drop shadows.
*
* @author Karl Tauber
*/
public class FlatPopupFactory
extends PopupFactory
{
private Method java8getPopupMethod;
private Method java9getPopupMethod;
@Override
public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
{
Point pt = fixToolTipLocation( owner, contents, x, y );
if( pt != null ) {
x = pt.x;
y = pt.y;
}
if( !isDropShadowPainted( owner, contents ) )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, false ), contents );
// macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
Popup popup = getPopupForScreenOfOwner( owner, contents, x, y, true );
if( popup == null )
popup = getPopupForScreenOfOwner( owner, contents, x, y, false );
return new NonFlashingPopup( popup, contents );
}
// create drop shadow popup
return new DropShadowPopup( getPopupForScreenOfOwner( owner, contents, x, y, false ), owner, contents );
}
/**
* Creates a popup for the screen that the owner component is on.
* <p>
* PopupFactory caches heavy weight popup windows and reuses them.
* On a dual screen setup, if the popup owner has moved from one screen to the other one,
* then the cached heavy weight popup window may be connected to the wrong screen.
* If the two screens use different scaling factors, then the popup location and size
* is scaled when the popup becomes visible, which shows the popup in the wrong location
* (or on wrong screen). The re-scaling is done in WWindowPeer.setBounds() (Java 9+).
* <p>
* To fix this, dispose popup windows that are on wrong screen and get new popup.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private Popup getPopupForScreenOfOwner( Component owner, Component contents, int x, int y, boolean forceHeavyWeight )
throws IllegalArgumentException
{
int count = 0;
for(;;) {
// create new or get cached popup
Popup popup = forceHeavyWeight
? getHeavyWeightPopup( owner, contents, x, y )
: super.getPopup( owner, contents, x, y );
// get heavy weight popup window; is null for non-heavy weight popup
Window popupWindow = SwingUtilities.windowForComponent( contents );
// check whether heavy weight popup window is on same screen as owner component
if( popupWindow == null ||
popupWindow.getGraphicsConfiguration() == owner.getGraphicsConfiguration() )
return popup;
// remove contents component from popup window
if( popupWindow instanceof JWindow )
((JWindow)popupWindow).getContentPane().removeAll();
// 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;
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <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.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
private boolean isDropShadowPainted( Component owner, Component contents ) {
Boolean b = isDropShadowPainted( owner );
if( b != null )
return b;
b = isDropShadowPainted( contents );
if( b != null )
return b;
return UIManager.getBoolean( "Popup.dropShadowPainted" );
}
private Boolean isDropShadowPainted( Component c ) {
if( !(c instanceof JComponent) )
return null;
Object value = ((JComponent)c).getClientProperty( FlatClientProperties.POPUP_DROP_SHADOW_PAINTED );
return (value instanceof Boolean ) ? (Boolean) value : null;
}
/**
* There is no API in Java 8 to force creation of heavy weight popups,
* but it is possible with reflection. Java 9 provides a new method.
*
* When changing FlatLaf system requirements to Java 9+,
* then this method can be replaced with:
* return getPopup( owner, contents, x, y, true );
*/
private Popup getHeavyWeightPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
{
try {
if( SystemInfo.isJava_9_orLater ) {
if( java9getPopupMethod == null ) {
java9getPopupMethod = PopupFactory.class.getDeclaredMethod(
"getPopup", Component.class, Component.class, int.class, int.class, boolean.class );
}
return (Popup) java9getPopupMethod.invoke( this, owner, contents, x, y, true );
} else {
// Java 8
if( java8getPopupMethod == null ) {
java8getPopupMethod = PopupFactory.class.getDeclaredMethod(
"getPopup", Component.class, Component.class, int.class, int.class, int.class );
java8getPopupMethod.setAccessible( true );
}
return (Popup) java8getPopupMethod.invoke( this, owner, contents, x, y, /*HEAVY_WEIGHT_POPUP*/ 2 );
}
} catch( NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException ex ) {
// ignore
return null;
}
}
/**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen,
* ToolTipManagerthe changes the location so that the entire tooltip fits on screen.
* But this can place the tooltip under the mouse location and hide the owner component.
* <p>
* This method checks whether the current mouse location is within tooltip bounds
* and corrects the y-location so that the tooltip is placed above the mouse location.
*/
private Point fixToolTipLocation( Component owner, Component contents, int x, int y ) {
if( !(contents instanceof JToolTip) || !wasInvokedFromToolTipManager() )
return null;
Point mouseLocation = MouseInfo.getPointerInfo().getLocation();
Dimension tipSize = contents.getPreferredSize();
// check whether mouse location is within tooltip bounds
Rectangle tipBounds = new Rectangle( x, y, tipSize.width, tipSize.height );
if( !tipBounds.contains( mouseLocation ) )
return null;
// place tooltip above mouse location
return new Point( x, mouseLocation.y - tipSize.height - UIScale.scale( 20 ) );
}
private boolean wasInvokedFromToolTipManager() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for( StackTraceElement stackTraceElement : stackTrace ) {
if( "javax.swing.ToolTipManager".equals( stackTraceElement.getClassName() ) &&
"showTipWindow".equals( stackTraceElement.getMethodName() ) )
return true;
}
return false;
}
//---- class NonFlashingPopup ---------------------------------------------
private class NonFlashingPopup
extends Popup
{
private Popup delegate;
private Component contents;
// heavy weight
protected Window popupWindow;
private Color oldPopupWindowBackground;
NonFlashingPopup( Popup delegate, Component contents ) {
this.delegate = delegate;
this.contents = contents;
popupWindow = SwingUtilities.windowForComponent( contents );
if( popupWindow != null ) {
// heavy weight popup
// fix background flashing which may occur on some platforms
// (e.g. macOS and Linux) when using dark theme
oldPopupWindowBackground = popupWindow.getBackground();
popupWindow.setBackground( contents.getBackground() );
}
}
@Override
public void show() {
if( delegate != null ) {
showPopupAndFixLocation( delegate, popupWindow );
// increase tooltip size if necessary because it may be too small on HiDPI screens
// https://bugs.openjdk.java.net/browse/JDK-8213535
if( contents instanceof JToolTip ) {
Container parent = contents.getParent();
if( parent instanceof JPanel ) {
Dimension prefSize = parent.getPreferredSize();
if( !prefSize.equals( parent.getSize() ) ) {
Container panel = SwingUtilities.getAncestorOfClass( Panel.class, parent );
if( panel != null )
panel.setSize( prefSize ); // for medium weight popup
else
parent.setSize( prefSize ); // for light weight popup
}
}
}
}
}
@Override
public void hide() {
if( delegate != null ) {
delegate.hide();
delegate = null;
contents = null;
}
if( popupWindow != null ) {
// restore background so that it can not affect other LaFs (when switching)
// because popup windows are cached and reused
popupWindow.setBackground( oldPopupWindowBackground );
popupWindow = null;
}
}
}
//---- class DropShadowPopup ----------------------------------------------
private class DropShadowPopup
extends NonFlashingPopup
{
private final Component owner;
// light weight
private JComponent lightComp;
private Border oldBorder;
private boolean oldOpaque;
// medium weight
private boolean mediumWeightShown;
private Panel mediumWeightPanel;
private JPanel dropShadowPanel;
private ComponentListener mediumPanelListener;
// heavy weight
private Popup dropShadowDelegate;
private Window dropShadowWindow;
private Color oldDropShadowWindowBackground;
DropShadowPopup( Popup delegate, Component owner, Component contents ) {
super( delegate, contents );
this.owner = owner;
Dimension size = contents.getPreferredSize();
if( size.width <= 0 || size.height <= 0 )
return;
if( popupWindow != null ) {
// heavy weight popup
// Since Java has a problem with sub-pixel text rendering on translucent
// windows, we can not make the popup window translucent for the drop shadow.
// (see https://bugs.openjdk.java.net/browse/JDK-8215980)
// The solution is to create a second translucent window that paints
// the drop shadow and is positioned behind the popup window.
// create panel that paints the drop shadow
JPanel dropShadowPanel = new JPanel();
dropShadowPanel.setBorder( createDropShadowBorder() );
dropShadowPanel.setOpaque( false );
// set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setPreferredSize( new Dimension(
prefSize.width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom ) );
// create heavy weight popup for drop shadow
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel, x, y, true );
// make drop shadow popup window translucent
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel );
if( dropShadowWindow != null ) {
oldDropShadowWindowBackground = dropShadowWindow.getBackground();
dropShadowWindow.setBackground( new Color( 0, true ) );
}
} else {
mediumWeightPanel = (Panel) SwingUtilities.getAncestorOfClass( Panel.class, contents );
if( mediumWeightPanel != null ) {
// medium weight popup
dropShadowPanel = new JPanel();
dropShadowPanel.setBorder( createDropShadowBorder() );
dropShadowPanel.setOpaque( false );
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
} else {
// light weight popup
Container p = contents.getParent();
if( !(p instanceof JComponent) )
return;
lightComp = (JComponent) p;
oldBorder = lightComp.getBorder();
oldOpaque = lightComp.isOpaque();
lightComp.setBorder( createDropShadowBorder() );
lightComp.setOpaque( false );
lightComp.setSize( lightComp.getPreferredSize() );
}
}
}
private Border createDropShadowBorder() {
return new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ),
UIManager.getInsets( "Popup.dropShadowInsets" ),
FlatUIUtils.getUIFloat( "Popup.dropShadowOpacity", 0.5f ) );
}
@Override
public void show() {
if( dropShadowDelegate != null )
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
if( mediumWeightPanel != null )
showMediumWeightDropShadow();
super.show();
// fix location of light weight popup in case it has left or top drop shadow
if( lightComp != null ) {
Insets insets = lightComp.getInsets();
if( insets.left != 0 || insets.top != 0 )
lightComp.setLocation( lightComp.getX() - insets.left, lightComp.getY() - insets.top );
}
}
@Override
public void hide() {
if( dropShadowDelegate != null ) {
dropShadowDelegate.hide();
dropShadowDelegate = null;
}
if( mediumWeightPanel != null ) {
hideMediumWeightDropShadow();
dropShadowPanel = null;
mediumWeightPanel = null;
}
super.hide();
if( dropShadowWindow != null ) {
dropShadowWindow.setBackground( oldDropShadowWindowBackground );
dropShadowWindow = null;
}
if( lightComp != null ) {
lightComp.setBorder( oldBorder );
lightComp.setOpaque( oldOpaque );
lightComp = null;
}
}
private void showMediumWeightDropShadow() {
if( mediumWeightShown )
return;
mediumWeightShown = true;
Window window = SwingUtilities.windowForComponent( owner );
if( window == null )
return;
if( !(window instanceof RootPaneContainer) )
return;
dropShadowPanel.setVisible( false );
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
layeredPane.add( dropShadowPanel, JLayeredPane.POPUP_LAYER, 0 );
mediumPanelListener = new ComponentListener() {
@Override
public void componentShown( ComponentEvent e ) {
if( dropShadowPanel != null )
dropShadowPanel.setVisible( true );
}
@Override
public void componentHidden( ComponentEvent e ) {
if( dropShadowPanel != null )
dropShadowPanel.setVisible( false );
}
@Override
public void componentMoved( ComponentEvent e ) {
if( dropShadowPanel != null && mediumWeightPanel != null ) {
Point location = mediumWeightPanel.getLocation();
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setLocation( location.x - insets.left, location.y - insets.top );
}
}
@Override
public void componentResized( ComponentEvent e ) {
if( dropShadowPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
}
};
mediumWeightPanel.addComponentListener( mediumPanelListener );
}
private void hideMediumWeightDropShadow() {
mediumWeightPanel.removeComponentListener( mediumPanelListener );
Container parent = dropShadowPanel.getParent();
if( parent != null ) {
Rectangle bounds = dropShadowPanel.getBounds();
parent.remove( dropShadowPanel );
parent.repaint( bounds.x, bounds.y, bounds.width, bounds.height );
}
}
}
}

View File

@@ -38,12 +38,8 @@ import javax.swing.plaf.ComponentUI;
public class FlatPopupMenuSeparatorUI public class FlatPopupMenuSeparatorUI
extends FlatSeparatorUI extends FlatSeparatorUI
{ {
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, FlatPopupMenuSeparatorUI::new );
instance = new FlatPopupMenuSeparatorUI();
return instance;
} }
@Override @Override

View File

@@ -31,6 +31,7 @@ import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicProgressBarUI; import javax.swing.plaf.basic.BasicProgressBarUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -194,6 +195,11 @@ public class FlatProgressBarUI
} }
} }
@Override
protected void paintString( Graphics g, int x, int y, int width, int height, int amountFull, Insets b ) {
super.paintString( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ), x, y, width, height, amountFull, b );
}
@Override @Override
protected void setAnimationIndex( int newValue ) { protected void setAnimationIndex( int newValue ) {
super.setAnimationIndex( newValue ); super.setAnimationIndex( newValue );

View File

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

View File

@@ -23,7 +23,6 @@ import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.CellRendererPane;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -61,12 +60,8 @@ public class FlatRadioButtonUI
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, FlatRadioButtonUI::new );
instance = new FlatRadioButtonUI();
return instance;
} }
@Override @Override
@@ -122,11 +117,10 @@ public class FlatRadioButtonUI
public void paint( Graphics g, JComponent c ) { public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque if // fill background even if not opaque if
// - contentAreaFilled is true and // - contentAreaFilled is true and
// - used as cell renderer (because of selection background) // - if background was explicitly set to a non-UIResource color
// - or if background was explicitly set to a non-UIResource color
if( !c.isOpaque() && if( !c.isOpaque() &&
((AbstractButton)c).isContentAreaFilled() && ((AbstractButton)c).isContentAreaFilled() &&
(c.getParent() instanceof CellRendererPane || !(c.getBackground() instanceof UIResource))) !(c.getBackground() instanceof UIResource) )
{ {
g.setColor( c.getBackground() ); g.setColor( c.getBackground() );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() ); g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
@@ -155,7 +149,7 @@ public class FlatRadioButtonUI
} }
} }
super.paint( g, c ); super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
} }
@Override @Override

View File

@@ -16,23 +16,411 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Window;
import java.beans.PropertyChangeEvent;
import java.util.function.Function;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRootPaneUI; import javax.swing.plaf.basic.BasicRootPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRootPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JRootPane}.
* *
* <!-- FlatRootPaneUI -->
*
* @uiDefault RootPane.border Border
* @uiDefault RootPane.activeBorderColor Color
* @uiDefault RootPane.inactiveBorderColor Color
* @uiDefault TitlePane.borderColor Color optional
*
* <!-- FlatWindowResizer -->
*
* @uiDefault RootPane.borderDragThickness int
* @uiDefault RootPane.cornerDragWidth int
* @uiDefault RootPane.honorFrameMinimumSizeOnResize boolean
* @uiDefault RootPane.honorDialogMinimumSizeOnResize boolean
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatRootPaneUI public class FlatRootPaneUI
extends BasicRootPaneUI extends BasicRootPaneUI
{ {
private static ComponentUI instance; // check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
static final boolean canUseJBRCustomDecorations
= SystemInfo.isJetBrainsJVM_11_orLater && SystemInfo.isWindows_10_orLater;
protected final Color borderColor = UIManager.getColor( "TitlePane.borderColor" );
protected JRootPane rootPane;
protected FlatTitlePane titlePane;
protected FlatWindowResizer windowResizer;
private LayoutManager oldLayout;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return new FlatRootPaneUI();
instance = new FlatRootPaneUI(); }
return instance;
@Override
public void installUI( JComponent c ) {
super.installUI( c );
rootPane = (JRootPane) c;
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE )
installClientDecorations();
else
installBorder();
if( canUseJBRCustomDecorations )
JBRCustomDecorations.install( rootPane );
}
protected void installBorder() {
if( borderColor != null ) {
Border b = rootPane.getBorder();
if( b == null || b instanceof UIResource )
rootPane.setBorder( new FlatWindowTitleBorder( borderColor ) );
}
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
uninstallClientDecorations();
rootPane = null;
}
@Override
protected void installDefaults( JRootPane c ) {
super.installDefaults( c );
// Update background color of JFrame or JDialog parent to avoid bad border
// on HiDPI screens when switching from light to dark Laf.
// 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.
Container parent = c.getParent();
if( parent instanceof JFrame || parent instanceof JDialog ) {
Color background = parent.getBackground();
if( background == null || background instanceof UIResource )
parent.setBackground( UIManager.getColor( "control" ) );
}
// enable dark window appearance on macOS when running in JetBrains Runtime
if( SystemInfo.isJetBrainsJVM && SystemInfo.isMacOS_10_14_Mojave_orLater )
c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() );
}
protected void installClientDecorations() {
boolean isJBRSupported = canUseJBRCustomDecorations && JBRCustomDecorations.isSupported();
// install border
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE && !isJBRSupported )
LookAndFeel.installBorder( rootPane, "RootPane.border" );
else
LookAndFeel.uninstallBorder( rootPane );
// install title pane
setTitlePane( createTitlePane() );
// install layout
oldLayout = rootPane.getLayout();
rootPane.setLayout( createRootLayout() );
// install window resizer
if( !isJBRSupported )
windowResizer = createWindowResizer();
}
protected void uninstallClientDecorations() {
LookAndFeel.uninstallBorder( rootPane );
setTitlePane( null );
if( windowResizer != null ) {
windowResizer.uninstall();
windowResizer = null;
}
if( oldLayout != null ) {
rootPane.setLayout( oldLayout );
oldLayout = null;
}
if( rootPane.getWindowDecorationStyle() == JRootPane.NONE ) {
rootPane.revalidate();
rootPane.repaint();
}
}
protected FlatRootLayout createRootLayout() {
return new FlatRootLayout();
}
protected FlatWindowResizer createWindowResizer() {
return new FlatWindowResizer.WindowResizer( rootPane );
}
protected FlatTitlePane createTitlePane() {
return new FlatTitlePane( rootPane );
}
// layer title pane under frame content layer to allow placing menu bar over title pane
protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane != null )
layeredPane.remove( titlePane );
if( newTitlePane != null )
layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
titlePane = newTitlePane;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
switch( e.getPropertyName() ) {
case "windowDecorationStyle":
uninstallClientDecorations();
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE )
installClientDecorations();
else
installBorder();
break;
case FlatClientProperties.MENU_BAR_EMBEDDED:
if( titlePane != null ) {
titlePane.menuBarChanged();
rootPane.revalidate();
rootPane.repaint();
}
break;
}
}
//---- class FlatRootLayout -----------------------------------------------
protected class FlatRootLayout
implements LayoutManager2
{
@Override public void addLayoutComponent( String name, Component comp ) {}
@Override public void addLayoutComponent( Component comp, Object constraints ) {}
@Override public void removeLayoutComponent( Component comp ) {}
@Override
public Dimension preferredLayoutSize( Container parent ) {
return computeLayoutSize( parent, c -> c.getPreferredSize() );
}
@Override
public Dimension minimumLayoutSize( Container parent ) {
return computeLayoutSize( parent, c -> c.getMinimumSize() );
}
@Override
public Dimension maximumLayoutSize( Container parent ) {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent;
Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize();
int width = Math.max( titlePaneSize.width, contentSize.width );
int height = titlePaneSize.height + contentSize.height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
? getSizeFunc.apply( menuBar )
: new Dimension();
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
}
Insets insets = rootPane.getInsets();
return new Dimension(
width + insets.left + insets.right,
height + insets.top + insets.bottom );
}
@Override
public void layoutContainer( Container parent ) {
JRootPane rootPane = (JRootPane) parent;
Insets insets = rootPane.getInsets();
int x = insets.left;
int y = insets.top;
int width = rootPane.getWidth() - insets.left - insets.right;
int height = rootPane.getHeight() - insets.top - insets.bottom;
if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height );
if( rootPane.getGlassPane() != null )
rootPane.getGlassPane().setBounds( x, y, width, height );
int nextY = 0;
if( titlePane != null ) {
Dimension prefSize = titlePane.getPreferredSize();
titlePane.setBounds( 0, 0, width, prefSize.height );
nextY += prefSize.height;
}
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
if( titlePane != null && titlePane.isMenuBarEmbedded() ) {
titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() );
} else {
Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( 0, nextY, width, prefSize.height );
nextY += prefSize.height;
}
}
Container contentPane = rootPane.getContentPane();
if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
if( titlePane != null )
titlePane.menuBarLayouted();
}
@Override
public void invalidateLayout( Container parent ) {
if( titlePane != null )
titlePane.menuBarChanged();
}
@Override
public float getLayoutAlignmentX( Container target ) {
return 0;
}
@Override
public float getLayoutAlignmentY( Container target ) {
return 0;
}
}
//---- class FlatWindowBorder ---------------------------------------------
public static class FlatWindowBorder
extends BorderUIResource.EmptyBorderUIResource
{
protected final Color activeBorderColor = UIManager.getColor( "RootPane.activeBorderColor" );
protected final Color inactiveBorderColor = UIManager.getColor( "RootPane.inactiveBorderColor" );
protected final Color baseBorderColor = UIManager.getColor( "Panel.background" );
public FlatWindowBorder() {
super( 1, 1, 1, 1 );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( isWindowMaximized( c ) ) {
// hide border if window is maximized
insets.top = insets.left = insets.bottom = insets.right = 0;
return insets;
} else
return super.getBorderInsets( c, insets );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( isWindowMaximized( c ) )
return;
Container parent = c.getParent();
boolean active = parent instanceof Window ? ((Window)parent).isActive() : false;
g.setColor( FlatUIUtils.deriveColor( active ? activeBorderColor : inactiveBorderColor, baseBorderColor ) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.drawRect( x, y, width - 1, height - 1 );
}
protected boolean isWindowMaximized( Component c ) {
Container parent = c.getParent();
return parent instanceof Frame
? (((Frame)parent).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0
: false;
}
}
//---- class FlatWindowTitleBorder ----------------------------------------
private static class FlatWindowTitleBorder
extends BorderUIResource.EmptyBorderUIResource
{
private final Color borderColor;
FlatWindowTitleBorder( Color borderColor ) {
super( 0, 0, 0, 0 );
this.borderColor = borderColor;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( showBorder( c ) ) {
float lineHeight = UIScale.scale( (float) 1 );
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y, width, lineHeight );
}
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
insets.set( showBorder( c ) ? 1 : 0, 0, 0, 0 );
return insets;
}
private boolean showBorder( Component c ) {
Container parent = c.getParent();
return
(parent instanceof JFrame &&
(((JFrame)parent).getJMenuBar() == null ||
!((JFrame)parent).getJMenuBar().isVisible())) ||
parent instanceof JDialog;
}
} }
} }

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component; import java.awt.Component;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -33,7 +32,11 @@ public class FlatRoundBorder
protected final int arc = UIManager.getInt( "Component.arc" ); protected final int arc = UIManager.getInt( "Component.arc" );
@Override @Override
protected float getArc( Component c ) { protected int getArc( Component c ) {
return scale( (float) arc ); if( isCellEditor( c ) )
return 0;
Boolean roundRect = FlatUIUtils.isRoundRect( c );
return roundRect != null ? (roundRect ? Short.MAX_VALUE : 0) : arc;
} }
} }

View File

@@ -19,6 +19,8 @@ package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@@ -28,6 +30,7 @@ import java.util.Objects;
import javax.swing.InputMap; import javax.swing.InputMap;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -52,29 +55,49 @@ import com.formdev.flatlaf.util.UIScale;
* *
* <!-- FlatScrollBarUI --> * <!-- FlatScrollBarUI -->
* *
* @uiDefault ScrollBar.hoverTrackColor Color * @uiDefault ScrollBar.trackInsets Insets
* @uiDefault ScrollBar.hoverThumbColor Color * @uiDefault ScrollBar.thumbInsets Insets
* @uiDefault ScrollBar.trackArc int
* @uiDefault ScrollBar.thumbArc int
* @uiDefault ScrollBar.hoverTrackColor Color optional
* @uiDefault ScrollBar.hoverThumbColor Color optional
* @uiDefault ScrollBar.hoverThumbWithTrack boolean
* @uiDefault ScrollBar.pressedTrackColor Color optional
* @uiDefault ScrollBar.pressedThumbColor Color optional
* @uiDefault ScrollBar.pressedThumbWithTrack boolean
* @uiDefault Component.arrowType String triangle (default) or chevron * @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault ScrollBar.showButtons boolean * @uiDefault ScrollBar.showButtons boolean
* @uiDefault ScrollBar.buttonArrowColor Color * @uiDefault ScrollBar.buttonArrowColor Color
* @uiDefault ScrollBar.buttonDisabledArrowColor Color * @uiDefault ScrollBar.buttonDisabledArrowColor Color
* @uiDefault ScrollBar.hoverButtonBackground Color optional
* @uiDefault ScrollBar.pressedButtonBackground Color optional
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatScrollBarUI public class FlatScrollBarUI
extends BasicScrollBarUI extends BasicScrollBarUI
{ {
protected Insets trackInsets;
protected Insets thumbInsets;
protected int trackArc;
protected int thumbArc;
protected Color hoverTrackColor; protected Color hoverTrackColor;
protected Color hoverThumbColor; protected Color hoverThumbColor;
protected boolean hoverThumbWithTrack;
protected Color pressedTrackColor;
protected Color pressedThumbColor;
protected boolean pressedThumbWithTrack;
protected boolean showButtons; protected boolean showButtons;
protected String arrowType; protected String arrowType;
protected Color buttonArrowColor; protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor; protected Color buttonDisabledArrowColor;
protected Color hoverButtonBackground;
protected Color pressedButtonBackground;
private MouseAdapter hoverListener; private MouseAdapter hoverListener;
private boolean hoverTrack; protected boolean hoverTrack;
private boolean hoverThumb; protected boolean hoverThumb;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatScrollBarUI(); return new FlatScrollBarUI();
@@ -102,24 +125,40 @@ public class FlatScrollBarUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
trackInsets = UIManager.getInsets( "ScrollBar.trackInsets" );
thumbInsets = UIManager.getInsets( "ScrollBar.thumbInsets" );
trackArc = UIManager.getInt( "ScrollBar.trackArc" );
thumbArc = UIManager.getInt( "ScrollBar.thumbArc" );
hoverTrackColor = UIManager.getColor( "ScrollBar.hoverTrackColor" ); hoverTrackColor = UIManager.getColor( "ScrollBar.hoverTrackColor" );
hoverThumbColor = UIManager.getColor( "ScrollBar.hoverThumbColor" ); hoverThumbColor = UIManager.getColor( "ScrollBar.hoverThumbColor" );
hoverThumbWithTrack = UIManager.getBoolean( "ScrollBar.hoverThumbWithTrack" );
pressedTrackColor = UIManager.getColor( "ScrollBar.pressedTrackColor" );
pressedThumbColor = UIManager.getColor( "ScrollBar.pressedThumbColor" );
pressedThumbWithTrack = UIManager.getBoolean( "ScrollBar.pressedThumbWithTrack" );
showButtons = UIManager.getBoolean( "ScrollBar.showButtons" ); showButtons = UIManager.getBoolean( "ScrollBar.showButtons" );
arrowType = UIManager.getString( "Component.arrowType" ); arrowType = UIManager.getString( "Component.arrowType" );
buttonArrowColor = UIManager.getColor( "ScrollBar.buttonArrowColor" ); buttonArrowColor = UIManager.getColor( "ScrollBar.buttonArrowColor" );
buttonDisabledArrowColor = UIManager.getColor( "ScrollBar.buttonDisabledArrowColor" ); buttonDisabledArrowColor = UIManager.getColor( "ScrollBar.buttonDisabledArrowColor" );
hoverButtonBackground = UIManager.getColor( "ScrollBar.hoverButtonBackground" );
pressedButtonBackground = UIManager.getColor( "ScrollBar.pressedButtonBackground" );
} }
@Override @Override
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
trackInsets = null;
thumbInsets = null;
hoverTrackColor = null; hoverTrackColor = null;
hoverThumbColor = null; hoverThumbColor = null;
pressedTrackColor = null;
pressedThumbColor = null;
buttonArrowColor = null; buttonArrowColor = null;
buttonDisabledArrowColor = null; buttonDisabledArrowColor = null;
hoverButtonBackground = null;
pressedButtonBackground = null;
} }
@Override @Override
@@ -159,50 +198,61 @@ public class FlatScrollBarUI
@Override @Override
protected JButton createDecreaseButton( int orientation ) { protected JButton createDecreaseButton( int orientation ) {
return createArrowButton( orientation ); return new FlatScrollBarButton( orientation );
} }
@Override @Override
protected JButton createIncreaseButton( int orientation ) { protected JButton createIncreaseButton( int orientation ) {
return createArrowButton( orientation ); return new FlatScrollBarButton( orientation );
} }
private JButton createArrowButton( int orientation ) { protected boolean isShowButtons() {
FlatArrowButton button = new FlatArrowButton( orientation,
arrowType, buttonArrowColor, buttonDisabledArrowColor, null, hoverTrackColor )
{
@Override
public Dimension getPreferredSize() {
if( isShowButtons() ) {
int w = UIScale.scale( scrollBarWidth );
return new Dimension( w, w );
} else
return new Dimension();
}
@Override
public Dimension getMinimumSize() {
return isShowButtons() ? super.getMinimumSize() : new Dimension();
}
@Override
public Dimension getMaximumSize() {
return isShowButtons() ? super.getMaximumSize() : new Dimension();
}
};
button.setArrowWidth( FlatArrowButton.DEFAULT_ARROW_WIDTH - 2 );
button.setFocusable( false );
button.setRequestFocusEnabled( false );
return button;
}
private boolean isShowButtons() {
Object showButtons = scrollbar.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS ); Object showButtons = scrollbar.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollbar.getParent() instanceof JScrollPane ) if( showButtons == null && scrollbar.getParent() instanceof JScrollPane )
showButtons = ((JScrollPane)scrollbar.getParent()).getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS ); showButtons = ((JScrollPane)scrollbar.getParent()).getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
return (showButtons != null) ? Objects.equals( showButtons, true ) : this.showButtons; return (showButtons != null) ? Objects.equals( showButtons, true ) : this.showButtons;
} }
@Override
public void paint( Graphics g, JComponent c ) {
FlatUIUtils.setRenderingHints( (Graphics2D) g );
super.paint( g, c );
}
@Override
protected void paintTrack( Graphics g, JComponent c, Rectangle trackBounds ) {
g.setColor( getTrackColor( c, hoverTrack, isPressed && hoverTrack && !hoverThumb ) );
paintTrackOrThumb( g, c, trackBounds, trackInsets, trackArc );
}
@Override
protected void paintThumb( Graphics g, JComponent c, Rectangle thumbBounds ) {
if( thumbBounds.isEmpty() || !scrollbar.isEnabled() )
return;
g.setColor( getThumbColor( c, hoverThumb || (hoverThumbWithTrack && hoverTrack),
isPressed && (hoverThumb || (pressedThumbWithTrack && hoverTrack)) ) );
paintTrackOrThumb( g, c, thumbBounds, thumbInsets, thumbArc );
}
protected void paintTrackOrThumb( Graphics g, JComponent c, Rectangle bounds, Insets insets, int arc ) {
// rotate insets for horizontal orientation because they are given for vertical orientation
if( scrollbar.getOrientation() == JScrollBar.HORIZONTAL )
insets = new Insets( insets.right, insets.top, insets.left, insets.bottom );
// subtract insets from bounds
bounds = FlatUIUtils.subtractInsets( bounds, UIScale.scale( insets ) );
if( arc <= 0 ) {
// paint rectangle
g.fillRect( bounds.x, bounds.y, bounds.width, bounds.height );
} else {
// paint round rectangle
arc = Math.min( UIScale.scale( arc ), Math.min( bounds.width, bounds.height ) );
g.fillRoundRect( bounds.x, bounds.y, bounds.width, bounds.height, arc, arc );
}
}
@Override @Override
protected void paintDecreaseHighlight( Graphics g ) { protected void paintDecreaseHighlight( Graphics g ) {
// do not paint // do not paint
@@ -213,29 +263,33 @@ public class FlatScrollBarUI
// do not paint // do not paint
} }
@Override protected Color getTrackColor( JComponent c, boolean hover, boolean pressed ) {
protected void paintTrack( Graphics g, JComponent c, Rectangle trackBounds ) { Color trackColor = FlatUIUtils.deriveColor( this.trackColor, c.getBackground() );
g.setColor( hoverTrack ? hoverTrackColor : trackColor ); return (pressed && pressedTrackColor != null)
g.fillRect( trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height ); ? FlatUIUtils.deriveColor( pressedTrackColor, trackColor )
: ((hover && hoverTrackColor != null)
? FlatUIUtils.deriveColor( hoverTrackColor, trackColor )
: trackColor);
} }
@Override protected Color getThumbColor( JComponent c, boolean hover, boolean pressed ) {
protected void paintThumb( Graphics g, JComponent c, Rectangle thumbBounds ) { Color trackColor = FlatUIUtils.deriveColor( this.trackColor, c.getBackground() );
if( thumbBounds.isEmpty() || !scrollbar.isEnabled() ) Color thumbColor = FlatUIUtils.deriveColor( this.thumbColor, trackColor );
return; return (pressed && pressedThumbColor != null)
? FlatUIUtils.deriveColor( pressedThumbColor, thumbColor )
g.setColor( hoverThumb ? hoverThumbColor : thumbColor ); : ((hover && hoverThumbColor != null)
g.fillRect( thumbBounds.x, thumbBounds.y, thumbBounds.width, thumbBounds.height ); ? FlatUIUtils.deriveColor( hoverThumbColor, thumbColor )
: thumbColor);
} }
@Override @Override
protected Dimension getMinimumThumbSize() { protected Dimension getMinimumThumbSize() {
return UIScale.scale( super.getMinimumThumbSize() ); return UIScale.scale( FlatUIUtils.addInsets( super.getMinimumThumbSize(), thumbInsets ) );
} }
@Override @Override
protected Dimension getMaximumThumbSize() { protected Dimension getMaximumThumbSize() {
return UIScale.scale( super.getMaximumThumbSize() ); return UIScale.scale( FlatUIUtils.addInsets( super.getMaximumThumbSize(), thumbInsets ) );
} }
//---- class ScrollBarHoverListener --------------------------------------- //---- class ScrollBarHoverListener ---------------------------------------
@@ -263,11 +317,14 @@ public class FlatScrollBarUI
@Override @Override
public void mousePressed( MouseEvent e ) { public void mousePressed( MouseEvent e ) {
isPressed = true; isPressed = true;
repaint();
} }
@Override @Override
public void mouseReleased( MouseEvent e ) { public void mouseReleased( MouseEvent e ) {
isPressed = false; isPressed = false;
repaint();
update( e.getX(), e.getY() ); update( e.getX(), e.getY() );
} }
@@ -286,4 +343,49 @@ public class FlatScrollBarUI
scrollbar.repaint(); scrollbar.repaint();
} }
} }
//---- class FlatScrollBarButton ------------------------------------------
protected class FlatScrollBarButton
extends FlatArrowButton
{
protected FlatScrollBarButton( int direction ) {
this( direction, arrowType, buttonArrowColor, buttonDisabledArrowColor,
null, hoverButtonBackground, pressedButtonBackground );
}
protected FlatScrollBarButton( int direction, String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground, Color pressedBackground )
{
super( direction, type, foreground, disabledForeground, hoverForeground, hoverBackground, pressedBackground );
setArrowWidth( FlatArrowButton.DEFAULT_ARROW_WIDTH - 2 );
setFocusable( false );
setRequestFocusEnabled( false );
}
@Override
protected Color deriveBackground( Color background ) {
return FlatUIUtils.deriveColor( background, scrollbar.getBackground() );
}
@Override
public Dimension getPreferredSize() {
if( isShowButtons() ) {
int w = UIScale.scale( scrollBarWidth );
return new Dimension( w, w );
} else
return new Dimension();
}
@Override
public Dimension getMinimumSize() {
return isShowButtons() ? super.getMinimumSize() : new Dimension();
}
@Override
public Dimension getMaximumSize() {
return isShowButtons() ? super.getMaximumSize() : new Dimension();
}
}
} }

View File

@@ -83,7 +83,7 @@ public class FlatScrollPaneUI
int focusWidth = UIManager.getInt( "Component.focusWidth" ); int focusWidth = UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 ); LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
MigLayoutVisualPadding.install( scrollpane, focusWidth ); MigLayoutVisualPadding.install( scrollpane );
} }
@Override @Override
@@ -114,10 +114,7 @@ public class FlatScrollPaneUI
return new BasicScrollPaneUI.MouseWheelHandler() { return new BasicScrollPaneUI.MouseWheelHandler() {
@Override @Override
public void mouseWheelMoved( MouseWheelEvent e ) { public void mouseWheelMoved( MouseWheelEvent e ) {
// Note: Getting UI value "ScrollPane.smoothScrolling" here to allow if( isSmoothScrollingEnabled() &&
// applications to turn smooth scrolling on or off at any time
// (e.g. in application options dialog).
if( UIManager.getBoolean( "ScrollPane.smoothScrolling" ) &&
scrollpane.isWheelScrollingEnabled() && scrollpane.isWheelScrollingEnabled() &&
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL && e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
e.getPreciseWheelRotation() != 0 && e.getPreciseWheelRotation() != 0 &&
@@ -130,6 +127,17 @@ public class FlatScrollPaneUI
}; };
} }
protected boolean isSmoothScrollingEnabled() {
Object smoothScrolling = scrollpane.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
if( smoothScrolling instanceof Boolean )
return (Boolean) smoothScrolling;
// Note: Getting UI value "ScrollPane.smoothScrolling" here to allow
// applications to turn smooth scrolling on or off at any time
// (e.g. in application options dialog).
return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
}
private static final double EPSILON = 1e-5d; private static final double EPSILON = 1e-5d;
private void mouseWheelMovedSmooth( MouseWheelEvent e ) { private void mouseWheelMovedSmooth( MouseWheelEvent e ) {

View File

@@ -52,12 +52,8 @@ public class FlatSeparatorUI
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatSeparatorUI.class, FlatSeparatorUI::new );
instance = new FlatSeparatorUI();
return instance;
} }
@Override @Override

View File

@@ -201,7 +201,7 @@ public class FlatSliderUI
} }
if( coloredTrack != null ) { if( coloredTrack != null ) {
FlatUIUtils.setColor( g, slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor), thumbColor ); g.setColor( FlatUIUtils.deriveColor( FlatUIUtils.isPermanentFocusOwner( slider ) ? focusColor : (hover ? hoverColor : thumbColor), thumbColor ) );
((Graphics2D)g).fill( coloredTrack ); ((Graphics2D)g).fill( coloredTrack );
} }
@@ -211,10 +211,10 @@ public class FlatSliderUI
@Override @Override
public void paintThumb( Graphics g ) { public void paintThumb( Graphics g ) {
FlatUIUtils.setColor( g, slider.isEnabled() g.setColor( FlatUIUtils.deriveColor( slider.isEnabled()
? (slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor)) ? (FlatUIUtils.isPermanentFocusOwner( slider ) ? focusColor : (hover ? hoverColor : thumbColor))
: disabledForeground, : disabledForeground,
thumbColor ); thumbColor ) );
if( isRoundThumb() ) if( isRoundThumb() )
g.fillOval( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height ); g.fillOval( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height );

View File

@@ -40,6 +40,7 @@ import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSpinnerUI; import javax.swing.plaf.basic.BasicSpinnerUI;
import com.formdev.flatlaf.FlatClientProperties;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}. * Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}.
@@ -56,9 +57,8 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
* *
* <!-- FlatSpinnerUI --> * <!-- FlatSpinnerUI -->
* *
* @uiDefault Component.focusWidth int
* @uiDefault Component.arc int
* @uiDefault Component.minimumWidth int * @uiDefault Component.minimumWidth int
* @uiDefault Spinner.buttonStyle String button (default) or none
* @uiDefault Component.arrowType String triangle (default) or chevron * @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color * @uiDefault Component.borderColor Color
@@ -78,9 +78,8 @@ public class FlatSpinnerUI
{ {
private Handler handler; private Handler handler;
protected int focusWidth;
protected int arc;
protected int minimumWidth; protected int minimumWidth;
protected String buttonStyle;
protected String arrowType; protected String arrowType;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color borderColor; protected Color borderColor;
@@ -103,9 +102,8 @@ public class FlatSpinnerUI
LookAndFeel.installProperty( spinner, "opaque", false ); LookAndFeel.installProperty( spinner, "opaque", false );
focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( "Component.arc" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
buttonStyle = UIManager.getString( "Spinner.buttonStyle" );
arrowType = UIManager.getString( "Component.arrowType" ); arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" ); borderColor = UIManager.getColor( "Component.borderColor" );
@@ -121,7 +119,7 @@ public class FlatSpinnerUI
// scale // scale
padding = scale( padding ); padding = scale( padding );
MigLayoutVisualPadding.install( spinner, focusWidth ); MigLayoutVisualPadding.install( spinner );
} }
@Override @Override
@@ -146,6 +144,7 @@ public class FlatSpinnerUI
super.installListeners(); super.installListeners();
addEditorFocusListener( spinner.getEditor() ); addEditorFocusListener( spinner.getEditor() );
spinner.addFocusListener( getHandler() );
spinner.addPropertyChangeListener( getHandler() ); spinner.addPropertyChangeListener( getHandler() );
} }
@@ -154,6 +153,7 @@ public class FlatSpinnerUI
super.uninstallListeners(); super.uninstallListeners();
removeEditorFocusListener( spinner.getEditor() ); removeEditorFocusListener( spinner.getEditor() );
spinner.removeFocusListener( getHandler() );
spinner.removePropertyChangeListener( getHandler() ); spinner.removePropertyChangeListener( getHandler() );
handler = null; handler = null;
@@ -206,17 +206,27 @@ public class FlatSpinnerUI
// use non-UIResource colors because when SwingUtilities.updateComponentTreeUI() // use non-UIResource colors because when SwingUtilities.updateComponentTreeUI()
// is used, then the text field is updated after the spinner and the // is used, then the text field is updated after the spinner and the
// colors are again replaced with default colors // colors are again replaced with default colors
textField.setForeground( FlatUIUtils.nonUIResource( spinner.getForeground() ) ); textField.setForeground( FlatUIUtils.nonUIResource( getForeground( true ) ) );
textField.setDisabledTextColor( FlatUIUtils.nonUIResource( disabledForeground ) ); textField.setDisabledTextColor( FlatUIUtils.nonUIResource( getForeground( false ) ) );
} }
} }
private JTextField getEditorTextField( JComponent editor ) { private static JTextField getEditorTextField( JComponent editor ) {
return editor instanceof JSpinner.DefaultEditor return editor instanceof JSpinner.DefaultEditor
? ((JSpinner.DefaultEditor)editor).getTextField() ? ((JSpinner.DefaultEditor)editor).getTextField()
: null; : null;
} }
protected Color getBackground( boolean enabled ) {
return enabled
? spinner.getBackground()
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( spinner ) : disabledBackground);
}
protected Color getForeground( boolean enabled ) {
return enabled ? spinner.getForeground() : disabledForeground;
}
@Override @Override
protected LayoutManager createLayout() { protected LayoutManager createLayout() {
return getHandler(); return getHandler();
@@ -246,8 +256,11 @@ public class FlatSpinnerUI
@Override @Override
public void update( Graphics g, JComponent c ) { public void update( Graphics g, JComponent c ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
float arc = FlatUIUtils.getBorderArc( c );
// fill background if opaque to avoid garbage if user sets opaque to true // fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && (focusWidth > 0 || arc != 0) ) if( c.isOpaque() && (focusWidth > 0 || arc > 0) )
FlatUIUtils.paintParentBackground( g, c ); FlatUIUtils.paintParentBackground( g, c );
Graphics2D g2 = (Graphics2D) g; Graphics2D g2 = (Graphics2D) g;
@@ -255,20 +268,21 @@ public class FlatSpinnerUI
int width = c.getWidth(); int width = c.getWidth();
int height = c.getHeight(); int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
Component nextButton = getHandler().nextButton;
int arrowX = nextButton.getX();
int arrowWidth = nextButton.getWidth();
boolean enabled = spinner.isEnabled(); boolean enabled = spinner.isEnabled();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
// paint background // paint background
g2.setColor( enabled g2.setColor( getBackground( enabled ) );
? c.getBackground()
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint button background and separator
boolean paintButton = !"none".equals( buttonStyle );
Handler handler = getHandler();
if( paintButton && (handler.nextButton != null || handler.previousButton != null) ) {
Component button = (handler.nextButton != null) ? handler.nextButton : handler.previousButton;
int arrowX = button.getX();
int arrowWidth = button.getWidth();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
// paint arrow buttons background // paint arrow buttons background
if( enabled ) { if( enabled ) {
g2.setColor( buttonBackground ); g2.setColor( buttonBackground );
@@ -286,6 +300,7 @@ public class FlatSpinnerUI
float lw = scale( 1f ); float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) ); g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
}
paint( g, c ); paint( g, c );
} }
@@ -328,8 +343,9 @@ public class FlatSpinnerUI
// the arrows width is the same as the inner height so that the arrows area is square // the arrows width is the same as the inner height so that the arrows area is square
int minimumWidth = FlatUIUtils.minimumWidth( spinner, FlatSpinnerUI.this.minimumWidth ); int minimumWidth = FlatUIUtils.minimumWidth( spinner, FlatSpinnerUI.this.minimumWidth );
int innerHeight = editorSize.height + padding.top + padding.bottom; int innerHeight = editorSize.height + padding.top + padding.bottom;
float focusWidth = FlatUIUtils.getBorderFocusWidth( spinner );
return new Dimension( return new Dimension(
Math.max( insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, scale( minimumWidth + (focusWidth * 2) ) ), Math.max( insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, scale( minimumWidth ) + Math.round( focusWidth * 2 ) ),
insets.top + insets.bottom + innerHeight ); insets.top + insets.bottom + innerHeight );
} }
@@ -346,7 +362,7 @@ public class FlatSpinnerUI
if( nextButton == null && previousButton == null ) { if( nextButton == null && previousButton == null ) {
if( editor != null ) if( editor != null )
editor.setBounds( r ); editor.setBounds( FlatUIUtils.subtractInsets( r, padding ) );
return; return;
} }
@@ -368,12 +384,14 @@ public class FlatSpinnerUI
if( editor != null ) if( editor != null )
editor.setBounds( FlatUIUtils.subtractInsets( editorRect, padding ) ); editor.setBounds( FlatUIUtils.subtractInsets( editorRect, padding ) );
int nextHeight = Math.round( buttonsRect.height / 2f ); int nextHeight = (buttonsRect.height / 2) + (buttonsRect.height % 2); // round up
if( nextButton != null ) if( nextButton != null )
nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight ); nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight );
if( previousButton != null ) { if( previousButton != null ) {
previousButton.setBounds( buttonsRect.x, buttonsRect.y + nextHeight, // for precise layout of arrows, the "previous" button has the same height
buttonsRect.width, buttonsRect.height - nextHeight ); // as the "next" button and may overlap on uneven buttonsRect.height
int previousY = buttonsRect.y + buttonsRect.height - nextHeight;
previousButton.setBounds( buttonsRect.x, previousY, buttonsRect.width, nextHeight );
} }
} }
@@ -382,6 +400,13 @@ public class FlatSpinnerUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
spinner.repaint(); spinner.repaint();
// if spinner gained focus, transfer it to the editor text field
if( e.getComponent() == spinner ) {
JTextField textField = getEditorTextField( spinner.getEditor() );
if( textField != null )
textField.requestFocusInWindow();
}
} }
@Override @Override
@@ -398,6 +423,14 @@ public class FlatSpinnerUI
case "enabled": case "enabled":
updateEditorColors(); updateEditorColors();
break; break;
case FlatClientProperties.COMPONENT_ROUND_RECT:
spinner.repaint();
break;
case FlatClientProperties.MINIMUM_WIDTH:
spinner.revalidate();
break;
} }
} }
} }

View File

@@ -17,11 +17,16 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JSplitPane; import javax.swing.JSplitPane;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneDivider;
@@ -54,8 +59,8 @@ public class FlatSplitPaneUI
{ {
protected String arrowType; protected String arrowType;
private Boolean continuousLayout; private Boolean continuousLayout;
private Color oneTouchArrowColor; protected Color oneTouchArrowColor;
private Color oneTouchHoverArrowColor; protected Color oneTouchHoverArrowColor;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatSplitPaneUI(); return new FlatSplitPaneUI();
@@ -87,11 +92,13 @@ public class FlatSplitPaneUI
//---- class FlatSplitPaneDivider ----------------------------------------- //---- class FlatSplitPaneDivider -----------------------------------------
private class FlatSplitPaneDivider protected class FlatSplitPaneDivider
extends BasicSplitPaneDivider extends BasicSplitPaneDivider
{ {
public FlatSplitPaneDivider( BasicSplitPaneUI ui ) { protected FlatSplitPaneDivider( BasicSplitPaneUI ui ) {
super( ui ); super( ui );
setLayout( new FlatDividerLayout() );
} }
@Override @Override
@@ -109,16 +116,45 @@ public class FlatSplitPaneUI
return new FlatOneTouchButton( false ); return new FlatOneTouchButton( false );
} }
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
switch( e.getPropertyName() ) {
case JSplitPane.DIVIDER_LOCATION_PROPERTY:
// necessary to show/hide one-touch buttons on expand/collapse
revalidate();
break;
}
}
protected boolean isLeftCollapsed() {
int location = splitPane.getDividerLocation();
Insets insets = splitPane.getInsets();
return (orientation == JSplitPane.VERTICAL_SPLIT)
? location == insets.top
: location == insets.left;
}
protected boolean isRightCollapsed() {
int location = splitPane.getDividerLocation();
Insets insets = splitPane.getInsets();
return (orientation == JSplitPane.VERTICAL_SPLIT)
? location == (splitPane.getHeight() - getHeight() - insets.bottom)
: location == (splitPane.getWidth() - getWidth() - insets.right);
}
//---- class FlatOneTouchButton --------------------------------------- //---- class FlatOneTouchButton ---------------------------------------
private class FlatOneTouchButton protected class FlatOneTouchButton
extends FlatArrowButton extends FlatArrowButton
{ {
private final boolean left; protected final boolean left;
public FlatOneTouchButton( boolean left ) { protected FlatOneTouchButton( boolean left ) {
super( SwingConstants.NORTH, arrowType, oneTouchArrowColor, null, oneTouchHoverArrowColor, null ); super( SwingConstants.NORTH, arrowType, oneTouchArrowColor, null, oneTouchHoverArrowColor, null );
setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) ); setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
ToolTipManager.sharedInstance().registerComponent( this );
this.left = left; this.left = left;
} }
@@ -129,7 +165,67 @@ public class FlatSplitPaneUI
? (left ? SwingConstants.NORTH : SwingConstants.SOUTH) ? (left ? SwingConstants.NORTH : SwingConstants.SOUTH)
: (left ? SwingConstants.WEST : SwingConstants.EAST); : (left ? SwingConstants.WEST : SwingConstants.EAST);
} }
@Override
public String getToolTipText( MouseEvent e ) {
String key = (orientation == JSplitPane.VERTICAL_SPLIT)
? (left
? (isRightCollapsed()
? "SplitPaneDivider.expandBottomToolTipText"
: "SplitPaneDivider.collapseTopToolTipText")
: (isLeftCollapsed()
? "SplitPaneDivider.expandTopToolTipText"
: "SplitPaneDivider.collapseBottomToolTipText"))
: (left
? (isRightCollapsed()
? "SplitPaneDivider.expandRightToolTipText"
: "SplitPaneDivider.collapseLeftToolTipText")
: (isLeftCollapsed()
? "SplitPaneDivider.expandLeftToolTipText"
: "SplitPaneDivider.collapseRightToolTipText"));
// get text from client property
Object value = splitPane.getClientProperty( key );
if( value instanceof String )
return (String) value;
// get text from bundle
return UIManager.getString( key, getLocale() );
} }
} }
//---- class FlatDividerLayout ----------------------------------------
protected class FlatDividerLayout
extends DividerLayout
{
@Override
public void layoutContainer( Container c ) {
super.layoutContainer( c );
if( leftButton == null || rightButton == null || !splitPane.isOneTouchExpandable() )
return;
// increase side 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 ) {
leftButton.setSize( leftButton.getWidth() + extraSize, leftButton.getHeight() );
rightButton.setBounds( leftButton.getX() + leftButton.getWidth(), rightButton.getY(),
rightButton.getWidth() + extraSize, rightButton.getHeight() );
} else {
leftButton.setSize( leftButton.getWidth(), leftButton.getHeight() + extraSize );
rightButton.setBounds( rightButton.getX(), leftButton.getY() + leftButton.getHeight(),
rightButton.getWidth(), rightButton.getHeight() + extraSize );
}
// hide buttons if not applicable
boolean leftCollapsed = isLeftCollapsed();
if( leftCollapsed )
rightButton.setLocation( leftButton.getLocation() );
leftButton.setVisible( !leftCollapsed );
rightButton.setVisible( !isRightCollapsed() );
}
}
}
} }

View File

@@ -86,7 +86,7 @@ public class FlatTableCellBorder
/** /**
* Checks whether at least one selected cell is editable. * Checks whether at least one selected cell is editable.
*/ */
private boolean isSelectionEditable( JTable table ) { protected boolean isSelectionEditable( JTable table ) {
if( table.getRowSelectionAllowed() ) { if( table.getRowSelectionAllowed() ) {
int columnCount = table.getColumnCount(); int columnCount = table.getColumnCount();
int[] selectedRows = table.getSelectedRows(); int[] selectedRows = table.getSelectedRows();

View File

@@ -86,26 +86,12 @@ public class FlatTableHeaderUI
case "top": sortIconPosition = SwingConstants.TOP; break; case "top": sortIconPosition = SwingConstants.TOP; break;
case "bottom": sortIconPosition = SwingConstants.BOTTOM; break; case "bottom": sortIconPosition = SwingConstants.BOTTOM; break;
} }
// use own renderer if necessary
if( sortIconPosition != SwingConstants.RIGHT ) {
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
if( defaultRenderer instanceof UIResource )
header.setDefaultRenderer( new FlatTableCellHeaderRenderer( defaultRenderer ) );
}
} }
@Override @Override
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
// restore default renderer
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
if( defaultRenderer instanceof FlatTableCellHeaderRenderer ) {
((FlatTableCellHeaderRenderer)defaultRenderer).reset();
header.setDefaultRenderer( ((FlatTableCellHeaderRenderer)defaultRenderer).delegate );
}
separatorColor = null; separatorColor = null;
bottomSeparatorColor = null; bottomSeparatorColor = null;
} }
@@ -125,8 +111,22 @@ public class FlatTableHeaderUI
if( paintBorders ) if( paintBorders )
paintColumnBorders( g, c ); paintColumnBorders( g, c );
// temporary use own default renderer if necessary
FlatTableCellHeaderRenderer sortIconRenderer = null;
if( sortIconPosition != SwingConstants.RIGHT ) {
sortIconRenderer = new FlatTableCellHeaderRenderer( header.getDefaultRenderer() );
header.setDefaultRenderer( sortIconRenderer );
}
// paint header
super.paint( g, c ); super.paint( g, c );
// restore default renderer
if( sortIconRenderer != null ) {
sortIconRenderer.reset();
header.setDefaultRenderer( sortIconRenderer.delegate );
}
if( paintBorders ) if( paintBorders )
paintDraggedColumnBorders( g, c ); paintDraggedColumnBorders( g, c );
} }
@@ -158,7 +158,7 @@ public class FlatTableHeaderUI
g2.setColor( separatorColor ); g2.setColor( separatorColor );
int sepCount = columnCount; int sepCount = columnCount;
if( header.getTable().getAutoResizeMode() != JTable.AUTO_RESIZE_OFF && !isVerticalScrollBarVisible() ) if( header.getTable() != null && header.getTable().getAutoResizeMode() != JTable.AUTO_RESIZE_OFF && !isVerticalScrollBarVisible() )
sepCount--; sepCount--;
if( header.getComponentOrientation().isLeftToRight() ) { if( header.getComponentOrientation().isLeftToRight() ) {
@@ -257,6 +257,7 @@ public class FlatTableHeaderUI
{ {
private final TableCellRenderer delegate; private final TableCellRenderer delegate;
private JLabel l;
private int oldHorizontalTextPosition = -1; private int oldHorizontalTextPosition = -1;
private Border origBorder; private Border origBorder;
private Icon sortIcon; private Icon sortIcon;
@@ -273,7 +274,7 @@ public class FlatTableHeaderUI
if( !(c instanceof JLabel) ) if( !(c instanceof JLabel) )
return c; return c;
JLabel l = (JLabel) c; l = (JLabel) c;
if( sortIconPosition == SwingConstants.LEFT ) { if( sortIconPosition == SwingConstants.LEFT ) {
if( oldHorizontalTextPosition < 0 ) if( oldHorizontalTextPosition < 0 )
@@ -291,11 +292,8 @@ public class FlatTableHeaderUI
} }
void reset() { void reset() {
if( sortIconPosition == SwingConstants.LEFT && oldHorizontalTextPosition >= 0 ) { if( l != null && sortIconPosition == SwingConstants.LEFT && oldHorizontalTextPosition >= 0 )
Component c = getTableCellRendererComponent( header.getTable(), "", false, false, -1, 0 ); l.setHorizontalTextPosition( oldHorizontalTextPosition );
if( c instanceof JLabel && ((JLabel)c).getHorizontalTextPosition() == SwingConstants.RIGHT )
((JLabel)c).setHorizontalTextPosition( oldHorizontalTextPosition );
}
} }
@Override @Override

View File

@@ -18,13 +18,16 @@ package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import javax.swing.JCheckBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTableUI; import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.TableCellRenderer;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -68,6 +71,10 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.cellFocusColor Color * @uiDefault Table.cellFocusColor Color
* @uiDefault Table.showCellFocusIndicator boolean * @uiDefault Table.showCellFocusIndicator boolean
* *
* <!-- FlatInputMaps -->
*
* @uiDefault Table.consistentHomeEndKeyBehavior boolean
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatTableUI public class FlatTableUI
@@ -90,16 +97,6 @@ public class FlatTableUI
return new FlatTableUI(); return new FlatTableUI();
} }
@Override
public void installUI( JComponent c ) {
super.installUI( c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
@@ -113,7 +110,7 @@ public class FlatTableUI
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
toggleSelectionColors( table.hasFocus() ); toggleSelectionColors();
int rowHeight = FlatUIUtils.getUIInt( "Table.rowHeight", 16 ); int rowHeight = FlatUIUtils.getUIInt( "Table.rowHeight", 16 );
if( rowHeight > 0 ) if( rowHeight > 0 )
@@ -132,6 +129,12 @@ public class FlatTableUI
oldIntercellSpacing = table.getIntercellSpacing(); oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing ); table.setIntercellSpacing( intercellSpacing );
} }
// checkbox is non-opaque in FlatLaf and therefore would not paint selection
// --> make checkbox renderer opaque (but opaque in Metal or Windows LaF)
TableCellRenderer booleanRenderer = table.getDefaultRenderer( Boolean.class );
if( booleanRenderer instanceof JCheckBox )
((JCheckBox)booleanRenderer).setOpaque( true );
} }
@Override @Override
@@ -160,13 +163,17 @@ public class FlatTableUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
toggleSelectionColors( true ); toggleSelectionColors();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
toggleSelectionColors( false );
// use invokeLater for the case that the window is deactivated
EventQueue.invokeLater( () -> {
toggleSelectionColors();
} );
} }
}; };
} }
@@ -180,8 +187,11 @@ public class FlatTableUI
* already used in applications. Then either the inactive colors are not used, * already used in applications. Then either the inactive colors are not used,
* or the application has to be changed to extend a FlatLaf renderer. * or the application has to be changed to extend a FlatLaf renderer.
*/ */
private void toggleSelectionColors( boolean focused ) { private void toggleSelectionColors() {
if( focused ) { if( table == null )
return;
if( FlatUIUtils.isPermanentFocusOwner( table ) ) {
if( table.getSelectionBackground() == selectionInactiveBackground ) if( table.getSelectionBackground() == selectionInactiveBackground )
table.setSelectionBackground( selectionBackground ); table.setSelectionBackground( selectionBackground );
if( table.getSelectionForeground() == selectionInactiveForeground ) if( table.getSelectionForeground() == selectionInactiveForeground )

View File

@@ -16,10 +16,11 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JTextArea; import javax.swing.JTextArea;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -27,6 +28,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextAreaUI; import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.HiDPIUtils;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextArea}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTextArea}.
@@ -58,6 +60,7 @@ public class FlatTextAreaUI
{ {
protected int minimumWidth; protected int minimumWidth;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color background;
protected Color disabledBackground; protected Color disabledBackground;
protected Color inactiveBackground; protected Color inactiveBackground;
@@ -65,12 +68,20 @@ public class FlatTextAreaUI
return new FlatTextAreaUI(); return new FlatTextAreaUI();
} }
@Override
public void installUI( JComponent c ) {
super.installUI( c );
updateBackground();
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( "TextArea.background" );
disabledBackground = UIManager.getColor( "TextArea.disabledBackground" ); disabledBackground = UIManager.getColor( "TextArea.disabledBackground" );
inactiveBackground = UIManager.getColor( "TextArea.inactiveBackground" ); inactiveBackground = UIManager.getColor( "TextArea.inactiveBackground" );
} }
@@ -79,46 +90,80 @@ public class FlatTextAreaUI
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
background = null;
disabledBackground = null; disabledBackground = null;
inactiveBackground = null; inactiveBackground = null;
} }
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e );
switch( e.getPropertyName() ) {
case "editable":
case "enabled":
updateBackground();
break;
}
}
private void updateBackground() {
JTextComponent c = getComponent();
Color background = c.getBackground();
if( !(background instanceof UIResource) )
return;
// do not update background if it currently has a unknown color (assigned from outside)
if( background != this.background &&
background != disabledBackground &&
background != inactiveBackground )
return;
Color newBackground = !c.isEnabled()
? disabledBackground
: (!c.isEditable()
? inactiveBackground
: this.background);
if( newBackground != background )
c.setBackground( newBackground );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( c, super.getPreferredSize( c ) );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( c, super.getMinimumSize( c ) );
}
private Dimension applyMinimumWidth( JComponent c, Dimension size ) {
// do not apply minimum width if JTextArea.columns is set
if( c instanceof JTextArea && ((JTextArea)c).getColumns() > 0 )
return size;
return FlatEditorPaneUI.applyMinimumWidth( c, size, minimumWidth );
}
@Override
protected void paintSafely( Graphics g ) {
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
}
@Override @Override
protected void paintBackground( Graphics g ) { protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent(); JTextComponent c = getComponent();
Color background = c.getBackground(); // for compatibility with IntelliJ themes
g.setColor( !(background instanceof UIResource) if( isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) && (c.getBackground() instanceof UIResource) ) {
? background FlatUIUtils.paintParentBackground( g, c );
: (isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) return;
? FlatUIUtils.getParentBackground( c )
: (!c.isEnabled()
? disabledBackground
: (!c.isEditable() ? inactiveBackground : background))) );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
} }
@Override super.paintBackground( g );
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ), c );
}
private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
// do not apply minimum width if JTextArea.columns is set
if( c instanceof JTextArea && ((JTextArea)c).getColumns() > 0 )
return size;
// Assume that text area is in a scroll pane (that displays the border)
// and subtract 1px border line width.
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size;
} }
} }

View File

@@ -16,14 +16,13 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component; import java.awt.Component;
import javax.swing.UIManager; import javax.swing.UIManager;
/** /**
* Border for various text components (e.g. {@link javax.swing.JTextField}). * Border for various text components (e.g. {@link javax.swing.JTextField}).
* *
* @uiDefault Component.arc int * @uiDefault TextComponent.arc int
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -33,7 +32,11 @@ public class FlatTextBorder
protected final int arc = UIManager.getInt( "TextComponent.arc" ); protected final int arc = UIManager.getInt( "TextComponent.arc" );
@Override @Override
protected float getArc( Component c ) { protected int getArc( Component c ) {
return scale( (float) arc ); if( isCellEditor( c ) )
return 0;
Boolean roundRect = FlatUIUtils.isRoundRect( c );
return roundRect != null ? (roundRect ? Short.MAX_VALUE : 0) : arc;
} }
} }

View File

@@ -32,13 +32,13 @@ import javax.swing.JSpinner;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.Caret; import javax.swing.text.Caret;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.HiDPIUtils;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}.
@@ -60,8 +60,6 @@ import com.formdev.flatlaf.FlatClientProperties;
* *
* <!-- FlatTextFieldUI --> * <!-- FlatTextFieldUI -->
* *
* @uiDefault TextComponent.arc int
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int * @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextField.placeholderForeground Color * @uiDefault TextField.placeholderForeground Color
@@ -72,8 +70,6 @@ import com.formdev.flatlaf.FlatClientProperties;
public class FlatTextFieldUI public class FlatTextFieldUI
extends BasicTextFieldUI extends BasicTextFieldUI
{ {
protected int arc;
protected int focusWidth;
protected int minimumWidth; protected int minimumWidth;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color placeholderForeground; protected Color placeholderForeground;
@@ -89,15 +85,13 @@ public class FlatTextFieldUI
super.installDefaults(); super.installDefaults();
String prefix = getPropertyPrefix(); String prefix = getPropertyPrefix();
arc = UIManager.getInt( "TextComponent.arc" );
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" ); placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 ); LookAndFeel.installProperty( getComponent(), "opaque", false );
MigLayoutVisualPadding.install( getComponent(), focusWidth ); MigLayoutVisualPadding.install( getComponent() );
} }
@Override @Override
@@ -133,16 +127,28 @@ public class FlatTextFieldUI
@Override @Override
protected void propertyChange( PropertyChangeEvent e ) { protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e ); super.propertyChange( e );
propertyChange( getComponent(), e );
}
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) ) static void propertyChange( JTextComponent c, PropertyChangeEvent e ) {
getComponent().repaint(); switch( e.getPropertyName() ) {
case FlatClientProperties.PLACEHOLDER_TEXT:
case FlatClientProperties.COMPONENT_ROUND_RECT:
c.repaint();
break;
case FlatClientProperties.MINIMUM_WIDTH:
c.revalidate();
break;
}
} }
@Override @Override
protected void paintSafely( Graphics g ) { protected void paintSafely( Graphics g ) {
paintBackground( g, getComponent(), focusWidth, arc, isIntelliJTheme ); paintBackground( g, getComponent(), isIntelliJTheme );
paintPlaceholder( g, getComponent(), placeholderForeground ); paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g );
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
} }
@Override @Override
@@ -150,19 +156,20 @@ public class FlatTextFieldUI
// background is painted elsewhere // background is painted elsewhere
} }
static void paintBackground( Graphics g, JTextComponent c, int focusWidth, int arc, boolean isIntelliJTheme ) { static void paintBackground( Graphics g, JTextComponent c, boolean isIntelliJTheme ) {
Border border = c.getBorder();
// do not paint background if: // do not paint background if:
// - not opaque and // - not opaque and
// - border is not a flat border and // - border is not a flat border and
// - opaque was explicitly set (to false) // - opaque was explicitly set (to false)
// (same behaviour as in AquaTextFieldUI) // (same behaviour as in AquaTextFieldUI)
if( !c.isOpaque() && !(border instanceof FlatBorder) && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) ) if( !c.isOpaque() && FlatUIUtils.getOutsideFlatBorder( c ) == null && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) )
return; return;
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
float arc = FlatUIUtils.getBorderArc( c );
// fill background if opaque to avoid garbage if user sets opaque to true // fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && focusWidth > 0 ) if( c.isOpaque() && (focusWidth > 0 || arc > 0) )
FlatUIUtils.paintParentBackground( g, c ); FlatUIUtils.paintParentBackground( g, c );
// paint background // paint background
@@ -170,16 +177,13 @@ public class FlatTextFieldUI
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
float fFocusWidth = (border instanceof FlatBorder) ? scale( (float) focusWidth ) : 0;
float fArc = (border instanceof FlatTextBorder) ? scale( (float) arc ) : 0;
Color background = c.getBackground(); Color background = c.getBackground();
g2.setColor( !(background instanceof UIResource) g2.setColor( !(background instanceof UIResource)
? background ? background
: (isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) : (isIntelliJTheme && (!c.isEnabled() || !c.isEditable())
? FlatUIUtils.getParentBackground( c ) ? FlatUIUtils.getParentBackground( c )
: background) ); : background) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, c.getWidth(), c.getHeight(), fFocusWidth, fArc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
@@ -212,28 +216,29 @@ public class FlatTextFieldUI
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c ); return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
} }
@Override @Override
public Dimension getMinimumSize( JComponent c ) { public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ), c ); return applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
} }
private Dimension applyMinimumWidth( Dimension size, JComponent c ) { static Dimension applyMinimumWidth( JComponent c, Dimension size, int minimumWidth ) {
// do not apply minimum width if JTextField.columns is set // do not apply minimum width if JTextField.columns is set
if( c instanceof JTextField && ((JTextField)c).getColumns() > 0 ) if( c instanceof JTextField && ((JTextField)c).getColumns() > 0 )
return size; return size;
// do not apply minimum width if used in combobox or spinner
Container parent = c.getParent(); Container parent = c.getParent();
if( parent instanceof JComboBox || if( parent instanceof JComboBox ||
parent instanceof JSpinner || parent instanceof JSpinner ||
(parent != null && parent.getParent() instanceof JSpinner) ) (parent != null && parent.getParent() instanceof JSpinner) )
return size; return size;
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth ); minimumWidth = FlatUIUtils.minimumWidth( c, minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0; float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) ); size.width = Math.max( size.width, scale( minimumWidth ) + Math.round( focusWidth * 2 ) );
return size; return size;
} }
} }

View File

@@ -16,9 +16,10 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JEditorPane; import javax.swing.JEditorPane;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -26,6 +27,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextPaneUI; import javax.swing.plaf.basic.BasicTextPaneUI;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.HiDPIUtils;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTextPane}.
@@ -83,24 +85,25 @@ public class FlatTextPaneUI
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties ); getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
} }
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e );
}
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ) ); return FlatEditorPaneUI.applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
} }
@Override @Override
public Dimension getMinimumSize( JComponent c ) { public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ) ); return FlatEditorPaneUI.applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
} }
private Dimension applyMinimumWidth( Dimension size ) { @Override
// Assume that text area is in a scroll pane (that displays the border) protected void paintSafely( Graphics g ) {
// and subtract 1px border line width. super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size;
} }
@Override @Override

View File

@@ -0,0 +1,867 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
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;
import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
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.background Color
* @uiDefault TitlePane.inactiveBackground Color
* @uiDefault TitlePane.foreground Color
* @uiDefault TitlePane.inactiveForeground Color
* @uiDefault TitlePane.embeddedForeground Color
* @uiDefault TitlePane.borderColor Color optional
* @uiDefault TitlePane.iconSize Dimension
* @uiDefault TitlePane.iconMargins Insets
* @uiDefault TitlePane.titleMargins Insets
* @uiDefault TitlePane.menuBarMargins Insets
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.buttonMaximizedHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
* @uiDefault TitlePane.restoreIcon Icon
*
* @author Karl Tauber
*/
public class FlatTitlePane
extends JComponent
{
protected final Color activeBackground = UIManager.getColor( "TitlePane.background" );
protected final Color inactiveBackground = UIManager.getColor( "TitlePane.inactiveBackground" );
protected final Color activeForeground = UIManager.getColor( "TitlePane.foreground" );
protected final Color inactiveForeground = UIManager.getColor( "TitlePane.inactiveForeground" );
protected final Color embeddedForeground = UIManager.getColor( "TitlePane.embeddedForeground" );
protected final Color borderColor = UIManager.getColor( "TitlePane.borderColor" );
protected final Insets menuBarMargins = UIManager.getInsets( "TitlePane.menuBarMargins" );
protected final Dimension iconSize = UIManager.getDimension( "TitlePane.iconSize" );
protected final int buttonMaximizedHeight = UIManager.getInt( "TitlePane.buttonMaximizedHeight" );
protected final JRootPane rootPane;
protected JPanel leftPanel;
protected JLabel iconLabel;
protected JComponent menuBarPlaceholder;
protected JLabel titleLabel;
protected JPanel buttonPanel;
protected JButton iconifyButton;
protected JButton maximizeButton;
protected JButton restoreButton;
protected JButton closeButton;
protected Window window;
private final Handler handler;
public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane;
handler = createHandler();
setBorder( createTitlePaneBorder() );
addSubComponents();
activeChanged( true );
addMouseListener( handler );
addMouseMotionListener( handler );
// necessary for closing window with double-click on icon
iconLabel.addMouseListener( handler );
}
protected FlatTitlePaneBorder createTitlePaneBorder() {
return new FlatTitlePaneBorder();
}
protected Handler createHandler() {
return new Handler();
}
protected void addSubComponents() {
leftPanel = new JPanel();
iconLabel = new JLabel();
titleLabel = new JLabel();
iconLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.iconMargins" ) ) );
titleLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.titleMargins" ) ) );
leftPanel.setLayout( new BoxLayout( leftPanel, BoxLayout.LINE_AXIS ) );
leftPanel.setOpaque( false );
leftPanel.add( iconLabel );
menuBarPlaceholder = new JComponent() {
@Override
public Dimension getPreferredSize() {
JMenuBar menuBar = rootPane.getJMenuBar();
return (menuBar != null && menuBar.isVisible() && isMenuBarEmbedded())
? FlatUIUtils.addInsets( menuBar.getPreferredSize(), UIScale.scale( menuBarMargins ) )
: new Dimension();
}
};
leftPanel.add( menuBarPlaceholder );
createButtons();
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
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() );
}
}
} );
add( leftPanel, BorderLayout.LINE_START );
add( titleLabel, BorderLayout.CENTER );
add( buttonPanel, BorderLayout.LINE_END );
}
protected void createButtons() {
iconifyButton = createButton( "TitlePane.iconifyIcon", "Iconify", e -> iconify() );
maximizeButton = createButton( "TitlePane.maximizeIcon", "Maximize", e -> maximize() );
restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
buttonPanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if( buttonMaximizedHeight > 0 &&
window instanceof Frame &&
(((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
{
// make title pane height smaller when frame is maximized
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight ) ) );
}
return size;
}
};
buttonPanel.setOpaque( false );
buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
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
// later in frameStateChanged(), which is invoked from addNotify()
restoreButton.setVisible( false );
buttonPanel.add( iconifyButton );
buttonPanel.add( maximizeButton );
buttonPanel.add( restoreButton );
}
buttonPanel.add( closeButton );
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
JButton button = new JButton( UIManager.getIcon( iconKey ) );
button.setFocusable( false );
button.setContentAreaFilled( false );
button.setBorder( BorderFactory.createEmptyBorder() );
button.putClientProperty( AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName );
button.addActionListener( action );
return button;
}
protected void activeChanged( boolean active ) {
boolean hasEmbeddedMenuBar = rootPane.getJMenuBar() != null && rootPane.getJMenuBar().isVisible() && isMenuBarEmbedded();
Color background = FlatUIUtils.nonUIResource( active ? activeBackground : inactiveBackground );
Color foreground = FlatUIUtils.nonUIResource( active ? activeForeground : inactiveForeground );
Color titleForeground = (hasEmbeddedMenuBar && active) ? FlatUIUtils.nonUIResource( embeddedForeground ) : foreground;
setBackground( background );
titleLabel.setForeground( titleForeground );
iconifyButton.setForeground( foreground );
maximizeButton.setForeground( foreground );
restoreButton.setForeground( foreground );
closeButton.setForeground( foreground );
titleLabel.setHorizontalAlignment( hasEmbeddedMenuBar ? SwingConstants.CENTER : SwingConstants.LEADING );
// this is necessary because hover/pressed colors are derived from background color
iconifyButton.setBackground( background );
maximizeButton.setBackground( background );
restoreButton.setBackground( background );
closeButton.setBackground( background );
}
protected void frameStateChanged() {
if( window == null || rootPane.getWindowDecorationStyle() != JRootPane.FRAME )
return;
if( window instanceof Frame ) {
Frame frame = (Frame) window;
boolean resizable = frame.isResizable();
boolean maximized = ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0);
iconifyButton.setVisible( true );
maximizeButton.setVisible( resizable && !maximized );
restoreButton.setVisible( resizable && maximized );
if( maximized &&
rootPane.getClientProperty( "_flatlaf.maximizedBoundsUpToDate" ) == null )
{
rootPane.putClientProperty( "_flatlaf.maximizedBoundsUpToDate", null );
// In case that frame was maximized from custom code (e.g. when restoring
// window state on application startup), then maximized bounds is not set
// and the window would overlap Windows task bar.
// To avoid this, update maximized bounds here and if it has changed
// re-maximize windows so that maximized bounds are used.
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
updateMaximizedBounds();
Rectangle newMaximizedBounds = frame.getMaximizedBounds();
if( newMaximizedBounds != null && !newMaximizedBounds.equals( oldMaximizedBounds ) ) {
int oldExtendedState = frame.getExtendedState();
frame.setExtendedState( oldExtendedState & ~Frame.MAXIMIZED_BOTH );
frame.setExtendedState( oldExtendedState );
}
}
} else {
// hide buttons because they are only supported in frames
iconifyButton.setVisible( false );
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
revalidate();
repaint();
}
}
protected void updateIcon() {
// get window images
List<Image> images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
images = owner.getIconImages();
if( !images.isEmpty() )
break;
}
}
boolean hasIcon = true;
// set icon
if( !images.isEmpty() )
iconLabel.setIcon( FlatTitlePaneIcon.create( images, iconSize ) );
else {
// no icon set on window --> use default icon
Icon defaultIcon = UIManager.getIcon( "InternalFrame.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;
}
// show/hide icon
iconLabel.setVisible( hasIcon );
updateJBRHitTestSpotsAndTitleBarHeightLater();
}
@Override
public void addNotify() {
super.addNotify();
uninstallWindowListeners();
window = SwingUtilities.getWindowAncestor( this );
if( window != null ) {
frameStateChanged();
activeChanged( window.isActive() );
updateIcon();
titleLabel.setText( getWindowTitle() );
installWindowListeners();
}
updateJBRHitTestSpotsAndTitleBarHeightLater();
}
@Override
public void removeNotify() {
super.removeNotify();
uninstallWindowListeners();
window = null;
}
protected String getWindowTitle() {
if( window instanceof Frame )
return ((Frame)window).getTitle();
if( window instanceof Dialog )
return ((Dialog)window).getTitle();
return null;
}
protected void installWindowListeners() {
if( window == null )
return;
window.addPropertyChangeListener( handler );
window.addWindowListener( handler );
window.addWindowStateListener( handler );
window.addComponentListener( handler );
}
protected void uninstallWindowListeners() {
if( window == null )
return;
window.removePropertyChangeListener( handler );
window.removeWindowListener( handler );
window.removeWindowStateListener( handler );
window.removeComponentListener( handler );
}
protected boolean isMenuBarEmbedded() {
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return UIManager.getBoolean( "TitlePane.menuBarEmbedded" ) &&
FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.MENU_BAR_EMBEDDED, true ) &&
FlatSystemProperties.getBoolean( FlatSystemProperties.MENUBAR_EMBEDDED, true );
}
protected Rectangle getMenuBarBounds() {
Insets insets = rootPane.getInsets();
Rectangle bounds = new Rectangle(
SwingUtilities.convertPoint( menuBarPlaceholder, -insets.left, -insets.top, rootPane ),
menuBarPlaceholder.getSize() );
// add menu bar bottom border insets to bounds so that menu bar overlaps
// title pane border (menu bar border is painted over title pane border)
Insets borderInsets = getBorder().getBorderInsets( this );
bounds.height += borderInsets.bottom;
return FlatUIUtils.subtractInsets( bounds, UIScale.scale( getMenuBarMargins() ) );
}
protected Insets getMenuBarMargins() {
return getComponentOrientation().isLeftToRight()
? menuBarMargins
: new Insets( menuBarMargins.top, menuBarMargins.right, menuBarMargins.bottom, menuBarMargins.left );
}
protected void menuBarChanged() {
menuBarPlaceholder.invalidate();
// necessary for the case that an embedded menu bar is made invisible
// and a border color is specified
repaint();
// update title foreground color
EventQueue.invokeLater( () -> {
activeChanged( window == null || window.isActive() );
} );
}
protected void menuBarLayouted() {
updateJBRHitTestSpotsAndTitleBarHeightLater();
}
/*debug
@Override
public void paint( Graphics g ) {
super.paint( g );
if( debugTitleBarHeight > 0 ) {
g.setColor( Color.green );
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
}
if( debugHitTestSpots != null ) {
g.setColor( Color.blue );
for( Rectangle r : debugHitTestSpots )
g.drawRect( r.x, r.y, r.width, r.height );
}
}
debug*/
@Override
protected void paintComponent( Graphics g ) {
g.setColor( getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
protected void repaintWindowBorder() {
int width = rootPane.getWidth();
int height = rootPane.getHeight();
Insets insets = rootPane.getInsets();
rootPane.repaint( 0, 0, width, insets.top ); // top
rootPane.repaint( 0, 0, insets.left, height ); // left
rootPane.repaint( 0, height - insets.bottom, width, insets.bottom ); // bottom
rootPane.repaint( width - insets.right, 0, insets.right, height ); // right
}
/**
* Iconifies the window.
*/
protected void iconify() {
if( window instanceof Frame ) {
Frame frame = (Frame) window;
frame.setExtendedState( frame.getExtendedState() | Frame.ICONIFIED );
}
}
/**
* Maximizes the window.
*/
protected void maximize() {
if( !(window instanceof Frame) )
return;
Frame frame = (Frame) window;
updateMaximizedBounds();
// let our WindowStateListener know that the maximized bounds are up-to-date
rootPane.putClientProperty( "_flatlaf.maximizedBoundsUpToDate", true );
// maximize window
frame.setExtendedState( frame.getExtendedState() | Frame.MAXIMIZED_BOTH );
}
protected void updateMaximizedBounds() {
Frame frame = (Frame) window;
// set maximized bounds to avoid that maximized window overlaps Windows task bar
// (if not running in JBR and if not modified from the application)
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
if( !hasJBRCustomDecoration() &&
(oldMaximizedBounds == null ||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
{
GraphicsConfiguration gc = window.getGraphicsConfiguration();
// Screen bounds, which may be smaller than physical size on Java 9+.
// E.g. if running a 3840x2160 screen at 200%, screenBounds.size is 1920x1080.
// In Java 9+, each screen can have its own scale factor.
//
// On Java 8, which does not scale, screenBounds.size of the primary screen
// is identical to its physical size. But when the primary screen is scaled,
// then screenBounds.size of secondary screens is scaled with the scale factor
// of the primary screen.
// E.g. primary 3840x2160 screen at 150%, secondary 1920x1080 screen at 100%,
// then screenBounds.size is 3840x2160 on primary and 2880x1560 on secondary.
Rectangle screenBounds = gc.getBounds();
int maximizedX = screenBounds.x;
int maximizedY = screenBounds.y;
int maximizedWidth = screenBounds.width;
int maximizedHeight = screenBounds.height;
if( !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;
// scale maximized screen size to get physical screen size for Java 9 to 14
AffineTransform defaultTransform = gc.getDefaultTransform();
maximizedWidth = (int) (maximizedWidth * defaultTransform.getScaleX());
maximizedHeight = (int) (maximizedHeight * defaultTransform.getScaleY());
}
// screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
// https://bugs.openjdk.java.net/browse/JDK-8176359)
// and except for Java 8 on secondary screens where primary screen is scaled
Rectangle newMaximizedBounds = new Rectangle(
maximizedX + screenInsets.left,
maximizedY + screenInsets.top,
maximizedWidth - screenInsets.left - screenInsets.right,
maximizedHeight - screenInsets.top - screenInsets.bottom );
if( !Objects.equals( oldMaximizedBounds, newMaximizedBounds ) ) {
// change maximized bounds
frame.setMaximizedBounds( newMaximizedBounds );
// remember maximized bounds in client property to be able to detect
// whether maximized bounds are modified from the application
rootPane.putClientProperty( "_flatlaf.maximizedBounds", newMaximizedBounds );
}
}
}
/**
* Frame.setMaximizedBounds() behaves different on some Java versions after issues
* https://bugs.openjdk.java.net/browse/JDK-8231564 and
* https://bugs.openjdk.java.net/browse/JDK-8176359
* (see also https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8176359)
* were fixed in Java 15 and backported to 11.0.8 and 13.0.4.
*/
private boolean isMaximizedBoundsFixed() {
return SystemInfo.isJava_15_orLater ||
(SystemInfo.javaVersion >= SystemInfo.toVersion( 11, 0, 8, 0 ) &&
SystemInfo.javaVersion < SystemInfo.toVersion( 12, 0, 0, 0 )) ||
(SystemInfo.javaVersion >= SystemInfo.toVersion( 13, 0, 4, 0 ) &&
SystemInfo.javaVersion < SystemInfo.toVersion( 14, 0, 0, 0 ));
}
/**
* Restores the window size.
*/
protected void restore() {
if( window instanceof Frame ) {
Frame frame = (Frame) window;
int state = frame.getExtendedState();
frame.setExtendedState( ((state & Frame.ICONIFIED) != 0)
? (state & ~Frame.ICONIFIED)
: (state & ~Frame.MAXIMIZED_BOTH) );
}
}
/**
* Closes the window.
*/
protected void close() {
if( window != null )
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
}
protected boolean hasJBRCustomDecoration() {
return FlatRootPaneUI.canUseJBRCustomDecorations &&
window != null &&
JBRCustomDecorations.hasCustomDecoration( window );
}
protected void updateJBRHitTestSpotsAndTitleBarHeightLater() {
EventQueue.invokeLater( () -> {
updateJBRHitTestSpotsAndTitleBarHeight();
} );
}
protected void updateJBRHitTestSpotsAndTitleBarHeight() {
if( !isDisplayable() )
return;
if( !hasJBRCustomDecoration() )
return;
List<Rectangle> hitTestSpots = new ArrayList<>();
if( iconLabel.isVisible() )
addJBRHitTestSpot( iconLabel, false, hitTestSpots );
addJBRHitTestSpot( buttonPanel, false, hitTestSpots );
addJBRHitTestSpot( menuBarPlaceholder, true, hitTestSpots );
int titleBarHeight = getHeight();
// slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 )
titleBarHeight--;
JBRCustomDecorations.setHitTestSpotsAndTitleBarHeight( window, hitTestSpots, titleBarHeight );
/*debug
debugHitTestSpots = hitTestSpots;
debugTitleBarHeight = titleBarHeight;
repaint();
debug*/
}
protected void addJBRHitTestSpot( JComponent c, boolean subtractMenuBarMargins, List<Rectangle> hitTestSpots ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
return;
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
if( subtractMenuBarMargins )
r = FlatUIUtils.subtractInsets( r, UIScale.scale( getMenuBarMargins() ) );
// slightly increase rectangle so that component receives mouseExit events
r.grow( 2, 2 );
hitTestSpots.add( r );
}
/*debug
private List<Rectangle> debugHitTestSpots;
private int debugTitleBarHeight;
debug*/
//---- class TitlePaneBorder ----------------------------------------------
protected class FlatTitlePaneBorder
extends AbstractBorder
{
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
super.getBorderInsets( c, insets );
Border menuBarBorder = getMenuBarBorder();
if( menuBarBorder != null ) {
// if menu bar is embedded, add bottom insets of menu bar border
Insets menuBarInsets = menuBarBorder.getBorderInsets( c );
insets.bottom += menuBarInsets.bottom;
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 );
if( hasJBRCustomDecoration() )
insets = FlatUIUtils.addInsets( insets, JBRWindowTopBorder.getInstance().getBorderInsets() );
return insets;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
// paint bottom border
Border menuBarBorder = getMenuBarBorder();
if( menuBarBorder != null ) {
// if menu bar is embedded, paint menu bar border
menuBarBorder.paintBorder( c, 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( hasJBRCustomDecoration() )
JBRWindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
}
protected Border getMenuBarBorder() {
JMenuBar menuBar = rootPane.getJMenuBar();
return (menuBar != null && menuBar.isVisible() && isMenuBarEmbedded()) ? menuBar.getBorder() : null;
}
}
//---- class Handler ------------------------------------------------------
protected class Handler
extends WindowAdapter
implements PropertyChangeListener, MouseListener, MouseMotionListener, ComponentListener
{
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case "title":
titleLabel.setText( getWindowTitle() );
break;
case "resizable":
if( window instanceof Frame )
frameStateChanged();
break;
case "iconImage":
updateIcon();
break;
case "componentOrientation":
updateJBRHitTestSpotsAndTitleBarHeightLater();
break;
}
}
//---- interface WindowListener ----
@Override
public void windowActivated( WindowEvent e ) {
activeChanged( true );
updateJBRHitTestSpotsAndTitleBarHeight();
if( hasJBRCustomDecoration() )
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
}
@Override
public void windowDeactivated( WindowEvent e ) {
activeChanged( false );
updateJBRHitTestSpotsAndTitleBarHeight();
if( hasJBRCustomDecoration() )
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
}
@Override
public void windowStateChanged( WindowEvent e ) {
frameStateChanged();
updateJBRHitTestSpotsAndTitleBarHeight();
}
//---- interface MouseListener ----
private Point dragOffset;
@Override
public void mouseClicked( MouseEvent e ) {
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( e.getSource() == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasJBRCustomDecoration() &&
window instanceof Frame &&
((Frame)window).isResizable() )
{
// maximize/restore on double-click
Frame frame = (Frame) window;
if( (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
restore();
else
maximize();
}
}
}
@Override
public void mousePressed( MouseEvent e ) {
if( window == null )
return; // should newer occur
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
}
@Override public void mouseReleased( MouseEvent e ) {}
@Override public void mouseEntered( MouseEvent e ) {}
@Override public void mouseExited( MouseEvent e ) {}
//---- interface MouseMotionListener ----
@Override
public void mouseDragged( MouseEvent e ) {
if( window == null )
return; // should newer occur
if( hasJBRCustomDecoration() )
return; // do nothing if running in JBR
// restore window if it is maximized
if( window instanceof Frame ) {
Frame frame = (Frame) window;
int state = frame.getExtendedState();
if( (state & Frame.MAXIMIZED_BOTH) != 0 ) {
int maximizedWidth = window.getWidth();
// restore window size, which also moves window to pre-maximized location
frame.setExtendedState( state & ~Frame.MAXIMIZED_BOTH );
// fix drag offset to ensure that window remains under mouse position
// for the case that dragging starts in the right area of the maximized window
int restoredWidth = window.getWidth();
int center = restoredWidth / 2;
if( dragOffset.x > center ) {
// this is same/similar to what Windows 10 does
if( dragOffset.x > maximizedWidth - center )
dragOffset.x = restoredWidth - (maximizedWidth - dragOffset.x);
else
dragOffset.x = center;
}
}
}
// compute new window location
int newX = e.getXOnScreen() - dragOffset.x;
int newY = e.getYOnScreen() - dragOffset.y;
if( newX == window.getX() && newY == window.getY() )
return;
// move window
window.setLocation( newX, newY );
}
@Override public void mouseMoved( MouseEvent e ) {}
//---- interface ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
updateJBRHitTestSpotsAndTitleBarHeightLater();
}
@Override
public void componentShown( ComponentEvent e ) {
// necessary for the case that the frame is maximized before it is shown
frameStateChanged();
}
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.ScaledImageIcon;
/**
* @author Karl Tauber
*/
public class FlatTitlePaneIcon
extends ScaledImageIcon
{
public static Icon create( List<Image> images, Dimension size ) {
// collect all images including multi-resolution variants
List<Image> allImages = new ArrayList<>();
for( Image image : images ) {
if( MultiResolutionImageSupport.isMultiResolutionImage( image ) )
allImages.addAll( MultiResolutionImageSupport.getResolutionVariants( image ) );
else
allImages.add( image );
}
// sort images by size
allImages.sort( (image1, image2) -> {
return image1.getWidth( null ) - image2.getWidth( null );
} );
// create icon
return new FlatTitlePaneIcon( allImages, size );
}
private final List<Image> images;
private FlatTitlePaneIcon( List<Image> images, Dimension size ) {
super( new ImageIcon( images.get( 0 ) ), size.width, size.height );
this.images = images;
}
@Override
protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) {
for( Image image : images ) {
if( destImageWidth <= image.getWidth( null ) &&
destImageHeight <= image.getHeight( null ) )
return image;
}
return images.get( images.size() - 1 );
}
}

View File

@@ -22,7 +22,6 @@ import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JToggleButton; import javax.swing.JToggleButton;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -50,17 +49,17 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ToggleButton.startBackground Color optional; if set, a gradient paint is used and ToggleButton.background is ignored * @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.endBackground Color optional; if set, a gradient paint is used
* @uiDefault ToggleButton.pressedBackground Color * @uiDefault ToggleButton.pressedBackground Color
* @uiDefault ToggleButton.disabledText Color
* @uiDefault ToggleButton.toolbar.hoverBackground Color
* @uiDefault ToggleButton.toolbar.pressedBackground Color
*
* <!-- FlatToggleButtonUI -->
*
* @uiDefault ToggleButton.selectedBackground Color * @uiDefault ToggleButton.selectedBackground Color
* @uiDefault ToggleButton.selectedForeground Color * @uiDefault ToggleButton.selectedForeground Color
* @uiDefault ToggleButton.disabledBackground Color optional
* @uiDefault ToggleButton.disabledText Color
* @uiDefault ToggleButton.disabledSelectedBackground Color * @uiDefault ToggleButton.disabledSelectedBackground Color
* @uiDefault ToggleButton.toolbar.hoverBackground Color
* @uiDefault ToggleButton.toolbar.pressedBackground Color
* @uiDefault ToggleButton.toolbar.selectedBackground Color * @uiDefault ToggleButton.toolbar.selectedBackground Color
* *
* <!-- FlatToggleButtonUI -->
*
* @uiDefault ToggleButton.tab.underlineHeight int * @uiDefault ToggleButton.tab.underlineHeight int
* @uiDefault ToggleButton.tab.underlineColor Color * @uiDefault ToggleButton.tab.underlineColor Color
* @uiDefault ToggleButton.tab.disabledUnderlineColor Color * @uiDefault ToggleButton.tab.disabledUnderlineColor Color
@@ -74,12 +73,6 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatToggleButtonUI public class FlatToggleButtonUI
extends FlatButtonUI extends FlatButtonUI
{ {
protected Color selectedBackground;
protected Color selectedForeground;
protected Color disabledSelectedBackground;
protected Color toolbarSelectedBackground;
protected int tabUnderlineHeight; protected int tabUnderlineHeight;
protected Color tabUnderlineColor; protected Color tabUnderlineColor;
protected Color tabDisabledUnderlineColor; protected Color tabDisabledUnderlineColor;
@@ -89,12 +82,8 @@ public class FlatToggleButtonUI
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatToggleButtonUI.class, FlatToggleButtonUI::new );
instance = new FlatToggleButtonUI();
return instance;
} }
@Override @Override
@@ -107,12 +96,6 @@ public class FlatToggleButtonUI
super.installDefaults( b ); super.installDefaults( b );
if( !defaults_initialized ) { if( !defaults_initialized ) {
selectedBackground = UIManager.getColor( "ToggleButton.selectedBackground" );
selectedForeground = UIManager.getColor( "ToggleButton.selectedForeground" );
disabledSelectedBackground = UIManager.getColor( "ToggleButton.disabledSelectedBackground" );
toolbarSelectedBackground = UIManager.getColor( "ToggleButton.toolbar.selectedBackground" );
tabUnderlineHeight = UIManager.getInt( "ToggleButton.tab.underlineHeight" ); tabUnderlineHeight = UIManager.getInt( "ToggleButton.tab.underlineHeight" );
tabUnderlineColor = UIManager.getColor( "ToggleButton.tab.underlineColor" ); tabUnderlineColor = UIManager.getColor( "ToggleButton.tab.underlineColor" );
tabDisabledUnderlineColor = UIManager.getColor( "ToggleButton.tab.disabledUnderlineColor" ); tabDisabledUnderlineColor = UIManager.getColor( "ToggleButton.tab.disabledUnderlineColor" );
@@ -138,7 +121,7 @@ public class FlatToggleButtonUI
case BUTTON_TYPE: case BUTTON_TYPE:
if( BUTTON_TYPE_TAB.equals( e.getOldValue() ) || BUTTON_TYPE_TAB.equals( e.getNewValue() ) ) { if( BUTTON_TYPE_TAB.equals( e.getOldValue() ) || BUTTON_TYPE_TAB.equals( e.getNewValue() ) ) {
MigLayoutVisualPadding.uninstall( b ); MigLayoutVisualPadding.uninstall( b );
MigLayoutVisualPadding.install( b, getFocusWidth( b ) ); MigLayoutVisualPadding.install( b );
b.revalidate(); b.revalidate();
} }
@@ -184,37 +167,4 @@ public class FlatToggleButtonUI
} else } else
super.paintBackground( g, c ); super.paintBackground( g, c );
} }
@Override
protected Color getBackground( JComponent c ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isSelected() ) {
// in toolbar use same colors for disabled and enabled because
// we assume that toolbar icon is shown disabled
boolean toolBarButton = isToolBarButton( c );
return buttonStateColor( c,
toolBarButton ? toolbarSelectedBackground : selectedBackground,
toolBarButton ? toolbarSelectedBackground : disabledSelectedBackground,
null, null,
toolBarButton ? toolbarPressedBackground : pressedBackground );
}
return super.getBackground( c );
}
@Override
protected Color getForeground( JComponent c ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isSelected() && !isToolBarButton( c ) )
return selectedForeground;
return super.getForeground( c );
}
@Override
protected int getFocusWidth( JComponent c ) {
return isTabButton( c ) ? 0 : super.getFocusWidth( c );
}
} }

View File

@@ -50,12 +50,8 @@ public class FlatToolBarSeparatorUI
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatToolBarSeparatorUI.class, FlatToolBarSeparatorUI::new );
instance = new FlatToolBarSeparatorUI();
return instance;
} }
@Override @Override

View File

@@ -27,7 +27,9 @@ import javax.swing.JComponent;
import javax.swing.JToolTip; import javax.swing.JToolTip;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicToolTipUI; import javax.swing.plaf.basic.BasicToolTipUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
/** /**
@@ -50,12 +52,8 @@ public class FlatToolTipUI
{ {
private static PropertyChangeListener sharedPropertyChangedListener; private static PropertyChangeListener sharedPropertyChangedListener;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatToolTipUI.class, FlatToolTipUI::new );
instance = new FlatToolTipUI();
return instance;
} }
@Override @Override
@@ -92,6 +90,11 @@ public class FlatToolTipUI
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
// do not show tool tip if text is empty
String text = ((JToolTip)c).getTipText();
if( text == null || text.isEmpty() )
return new Dimension();
if( isMultiLine( c ) ) { if( isMultiLine( c ) ) {
FontMetrics fm = c.getFontMetrics( c.getFont() ); FontMetrics fm = c.getFontMetrics( c.getFont() );
Insets insets = c.getInsets(); Insets insets = c.getInsets();
@@ -102,7 +105,7 @@ public class FlatToolTipUI
for( String line : lines ) for( String line : lines )
width = Math.max( width, SwingUtilities.computeStringWidth( fm, line ) ); width = Math.max( width, SwingUtilities.computeStringWidth( fm, line ) );
return new Dimension( insets.left + width + insets.right, insets.top + height + insets.bottom ); return new Dimension( insets.left + width + insets.right + 6, insets.top + height + insets.bottom );
} else } else
return super.getPreferredSize( c ); return super.getPreferredSize( c );
} }
@@ -118,8 +121,8 @@ public class FlatToolTipUI
List<String> lines = StringUtils.split( ((JToolTip)c).getTipText(), '\n' ); List<String> lines = StringUtils.split( ((JToolTip)c).getTipText(), '\n' );
int x = insets.left; int x = insets.left + 3;
int x2 = c.getWidth() - insets.right; int x2 = c.getWidth() - insets.right - 3;
int y = insets.top - fm.getDescent(); int y = insets.top - fm.getDescent();
int lineHeight = fm.getHeight(); int lineHeight = fm.getHeight();
JComponent comp = ((JToolTip)c).getComponent(); JComponent comp = ((JToolTip)c).getComponent();
@@ -129,11 +132,11 @@ public class FlatToolTipUI
FlatUIUtils.drawString( c, g, line, leftToRight ? x : x2 - SwingUtilities.computeStringWidth( fm, line ), y ); FlatUIUtils.drawString( c, g, line, leftToRight ? x : x2 - SwingUtilities.computeStringWidth( fm, line ), y );
} }
} else } else
super.paint( g, c ); super.paint( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ), c );
} }
private boolean isMultiLine( JComponent c ) { private boolean isMultiLine( JComponent c ) {
String text = ((JToolTip)c).getTipText(); String text = ((JToolTip)c).getTipText();
return c.getClientProperty( "html" ) == null && text != null && text.indexOf( '\n' ) >= 0; return c.getClientProperty( BasicHTML.propertyKey ) == null && text != null && text.indexOf( '\n' ) >= 0;
} }
} }

View File

@@ -25,6 +25,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import javax.swing.CellRendererPane;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JTree; import javax.swing.JTree;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
@@ -75,6 +76,11 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Tree.dropCellBackground Color * @uiDefault Tree.dropCellBackground Color
* @uiDefault Tree.dropCellForeground Color * @uiDefault Tree.dropCellForeground Color
* *
* <!-- DefaultTreeCellEditor -->
*
* @uiDefault Tree.editorBorder Border
* @uiDefault Tree.editorBorderSelectionColor Color
*
* <!-- FlatTreeUI --> * <!-- FlatTreeUI -->
* *
* @uiDefault Tree.border Border * @uiDefault Tree.border Border
@@ -221,11 +227,16 @@ public class FlatTreeUI
TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf ) TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{ {
boolean isEditing = (editingComponent != null && editingRow == row); boolean isEditing = (editingComponent != null && editingRow == row);
boolean hasFocus = tree.hasFocus(); boolean hasFocus = FlatUIUtils.isPermanentFocusOwner( tree );
boolean cellHasFocus = hasFocus && (row == getLeadSelectionRow()); boolean cellHasFocus = hasFocus && (row == getLeadSelectionRow());
boolean isSelected = tree.isRowSelected( row ); boolean isSelected = tree.isRowSelected( row );
boolean isDropRow = isDropRow( row ); boolean isDropRow = isDropRow( row );
// if tree is used as cell renderer in another component (e.g. in Rhino JavaScript debugger),
// check whether that component is focused to get correct selection colors
if( !hasFocus && isSelected && tree.getParent() instanceof CellRendererPane )
hasFocus = FlatUIUtils.isPermanentFocusOwner( tree.getParent().getParent() );
// wide selection background // wide selection background
if( wideSelection && (isSelected || isDropRow) ) { if( wideSelection && (isSelected || isDropRow) ) {
// fill background // fill background

View File

@@ -24,6 +24,7 @@ import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.Shape; import java.awt.Shape;
@@ -34,15 +35,23 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.util.IdentityHashMap;
import java.util.WeakHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -54,6 +63,8 @@ public class FlatUIUtils
{ {
public static final boolean MAC_USE_QUARTZ = Boolean.getBoolean( "apple.awt.graphics.UseQuartz" ); public static final boolean MAC_USE_QUARTZ = Boolean.getBoolean( "apple.awt.graphics.UseQuartz" );
private static WeakHashMap<LookAndFeel, IdentityHashMap<Object, ComponentUI>> sharedUIinstances = new WeakHashMap<>();
public static Rectangle addInsets( Rectangle r, Insets insets ) { public static Rectangle addInsets( Rectangle r, Insets insets ) {
return new Rectangle( return new Rectangle(
r.x - insets.left, r.x - insets.left,
@@ -121,7 +132,7 @@ public class FlatUIUtils
} }
public static Font nonUIResource( Font font ) { public static Font nonUIResource( Font font ) {
return (font instanceof UIResource) ? new Font( font.getName(), font.getStyle(), font.getSize() ) : font; return (font instanceof UIResource) ? font.deriveFont( font.getStyle() ) : font;
} }
public static int minimumWidth( JComponent c, int minimumWidth ) { public static int minimumWidth( JComponent c, int minimumWidth ) {
@@ -132,10 +143,82 @@ public class FlatUIUtils
return FlatClientProperties.clientPropertyInt( c, FlatClientProperties.MINIMUM_HEIGHT, minimumHeight ); return FlatClientProperties.clientPropertyInt( c, FlatClientProperties.MINIMUM_HEIGHT, minimumHeight );
} }
public static boolean isTableCellEditor( Component c ) { public static boolean isCellEditor( Component c ) {
// check whether used in cell editor (check 3 levels up)
Component c2 = c;
for( int i = 0; i <= 2 && c2 != null; i++ ) {
Container parent = c2.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c2 )
return true;
c2 = parent;
}
// check whether used as cell editor
// Table.editor is set in JTable.GenericEditor constructor
// Tree.cellEditor is set in sun.swing.FilePane.editFileName()
String name = c.getName();
if( "Table.editor".equals( name ) || "Tree.cellEditor".equals( name ) )
return true;
// for using combo box as cell editor in table
// JComboBox.isTableCellEditor is set in javax.swing.DefaultCellEditor(JComboBox) constructor
return c instanceof JComponent && Boolean.TRUE.equals( ((JComponent)c).getClientProperty( "JComboBox.isTableCellEditor" ) ); return c instanceof JComponent && Boolean.TRUE.equals( ((JComponent)c).getClientProperty( "JComboBox.isTableCellEditor" ) );
} }
/**
* Returns whether the given component is the permanent focus owner and
* is in the active window. Used to paint focus indicators.
*/
public static boolean isPermanentFocusOwner( Component c ) {
KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
return keyboardFocusManager.getPermanentFocusOwner() == c &&
keyboardFocusManager.getActiveWindow() == SwingUtilities.windowForComponent( c );
}
public static Boolean isRoundRect( Component c ) {
return (c instanceof JComponent)
? FlatClientProperties.clientPropertyBooleanStrict(
(JComponent) c, FlatClientProperties.COMPONENT_ROUND_RECT, null )
: null;
}
/**
* Returns the scaled thickness of the outer focus border for the given component.
*/
public static float getBorderFocusWidth( JComponent c ) {
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? UIScale.scale( (float) border.getFocusWidth( c ) )
: 0;
}
/**
* Returns the scaled arc diameter of the border for the given component.
*/
public static float getBorderArc( JComponent c ) {
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? UIScale.scale( (float) border.getArc( c ) )
: 0;
}
public static boolean hasRoundBorder( JComponent c ) {
return getBorderArc( c ) >= c.getHeight();
}
public static FlatBorder getOutsideFlatBorder( JComponent c ) {
Border border = c.getBorder();
for(;;) {
if( border instanceof FlatBorder )
return (FlatBorder) border;
else if( border instanceof CompoundBorder )
border = ((CompoundBorder)border).getOutsideBorder();
else
return null;
}
}
/** /**
* Sets rendering hints used for painting. * Sets rendering hints used for painting.
*/ */
@@ -145,17 +228,17 @@ public class FlatUIUtils
MAC_USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE ); MAC_USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE );
} }
public static void setColor( Graphics g, Color color, Color baseColor ) { public static Color deriveColor( Color color, Color baseColor ) {
if( color instanceof DerivedColor ) return (color instanceof DerivedColor)
color = ((DerivedColor)color).derive( baseColor ); ? ((DerivedColor)color).derive( baseColor )
g.setColor( color ); : color;
} }
/** /**
* Paints an outer border, which is usually a focus border. * Paints an outer border, which is usually a focus border.
* <p> * <p>
* The outside bounds of the painted border are {@code x,y,width,height}. * The outside bounds of the painted border are {@code x,y,width,height}.
* The line width of the painted border is {@code focusWidth + lineWidth}. * The line thickness of the painted border is {@code focusWidth + lineWidth}.
* The given arc diameter refers to the inner rectangle ({@code x,y,width,height} minus {@code focusWidth}). * The given arc diameter refers to the inner rectangle ({@code x,y,width,height} minus {@code focusWidth}).
* *
* @see #paintComponentBorder * @see #paintComponentBorder
@@ -164,6 +247,9 @@ public class FlatUIUtils
public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height, public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc ) float focusWidth, float lineWidth, float arc )
{ {
if( focusWidth + lineWidth == 0 )
return; // nothing to paint
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
@@ -189,14 +275,9 @@ public class FlatUIUtils
if( arc > 0 && arc < UIScale.scale( 10 ) ) if( arc > 0 && arc < UIScale.scale( 10 ) )
outerArc -= UIScale.scale( 2f ); outerArc -= UIScale.scale( 2f );
if( outerArc < 0 )
outerArc = 0;
if( innerArc < 0 )
innerArc = 0;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new RoundRectangle2D.Float( x, y, width, height, outerArc, outerArc ), false ); path.append( createComponentRectangle( x, y, width, height, outerArc ), false );
path.append( new RoundRectangle2D.Float( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerArc, innerArc ), false ); path.append( createComponentRectangle( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerArc ), false );
g.fill( path ); g.fill( path );
} }
@@ -205,6 +286,7 @@ public class FlatUIUtils
* <p> * <p>
* The outside bounds of the painted border are * The outside bounds of the painted border are
* {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}. * {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}.
* The line thickness of the painted border is {@code lineWidth}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}). * The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
* *
* @see #paintComponentOuterBorder * @see #paintComponentOuterBorder
@@ -213,6 +295,9 @@ public class FlatUIUtils
public static void paintComponentBorder( Graphics2D g, int x, int y, int width, int height, public static void paintComponentBorder( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc ) float focusWidth, float lineWidth, float arc )
{ {
if( lineWidth == 0 )
return; // nothing to paint
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
@@ -230,19 +315,16 @@ public class FlatUIUtils
private static void paintComponentBorderImpl( Graphics2D g, int x, int y, int width, int height, private static void paintComponentBorderImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc ) float focusWidth, float lineWidth, float arc )
{ {
float x1 = x + focusWidth;
float y1 = y + focusWidth;
float width1 = width - focusWidth * 2;
float height1 = height - focusWidth * 2;
float arc2 = arc - (lineWidth * 2); float arc2 = arc - (lineWidth * 2);
if( arc < 0 ) Shape r1 = createComponentRectangle( x1, y1, width1, height1, arc );
arc = 0; Shape r2 = createComponentRectangle(
if( arc2 < 0 ) x1 + lineWidth, y1 + lineWidth,
arc2 = 0; width1 - lineWidth * 2, height1 - lineWidth * 2, arc2 );
RoundRectangle2D.Float r1 = new RoundRectangle2D.Float(
x + focusWidth, y + focusWidth,
width - focusWidth * 2, height - focusWidth * 2, arc, arc );
RoundRectangle2D.Float r2 = new RoundRectangle2D.Float(
r1.x + lineWidth, r1.y + lineWidth,
r1.width - lineWidth * 2, r1.height - lineWidth * 2, arc2, arc2 );
Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD ); Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
border.append( r1, false ); border.append( r1, false );
@@ -280,12 +362,32 @@ public class FlatUIUtils
private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height, private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc ) float focusWidth, float arc )
{ {
if( arc < 0 ) g.fill( createComponentRectangle(
arc = 0;
g.fill( new RoundRectangle2D.Float(
x + focusWidth, y + focusWidth, x + focusWidth, y + focusWidth,
width - focusWidth * 2, height - focusWidth * 2, arc, arc ) ); width - focusWidth * 2, height - focusWidth * 2, arc ) );
}
/**
* Creates a (rounded) rectangle used to paint components (border, background, etc).
* The given arc diameter is limited to min(width,height).
*/
public static Shape createComponentRectangle( float x, float y, float w, float h, float arc ) {
if( arc <= 0 )
return new Rectangle2D.Float( x, y, w, h );
arc = Math.min( arc, Math.min( w, h ) );
return new RoundRectangle2D.Float( x, y, w, h, arc, arc );
}
static void paintFilledRectangle( Graphics g, Color color, float x, float y, float w, float h ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( color );
g2.fill( new Rectangle2D.Float( x, y, w, h ) );
} finally {
g2.dispose();
}
} }
/** /**
@@ -354,14 +456,12 @@ public class FlatUIUtils
if( arcTopLeft <= 0 && arcTopRight <= 0 && arcBottomLeft <= 0 && arcBottomRight <= 0 ) if( arcTopLeft <= 0 && arcTopRight <= 0 && arcBottomLeft <= 0 && arcBottomRight <= 0 )
return new Rectangle2D.Float( x, y, width, height ); return new Rectangle2D.Float( x, y, width, height );
if( arcTopLeft < 0 ) // limit arcs to min(width,height)
arcTopLeft = 0; float maxArc = Math.min( width, height ) / 2;
if( arcTopRight < 0 ) arcTopLeft = (arcTopLeft > 0) ? Math.min( arcTopLeft, maxArc ) : 0;
arcTopRight = 0; arcTopRight = (arcTopRight > 0) ? Math.min( arcTopRight, maxArc ) : 0;
if( arcBottomLeft < 0 ) arcBottomLeft = (arcBottomLeft > 0) ? Math.min( arcBottomLeft, maxArc ) : 0;
arcBottomLeft = 0; arcBottomRight = (arcBottomRight > 0) ? Math.min( arcBottomRight, maxArc ) : 0;
if( arcBottomRight < 0 )
arcBottomRight = 0;
float x2 = x + width; float x2 = x + width;
float y2 = y + height; float y2 = y + height;
@@ -401,28 +501,46 @@ public class FlatUIUtils
} }
/** /**
* Draws the given string at the specified location using text properties * Draws the given string at the specified location.
* and anti-aliasing hints from the provided component. * The provided component is used to query text properties and anti-aliasing hints.
* * <p>
* Use this method instead of Graphics.drawString() for correct anti-aliasing. * Use this method instead of {@link Graphics#drawString(String, int, int)} for correct anti-aliasing.
* * <p>
* Replacement for SwingUtilities2.drawString() * Replacement for {@code SwingUtilities2.drawString()}.
* Uses {@link HiDPIUtils#drawStringWithYCorrection(JComponent, Graphics2D, String, int, int)}.
*/ */
public static void drawString( JComponent c, Graphics g, String text, int x, int y ) { public static void drawString( JComponent c, Graphics g, String text, int x, int y ) {
JavaCompatibility.drawStringUnderlineCharAt( c, g, text, -1, x, y ); HiDPIUtils.drawStringWithYCorrection( c, (Graphics2D) g, text, x, y );
} }
/** /**
* Draws the given string at the specified location underlining the specified * Draws the given string at the specified location underlining the specified character.
* character. The provided component is used to query text properties and * The provided component is used to query text properties and anti-aliasing hints.
* anti-aliasing hints. * <p>
* * Replacement for {@code SwingUtilities2.drawStringUnderlineCharAt()}.
* Replacement for SwingUtilities2.drawStringUnderlineCharAt() * Uses {@link HiDPIUtils#drawStringUnderlineCharAtWithYCorrection(JComponent, Graphics2D, String, int, int, int)}.
*/ */
public static void drawStringUnderlineCharAt( JComponent c, Graphics g, public static void drawStringUnderlineCharAt( JComponent c, Graphics g,
String text, int underlinedIndex, int x, int y ) String text, int underlinedIndex, int x, int y )
{ {
JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y ); // scale underline height if necessary
if( underlinedIndex >= 0 && UIScale.getUserScaleFactor() > 1 ) {
g = new Graphics2DProxy( (Graphics2D) g ) {
@Override
public void fillRect( int x, int y, int width, int height ) {
if( height == 1 ) {
// scale height and correct y position
// (using 0.9f so that underline height is 1 at scale factor 1.5x)
height = Math.round( UIScale.scale( 0.9f ) );
y += height - 1;
}
super.fillRect( x, y, width, height );
}
};
}
HiDPIUtils.drawStringUnderlineCharAtWithYCorrection( c, (Graphics2D) g, text, underlinedIndex, x, y );
} }
public static boolean hasOpaqueBeenExplicitlySet( JComponent c ) { public static boolean hasOpaqueBeenExplicitlySet( JComponent c ) {
@@ -433,6 +551,19 @@ public class FlatUIUtils
return explicitlySet; return explicitlySet;
} }
/**
* Creates a shared component UI for the given key and the current Laf.
* Each Laf instance has its own shared component UI instance.
* <p>
* This is for GUI builders that support Laf switching and
* may use multiple Laf instances at the same time.
*/
public static ComponentUI createSharedUI( Object key, Supplier<ComponentUI> newInstanceSupplier ) {
return sharedUIinstances
.computeIfAbsent( UIManager.getLookAndFeel(), k -> new IdentityHashMap<>() )
.computeIfAbsent( key, k -> newInstanceSupplier.get() );
}
//---- class HoverListener ------------------------------------------------ //---- class HoverListener ------------------------------------------------
public static class HoverListener public static class HoverListener

View File

@@ -38,12 +38,8 @@ import javax.swing.plaf.basic.BasicViewportUI;
public class FlatViewportUI public class FlatViewportUI
extends BasicViewportUI extends BasicViewportUI
{ {
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
if( instance == null ) return FlatUIUtils.createSharedUI( FlatViewportUI.class, FlatViewportUI::new );
instance = new FlatViewportUI();
return instance;
} }
@Override @Override

View File

@@ -0,0 +1,573 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static java.awt.Cursor.*;
import static javax.swing.SwingConstants.*;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.function.Supplier;
import javax.swing.DesktopManager;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.UIManager;
import com.formdev.flatlaf.util.UIScale;
/**
* Resizes frames, dialogs or internal frames.
* <p>
* Could also be used to implement resize support for any Swing component
* by creating a new subclass.
*
* @author Karl Tauber
*/
public abstract class FlatWindowResizer
implements PropertyChangeListener, ComponentListener
{
protected final static Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1;
protected final JComponent resizeComp;
protected final int borderDragThickness = FlatUIUtils.getUIInt( "RootPane.borderDragThickness", 5 );
protected final int cornerDragWidth = FlatUIUtils.getUIInt( "RootPane.cornerDragWidth", 16 );
protected final boolean honorFrameMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorFrameMinimumSizeOnResize" );
protected final boolean honorDialogMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorDialogMinimumSizeOnResize" );
protected final DragBorderComponent topDragComp;
protected final DragBorderComponent bottomDragComp;
protected final DragBorderComponent leftDragComp;
protected final DragBorderComponent rightDragComp;
protected FlatWindowResizer( JComponent resizeComp ) {
this.resizeComp = resizeComp;
topDragComp = createDragBorderComponent( NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR );
bottomDragComp = createDragBorderComponent( SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR );
leftDragComp = createDragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR );
rightDragComp = createDragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR );
Container cont = (resizeComp instanceof JRootPane) ? ((JRootPane)resizeComp).getLayeredPane() : resizeComp;
Object cons = (cont instanceof JLayeredPane) ? WINDOW_RESIZER_LAYER : null;
cont.add( topDragComp, cons, 0 );
cont.add( bottomDragComp, cons, 1 );
cont.add( leftDragComp, cons, 2 );
cont.add( rightDragComp, cons, 3 );
resizeComp.addComponentListener( this );
resizeComp.addPropertyChangeListener( "ancestor", this );
if( resizeComp.isDisplayable() )
addNotify();
}
protected DragBorderComponent createDragBorderComponent( int leadingResizeDir, int centerResizeDir, int trailingResizeDir ) {
return new DragBorderComponent( leadingResizeDir, centerResizeDir, trailingResizeDir );
}
public void uninstall() {
removeNotify();
resizeComp.removeComponentListener( this );
resizeComp.removePropertyChangeListener( "ancestor", this );
Container cont = topDragComp.getParent();
cont.remove( topDragComp );
cont.remove( bottomDragComp );
cont.remove( leftDragComp );
cont.remove( rightDragComp );
}
public void doLayout() {
if( !topDragComp.isVisible() )
return;
int x = 0;
int y = 0;
int width = resizeComp.getWidth();
int height = resizeComp.getHeight();
if( width == 0 || height == 0 )
return;
Insets resizeInsets = getResizeInsets();
int thickness = UIScale.scale( borderDragThickness );
int topThickness = Math.max( resizeInsets.top, thickness );
int bottomThickness = Math.max( resizeInsets.bottom, thickness );
int leftThickness = Math.max( resizeInsets.left, thickness );
int rightThickness = Math.max( resizeInsets.right, thickness );
int y2 = y + topThickness;
int height2 = height - topThickness - bottomThickness;
// set bounds of drag components
topDragComp.setBounds( x, y, width, topThickness );
bottomDragComp.setBounds( x, y + height - bottomThickness, width, bottomThickness );
leftDragComp.setBounds( x, y2, leftThickness, height2 );
rightDragComp.setBounds( x + width - rightThickness, y2, rightThickness, height2 );
// set corner drag widths
int cornerDelta = UIScale.scale( cornerDragWidth - borderDragThickness );
topDragComp.setCornerDragWidths( leftThickness + cornerDelta, rightThickness + cornerDelta );
bottomDragComp.setCornerDragWidths( leftThickness + cornerDelta, rightThickness + cornerDelta );
leftDragComp.setCornerDragWidths( cornerDelta, cornerDelta );
rightDragComp.setCornerDragWidths( cornerDelta, cornerDelta );
}
protected Insets getResizeInsets() {
return new Insets( 0, 0, 0, 0 );
}
protected void addNotify() {
updateVisibility();
}
protected void removeNotify() {
updateVisibility();
}
protected void updateVisibility() {
boolean visible = isWindowResizable();
if( visible == topDragComp.isVisible() )
return;
topDragComp.setVisible( visible );
bottomDragComp.setVisible( visible );
leftDragComp.setVisible( visible );
// The east component is not hidden, instead its bounds are set to 0,0,1,1 and
// it is disabled. This is necessary so that DragBorderComponent.paintComponent() is invoked.
rightDragComp.setEnabled( visible );
if( visible ) {
rightDragComp.setVisible( true ); // necessary because it is initially invisible
doLayout();
} else
rightDragComp.setBounds( 0, 0, 1, 1 );
}
boolean isDialog() {
return false;
}
protected abstract boolean isWindowResizable();
protected abstract Rectangle getWindowBounds();
protected abstract void setWindowBounds( Rectangle r );
protected abstract boolean honorMinimumSizeOnResize();
protected abstract Dimension getWindowMinimumSize();
protected void beginResizing( int direction ) {}
protected void endResizing() {}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case "ancestor":
if( e.getNewValue() != null )
addNotify();
else
removeNotify();
break;
case "resizable":
updateVisibility();
break;
}
}
//---- interface ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
doLayout();
}
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
//---- class WindowResizer ------------------------------------------------
/**
* Resizes frames and dialogs.
*/
public static class WindowResizer
extends FlatWindowResizer
implements WindowStateListener
{
protected Window window;
public WindowResizer( JRootPane rootPane ) {
super( rootPane );
}
@Override
protected void addNotify() {
Container parent = resizeComp.getParent();
window = (parent instanceof Window) ? (Window) parent : null;
if( window instanceof Frame ) {
window.addPropertyChangeListener( "resizable", this );
window.addWindowStateListener( this );
}
super.addNotify();
}
@Override
protected void removeNotify() {
if( window instanceof Frame ) {
window.removePropertyChangeListener( "resizable", this );
window.removeWindowStateListener( this );
}
window = null;
super.removeNotify();
}
@Override
protected boolean isWindowResizable() {
if( window instanceof Frame )
return ((Frame)window).isResizable() && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) == 0;
if( window instanceof Dialog )
return ((Dialog)window).isResizable();
return false;
}
@Override
protected Rectangle getWindowBounds() {
return window.getBounds();
}
@Override
protected void setWindowBounds( Rectangle r ) {
window.setBounds( r );
// immediately layout drag border components
doLayout();
if( Toolkit.getDefaultToolkit().isDynamicLayoutActive() ) {
window.validate();
resizeComp.repaint();
}
}
@Override
protected boolean honorMinimumSizeOnResize() {
return
(honorFrameMinimumSizeOnResize && window instanceof Frame) ||
(honorDialogMinimumSizeOnResize && window instanceof Dialog);
}
@Override
protected Dimension getWindowMinimumSize() {
return window.getMinimumSize();
}
@Override
boolean isDialog() {
return window instanceof Dialog;
}
@Override
public void windowStateChanged( WindowEvent e ) {
updateVisibility();
}
}
//---- class InternalFrameResizer -----------------------------------------
/**
* Resizes internal frames.
*/
public static class InternalFrameResizer
extends FlatWindowResizer
{
protected final Supplier<DesktopManager> desktopManager;
public InternalFrameResizer( JInternalFrame frame, Supplier<DesktopManager> desktopManager ) {
super( frame );
this.desktopManager = desktopManager;
frame.addPropertyChangeListener( "resizable", this );
}
@Override
public void uninstall() {
getFrame().removePropertyChangeListener( "resizable", this );
super.uninstall();
}
private JInternalFrame getFrame() {
return (JInternalFrame) resizeComp;
}
@Override
protected Insets getResizeInsets() {
return getFrame().getInsets();
}
@Override
protected boolean isWindowResizable() {
return getFrame().isResizable();
}
@Override
protected Rectangle getWindowBounds() {
return getFrame().getBounds();
}
@Override
protected void setWindowBounds( Rectangle r ) {
desktopManager.get().resizeFrame( getFrame(), r.x, r.y, r.width, r.height );
}
@Override
protected boolean honorMinimumSizeOnResize() {
return true;
}
@Override
protected Dimension getWindowMinimumSize() {
return getFrame().getMinimumSize();
}
@Override
protected void beginResizing( int direction ) {
desktopManager.get().beginResizingFrame( getFrame(), direction );
}
@Override
protected void endResizing() {
desktopManager.get().endResizingFrame( getFrame() );
}
}
//---- class DragBorderComponent ------------------------------------------
protected class DragBorderComponent
extends JComponent
implements MouseListener, MouseMotionListener
{
private final int leadingResizeDir;
private final int centerResizeDir;
private final int trailingResizeDir;
private int resizeDir = -1;
private int leadingCornerDragWidth;
private int trailingCornerDragWidth;
// offsets of mouse position to window edges
private int dragLeftOffset;
private int dragRightOffset;
private int dragTopOffset;
private int dragBottomOffset;
protected DragBorderComponent( int leadingResizeDir, int centerResizeDir, int trailingResizeDir ) {
this.leadingResizeDir = leadingResizeDir;
this.centerResizeDir = centerResizeDir;
this.trailingResizeDir = trailingResizeDir;
setResizeDir( centerResizeDir );
setVisible( false );
addMouseListener( this );
addMouseMotionListener( this );
}
void setCornerDragWidths( int leading, int trailing ) {
leadingCornerDragWidth = leading;
trailingCornerDragWidth = trailing;
}
protected void setResizeDir( int resizeDir ) {
if( this.resizeDir == resizeDir )
return;
this.resizeDir = resizeDir;
setCursor( getPredefinedCursor( resizeDir ) );
}
@Override
public Dimension getPreferredSize() {
int thickness = UIScale.scale( borderDragThickness );
return new Dimension( thickness, thickness );
}
@Override
protected void paintComponent( Graphics g ) {
super.paintChildren( g );
// this is necessary because Dialog.setResizable() does not fire events
if( isDialog() )
updateVisibility();
/*debug
int width = getWidth();
int height = getHeight();
g.setColor( java.awt.Color.blue );
boolean topOrBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR);
if( topOrBottom ) {
g.drawLine( leadingCornerDragWidth, 0, leadingCornerDragWidth, height );
g.drawLine( width - trailingCornerDragWidth, 0, width - trailingCornerDragWidth, height );
} else {
g.drawLine( 0, leadingCornerDragWidth, width, leadingCornerDragWidth );
g.drawLine( 0, height - trailingCornerDragWidth, width, height - trailingCornerDragWidth );
}
g.setColor( java.awt.Color.red );
g.drawRect( 0, 0, width - 1, height - 1 );
debug*/
}
@Override
public void mouseClicked( MouseEvent e ) {
}
@Override
public void mousePressed( MouseEvent e ) {
if( !isWindowResizable() )
return;
int xOnScreen = e.getXOnScreen();
int yOnScreen = e.getYOnScreen();
Rectangle windowBounds = getWindowBounds();
// compute offsets of mouse position to window edges
dragLeftOffset = xOnScreen - windowBounds.x;
dragTopOffset = yOnScreen - windowBounds.y;
dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen;
dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen;
int direction = 0;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
case S_RESIZE_CURSOR: direction = SOUTH; break;
case W_RESIZE_CURSOR: direction = WEST; break;
case E_RESIZE_CURSOR: direction = EAST; break;
case NW_RESIZE_CURSOR: direction = NORTH_WEST; break;
case NE_RESIZE_CURSOR: direction = NORTH_EAST; break;
case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break;
case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break;
}
beginResizing( direction );
}
@Override
public void mouseReleased( MouseEvent e ) {
if( !isWindowResizable() )
return;
dragLeftOffset = dragRightOffset = dragTopOffset = dragBottomOffset = 0;
endResizing();
}
@Override public void mouseEntered( MouseEvent e ) {}
@Override public void mouseExited( MouseEvent e ) {}
@Override
public void mouseMoved( MouseEvent e ) {
boolean topOrBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR);
int xy = topOrBottom ? e.getX() : e.getY();
int wh = topOrBottom ? getWidth() : getHeight();
setResizeDir( xy <= leadingCornerDragWidth
? leadingResizeDir
: (xy >= wh - trailingCornerDragWidth
? trailingResizeDir
: centerResizeDir) );
}
@Override
public void mouseDragged( MouseEvent e ) {
if( !isWindowResizable() )
return;
int xOnScreen = e.getXOnScreen();
int yOnScreen = e.getYOnScreen();
// Get current window bounds and compute new bounds based them.
// This is necessary because window manager may alter window bounds while resizing.
// E.g. when having two monitors with different scale factors and resizing
// a window on first screen to the second screen, then the window manager may
// decide at some point that the window should be only on second screen
// and adjusts its bounds.
Rectangle oldBounds = getWindowBounds();
Rectangle newBounds = new Rectangle( oldBounds );
// compute new window bounds
// top
if( resizeDir == N_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR ) {
newBounds.y = yOnScreen - dragTopOffset;
newBounds.height += (oldBounds.y - newBounds.y);
}
// bottom
if( resizeDir == S_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
newBounds.height = (yOnScreen + dragBottomOffset) - newBounds.y;
// left
if( resizeDir == W_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR ) {
newBounds.x = xOnScreen - dragLeftOffset;
newBounds.width += (oldBounds.x - newBounds.x);
}
// right
if( resizeDir == E_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
newBounds.width = (xOnScreen + dragRightOffset) - newBounds.x;
// apply minimum window size
Dimension minimumSize = honorMinimumSizeOnResize() ? getWindowMinimumSize() : null;
if( minimumSize == null )
minimumSize = UIScale.scale( new Dimension( 150, 50 ) );
if( newBounds.width < minimumSize.width ) {
if( newBounds.x != oldBounds.x )
newBounds.x -= (minimumSize.width - newBounds.width);
newBounds.width = minimumSize.width;
}
if( newBounds.height < minimumSize.height ) {
if( newBounds.y != oldBounds.y )
newBounds.y -= (minimumSize.height - newBounds.height);
newBounds.height = minimumSize.height;
}
// set window bounds
if( !newBounds.equals( oldBounds ) )
setWindowBounds( newBounds );
}
}
}

View File

@@ -0,0 +1,315 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.BorderUIResource;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Support for custom window decorations provided by JetBrains Runtime (based on OpenJDK).
* Requires that the application runs on Windows 10 in a JetBrains Runtime 11 or later.
* <ul>
* <li><a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime</a></li>
* <li><a href="https://github.com/JetBrains/JetBrainsRuntime">https://github.com/JetBrains/JetBrainsRuntime</a></li>
* </ul>
*
* @author Karl Tauber
*/
public class JBRCustomDecorations
{
private static boolean initialized;
private static Method Window_hasCustomDecoration;
private static Method Window_setHasCustomDecoration;
private static Method WWindowPeer_setCustomDecorationHitTestSpots;
private static Method WWindowPeer_setCustomDecorationTitleBarHeight;
private static Method AWTAccessor_getComponentAccessor;
private static Method AWTAccessor_ComponentAccessor_getPeer;
public static boolean isSupported() {
initialize();
return Window_setHasCustomDecoration != null;
}
static void install( JRootPane rootPane ) {
if( !isSupported() )
return;
// check whether root pane already has a parent, which is the case when switching LaF
if( rootPane.getParent() != null )
return;
// Use hierarchy listener to wait until the root pane is added to a window.
// Enabling JBR decorations must be done very early, probably before
// window becomes displayable (window.isDisplayable()). Tried also using
// "ancestor" property change event on root pane, but this is invoked too late.
HierarchyListener addListener = new HierarchyListener() {
@Override
public void hierarchyChanged( HierarchyEvent e ) {
if( e.getChanged() != rootPane || (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0 )
return;
Container parent = e.getChangedParent();
if( parent instanceof Window )
install( (Window) parent );
// use invokeLater to remove listener to avoid that listener
// is removed while listener queue is processed
EventQueue.invokeLater( () -> {
rootPane.removeHierarchyListener( this );
} );
}
};
rootPane.addHierarchyListener( addListener );
}
static void install( Window window ) {
if( !isSupported() )
return;
// do not enable JBR decorations if LaF provides decorations
if( UIManager.getLookAndFeel().getSupportsWindowDecorations() )
return;
if( window instanceof JFrame ) {
JFrame frame = (JFrame) window;
// do not enable JBR decorations if JFrame should use system window decorations
// and if not forced to use JBR decorations
if( !JFrame.isDefaultLookAndFeelDecorated() &&
!FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
return;
// do not enable JBR decorations if frame is undecorated
if( frame.isUndecorated() )
return;
// enable JBR custom window decoration for window
setHasCustomDecoration( frame );
// enable Swing window decoration
frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
} else if( window instanceof JDialog ) {
JDialog dialog = (JDialog) window;
// do not enable JBR decorations if JDialog should use system window decorations
// and if not forced to use JBR decorations
if( !JDialog.isDefaultLookAndFeelDecorated() &&
!FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
return;
// do not enable JBR decorations if dialog is undecorated
if( dialog.isUndecorated() )
return;
// enable JBR custom window decoration for window
setHasCustomDecoration( dialog );
// enable Swing window decoration
dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
}
}
static boolean hasCustomDecoration( Window window ) {
if( !isSupported() )
return false;
try {
return (Boolean) Window_hasCustomDecoration.invoke( window );
} catch( Exception ex ) {
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
return false;
}
}
static void setHasCustomDecoration( Window window ) {
if( !isSupported() )
return;
try {
Window_setHasCustomDecoration.invoke( window );
} catch( Exception ex ) {
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
}
}
static void setHitTestSpotsAndTitleBarHeight( Window window, List<Rectangle> hitTestSpots, int titleBarHeight ) {
if( !isSupported() )
return;
try {
Object compAccessor = AWTAccessor_getComponentAccessor.invoke( null );
Object peer = AWTAccessor_ComponentAccessor_getPeer.invoke( compAccessor, window );
WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
WWindowPeer_setCustomDecorationTitleBarHeight.invoke( peer, titleBarHeight );
} catch( Exception ex ) {
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
}
}
private static void initialize() {
if( initialized )
return;
initialized = true;
// requires JetBrains Runtime 11 and Windows 10
if( !SystemInfo.isJetBrainsJVM_11_orLater || !SystemInfo.isWindows_10_orLater )
return;
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, true ) )
return;
try {
Class<?> awtAcessorClass = Class.forName( "sun.awt.AWTAccessor" );
Class<?> compAccessorClass = Class.forName( "sun.awt.AWTAccessor$ComponentAccessor" );
AWTAccessor_getComponentAccessor = awtAcessorClass.getDeclaredMethod( "getComponentAccessor" );
AWTAccessor_ComponentAccessor_getPeer = compAccessorClass.getDeclaredMethod( "getPeer", Component.class );
Class<?> peerClass = Class.forName( "sun.awt.windows.WWindowPeer" );
WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
WWindowPeer_setCustomDecorationTitleBarHeight = peerClass.getDeclaredMethod( "setCustomDecorationTitleBarHeight", int.class );
WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
WWindowPeer_setCustomDecorationTitleBarHeight.setAccessible( true );
Window_hasCustomDecoration = Window.class.getDeclaredMethod( "hasCustomDecoration" );
Window_setHasCustomDecoration = Window.class.getDeclaredMethod( "setHasCustomDecoration" );
Window_hasCustomDecoration.setAccessible( true );
Window_setHasCustomDecoration.setAccessible( true );
} catch( Exception ex ) {
// ignore
}
}
//---- class JBRWindowTopBorder -------------------------------------------
static class JBRWindowTopBorder
extends BorderUIResource.EmptyBorderUIResource
{
private static JBRWindowTopBorder instance;
private final Color defaultActiveBorder = new Color( 0x707070 );
private final Color inactiveLightColor = new Color( 0xaaaaaa );
private final Color inactiveDarkColor = new Color( 0x3f3f3f );
private boolean colorizationAffectsBorders;
private Color activeColor = defaultActiveBorder;
static JBRWindowTopBorder getInstance() {
if( instance == null )
instance = new JBRWindowTopBorder();
return instance;
}
private JBRWindowTopBorder() {
super( 1, 0, 0, 0 );
colorizationAffectsBorders = calculateAffectsBorders();
activeColor = calculateActiveBorderColor();
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> {
colorizationAffectsBorders = calculateAffectsBorders();
activeColor = calculateActiveBorderColor();
} );
PropertyChangeListener l = e -> {
activeColor = calculateActiveBorderColor();
};
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor", l );
toolkit.addPropertyChangeListener( "win.dwm.colorizationColorBalance", l );
toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l );
}
private boolean calculateAffectsBorders() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" );
return (value instanceof Boolean) ? (Boolean) value : true;
}
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return defaultActiveBorder;
Toolkit toolkit = Toolkit.getDefaultToolkit();
Color colorizationColor = (Color) toolkit.getDesktopProperty( "win.dwm.colorizationColor" );
if( colorizationColor != null ) {
Object colorizationColorBalanceObj = toolkit.getDesktopProperty( "win.dwm.colorizationColorBalance" );
if( colorizationColorBalanceObj instanceof Integer ) {
int colorizationColorBalance = (Integer) colorizationColorBalanceObj;
if( colorizationColorBalance < 0 )
colorizationColorBalance = 100;
if( colorizationColorBalance == 0 )
return new Color( 0xD9D9D9 );
if( colorizationColorBalance == 100 )
return colorizationColor;
float alpha = colorizationColorBalance / 100.0f;
float remainder = 1 - alpha;
int r = Math.round( (colorizationColor.getRed() * alpha + 0xD9 * remainder) );
int g = Math.round( (colorizationColor.getGreen() * alpha + 0xD9 * remainder) );
int b = Math.round( (colorizationColor.getBlue() * alpha + 0xD9 * remainder) );
return new Color( r, g, b );
}
return colorizationColor;
}
Color activeBorderColor = (Color) toolkit.getDesktopProperty( "win.frame.activeBorderColor" );
return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Window window = SwingUtilities.windowForComponent( c );
boolean active = (window != null) ? window.isActive() : false;
g.setColor( active ? activeColor : (FlatLaf.isLafDark() ? inactiveDarkColor : inactiveLightColor) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.drawRect( x, y, width - 1, 0 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
}
}

View File

@@ -69,14 +69,17 @@ public class MigLayoutVisualPadding
/** /**
* Convenience method that checks whether component border is a FlatBorder. * Convenience method that checks whether component border is a FlatBorder.
*/ */
public static void install( JComponent c, int focusWidth ) { public static void install( JComponent c ) {
if( !migLayoutAvailable ) if( !migLayoutAvailable )
return; return;
install( c, c2 -> { install( c, c2 -> {
return (c2.getBorder() instanceof FlatBorder) FlatBorder border = FlatUIUtils.getOutsideFlatBorder( c2 );
? new Insets( focusWidth, focusWidth, focusWidth, focusWidth ) if( border != null ) {
: null; int focusWidth = border.getFocusWidth( c2 );
return new Insets( focusWidth, focusWidth, focusWidth, focusWidth );
} else
return null;
}, "border" ); }, "border" );
} }

View File

@@ -0,0 +1,340 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import java.util.ArrayList;
import javax.swing.Timer;
import com.formdev.flatlaf.FlatSystemProperties;
/**
* Simple animator based on ideas and concepts from "Filthy Rich Clients" book
* and "Timing Framework" library.
*
* @author Karl Tauber
*/
public class Animator
{
private int duration;
private int resolution = 10;
private Interpolator interpolator;
private final ArrayList<TimingTarget> targets = new ArrayList<>();
private final Runnable endRunnable;
private boolean running;
private boolean hasBegun;
private boolean timeToStop;
private long startTime;
private Timer timer;
/**
* Checks whether animations are enabled (the default) or disabled via
* system property {@code flatlaf.animation} set to {@code false}.
* This allows disabling all animations at command line with {@code -Dflatlaf.animation=false}.
*/
public static boolean useAnimation() {
return FlatSystemProperties.getBoolean( FlatSystemProperties.ANIMATION, true );
}
/**
* Creates an animation that runs duration milliseconds.
* Use {@link #addTarget(TimingTarget)} to receive timing events
* and {@link #start()} to start the animation.
*
* @param duration the duration of the animation in milliseconds
*/
public Animator( int duration ) {
this( duration, null, null );
}
/**
* Creates an animation that runs duration milliseconds.
* Use {@link #start()} to start the animation.
*
* @param duration the duration of the animation in milliseconds
* @param target the target that receives timing events
*/
public Animator( int duration, TimingTarget target ) {
this( duration, target, null );
}
/**
* Creates an animation that runs duration milliseconds.
* Use {@link #start()} to start the animation.
*
* @param duration the duration of the animation in milliseconds
* @param target the target that receives timing events
* @param endRunnable a runnable invoked when the animation ends; or {@code null}
*/
public Animator( int duration, TimingTarget target, Runnable endRunnable ) {
setDuration( duration );
addTarget( target );
this.endRunnable = endRunnable;
}
/**
* Returns the duration of the animation in milliseconds.
*/
public int getDuration() {
return duration;
}
/**
* Sets the duration of the animation in milliseconds.
*
* @throws IllegalStateException if animation is running
* @throws IllegalArgumentException if duration is &lt;= zero
*/
public void setDuration( int duration ) {
throwExceptionIfRunning();
if( duration <= 0 )
throw new IllegalArgumentException();
this.duration = duration;
}
/**
* Returns the resolution of the animation in milliseconds (default is 10).
* Resolution is the amount of time between timing events.
*/
public int getResolution() {
return resolution;
}
/**
* Sets the resolution of the animation in milliseconds.
*
* @param resolution the resolution of the animation in milliseconds
* @throws IllegalStateException if animation is running
* @throws IllegalArgumentException if resolution is &lt;= zero
*/
public void setResolution( int resolution ) {
throwExceptionIfRunning();
if( resolution <= 0 )
throw new IllegalArgumentException();
this.resolution = resolution;
}
/**
* Returns the interpolator for the animation.
* Default is {@code null}, which means linear.
*/
public Interpolator getInterpolator() {
return interpolator;
}
/**
* Sets the interpolator for the animation.
*
* @throws IllegalStateException if animation is running
*/
public void setInterpolator( Interpolator interpolator ) {
throwExceptionIfRunning();
this.interpolator = interpolator;
}
/**
* Adds a target to the animation that receives timing events.
*
* @param target the target that receives timing events
*/
public void addTarget( TimingTarget target ) {
if( target == null )
return;
synchronized( targets ) {
if( !targets.contains( target ) )
targets.add( target );
}
}
/**
* Removes a target from the animation.
*
* @param target the target that should be removed
*/
public void removeTarget( TimingTarget target ) {
synchronized( targets ) {
targets.remove( target );
}
}
/**
* Starts the animation.
*
* @throws IllegalStateException if animation is running
*/
public void start() {
throwExceptionIfRunning();
running = true;
hasBegun = false;
timeToStop = false;
startTime = System.nanoTime() / 1000000;
if( timer == null ) {
timer = new Timer( resolution, e -> {
if( !hasBegun ) {
begin();
hasBegun = true;
}
timingEvent( getTimingFraction() );
} );
} else
timer.setDelay( resolution );
timer.setInitialDelay( 0 );
timer.start();
}
/**
* Stops the animation before it normally ends.
* Invokes {@link TimingTarget#end()} on timing targets.
*/
public void stop() {
stop( false );
}
/**
* Cancels the animation before it normally ends.
* Does not invoke {@link TimingTarget#end()} on timing targets.
*/
public void cancel() {
stop( true );
}
private void stop( boolean cancel ) {
if( !running )
return;
if( timer != null )
timer.stop();
if( !cancel )
end();
running = false;
timeToStop = false;
}
/**
* Restarts the animation.
* Invokes {@link #cancel()} and {@link #start()}.
*/
public void restart() {
cancel();
start();
}
/**
* Returns whether this animation is running.
*/
public boolean isRunning() {
return running;
}
private float getTimingFraction() {
long currentTime = System.nanoTime() / 1000000;
long elapsedTime = currentTime - startTime;
timeToStop = (elapsedTime >= duration);
float fraction = clampFraction( (float) elapsedTime / duration );
if( interpolator != null )
fraction = clampFraction( interpolator.interpolate( fraction ) );
return fraction;
}
private float clampFraction( float fraction ) {
if( fraction < 0 )
return 0;
if( fraction > 1 )
return 1;
return fraction;
}
private void timingEvent( float fraction ) {
synchronized( targets ) {
for( TimingTarget target : targets )
target.timingEvent( fraction );
}
if( timeToStop )
stop();
}
private void begin() {
synchronized( targets ) {
for( TimingTarget target : targets )
target.begin();
}
}
private void end() {
synchronized( targets ) {
for( TimingTarget target : targets )
target.end();
}
if( endRunnable != null )
endRunnable.run();
}
private void throwExceptionIfRunning() {
if( isRunning() )
throw new IllegalStateException();
}
//---- interface TimingTarget ---------------------------------------------
/**
* Animation callbacks.
*/
@FunctionalInterface
public interface TimingTarget {
/**
* Invoked multiple times while animation is running.
*
* @param fraction the percent (0 to 1) elapsed of the current animation cycle
*/
void timingEvent( float fraction );
/**
* Invoked when the animation begins.
*/
default void begin() {}
/**
* Invoked when the animation ends.
*/
default void end() {}
}
//---- interface Interpolator ---------------------------------------------
/**
* Interpolator used by animation to change timing fraction. E.g. for easing.
*/
@FunctionalInterface
public interface Interpolator {
/**
* Interpolate the given fraction and returns a new fraction.
* Both fractions are in range [0, 1].
*
* @param fraction the percent (0 to 1) elapsed of the current animation cycle
* @return new fraction in range [0, 1]
*/
float interpolate( float fraction );
}
}

View File

@@ -35,7 +35,7 @@ public class ColorFunctions
return HSLColor.toRGB( hsl, alpha ); return HSLColor.toRGB( hsl, alpha );
} }
private static float clamp( float value ) { public static float clamp( float value ) {
return (value < 0) return (value < 0)
? 0 ? 0
: ((value > 100) : ((value > 100)
@@ -49,20 +49,26 @@ public class ColorFunctions
void apply( float[] hsl ); void apply( float[] hsl );
} }
//---- class Lighten ------------------------------------------------------ //---- class HSLIncreaseDecrease ------------------------------------------
/** /**
* Increase the lightness of a color in the HSL color space by an absolute * Increase or decrease hue, saturation or luminance of a color in the HSL color space
* or relative amount. * by an absolute or relative amount.
*/ */
public static class Lighten public static class HSLIncreaseDecrease
implements ColorFunction implements ColorFunction
{ {
private final float amount; public final int hslIndex;
private final boolean relative; public final boolean increase;
private final boolean autoInverse; public final float amount;
public final boolean relative;
public final boolean autoInverse;
public Lighten( float amount, boolean relative, boolean autoInverse ) { public HSLIncreaseDecrease( int hslIndex, boolean increase,
float amount, boolean relative, boolean autoInverse )
{
this.hslIndex = hslIndex;
this.increase = increase;
this.amount = amount; this.amount = amount;
this.relative = relative; this.relative = relative;
this.autoInverse = autoInverse; this.autoInverse = autoInverse;
@@ -70,33 +76,17 @@ public class ColorFunctions
@Override @Override
public void apply( float[] hsl ) { public void apply( float[] hsl ) {
float amount2 = autoInverse && shouldInverse( hsl ) ? -amount : amount; float amount2 = increase ? amount : -amount;
hsl[2] = clamp( relative amount2 = autoInverse && shouldInverse( hsl ) ? -amount2 : amount2;
? (hsl[2] * ((100 + amount2) / 100)) hsl[hslIndex] = clamp( relative
: (hsl[2] + amount2) ); ? (hsl[hslIndex] * ((100 + amount2) / 100))
: (hsl[hslIndex] + amount2) );
} }
protected boolean shouldInverse( float[] hsl ) { protected boolean shouldInverse( float[] hsl ) {
return hsl[2] >= 50; return increase
} ? hsl[hslIndex] >= 50
} : hsl[hslIndex] < 50;
//---- class Darken -------------------------------------------------------
/**
* Decrease the lightness of a color in the HSL color space by an absolute
* or relative amount.
*/
public static class Darken
extends Lighten
{
public Darken( float amount, boolean relative, boolean autoInverse ) {
super( -amount, relative, autoInverse );
}
@Override
protected boolean shouldInverse( float[] hsl ) {
return hsl[2] < 50;
} }
} }
} }

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