Compare commits

..

30 Commits

Author SHA1 Message Date
Karl Tauber
44a04cca2c FlatSmoothScrollingTest:
- better list/tree/etc items for easier recognizing jittery scrolling
- sliders to modify animation duration and resolution
- slider to invoke `scrollRectToVisible()`
- option to show row header for table
- use viewport.viewPosition for chart (instead of scrollbar.value)
- highlight methods in stack of tooltip (e.g. JViewport.setViewPosition())
2023-09-02 17:56:11 +02:00
Karl Tauber
b32b8db97a FlatSmoothScrollingTest: refactored line chart panel into own class for easier use in other test apps 2023-08-30 00:20:45 +02:00
Karl Tauber
c529dcb747 Smooth Scrolling:
- fixed jittery repeating-scrolling with PageUp/Down keys when reaching the top/bottom/left/right of the viewport (see FlatScrollBarUI.setValueAnimated())
- temporary change viewport scroll mode only if it is JViewport.BLIT_SCROLL_MODE
- use JViewport.SIMPLE_SCROLL_MODE when temporary disabling blitting
2023-08-27 14:31:30 +02:00
Karl Tauber
04658c2ef0 SmoothScrollingTest: fixed error reported by Error Prone 2023-08-25 17:43:58 +02:00
Karl Tauber
5cdef5409b Smooth Scrolling: fixed jittery scrolling with trackpad or Magic Mouse (if smooth scrolling is enabled) 2023-08-25 15:24:28 +02:00
Karl Tauber
6dfc204e40 SmoothScrollingTest added (from https://github.com/JFormDesigner/FlatLaf/pull/683#issuecomment-1585667066) 2023-08-25 15:23:59 +02:00
Karl Tauber
542e7d5f60 Smooth Scrolling: fixes too slow repeating block (page) scrolling (e.g. hold down PageUp key) for Tree, TextArea, TextPane and EditorPane 2023-08-24 22:38:52 +02:00
Karl Tauber
3628a03c9d introduced FlatUIAction 2023-08-24 11:54:32 +02:00
Karl Tauber
6ce2198cd6 FlatSmoothScrollingTest:
- added slider to horizontally scale chart
- improved chart legend
- record stack for points in chart and show in tooltip on hover
2023-08-23 15:53:55 +02:00
Karl Tauber
e2e3fd31e9 FlatSmoothScrollingTest:
- added small vertical line to indicate data points in chart
- added split pane to allow changing height of components
- Alt+C clears chart without moving focus to "Clear" button
- separate chart lines for smooth and non-smooth scrolling
2023-08-23 09:51:57 +02:00
Karl Tauber
cf70cfb50c ScrollBar: fixed temporary painting at wrong location during smooth scrolling when using mouse-wheel or scroll bar
(still occurs when scrolling by moving selection via keyboard)

many thanks to @Chrriis for the idea to temporary disable blitting mode on viewport
2023-08-23 09:51:57 +02:00
Karl Tauber
29f6c5fae9 FlatAnimatorTest: added test for precise scrolling with trackpad 2023-08-23 09:51:57 +02:00
Karl Tauber
419a689ca4 FlatAnimatorTest: added test for wheel scrolling (including chart) 2023-08-23 09:51:57 +02:00
Karl Tauber
865a56875f FlatSmoothScrollingTest: added "custom" scroll pane for testing smooth scrolling in case that scroll view does not implement Scrollable interface 2023-08-23 09:51:57 +02:00
Karl Tauber
3573188025 ScrollBar: support smooth scrolling via keyboard 2023-08-23 09:51:57 +02:00
Karl Tauber
1f2622819a FlatSmoothScrollingTest: support dark themes and added "Show table grid" and "Auto-resize mode" check boxes 2023-08-23 09:51:57 +02:00
Karl Tauber
305e9e602e ScrollBar: fixed jittery scrolling when in repeating mode (hold down mouse button) and smooth scrolling enabled 2023-08-23 09:51:57 +02:00
Karl Tauber
1ae31588c4 FlatSmoothScrollingTest: paint "temporary" scrollbar values in line chart using a lighter color 2023-08-23 09:51:56 +02:00
Karl Tauber
d64a8e93e1 FlatSmoothScrollingTest:
- use ChangeListener instead of AdjustmentListener because this is invoked before all other scrollbar listeners (which may run 20-30ms) and avoids a delay in the line chart
- use System.nanoTime() instead of System.currentTimeMillis() for better precision
- paint vertical lines in chart at every 200ms (was 1sec)
- print elapsed time between scrollbar events
2023-08-23 09:51:56 +02:00
Karl Tauber
e603bd81a1 FlatSmoothScrollingTest: added simple line chart that shows changes to scrollbar values 2023-08-23 09:51:56 +02:00
Karl Tauber
522ebb6fa3 FlatSmoothScrollingTest: allow enabling/disabling smooth scrolling with Alt+S without moving focus to checkbox; removed unused tree model 2023-08-23 09:51:56 +02:00
Karl Tauber
7a582c2d1f ScrollBar: fixed issue with updating thumb location (regressing since commit 2c3ef226692fa39b7e6eca3192d197c0b0753aa1) 2023-08-23 09:51:56 +02:00
Karl Tauber
762fe89867 FlatSmoothScrollingTest: added JTree, JTable, JTextArea, JTextPane and JEditorPane for testing smooth scrolling 2023-08-23 09:51:56 +02:00
Karl Tauber
1ebfe00f3c added system properties "flatlaf.animation" and "flatlaf.smoothScrolling" to disable all animations or smooth scrolling via command line (without modifying the application) 2023-08-23 09:51:56 +02:00
Karl Tauber
fdabca99b2 ScrollBar: fixed NPE when switching LaF while smooth scrolling animation is running (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
736305849a ScrollBar: set valueIsAdjusting property to true while smooth scrolling animation is running (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
889b5ea56a ScrollBar: fixed smooth scrolling issues when continuously scrolling (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
82514ccbfc Demo: added "Options > Smooth Scrolling" to menu (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
b67b701d1e ScrollPane: use smooth scrolling when rotating the mouse wheel (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
7f226a2742 ScrollBar: use smooth scrolling when clicking on track or on arrow button (issue #50) 2023-08-23 09:51:56 +02:00
268 changed files with 6577 additions and 20709 deletions

View File

@@ -9,14 +9,6 @@ on:
- '*' - '*'
tags: tags:
- '[0-9]*' - '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs: jobs:
build: build:
@@ -27,28 +19,28 @@ jobs:
# test against # test against
# - Java 8 (minimum requirement) # - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...) # - Java LTS versions (11, 17, ...)
# - latest Java version(s) # - lastest Java version(s)
java: java:
- 8 - 8
- 11 # LTS - 11 # LTS
- 17 # LTS - 17 # LTS
- 21 # LTS - 19
toolchain: [""] toolchain: [""]
include: include:
- java: 21 - java: 17
toolchain: 22 # latest toolchain: 20 # latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v2 - uses: gradle/wrapper-validation-action@v1
if: matrix.java == '8' if: matrix.java == '8'
- name: Setup Java ${{ matrix.java }} - name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11, 17 and 21 are pre-installed on ubuntu-latest distribution: temurin # Java 8, 11 and 17 are pre-installed on ubuntu-latest
cache: gradle cache: gradle
- name: Check with Error Prone - name: Check with Error Prone
@@ -59,7 +51,7 @@ jobs:
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }} run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: matrix.java == '11' if: matrix.java == '11'
with: with:
name: FlatLaf-build-artifacts name: FlatLaf-build-artifacts
@@ -79,10 +71,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf' github.repository == 'JFormDesigner/FlatLaf'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 11
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest
@@ -115,10 +107,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf' github.repository == 'JFormDesigner/FlatLaf'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 11
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest

View File

@@ -13,8 +13,6 @@ on:
- 'flatlaf-fonts/**' - 'flatlaf-fonts/**'
- '.github/workflows/fonts.yml' - '.github/workflows/fonts.yml'
- 'gradle/wrapper/gradle-wrapper.properties' - 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs: jobs:
Fonts: Fonts:
@@ -32,10 +30,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf' github.repository == 'JFormDesigner/FlatLaf'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 11
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest

View File

@@ -13,8 +13,6 @@ on:
- 'flatlaf-natives/**' - 'flatlaf-natives/**'
- '.github/workflows/natives.yml' - '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties' - 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs: jobs:
Natives: Natives:
@@ -22,18 +20,17 @@ jobs:
matrix: matrix:
os: os:
- windows - windows
- macos
- ubuntu - ubuntu
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v2 - uses: gradle/wrapper-validation-action@v1
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 11
distribution: temurin distribution: temurin
@@ -45,7 +42,7 @@ jobs:
run: ./gradlew build-natives --no-daemon run: ./gradlew build-natives --no-daemon
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }} name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: | path: |

View File

@@ -1,37 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: PR Snapshots
on:
pull_request:
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
snapshot:
runs-on: ubuntu-latest
if: github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Publish PR snapshot to oss.sonatype.org
run: >
./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
-Pgithub.event.pull_request.number=${{ github.event.pull_request.number }}
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}

View File

@@ -1,255 +1,6 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## 3.5.1
#### Fixed bugs
- HTML: Fixed occasional cutoff wrapped text when using multi-line text in HTML
tags `<h1>`...`<h6>`, `<code>`, `<kbd>`, `<big>`, `<small>` or `<samp>`.
(issue #873; regression in 3.5)
- Popup: Fixed `UnsupportedOperationException: PERPIXEL_TRANSLUCENT translucency
is not supported` exception on Haiku OS when showing popup (partly) outside of
window. (issue #869)
- HiDPI: Fixed occasional wrong repaint areas when using
`HiDPIUtils.installHiDPIRepaintManager()`. (see PR #864)
- Added system property `flatlaf.useSubMenuSafeTriangle` to allow disabling
submenu safe triangle (PR #490) for
[SWTSwing](https://github.com/Chrriis/SWTSwing). (issue #870)
## 3.5
#### New features and improvements
- Table: Support rounded selection. (PR #856)
- Button and ToggleButton: Added border colors for pressed and selected states.
(issue #848)
- Label: Support painting background with rounded corners. (issue #842)
- Popup: Fixed flicker of popups (e.g. tooltips) while they are moving (e.g.
following mouse pointer). (issues #832 and #672)
- FileChooser: Wrap shortcuts in scroll pane. (issue #828)
- Theme Editor: On macOS, use larger window title bar. (PR #779)
#### Fixed bugs
- macOS: Disabled rounded popup border (see PR #772) on macOS 14.4+ because it
may freeze the application and crash the macOS WindowServer process (reports
vary from Finder restarts to OS restarts). This is a temporary change until a
solution is found. See NetBeans issues
[apache/netbeans#7560](https://github.com/apache/netbeans/issues/7560#issuecomment-2226439215)
and
[apache/netbeans#6647](https://github.com/apache/netbeans/issues/6647#issuecomment-2070124442).
- FlatLaf window decorations: Window top border on Windows 10 in "full window
content" mode was not fully repainted when activating or deactivating window.
(issue #809)
- Button and ToggleButton: UI properties `[Toggle]Button.selectedForeground` and
`[Toggle]Button.pressedForeground` did not work for HTML text. (issue #848)
- HTML: Fixed font sizes for HTML tags `<h1>`...`<h6>`, `<code>`, `<kbd>`,
`<big>`, `<small>` and `<samp>` in HTML text for components Button, CheckBox,
RadioButton, MenuItem (and subclasses), JideLabel, JideButton, JXBusyLabel and
JXHyperlink. Also fixed for Label and ToolTip if using Java 11+.
- ScrollPane: Fixed/improved border painting at 125% - 175% scaling to avoid
different border thicknesses. (issue #743)
- Table: Fixed painting of alternating rows below table if auto-resize mode is
`JTable.AUTO_RESIZE_OFF` and table width is smaller than scroll pane (was not
updated when table width changed and was painted on wrong side in
right-to-left component orientation).
- Theme Editor: Fixed occasional empty window on startup on macOS.
- FlatLaf window decorations: Fixed black line sometimes painted on top of
(native) window border on Windows 11. (issue #852)
- HiDPI: Fixed incomplete component paintings at 125% or 175% scaling on Windows
where sometimes a 1px wide area at the right or bottom component edge is not
repainted. E.g. ScrollPane focus indicator border. (issues #860 and #582)
#### Incompatibilities
- ProgressBar: Log warning (including stack trace) when uninstalling
indeterminate progress bar UI or using `JProgressBar.setIndeterminate(false)`
not on AWT thread, because this may throw NPE in `FlatProgressBarUI.paint()`.
(issues #841 and #830)
- Panel: Rounded background of panel with rounded corners is now painted even if
panel is not opaque. (issue #840)
## 3.4.1
#### Fixed bugs
- SplitPane: Update divider when client property `JSplitPane.expandableSide`
changed.
- TabbedPane: Fixed swapped back and forward scroll buttons when using
`TabbedPane.scrollButtonsPlacement = trailing` (regression in FlatLaf 3.3).
- Fixed missing window top border on Windows 10 in "full window content" mode.
(issue #809)
- Extras:
- `FlatSVGIcon` color filters now support linear gradients. (PR #817)
- `FlatSVGIcon`: Use log level `CONFIG` instead of `SEVERE` and allow
disabling logging. (issue #823)
- Added support for `JSplitPane.expandableSide` client property to
`FlatSplitPane`.
- Native libraries: Added API version check to test whether native library
matches the JAR (bad builds could e.g. ship a newer JAR with an older
incompatible native library) and to test whether native methods can be invoked
(some security software allows loading native library but blocks method
invocation).
- macOS: Fixed crash when running in WebSwing. (issue #826; regression in 3.4)
#### Incompatibilities
- File names of custom properties files for nested Laf classes now must include
name of enclosing class name. E.g. nested Laf class `IntelliJTheme.ThemeLaf`
used `ThemeLaf.properties` in previous versions, but now needs to be named
`IntelliJTheme$ThemeLaf.properties`.
## 3.4
#### New features and improvements
- FlatLaf window decorations (Windows 10/11 and Linux): Support "full window
content" mode, which allows you to extend the content into the window title
bar. (PR #801)
- macOS: Support larger window title bar close/minimize/zoom buttons spacing in
[full window content](https://www.formdev.com/flatlaf/macos/#full_window_content)
mode and introduced "buttons placeholder". (PR #779)
- Native libraries:
- System property `flatlaf.nativeLibraryPath` now supports loading native
libraries named the same as on Maven central.
- Published `flatlaf-<version>-no-natives.jar` to Maven Central. This JAR is
equal to `flatlaf-<version>.jar`, except that it does not contain the
FlatLaf native libraries. The Maven "classifier" to use this JAR is
`no-natives`. You need to distribute the FlatLaf native libraries with your
application.
See https://www.formdev.com/flatlaf/native-libraries/ for more details.
- Improved log messages for loading fails.
- Fonts: Updated **Inter** to
[v4.0](https://github.com/rsms/inter/releases/tag/v4.0).
- Table: Select all text in cell editor when starting editing using `F2` key on
Windows or Linux. (issue #652)
#### Fixed bugs
- macOS: Setting window background (of undecorated window) to translucent color
(alpha < 255) did not show the window translucent. (issue #705)
- JIDE CommandMenuBar: Fixed `ClassCastException` when JIDE command bar displays
`JideMenu` in popup. (PR #794)
## 3.3
#### New features and improvements
- macOS (10.14+): Popups (`JPopupMenu`, `JComboBox`, `JToolTip`, etc.) now use
native macOS rounded borders. (PR #772; issue #715)
- Native libraries: Added `libflatlaf-macos-arm64.dylib` and
`libflatlaf-macos-x86_64.dylib`. See also
https://www.formdev.com/flatlaf/native-libraries/.
- ScrollPane: Support rounded border. (PR #713)
- SplitPane: Support divider hover and pressed background colors. (PR #788)
- TabbedPane: Support vertical tabs. (PR #758, issue #633)
- TabbedPane: Paint rounded tab area background for rounded cards. (issue #717)
- ToolBar: Added styling properties `separatorWidth` and `separatorColor`.
#### Fixed bugs
- Button and ToggleButton: Selected buttons did not use explicitly set
foreground color. (issue #756)
- FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use
default Windows exe icon. (see
[JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692))
- OptionPane: Fixed styling custom panel background in `JOptionPane`. (issue
#761)
- ScrollPane: Styling ScrollPane border properties did not work if view
component is a Table.
- Table:
- Switching theme looses table grid and intercell spacing. (issues #733 and
#750)
- Fixed background of `boolean` columns when using alternating row colors.
(issue #780)
- Fixed border arc of components in complex table cell editors. (issue #786)
- TableHeader:
- No longer temporary replace header cell renderer while painting. This avoids
a `StackOverflowError` in case that custom renderer does this too. (see
[NetBeans issue #6835](https://github.com/apache/netbeans/issues/6835)) This
also improves compatibility with custom table header implementations.
- Header cell renderer background/foreground colors were not restored after
hover if renderer uses `null` for background/foreground. (PR #790)
- TabbedPane:
- Avoid unnecessary repainting whole tabbed pane content area when layouting
leading/trailing components.
- Avoid unnecessary repainting of selected tab on temporary changes.
- Fixed "endless" layouting and repainting when using nested tabbed panes (top
and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping)
as tab content. (see
[jadx issue #2030](https://github.com/skylot/jadx/issues/2030))
- Fixed broken rendering after resizing window to minimum size and then
increasing size again. (issue #767)
#### Incompatibilities
- Removed support for JetBrains custom decorations, which required
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki) (JBR)
8 or 11. It did not work for JBR 17. System property
`flatlaf.useJetBrainsCustomDecorations` is now ignored. **Note**: FlatLaf
window decorations continue to work with JBR.
## 3.2.5
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Windows 10. (issue #753;
regression in 3.2.1 in fix for #626)
## 3.2.4
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Linux with Wayland and Java 21.
(issue #752; regression in 3.2.3)
## 3.2.3
#### Fixed bugs
- Popup: Popups that request focus were not shown on Linux with Wayland and Java 21.
(issue #752)
## 3.2.2
#### Fixed bugs
- Button: Fixed painting icon and text at wrong location when using HTML text,
left/right vertical alignment and running in Java 19+. (issue #746)
- CheckBox and RadioButton: Fixed cut off right side when border is removed and
horizontal alignment is set to `right`. (issue #734)
- TabbedPane: Fixed NPE when using focusable component as tab component and
switching theme. (issue #745)
## 3.2.1
#### Fixed bugs
- Fixed memory leak in
`MultiResolutionImageSupport.create(int,Dimension[],Function<Dimension,Image>)`,
which caches images created by the producer function. Used by
`FlatSVGIcon.getImage()` and `FlatSVGUtils.createWindowIconImages()`. If you
use one of these methods, it is **strongly recommended** to upgrade to this
version, because if the returned image is larger and painted very often it may
result in an out-of-memory situation. (issue #726)
- FileChooser: Fixed occasional NPE in `FlatShortcutsPanel` on Windows. (issue
#718)
- TextField: Fixed placeholder text painting, which did not respect horizontal
alignment property of `JTextField`. (issue #721)
- Popup: Fixed drop shadow if popup overlaps a heavyweight component. (Windows
10 only; issue #626)
## 3.2 ## 3.2
#### New features and improvements #### New features and improvements
@@ -372,6 +123,7 @@ FlatLaf Change Log
- Windows DLLs are now digitally signed with FormDev Software GmbH - Windows DLLs are now digitally signed with FormDev Software GmbH
certificate. certificate.
#### Fixed bugs #### Fixed bugs
- FlatLaf window decorations: - FlatLaf window decorations:
@@ -907,7 +659,7 @@ FlatLaf Change Log
- Native window decorations (Windows 10 only): - Native window decorations (Windows 10 only):
- Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357) - Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357)
- When window is initially shown, fill background with window background color - When window is initially shown, fill background with window background color
(instead of white), which avoids flickering in dark themes. (issue #339) (instead of white), which avoids flickering in dark themes. (issue 339)
- When resizing a window at the right/bottom edge, then first fill the new - When resizing a window at the right/bottom edge, then first fill the new
space with the window background color (instead of black) before the layout space with the window background color (instead of black) before the layout
is updated. is updated.

View File

@@ -62,7 +62,7 @@ build script:
artifactId: flatlaf artifactId: flatlaf
version: (see button below) version: (see button below)
Otherwise, download `flatlaf-<version>.jar` here: Otherwise download `flatlaf-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf)
@@ -141,6 +141,7 @@ details and downloads.
Buzz Buzz
---- ----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf 3.1 (and 3.0) announcement on Reddit](https://www.reddit.com/r/java/comments/12xgrsu/flatlaf_31_and_30_swing_look_and_feel/) - [FlatLaf 3.1 (and 3.0) announcement on Reddit](https://www.reddit.com/r/java/comments/12xgrsu/flatlaf_31_and_30_swing_look_and_feel/)
- [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/) - [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/) - [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)
@@ -311,9 +312,6 @@ Applications using FlatLaf
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg) - ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon [BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon
- ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) -
software used to make Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons,
and data packs without programming knowledge
- ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual - ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual
Tabletop for playing role-playing games Tabletop for playing role-playing games
- [MegaMek](https://github.com/MegaMek/megamek), - [MegaMek](https://github.com/MegaMek/megamek),

View File

@@ -18,12 +18,6 @@ import net.ltgt.gradle.errorprone.errorprone
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
val pullRequestNumber = findProperty( "github.event.pull_request.number" )
if( pullRequestNumber != null )
version = "PR-${pullRequestNumber}-SNAPSHOT"
allprojects { allprojects {
version = rootProject.version version = rootProject.version

View File

@@ -45,7 +45,7 @@ public class ReorderJarEntries
// 1st pass: copy .properties files // 1st pass: copy .properties files
copyFiles( zipOutStream, jarFile, name -> name.endsWith( ".properties" ) ); copyFiles( zipOutStream, jarFile, name -> name.endsWith( ".properties" ) );
// 2nd pass: copy other files // 2st pass: copy other files
copyFiles( zipOutStream, jarFile, name -> !name.endsWith( ".properties" ) ); copyFiles( zipOutStream, jarFile, name -> !name.endsWith( ".properties" ) );
} }

View File

@@ -27,7 +27,7 @@ library {
} }
with( linkTask.get() ) { with( linkTask.get() ) {
if( name.contains( "Release" ) ) if( name.contains( "Release" ) )
debuggable = false debuggable.set( false )
} }
} }
} }

View File

@@ -26,7 +26,7 @@ tasks {
// depend on :flatlaf-core:compileJava because it generates the JNI headers // depend on :flatlaf-core:compileJava because it generates the JNI headers
dependsOn( ":flatlaf-core:compileJava" ) dependsOn( ":flatlaf-core:compileJava" )
from( project( ":flatlaf-core" ).layout.buildDirectory.dir( "generated/jni-headers" ) ) from( project( ":flatlaf-core" ).buildDir.resolve( "generated/jni-headers" ) )
into( "src/main/headers" ) into( "src/main/headers" )
include( extension.headers ) include( extension.headers )
filter<org.apache.tools.ant.filters.FixCrLfFilter>( filter<org.apache.tools.ant.filters.FixCrLfFilter>(

View File

@@ -44,34 +44,34 @@ publishing {
pom { pom {
afterEvaluate { afterEvaluate {
this@pom.name = extension.name this@pom.name.set( extension.name )
this@pom.description = extension.description this@pom.description.set( extension.description )
} }
url = "https://github.com/JFormDesigner/FlatLaf" url.set( "https://github.com/JFormDesigner/FlatLaf" )
licenses { licenses {
license { license {
name = "The Apache License, Version 2.0" name.set( "The Apache License, Version 2.0" )
url = "https://www.apache.org/licenses/LICENSE-2.0.txt" url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" )
} }
} }
developers { developers {
developer { developer {
name = "Karl Tauber" name.set( "Karl Tauber" )
organization = "FormDev Software GmbH" organization.set( "FormDev Software GmbH" )
organizationUrl = "https://www.formdev.com/" organizationUrl.set( "https://www.formdev.com/" )
} }
} }
scm { scm {
connection = "scm:git:git://github.com/JFormDesigner/FlatLaf.git" connection.set( "scm:git:git://github.com/JFormDesigner/FlatLaf.git" )
url = "https://github.com/JFormDesigner/FlatLaf" url.set( "https://github.com/JFormDesigner/FlatLaf" )
} }
issueManagement { issueManagement {
system = "GitHub" system.set( "GitHub" )
url = "https://github.com/JFormDesigner/FlatLaf/issues" url.set( "https://github.com/JFormDesigner/FlatLaf/issues" )
} }
} }
@@ -124,7 +124,7 @@ tasks.withType<Sign>().configureEach {
} }
// check whether parallel build is enabled // check whether parallel build is enabled
tasks.withType<AbstractPublishToMaven>().configureEach { tasks.withType<PublishToMavenRepository>().configureEach {
doFirst { doFirst {
if( System.getProperty( "org.gradle.parallel" ) == "true" ) if( System.getProperty( "org.gradle.parallel" ) == "true" )
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." ) throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )

View File

@@ -21,6 +21,6 @@ plugins {
val toolchainJavaVersion = System.getProperty( "toolchain" ) val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() ) { if( !toolchainJavaVersion.isNullOrEmpty() ) {
java.toolchain { java.toolchain {
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion ) languageVersion.set( JavaLanguageVersion.of( toolchainJavaVersion ) )
} }
} }

View File

@@ -27,8 +27,8 @@ plugins {
val sigtest = configurations.create( "sigtest" ) val sigtest = configurations.create( "sigtest" )
dependencies { dependencies {
testImplementation( libs.junit ) testImplementation( libs.bundles.junit )
testRuntimeOnly( libs.junit.launcher ) testRuntimeOnly( libs.junit.engine )
// https://github.com/jtulach/netbeans-apitest // https://github.com/jtulach/netbeans-apitest
sigtest( libs.sigtest ) sigtest( libs.sigtest )
@@ -42,11 +42,11 @@ java {
tasks { tasks {
compileJava { compileJava {
// generate JNI headers // generate JNI headers
options.headerOutputDirectory = layout.buildDirectory.dir( "generated/jni-headers" ) options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) )
} }
jar { jar {
archiveBaseName = "flatlaf" archiveBaseName.set( "flatlaf" )
doLast { doLast {
ReorderJarEntries.reorderJarEntries( outputs.files.singleFile ); ReorderJarEntries.reorderJarEntries( outputs.files.singleFile );
@@ -54,32 +54,11 @@ tasks {
} }
named<Jar>( "sourcesJar" ) { named<Jar>( "sourcesJar" ) {
archiveBaseName = "flatlaf" archiveBaseName.set( "flatlaf" )
} }
named<Jar>( "javadocJar" ) { named<Jar>( "javadocJar" ) {
archiveBaseName = "flatlaf" archiveBaseName.set( "flatlaf" )
}
register<Zip>( "jarNoNatives" ) {
group = "build"
dependsOn( "jar" )
archiveBaseName = "flatlaf"
archiveClassifier = "no-natives"
archiveExtension = "jar"
destinationDirectory = layout.buildDirectory.dir( "libs" )
from( zipTree( jar.get().archiveFile.get().asFile ) )
exclude( "com/formdev/flatlaf/natives/**" )
}
withType<AbstractPublishToMaven>().configureEach {
dependsOn( "jarNoNatives" )
}
withType<Sign>().configureEach {
dependsOn( "jarNoNatives" )
} }
check { check {
@@ -148,13 +127,9 @@ flatlafPublish {
val natives = "src/main/resources/com/formdev/flatlaf/natives" val natives = "src/main/resources/com/formdev/flatlaf/natives"
nativeArtifacts = listOf( nativeArtifacts = listOf(
NativeArtifact( tasks.getByName( "jarNoNatives" ).outputs.files.asPath, "no-natives", "jar" ),
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),
NativeArtifact( "${natives}/libflatlaf-macos-arm64.dylib", "macos-arm64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-macos-x86_64.dylib", "macos-x86_64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ), NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
) )
} }

View File

@@ -1,5 +1,5 @@
#Signature file v4.1 #Signature file v4.1
#Version 3.5.1 #Version 3.2
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType" fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -12,13 +12,7 @@ fld public final static java.lang.String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarBu
fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner" fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect" fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect"
fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption" fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"
fld public final static java.lang.String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder"
fld public final static java.lang.String GLASS_PANE_FULL_HEIGHT = "JRootPane.glassPaneFullHeight" fld public final static java.lang.String GLASS_PANE_FULL_HEIGHT = "JRootPane.glassPaneFullHeight"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"
fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded" fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight" fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth" fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
@@ -29,7 +23,6 @@ fld public final static java.lang.String PLACEHOLDER_TEXT = "JTextField.placehol
fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius" fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"
fld public final static java.lang.String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted" fld public final static java.lang.String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"
fld public final static java.lang.String POPUP_FORCE_HEAVY_WEIGHT = "Popup.forceHeavyWeight" fld public final static java.lang.String POPUP_FORCE_HEAVY_WEIGHT = "Popup.forceHeavyWeight"
fld public final static java.lang.String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth"
fld public final static java.lang.String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight" fld public final static java.lang.String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight"
fld public final static java.lang.String PROGRESS_BAR_SQUARE = "JProgressBar.square" fld public final static java.lang.String PROGRESS_BAR_SQUARE = "JProgressBar.square"
fld public final static java.lang.String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons" fld public final static java.lang.String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons"
@@ -74,11 +67,6 @@ fld public final static java.lang.String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JT
fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight" fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"
fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement" fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"
fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets" fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_AUTO = "auto"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_LEFT = "left"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_NONE = "none"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_RIGHT = "right"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType" fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card" fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined" fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined"
@@ -253,7 +241,7 @@ meth public void setExtraDefaults(java.util.Map<java.lang.String,java.lang.Strin
meth public void uninitialize() meth public void uninitialize()
meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>) meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
supr javax.swing.plaf.basic.BasicLookAndFeel supr javax.swing.plaf.basic.BasicLookAndFeel
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,getUIMethod,getUIMethodInitialized,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -294,9 +282,7 @@ fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.ui
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled" fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange" fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange"
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations" fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
anno 0 java.lang.Deprecated()
fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary" fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
fld public final static java.lang.String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle"
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection" fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection"
fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont" fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations" fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
@@ -630,33 +616,16 @@ hfds alpha,hsl,rgb
CLSS public com.formdev.flatlaf.util.HiDPIUtils CLSS public com.formdev.flatlaf.util.HiDPIUtils
cons public init() cons public init()
innr public abstract interface static DirtyRegionCallback
innr public abstract interface static Painter innr public abstract interface static Painter
innr public static HiDPIRepaintManager
meth public static float computeTextYCorrection(java.awt.Graphics2D) meth public static float computeTextYCorrection(java.awt.Graphics2D)
meth public static java.awt.Graphics2D createGraphicsTextYCorrection(java.awt.Graphics2D) meth public static java.awt.Graphics2D createGraphicsTextYCorrection(java.awt.Graphics2D)
meth public static void addDirtyRegion(javax.swing.JComponent,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback)
meth public static void drawStringUnderlineCharAtWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int,int) meth public static void drawStringUnderlineCharAtWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int,int)
meth public static void drawStringWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int) meth public static void drawStringWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int)
meth public static void installHiDPIRepaintManager()
meth public static void paintAtScale1x(java.awt.Graphics2D,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$Painter) meth public static void paintAtScale1x(java.awt.Graphics2D,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void paintAtScale1x(java.awt.Graphics2D,javax.swing.JComponent,com.formdev.flatlaf.util.HiDPIUtils$Painter) meth public static void paintAtScale1x(java.awt.Graphics2D,javax.swing.JComponent,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void repaint(java.awt.Component)
meth public static void repaint(java.awt.Component,int,int,int,int)
meth public static void repaint(java.awt.Component,java.awt.Rectangle)
supr java.lang.Object supr java.lang.Object
hfds CORRECTION_INTER,CORRECTION_OPEN_SANS,CORRECTION_SEGOE_UI,CORRECTION_TAHOMA,SCALE_FACTORS,useDebugScaleFactor,useTextYCorrection hfds CORRECTION_INTER,CORRECTION_OPEN_SANS,CORRECTION_SEGOE_UI,CORRECTION_TAHOMA,SCALE_FACTORS,useDebugScaleFactor,useTextYCorrection
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback
outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
CLSS public static com.formdev.flatlaf.util.HiDPIUtils$HiDPIRepaintManager
outer com.formdev.flatlaf.util.HiDPIUtils
cons public init()
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
supr javax.swing.RepaintManager
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$Painter CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$Painter
outer com.formdev.flatlaf.util.HiDPIUtils outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double) meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double)
@@ -1155,31 +1124,6 @@ meth public void provideErrorFeedback(java.awt.Component)
meth public void uninitialize() meth public void uninitialize()
supr java.lang.Object supr java.lang.Object
CLSS public javax.swing.RepaintManager
cons public init()
meth public boolean isCompletelyDirty(javax.swing.JComponent)
meth public boolean isDoubleBufferingEnabled()
meth public java.awt.Dimension getDoubleBufferMaximumSize()
meth public java.awt.Image getOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Image getVolatileOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Rectangle getDirtyRegion(javax.swing.JComponent)
meth public java.lang.String toString()
meth public static javax.swing.RepaintManager currentManager(java.awt.Component)
meth public static javax.swing.RepaintManager currentManager(javax.swing.JComponent)
meth public static void setCurrentManager(javax.swing.RepaintManager)
meth public void addDirtyRegion(java.applet.Applet,int,int,int,int)
meth public void addDirtyRegion(java.awt.Window,int,int,int,int)
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
meth public void addInvalidComponent(javax.swing.JComponent)
meth public void markCompletelyClean(javax.swing.JComponent)
meth public void markCompletelyDirty(javax.swing.JComponent)
meth public void paintDirtyRegions()
meth public void removeInvalidComponent(javax.swing.JComponent)
meth public void setDoubleBufferMaximumSize(java.awt.Dimension)
meth public void setDoubleBufferingEnabled(boolean)
meth public void validateInvalidComponents()
supr java.lang.Object
CLSS public abstract javax.swing.border.AbstractBorder CLSS public abstract javax.swing.border.AbstractBorder
cons public init() cons public init()
intf java.io.Serializable intf java.io.Serializable

View File

@@ -33,7 +33,7 @@ public interface FlatClientProperties
//---- JButton ------------------------------------------------------------ //---- JButton ------------------------------------------------------------
/** /**
* Specifies type of 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>
@@ -102,17 +102,6 @@ public interface FlatClientProperties
*/ */
String BUTTON_TYPE_BORDERLESS = "borderless"; String BUTTON_TYPE_BORDERLESS = "borderless";
/**
* 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";
//---- JCheckBox ----------------------------------------------------------
/** /**
* Specifies selected state of a checkbox. * Specifies selected state of a checkbox.
* <p> * <p>
@@ -129,6 +118,14 @@ 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 --------------------------------------------------------- //---- JComponent ---------------------------------------------------------
@@ -260,116 +257,19 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"; String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";
/** /**
* Specifies whether a component shown in a window title bar area should behave as caption * Specifies whether a component in an embedded menu bar should behave as caption
* (left-click allows moving window, right-click shows window system menu). * (left-click allows moving window, right-click shows window system menu).
* The caption component does not receive mouse pressed/released/clicked/dragged events, * The component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events. * but it gets mouse entered/exited/moved events.
* <p> * <p>
* Since 3.4, this client property also supports using a function that can check
* whether a given location in the component should behave as caption.
* Useful for components that do not use mouse input on whole component bounds.
*
* <pre>{@code
* myComponent.putClientProperty( "JComponent.titleBarCaption",
* (Function<Point, Boolean>) pt -> {
* // parameter pt contains mouse location (in myComponent coordinates)
* // return true if the component is not interested in mouse input at the given location
* // return false if the component wants process mouse input at the given location
* // return null if the component children should be checked
* return ...; // check here
* } );
* }</pre>
* <b>Warning</b>:
* <ul>
* <li>This function is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This function is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br> * <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean} or {@link java.util.function.Function}&lt;Point, Boolean&gt; * <strong>Value type</strong> {@link java.lang.Boolean}
* *
* @since 2.5 * @since 2.5
*/ */
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"; String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
//---- Panel --------------------------------------------------------------
/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
* in fullWindowContent mode. See {@link #FULL_WINDOW_CONTENT}.
* <p>
* If fullWindowContent mode is enabled, the preferred size of the panel is equal
* to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}.
* <p>
* You're responsible to layout that panel at the top-left or top-right corner,
* depending on platform, where the iconfify/maximize/close buttons are located.
* <p>
* Syntax of the value string is: {@code "win|mac [horizontal|vertical] [zeroInFullScreen] [leftToRight|rightToLeft]"}.
* <p>
* The string must start with {@code "win"} (for Windows or Linux) or
* with {@code "mac"} (for macOS) and specifies the platform where the placeholder
* should be used. On macOS, you need the placeholder in the top-left corner,
* but on Windows/Linux you need it in the top-right corner. So if your application supports
* fullWindowContent mode on both platforms, you can add two placeholders to your layout
* and FlatLaf automatically uses only one of them. The other gets size {@code 0,0}.
* <p>
* Optionally, you can append following options to the value string (separated by space characters):
* <ul>
* <li>{@code "horizontal"} - preferred height is zero
* <li>{@code "vertical"} - preferred width is zero
* <li>{@code "zeroInFullScreen"} - in full-screen mode on macOS, preferred size is {@code 0,0}
* <li>{@code "leftToRight"} - in right-to-left component orientation, preferred size is {@code 0,0}
* <li>{@code "rightToLeft"} - in left-to-right component orientation, preferred size is {@code 0,0}
* </ul>
*
* Example for adding placeholder to top-left corner on macOS:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* // add tool bar items
*
* JPanel toolBarPanel = new JPanel( new BorderLayout() );
* toolBarPanel.add( placeholder, BorderLayout.WEST );
* toolBarPanel.add( toolBar, BorderLayout.CENTER );
*
* frame.getContentPane().add( toolBarPanel, BorderLayout.NORTH );
* }</pre>
*
* Or add placeholder as first item to the tool bar:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* toolBar.add( placeholder );
* // add tool bar items
*
* frame.getContentPane().add( toolBar, BorderLayout.NORTH );
* }</pre>
*
* If a tabbed pane is located at the top, you can add the placeholder
* as leading component to that tabbed pane:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_LEADING_COMPONENT, placeholder );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JPanel}<br>
* <strong>Value type</strong> {@link java.lang.String}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder";
//---- Popup -------------------------------------------------------------- //---- Popup --------------------------------------------------------------
/** /**
@@ -378,13 +278,12 @@ public interface FlatClientProperties
* <p> * <p>
* Note that this is not available on all platforms since it requires special support. * Note that this is not available on all platforms since it requires special support.
* Supported platforms: * Supported platforms:
* <ul> * <p>
* <li><strong>Windows 11</strong>: Only two corner radiuses are supported * <strong>Windows 11</strong> (x86 or x86_64): Only two corner radiuses are supported
* by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px. * by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px.
* If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used. * If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used.
* If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used. * If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used.
* <li><strong>macOS</strong> (10.14 and later): Any corner radius is supported. * <p>
* </ul>
* <strong>Component</strong> {@link javax.swing.JComponent}<br> * <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br> * <strong>Value type</strong> {@link java.lang.Integer}<br>
* *
@@ -392,24 +291,6 @@ public interface FlatClientProperties
*/ */
String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"; String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius";
/**
* Specifies the popup rounded border width 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>
* Only used if popup uses rounded border.
* <p>
* Note that this is not available on all platforms since it requires special support.
* Supported platforms:
* <ul>
* <li><strong>macOS</strong> (10.14 and later)
* </ul>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.Float}<br>
*
* @since 3.3
*/
String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth";
/** /**
* Specifies whether a drop shadow is painted if the component is shown in a 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. * or if the component is the owner of another component that is shown in a popup.
@@ -488,46 +369,6 @@ public interface FlatClientProperties
*/ */
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the content pane (and the glass pane) should be extended
* into the window title bar area
* (requires enabled window decorations). Default is {@code false}.
* <p>
* On macOS, use client property {@code apple.awt.fullWindowContent}
* (see <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">macOS Full window content</a>).
* <p>
* Setting this enables/disables full window content
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* If {@code true}, the content pane (and the glass pane) is extended into
* the title bar area. The window icon and title are hidden.
* Only the iconfify/maximize/close buttons stay visible in the upper right corner
* (and overlap the content pane).
* <p>
* The user can left-click-and-drag on the title bar area to move the window,
* except when clicking on a component that processes mouse events (e.g. buttons or menus).
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent";
/**
* Contains the current bounds of the iconfify/maximize/close buttons
* (in root pane coordinates) if fullWindowContent mode is enabled.
* Otherwise its value is {@code null}.
* <p>
* <b>Note</b>: Do not set this client property. It is set by FlatLaf.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Rectangle}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds";
/** /**
* Specifies whether the window icon should be shown in the window title bar * Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}. * (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}.
@@ -561,10 +402,10 @@ public interface FlatClientProperties
String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle"; String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle";
/** /**
* Specifies whether the "iconify" button should be shown in the window title bar * Specifies whether the "iconfify" button should be shown in the window title bar
* (requires enabled window decorations). Default is {@code true}. * (requires enabled window decorations). Default is {@code true}.
* <p> * <p>
* Setting this shows/hides the "iconify" button * Setting this shows/hides the "iconfify" button
* for the {@code JFrame} that contains the root pane. * for the {@code JFrame} that contains the root pane.
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br> * <strong>Component</strong> {@link javax.swing.JRootPane}<br>
@@ -646,7 +487,7 @@ public interface FlatClientProperties
* On macOS, Java supports this out of the box. * On macOS, Java supports this out of the box.
* <p> * <p>
* Note that this client property must be set before the window becomes displayable. * Note that this client property must be set before the window becomes displayable.
* Otherwise, an {@link IllegalComponentStateException} is thrown. * Otherwise an {@link IllegalComponentStateException} is thrown.
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br> * <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br> * <strong>Value type</strong> {@link java.lang.String}<br>
@@ -987,7 +828,7 @@ public interface FlatClientProperties
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br> * <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br> * <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong> * <strong>Allowed Values</strong>
* {@link SwingConstants#LEADING} (default), * {@link SwingConstants#LEADING} (default)
* {@link SwingConstants#TRAILING}, * {@link SwingConstants#TRAILING},
* {@link SwingConstants#CENTER}, * {@link SwingConstants#CENTER},
* {@link #TABBED_PANE_ALIGN_LEADING} (default), * {@link #TABBED_PANE_ALIGN_LEADING} (default),
@@ -1091,59 +932,6 @@ public interface FlatClientProperties
*/ */
String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"; String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement";
/**
* Specifies the rotation of the tabs (title, icon, etc.).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link SwingConstants#LEFT},
* {@link SwingConstants#RIGHT},
* {@link #TABBED_PANE_TAB_ROTATION_NONE} (default),
* {@link #TABBED_PANE_TAB_ROTATION_AUTO},
* {@link #TABBED_PANE_TAB_ROTATION_LEFT} or
* {@link #TABBED_PANE_TAB_ROTATION_RIGHT}
*
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation";
/**
* Tabs are not rotated.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_NONE = "none";
/**
* Tabs are rotated depending on tab placement.
* <p>
* For top and bottom tab placement, the tabs are not rotated.<br>
* For left tab placement, the tabs are rotated counter-clockwise.<br>
* For right tab placement, the tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_AUTO = "auto";
/**
* Tabs are rotated counter-clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_LEFT = "left";
/**
* Tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_RIGHT = "right";
/** /**
* Specifies a component that will be placed at the leading edge of the tabs area. * Specifies a component that will be placed at the leading edge of the tabs area.
* <p> * <p>
@@ -1348,8 +1136,8 @@ public interface FlatClientProperties
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br> * <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br> * <strong>Value type</strong> {@link java.lang.Integer}<br>
* <strong>Allowed Values</strong> * <strong>SupportedValues:</strong>
* {@link SwingConstants#BOTTOM} (default), * {@link SwingConstants#BOTTOM} (default)
* {@link SwingConstants#TOP}, * {@link SwingConstants#TOP},
* {@link SwingConstants#LEFT} or * {@link SwingConstants#LEFT} or
* {@link SwingConstants#RIGHT} * {@link SwingConstants#RIGHT}
@@ -1403,44 +1191,6 @@ public interface FlatClientProperties
String TREE_PAINT_SELECTION = "JTree.paintSelection"; String TREE_PAINT_SELECTION = "JTree.paintSelection";
//---- macOS --------------------------------------------------------------
/**
* Specifies the spacing around the macOS window close/minimize/zoom buttons.
* Useful if <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">full window content</a>
* is enabled.
* <p>
* (requires macOS 10.14+ for "medium" spacing and macOS 11+ for "large" spacing, requires Java 17+)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #MACOS_WINDOW_BUTTONS_SPACING_MEDIUM} or
* {@link #MACOS_WINDOW_BUTTONS_SPACING_LARGE} (requires macOS 11+)
*
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing";
/**
* Add medium spacing around the macOS window close/minimize/zoom buttons.
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium";
/**
* Add large spacing around the macOS window close/minimize/zoom buttons.
* <p>
* (requires macOS 11+; "medium" is used on older systems)
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large";
//---- helper methods ----------------------------------------------------- //---- helper methods -----------------------------------------------------
/** /**

View File

@@ -48,7 +48,7 @@ public abstract class FlatDefaultsAddon
public InputStream getDefaults( Class<?> lafClass ) { public InputStream getDefaults( Class<?> lafClass ) {
Class<?> addonClass = this.getClass(); Class<?> addonClass = this.getClass();
String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' ) String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' )
+ '/' + UIDefaultsLoader.simpleClassName( lafClass ) + ".properties"; + '/' + lafClass.getSimpleName() + ".properties";
return addonClass.getResourceAsStream( propertiesName ); return addonClass.getResourceAsStream( propertiesName );
} }

View File

@@ -71,7 +71,7 @@ class FlatInputMaps
); );
} }
// join ltr and rtl bindings to fix up/down/etc. keys in right-to-left component orientation // join ltr and rtl bindings to fix up/down/etc keys in right-to-left component orientation
Object[] bindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings" ); Object[] bindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings" );
Object[] rtlBindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings.RightToLeft" ); Object[] rtlBindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings.RightToLeft" );
if( bindings != null && rtlBindings != null ) { if( bindings != null && rtlBindings != null ) {

View File

@@ -30,6 +30,9 @@ import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.File; import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@@ -75,7 +78,6 @@ import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory; import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI; import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JavaCompatibility2;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.GrayFilter;
@@ -181,11 +183,17 @@ public abstract class FlatLaf
* This depends on the operating system and on the used Java runtime. * This depends on the operating system and on the used Java runtime.
* <p> * <p>
* This method returns {@code true} on Windows 10/11 (see exception below) * This method returns {@code true} on Windows 10/11 (see exception below)
* and on Linux, otherwise returns {@code false}. * and on Linux, {@code false} otherwise.
* <p>
* Returns also {@code false} on Windows 10/11 if
* FlatLaf native window border support is available (requires Windows 10/11).
* <p> * <p>
* Returns also {@code false} on Windows 10/11 if:
* <ul>
* <li>FlatLaf native window border support is available (requires Windows 10/11)</li>
* <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
* and JBR supports custom window decorations
* </li>
* </ul>
* In these cases, custom decorations are enabled by the root pane. * In these cases, custom decorations are enabled by the root pane.
* Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or * Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or
* {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary. * {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary.
@@ -1287,8 +1295,8 @@ public abstract class FlatLaf
* @since 2.5 * @since 2.5
*/ */
public static Map<String, Class<?>> getStyleableInfos( JComponent c ) { public static Map<String, Class<?>> getStyleableInfos( JComponent c ) {
ComponentUI ui = JavaCompatibility2.getUI( c ); StyleableUI ui = getStyleableUI( c );
return (ui instanceof StyleableUI) ? ((StyleableUI)ui).getStyleableInfos( c ) : null; return (ui != null) ? ui.getStyleableInfos( c ) : null;
} }
/** /**
@@ -1300,10 +1308,41 @@ public abstract class FlatLaf
*/ */
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public static <T> T getStyleableValue( JComponent c, String key ) { public static <T> T getStyleableValue( JComponent c, String key ) {
ComponentUI ui = JavaCompatibility2.getUI( c ); StyleableUI ui = getStyleableUI( c );
return (ui instanceof StyleableUI) ? (T) ((StyleableUI)ui).getStyleableValue( c, key ) : null; return (ui != null) ? (T) ui.getStyleableValue( c, key ) : null;
} }
private static StyleableUI getStyleableUI( JComponent c ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
if( SystemInfo.isJava_9_orLater ) {
try {
// JComponent.getUI() is available since Java 9
getUIMethod = MethodHandles.lookup().findVirtual( JComponent.class, "getUI",
MethodType.methodType( ComponentUI.class ) );
} catch( Exception ex ) {
// ignore
}
}
}
try {
Object ui;
if( getUIMethod != null )
ui = getUIMethod.invoke( c );
else
ui = c.getClass().getMethod( "getUI" ).invoke( c );
return (ui instanceof StyleableUI) ? (StyleableUI) ui : null;
} catch( Throwable ex ) {
// ignore
return null;
}
}
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/** /**
* Returns the preferred font family to be used for (nearly) all fonts; or {@code null}. * Returns the preferred font family to be used for (nearly) all fonts; or {@code null}.
* *

View File

@@ -103,10 +103,7 @@ public interface FlatSystemProperties
* <p> * <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br> * <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1) * <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*
* @deprecated No longer used since FlatLaf 3.3. Retained for API compatibility.
*/ */
@Deprecated
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"; String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/** /**
@@ -135,6 +132,14 @@ public interface FlatSystemProperties
*/ */
String ANIMATION = "flatlaf.animation"; String ANIMATION = "flatlaf.animation";
/**
* Specifies whether smooth scrolling is enabled.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String SMOOTH_SCROLLING = "flatlaf.smoothScrolling";
/** /**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens. * Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p> * <p>
@@ -172,47 +177,24 @@ public interface FlatSystemProperties
String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"; String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary";
/** /**
* Specifies a directory in which the FlatLaf native libraries are searched for. * Specifies a directory in which the native FlatLaf libraries have been extracted.
* The path can be absolute or relative to current application working directory. * The path can be absolute or relative to current application working directory.
* This can be used to avoid extraction of the native libraries to the temporary directory at runtime. * This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p> * <p>
* If the value is {@code "system"} (supported since FlatLaf 2.6), * If the value is {@code "system"}, then {@link System#loadLibrary(String)} is
* then {@link System#loadLibrary(String)} is used to load the native library. * used to load the native library.
* This searches for the native library in classloader of caller * Searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified * (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}. * in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p> * <p>
* If the native library can not be loaded from the given path (or via {@link System#loadLibrary(String)}), * If the native library can not loaded from the given path (or via {@link System#loadLibrary(String)}),
* then the embedded native library is extracted to the temporary directory and loaded from there. * then the embedded native library is extracted to the temporary directory and loaded from there.
* <p>
* The file names of the native libraries must be either:
* <ul>
* <li>the same as in flatlaf.jar in package 'com/formdev/flatlaf/natives' (required for "system") or
* <li>when downloaded from Maven central then as described here:
* <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* (requires FlatLaf 3.4)
* </ul>
* <p>
* <strong>Note</strong>: Since FlatLaf 3.1 it is recommended to download the
* FlatLaf native libraries from Maven central and distribute them with your
* application in the same directory as flatlaf.jar.
* Then it is <strong>not necessary</strong> to set this system property.
* See <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* for details.
* *
* @since 2 * @since 2
*/ */
String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"; String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath";
/**
* Specifies whether safe triangle is used to improve usability of submenus.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
* @since 3.5.1
*/
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
/** /**
* Checks whether a system property is set and returns {@code true} if its value * Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}. * is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

View File

@@ -203,7 +203,7 @@ class LinuxFontPolicy
* Gets the default font for KDE from KDE configuration files. * Gets the default font for KDE from KDE configuration files.
* *
* The Swing fonts are not updated when the user changes system font size * The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). An application restart is necessary. * (System Settings > Fonts > Force Font DPI). A application restart is necessary.
* This is the same behavior as in native KDE applications. * This is the same behavior as in native KDE applications.
* *
* The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used * The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used

View File

@@ -45,7 +45,6 @@ import javax.swing.UIManager;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/** /**
* Improves usability of submenus by using a * Improves usability of submenus by using a
@@ -65,7 +64,6 @@ class SubMenuUsabilityHelper
// https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607 // https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607
private static SubMenuUsabilityHelper instance; private static SubMenuUsabilityHelper instance;
private boolean eventQueuePushNotSupported;
private SubMenuEventQueue subMenuEventQueue; private SubMenuEventQueue subMenuEventQueue;
private SafeTrianglePainter safeTrianglePainter; private SafeTrianglePainter safeTrianglePainter;
private boolean changePending; private boolean changePending;
@@ -85,9 +83,6 @@ class SubMenuUsabilityHelper
if( instance != null ) if( instance != null )
return false; return false;
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SUB_MENU_SAFE_TRIANGLE, true ) )
return false;
instance = new SubMenuUsabilityHelper(); instance = new SubMenuUsabilityHelper();
MenuSelectionManager.defaultManager().addChangeListener( instance ); MenuSelectionManager.defaultManager().addChangeListener( instance );
return true; return true;
@@ -104,7 +99,7 @@ class SubMenuUsabilityHelper
@Override @Override
public void stateChanged( ChangeEvent e ) { public void stateChanged( ChangeEvent e ) {
if( eventQueuePushNotSupported || !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true )) if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
return; return;
// handle menu selection change later, but only once in case of temporary changes // handle menu selection change later, but only once in case of temporary changes
@@ -177,30 +172,9 @@ debug*/
targetTopY = popupLocation.y; targetTopY = popupLocation.y;
targetBottomY = popupLocation.y + popupSize.height; targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to suppress mouse events when mouse is moved within safe triangle // install own event queue to supress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null ) { if( subMenuEventQueue == null )
SubMenuEventQueue queue = new SubMenuEventQueue(); subMenuEventQueue = new SubMenuEventQueue();
try {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.getSystemEventQueue().push( queue );
// check whether push() worked
// (e.g. SWTSwing uses own event queue that does not support push())
if( toolkit.getSystemEventQueue() != queue ) {
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", null );
return;
}
subMenuEventQueue = queue;
} catch( RuntimeException ex ) {
// catch runtime exception from EventQueue.push()
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", ex );
return;
}
}
// create safe triangle painter // create safe triangle painter
if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) ) if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) )
@@ -273,6 +247,8 @@ debug*/
} }
} ); } );
timeoutTimer.setRepeats( false ); timeoutTimer.setRepeats( false );
Toolkit.getDefaultToolkit().getSystemEventQueue().push( this );
} }
void uninstall() { void uninstall() {

View File

@@ -161,7 +161,7 @@ class UIDefaultsLoader
classLoader = FlatLaf.class.getClassLoader(); classLoader = FlatLaf.class.getClassLoader();
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties"; String propertiesName = packageName + '/' + lafClass.getSimpleName() + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) { try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null ) if( in != null )
properties.load( in ); properties.load( in );
@@ -171,7 +171,7 @@ class UIDefaultsLoader
// load from package URL // load from package URL
URL packageUrl = (URL) source; URL packageUrl = (URL) source;
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" ); URL propertiesUrl = new URL( packageUrl + lafClass.getSimpleName() + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) { try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in ); properties.load( in );
@@ -183,7 +183,7 @@ class UIDefaultsLoader
// load from folder // load from folder
File folder = (File) source; File folder = (File) source;
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
File propertiesFile = new File( folder, simpleClassName( lafClass ) + ".properties" ); File propertiesFile = new File( folder, lafClass.getSimpleName() + ".properties" );
if( !propertiesFile.isFile() ) if( !propertiesFile.isFile() )
continue; continue;
@@ -294,14 +294,6 @@ class UIDefaultsLoader
} }
} }
/**
* Similar to Class.getSimpleName(), but includes enclosing class for nested classes.
*/
static String simpleClassName( Class<?> cls ) {
String className = cls.getName();
return className.substring( className.lastIndexOf( '.' ) + 1 );
}
static void logParseError( String key, String value, RuntimeException ex, boolean severe ) { static void logParseError( String key, String value, RuntimeException ex, boolean severe ) {
String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\''; String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\'';
if( severe ) if( severe )
@@ -638,18 +630,14 @@ class UIDefaultsLoader
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]] // Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' ); List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty()) ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null; : null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f;
? parseFloat( parts.get( 5 ) ) int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0;
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null || arc > 0) return (lineColor != null)
? new FlatLineBorder( insets, lineColor, lineThickness, arc ) ? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };

View File

@@ -90,7 +90,7 @@ public class FlatTabbedPaneCloseIcon
closeSize.width, closeSize.height, closeArc, closeArc ); closeSize.width, closeSize.height, closeArc, closeArc );
} }
// set color of cross // set cross color
Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground ); Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground );
g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) ); g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) );

View File

@@ -57,11 +57,11 @@ public class FlatTreeOpenIcon
double arc = 1.5; double arc = 1.5;
double arc2 = 0.5; double arc2 = 0.5;
path = FlatUIUtils.createPath( false, path = FlatUIUtils.createPath( false,
// bottom-left of opened part // bottom-left of opend part
2,13.5, 2,13.5,
// top-left of opened part // top-left of opend part
FlatUIUtils.ROUNDED, 4.5,7.5, arc, FlatUIUtils.ROUNDED, 4.5,7.5, arc,
// top-right of opened part // top-right of opend part
FlatUIUtils.ROUNDED, 15.5,7.5, arc2, FlatUIUtils.ROUNDED, 15.5,7.5, arc2,
// bottom-right // bottom-right

View File

@@ -71,7 +71,7 @@ public abstract class FlatWindowAbstractIcon
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 ) {
// disable antialiasing for background rectangle painting to avoid blurry edges when scaled (e.g. at 125% or 175%) // disable antialiasing for background rectangle painting to avoid blury edges when scaled (e.g. at 125% or 175%)
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING ); Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF ); g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );

View File

@@ -28,6 +28,7 @@ import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JSpinner; import javax.swing.JSpinner;
import javax.swing.JViewport;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders; import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -135,7 +136,7 @@ public class FlatBorder
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c ); Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc, focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null, c instanceof JScrollPane ); focusColor, borderColor, null );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
@@ -194,7 +195,8 @@ public class FlatBorder
protected boolean isEnabled( Component c ) { protected boolean isEnabled( Component c ) {
if( c instanceof JScrollPane ) { if( c instanceof JScrollPane ) {
// check whether view component is disabled // check whether view component is disabled
Component view = FlatScrollPaneUI.getView( (JScrollPane) c ); JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view != null && !isEnabled( view ) ) if( view != null && !isEnabled( view ) )
return false; return false;
} }
@@ -277,7 +279,7 @@ public class FlatBorder
} }
/** /**
* Returns the (unscaled) arc diameter of the border corners. * Returns the (unscaled) arc diameter of the border.
*/ */
protected int getArc( Component c ) { protected int getArc( Component c ) {
return 0; return 0;

View File

@@ -42,13 +42,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.disabledBorderColor Color * @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color * @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional * @uiDefault Button.hoverBorderColor Color optional
* @uiDefault Button.pressedBorderColor Color optional
*
* @uiDefault Button.selectedBorderColor Color optional
* @uiDefault Button.disabledSelectedBorderColor Color optional
* @uiDefault Button.focusedSelectedBorderColor Color optional
* @uiDefault Button.hoverSelectedBorderColor Color optional
* @uiDefault Button.pressedSelectedBorderColor Color optional
* *
* @uiDefault Button.default.borderWidth int or float * @uiDefault Button.default.borderWidth int or float
* @uiDefault Button.default.borderColor Color * @uiDefault Button.default.borderColor Color
@@ -56,7 +49,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used * @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.focusedBorderColor Color * @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color * @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.pressedBorderColor Color optional
* @uiDefault Button.default.hoverBorderColor Color optional * @uiDefault Button.default.hoverBorderColor Color optional
* *
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5 * @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5
@@ -73,13 +65,6 @@ public class FlatButtonBorder
protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" ); protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" ); @Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedBorderColor = UIManager.getColor( "Button.pressedBorderColor" );
/** @since 3.5 */ @Styleable protected Color selectedBorderColor = UIManager.getColor( "Button.selectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color disabledSelectedBorderColor = UIManager.getColor( "Button.disabledSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color focusedSelectedBorderColor = UIManager.getColor( "Button.focusedSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color hoverSelectedBorderColor = UIManager.getColor( "Button.hoverSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedSelectedBorderColor = UIManager.getColor( "Button.pressedSelectedBorderColor" );
@Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 ); @Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 );
@Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" ); @Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
@@ -87,7 +72,6 @@ public class FlatButtonBorder
@Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); @Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
@Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); @Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" ); @Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
/** @since 3.5 */ @Styleable(dot=true) protected Color defaultPressedBorderColor = UIManager.getColor( "Button.default.pressedBorderColor" );
/** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f ); /** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
/** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" ); /** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@@ -155,13 +139,12 @@ public class FlatButtonBorder
@Override @Override
protected Paint getBorderColor( Component c ) { protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c ); boolean def = FlatButtonUI.isDefaultButton( c );
boolean selected = (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
Paint color = FlatButtonUI.buttonStateColor( c, Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : ((selected && selectedBorderColor != null) ? selectedBorderColor : borderColor), def ? defaultBorderColor : borderColor,
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor, disabledBorderColor,
def ? defaultFocusedBorderColor : ((selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor), def ? defaultFocusedBorderColor : focusedBorderColor,
def ? defaultHoverBorderColor : ((selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor), def ? defaultHoverBorderColor : hoverBorderColor,
def ? defaultPressedBorderColor : ((selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor) ); null );
// change to gradient paint if start/end colors are specified // change to gradient paint if start/end colors are specified
Color startBg = def ? defaultBorderColor : borderColor; Color startBg = def ? defaultBorderColor : borderColor;

View File

@@ -29,7 +29,6 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.util.Map; import java.util.Map;
@@ -54,15 +53,12 @@ import javax.swing.plaf.ToolBarUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI; import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon; import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -302,10 +298,6 @@ public class FlatButtonUI
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case SQUARE_SIZE: case SQUARE_SIZE:
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
case MINIMUM_HEIGHT: case MINIMUM_HEIGHT:
@@ -314,11 +306,11 @@ public class FlatButtonUI
case BUTTON_TYPE: case BUTTON_TYPE:
b.revalidate(); b.revalidate();
HiDPIUtils.repaint( b ); b.repaint();
break; break;
case OUTLINE: case OUTLINE:
HiDPIUtils.repaint( b ); b.repaint();
break; break;
case STYLE: case STYLE:
@@ -330,7 +322,7 @@ public class FlatButtonUI
} else } else
installStyle( b ); installStyle( b );
b.revalidate(); b.revalidate();
HiDPIUtils.repaint( b ); b.repaint();
break; break;
} }
} }
@@ -559,52 +551,9 @@ public class FlatButtonUI
} }
} }
/**
* Similar to BasicButtonUI.paint(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
@Override @Override
public void paint( Graphics g, JComponent c ) { public void paint( Graphics g, JComponent c ) {
g = FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ); super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
AbstractButton b = (AbstractButton) c;
// layout
String clippedText = layout( b, b.getFontMetrics( b.getFont() ), b.getWidth(), b.getHeight() );
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
clearTextShiftOffset();
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
ButtonModel model = b.getModel();
if( model.isArmed() && model.isPressed() )
paintButtonPressed( g, b );
// paint icon
if( b.getIcon() != null )
paintIcon( g, b, iconR );
// paint text
if( clippedText != null && !clippedText.isEmpty() ) {
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// update foreground color in HTML view, which is necessary
// for selected and pressed states
// (only for enabled buttons, because UIManager.getColor("textInactiveText")
// is used for disabled components; see: javax.swing.text.GlyphView.paint())
if( b.isEnabled() )
FlatHTML.updateRendererCSSForeground( view, getForeground( b ) );
view.paint( g, textR ); // HTML text
} else
paintText( g, b, textR, clippedText );
}
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
if( b.isFocusPainted() && b.hasFocus() )
paintFocus( g, b, viewR, textR, iconR );
} }
@Override @Override
@@ -644,6 +593,8 @@ public class FlatButtonUI
} }
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 ) {
if(foreground == null)
foreground=Color.red;
FontMetrics fm = b.getFontMetrics( b.getFont() ); FontMetrics fm = b.getFontMetrics( b.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1; int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -732,15 +683,14 @@ public class FlatButtonUI
} }
protected Color getForeground( JComponent c ) { protected Color getForeground( JComponent c ) {
Color fg = c.getForeground();
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c ); boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state // selected state
if( ((AbstractButton)c).isSelected() ) { if( ((AbstractButton)c).isSelected() ) {
return buttonStateColor( c, return buttonStateColor( c,
toolBarButton toolBarButton
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : fg) ? (toolbarSelectedForeground != null ? toolbarSelectedForeground : c.getForeground())
: (isCustomForeground( fg ) ? fg : selectedForeground), : selectedForeground,
toolBarButton toolBarButton
? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText) ? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText)
: (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText), : (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText),
@@ -752,7 +702,7 @@ public class FlatButtonUI
// toolbar button // toolbar button
if( toolBarButton ) { if( toolBarButton ) {
return buttonStateColor( c, return buttonStateColor( c,
fg, c.getForeground(),
disabledText, disabledText,
null, null,
toolbarHoverForeground, toolbarHoverForeground,
@@ -763,7 +713,7 @@ public class FlatButtonUI
return buttonStateColor( c, return buttonStateColor( c,
getForegroundBase( c, def ), getForegroundBase( c, def ),
disabledText, disabledText,
isCustomForeground( fg ) ? null : (def ? defaultFocusedForeground : focusedForeground), isCustomForeground( c.getForeground() ) ? null : (def ? defaultFocusedForeground : focusedForeground),
def ? defaultHoverForeground : hoverForeground, def ? defaultHoverForeground : hoverForeground,
def ? defaultPressedForeground : pressedForeground ); def ? defaultPressedForeground : pressedForeground );
} }
@@ -836,67 +786,6 @@ public class FlatButtonUI
return margin instanceof UIResource && Objects.equals( margin, defaultMargin ); return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
} }
@Override
public int getBaseline( JComponent c, int width, int height ) {
return getBaselineImpl( c, width, height );
}
/**
* Similar to BasicButtonUI.getBaseline(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
static int getBaselineImpl( JComponent c, int width, int height ) {
if( width < 0 || height < 0 )
throw new IllegalArgumentException();
AbstractButton b = (AbstractButton) c;
String text = b.getText();
if( text == null || text.isEmpty() )
return -1;
FontMetrics fm = b.getFontMetrics( b.getFont() );
layout( b, fm, width, height );
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// HTML text
int baseline = BasicHTML.getHTMLBaseline( view, textR.width, textR.height );
return (baseline >= 0) ? textR.y + baseline : baseline;
} else
return textR.y + fm.getAscent();
}
/**
* Similar to BasicButtonUI.layout(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
private static String layout( AbstractButton b, FontMetrics fm, int width, int height ) {
// compute view rectangle
Insets insets = b.getInsets();
viewR.setBounds( insets.left, insets.top,
width - insets.left - insets.right,
height - insets.top - insets.bottom );
// reset rectangles
textR.setBounds( 0, 0, 0, 0 );
iconR.setBounds( 0, 0, 0, 0 );
String text = b.getText();
return SwingUtilities.layoutCompoundLabel( b, fm, text, b.getIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
viewR, iconR, textR,
(text != null) ? b.getIconTextGap() : 0 );
}
private static Rectangle viewR = new Rectangle();
private static Rectangle textR = new Rectangle();
private static Rectangle iconR = new Rectangle();
//---- class FlatButtonListener ------------------------------------------- //---- class FlatButtonListener -------------------------------------------
protected class FlatButtonListener protected class FlatButtonListener
@@ -917,7 +806,7 @@ public class FlatButtonUI
@Override @Override
public void stateChanged( ChangeEvent e ) { public void stateChanged( ChangeEvent e ) {
HiDPIUtils.repaint( b ); super.stateChanged( e );
// if button is in toolbar, repaint button groups // if button is in toolbar, repaint button groups
AbstractButton b = (AbstractButton) e.getSource(); AbstractButton b = (AbstractButton) e.getSource();
@@ -929,17 +818,5 @@ public class FlatButtonUI
((FlatToolBarUI)ui).repaintButtonGroup( b ); ((FlatToolBarUI)ui).repaintButtonGroup( b );
} }
} }
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( b );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( b );
}
} }
} }

View File

@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel; 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;
@@ -103,23 +102,13 @@ public class FlatCheckBoxMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener( return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -78,7 +78,6 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -221,7 +220,7 @@ public class FlatComboBoxUI
private void repaintArrowButton() { private void repaintArrowButton() {
if( arrowButton != null && !comboBox.isEditable() ) if( arrowButton != null && !comboBox.isEditable() )
HiDPIUtils.repaint( arrowButton ); arrowButton.repaint();
} }
}; };
comboBox.addMouseListener( hoverListener ); comboBox.addMouseListener( hoverListener );
@@ -352,15 +351,15 @@ public class FlatComboBoxUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
if( comboBox != null ) if( comboBox != null && comboBox.isEditable() )
HiDPIUtils.repaint( comboBox ); comboBox.repaint();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
if( comboBox != null ) if( comboBox != null && comboBox.isEditable() )
HiDPIUtils.repaint( comboBox ); comboBox.repaint();
} }
}; };
} }
@@ -387,12 +386,12 @@ public class FlatComboBoxUI
switch( propertyName ) { switch( propertyName ) {
case PLACEHOLDER_TEXT: case PLACEHOLDER_TEXT:
if( editor != null ) if( editor != null )
HiDPIUtils.repaint( editor ); editor.repaint();
break; break;
case COMPONENT_ROUND_RECT: case COMPONENT_ROUND_RECT:
case OUTLINE: case OUTLINE:
HiDPIUtils.repaint( comboBox ); comboBox.repaint();
break; break;
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
@@ -403,7 +402,7 @@ public class FlatComboBoxUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
comboBox.revalidate(); comboBox.revalidate();
HiDPIUtils.repaint( comboBox ); comboBox.repaint();
break; break;
} }
} }
@@ -927,7 +926,7 @@ public class FlatComboBoxUI
protected void configurePopup() { protected void configurePopup() {
super.configurePopup(); super.configurePopup();
// make opaque to avoid that background shines through border (e.g. at 150% scaling) // make opaque to avoid that background shines thru border (e.g. at 150% scaling)
setOpaque( true ); setOpaque( true );
// set popup border // set popup border
@@ -945,7 +944,7 @@ public class FlatComboBoxUI
if( popupBackground != null ) if( popupBackground != null )
list.setBackground( popupBackground ); list.setBackground( popupBackground );
// set popup background because it may shine through when scaled (e.g. at 150%) // set popup background because it may shine thru when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making // use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview // popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) ); setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
@@ -1091,7 +1090,7 @@ public class FlatComboBoxUI
} }
// using synchronized to avoid problems with code that modifies combo box // using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc.) not on AWT thread (which should be not done) // (model, selection, etc) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) { synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) ) if( !(c instanceof JComponent) )
return; return;
@@ -1243,7 +1242,7 @@ public class FlatComboBoxUI
* Key selection manager that delegates to the default manager. * Key selection manager that delegates to the default manager.
* Shows the popup if Space key is pressed and "typed characters" buffer is empty. * Shows the popup if Space key is pressed and "typed characters" buffer is empty.
* If items contain spaces (e.g. "a b") it is still possible to select them * If items contain spaces (e.g. "a b") it is still possible to select them
* by pressing keys 'a', 'Space' and 'b'. * by pressing keys a, Space and b.
*/ */
private class FlatKeySelectionManager private class FlatKeySelectionManager
implements JComboBox.KeySelectionManager, UIResource implements JComboBox.KeySelectionManager, UIResource

View File

@@ -31,6 +31,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicEditorPaneUI; import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.Caret; import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
@@ -145,6 +146,21 @@ public class FlatEditorPaneUI
focusListener = null; focusListener = null;
} }
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
installKeyboardActions( getComponent() );
}
static void installKeyboardActions( JTextComponent c ) {
FlatScrollPaneUI.installSmoothScrollingDelegateActions( c, false,
/* page-down */ DefaultEditorKit.pageDownAction, // PAGE_DOWN
/* page-up */ DefaultEditorKit.pageUpAction, // PAGE_UP
/* DefaultEditorKit.selectionPageDownAction */ "selection-page-down", // shift PAGE_DOWN
/* DefaultEditorKit.selectionPageUpAction */ "selection-page-up" // shift PAGE_UP
);
}
@Override @Override
protected Caret createCaret() { protected Caret createCaret() {
return new FlatCaret( null, false ); return new FlatCaret( null, false );
@@ -159,6 +175,11 @@ public class FlatEditorPaneUI
super.propertyChange( e ); super.propertyChange( e );
propertyChange( getComponent(), e, this::installStyle ); propertyChange( getComponent(), e, this::installStyle );
// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
// which removed our delegate actions
if( "editorKit".equals( propertyName ) )
installKeyboardActions( getComponent() );
} }
static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) { static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
@@ -171,7 +192,7 @@ public class FlatEditorPaneUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle.run(); installStyle.run();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
} }
} }

View File

@@ -24,16 +24,13 @@ import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.Insets; import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.File; import java.io.File;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.function.Function; import java.util.function.Function;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup; import javax.swing.ButtonGroup;
@@ -48,7 +45,6 @@ import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.JToggleButton; import javax.swing.JToggleButton;
import javax.swing.JToolBar; import javax.swing.JToolBar;
import javax.swing.Scrollable;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileSystemView;
@@ -167,7 +163,6 @@ public class FlatFileChooserUI
{ {
private final FlatFileView fileView = new FlatFileView(); private final FlatFileView fileView = new FlatFileView();
private FlatShortcutsPanel shortcutsPanel; private FlatShortcutsPanel shortcutsPanel;
private JScrollPane shortcutsScrollPane;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c ); return new FlatFileChooserUI( (JFileChooser) c );
@@ -187,10 +182,7 @@ public class FlatFileChooserUI
FlatShortcutsPanel panel = createShortcutsPanel( fc ); FlatShortcutsPanel panel = createShortcutsPanel( fc );
if( panel.getComponentCount() > 0 ) { if( panel.getComponentCount() > 0 ) {
shortcutsPanel = panel; shortcutsPanel = panel;
shortcutsScrollPane = new JScrollPane( shortcutsPanel, fc.add( shortcutsPanel, BorderLayout.LINE_START );
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
shortcutsScrollPane.setBorder( BorderFactory.createEmptyBorder() );
fc.add( shortcutsScrollPane, BorderLayout.LINE_START );
fc.addPropertyChangeListener( shortcutsPanel ); fc.addPropertyChangeListener( shortcutsPanel );
} }
} }
@@ -203,7 +195,6 @@ public class FlatFileChooserUI
if( shortcutsPanel != null ) { if( shortcutsPanel != null ) {
fc.removePropertyChangeListener( shortcutsPanel ); fc.removePropertyChangeListener( shortcutsPanel );
shortcutsPanel = null; shortcutsPanel = null;
shortcutsScrollPane = null;
} }
} }
@@ -332,7 +323,7 @@ public class FlatFileChooserUI
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
Dimension prefSize = super.getPreferredSize( c ); Dimension prefSize = super.getPreferredSize( c );
Dimension minSize = getMinimumSize( c ); Dimension minSize = getMinimumSize( c );
int shortcutsPanelWidth = (shortcutsScrollPane != null) ? shortcutsScrollPane.getPreferredSize().width : 0; int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0;
return new Dimension( return new Dimension(
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ), Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
Math.max( prefSize.height, minSize.height ) ); Math.max( prefSize.height, minSize.height ) );
@@ -378,11 +369,7 @@ public class FlatFileChooserUI
// get system icon // get system icon
if( f != null ) { if( f != null ) {
try {
icon = getFileChooser().getFileSystemView().getSystemIcon( f ); icon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
if( icon != null ) { if( icon != null ) {
if( icon instanceof ImageIcon ) if( icon instanceof ImageIcon )
@@ -409,7 +396,7 @@ public class FlatFileChooserUI
/** @since 2.3 */ /** @since 2.3 */
public static class FlatShortcutsPanel public static class FlatShortcutsPanel
extends JToolBar extends JToolBar
implements PropertyChangeListener, Scrollable implements PropertyChangeListener
{ {
private final JFileChooser fc; private final JFileChooser fc;
@@ -421,14 +408,13 @@ public class FlatFileChooserUI
protected final File[] files; protected final File[] files;
protected final JToggleButton[] buttons; protected final JToggleButton[] buttons;
protected final ButtonGroup buttonGroup = new ButtonGroup(); protected final ButtonGroup buttonGroup;
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public FlatShortcutsPanel( JFileChooser fc ) { public FlatShortcutsPanel( JFileChooser fc ) {
super( JToolBar.VERTICAL ); super( JToolBar.VERTICAL );
this.fc = fc; this.fc = fc;
setFloatable( false ); setFloatable( false );
putClientProperty( FlatClientProperties.STYLE, "hoverButtonGroupBackground: null" );
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) ); buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 ); iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
@@ -438,25 +424,22 @@ public class FlatFileChooserUI
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" ); iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView(); FileSystemView fsv = fc.getFileSystemView();
File[] files = JavaCompatibility2.getChooserShortcutPanelFiles( fsv ); File[] files = getChooserShortcutPanelFiles( fsv );
if( filesFunction != null ) if( filesFunction != null )
files = filesFunction.apply( files ); files = filesFunction.apply( files );
this.files = files;
// create toolbar buttons // create toolbar buttons
ArrayList<File> filesList = new ArrayList<>(); buttons = new JToggleButton[files.length];
ArrayList<JToggleButton> buttonsList = new ArrayList<>(); buttonGroup = new ButtonGroup();
for( File file : files ) { for( int i = 0; i < files.length; i++ ) {
if( file == null )
continue;
// wrap drive path // wrap drive path
if( fsv.isFileSystemRoot( file ) ) if( fsv.isFileSystemRoot( files[i] ) )
file = fsv.createFileObject( file.getAbsolutePath() ); files[i] = fsv.createFileObject( files[i].getAbsolutePath() );
File file = files[i];
String name = getDisplayName( fsv, file ); String name = getDisplayName( fsv, file );
Icon icon = getIcon( fsv, file ); Icon icon = getIcon( fsv, file );
if( name == null )
continue;
// remove path from name // remove path from name
int lastSepIndex = name.lastIndexOf( File.separatorChar ); int lastSepIndex = name.lastIndexOf( File.separatorChar );
@@ -470,22 +453,16 @@ public class FlatFileChooserUI
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height ); icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
// create button // create button
JToggleButton button = createButton( name, icon, file.toString() ); JToggleButton button = createButton( name, icon );
File f = file;
button.addActionListener( e -> { button.addActionListener( e -> {
fc.setCurrentDirectory( f ); fc.setCurrentDirectory( file );
} ); } );
add( button ); add( button );
buttonGroup.add( button ); buttonGroup.add( button );
buttons[i] = button;
filesList.add( file );
buttonsList.add( button );
} }
this.files = filesList.toArray( new File[filesList.size()] );
this.buttons = buttonsList.toArray( new JToggleButton[buttonsList.size()] );
directoryChanged( fc.getCurrentDirectory() ); directoryChanged( fc.getCurrentDirectory() );
} }
@@ -496,10 +473,8 @@ public class FlatFileChooserUI
return size; return size;
} }
/** @since 3.5 */ protected JToggleButton createButton( String name, Icon icon ) {
protected JToggleButton createButton( String name, Icon icon, String toolTip ) {
JToggleButton button = new JToggleButton( name, icon ); JToggleButton button = new JToggleButton( name, icon );
button.setToolTipText( toolTip );
button.setVerticalTextPosition( SwingConstants.BOTTOM ); button.setVerticalTextPosition( SwingConstants.BOTTOM );
button.setHorizontalTextPosition( SwingConstants.CENTER ); button.setHorizontalTextPosition( SwingConstants.CENTER );
button.setAlignmentX( Component.CENTER_ALIGNMENT ); button.setAlignmentX( Component.CENTER_ALIGNMENT );
@@ -509,6 +484,32 @@ public class FlatFileChooserUI
return button; return button;
} }
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
protected String getDisplayName( FileSystemView fsv, File file ) { protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) { if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file ); String name = displayNameFunction.apply( file );
@@ -529,7 +530,6 @@ public class FlatFileChooserUI
if( doNotUseSystemIcons() ) if( doNotUseSystemIcons() )
return new FlatFileViewDirectoryIcon(); return new FlatFileViewDirectoryIcon();
try {
// Java 17+ supports getting larger system icons // Java 17+ supports getting larger system icons
try { try {
if( SystemInfo.isJava_17_orLater ) { if( SystemInfo.isJava_17_orLater ) {
@@ -545,20 +545,14 @@ public class FlatFileChooserUI
return new ImageIcon( image ); return new ImageIcon( image );
} }
} }
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) { } catch( Exception ex ) {
// do not log InaccessibleObjectException because access
// may be denied via VM option '--illegal-access=deny' (default in Java 16)
// (not catching InaccessibleObjectException here because it is new in Java 9, but FlatLaf also runs on Java 8)
if( !"java.lang.reflect.InaccessibleObjectException".equals( ex.getClass().getName() ) )
LoggingFacade.INSTANCE.logSevere( null, ex ); LoggingFacade.INSTANCE.logSevere( null, ex );
} }
// get system icon in default size 16x16 // get system icon in default size 16x16
return fsv.getSystemIcon( file ); return fsv.getSystemIcon( file );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
return new FlatFileViewDirectoryIcon();
}
} }
protected void directoryChanged( File file ) { protected void directoryChanged( File file ) {
@@ -577,8 +571,6 @@ public class FlatFileChooserUI
buttonGroup.clearSelection(); buttonGroup.clearSelection();
} }
//---- interface PropertyChangeListener ----
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
@@ -587,41 +579,6 @@ public class FlatFileChooserUI
break; break;
} }
} }
//---- interface Scrollable ----
@Override
public Dimension getPreferredScrollableViewportSize() {
if( getComponentCount() > 0 ) {
Insets insets = getInsets();
int height = (getComponent( 0 ).getPreferredSize().height * 5) + insets.top + insets.bottom;
return new Dimension( getPreferredSize().width, height );
}
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
if( orientation == SwingConstants.VERTICAL && getComponentCount() > 0 )
return getComponent( 0 ).getPreferredSize().height;
return getScrollableBlockIncrement( visibleRect, orientation, direction ) / 10;
}
@Override
public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) {
return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
} }
//---- class ShortcutIcon ------------------------------------------------- //---- class ShortcutIcon -------------------------------------------------

View File

@@ -1,255 +0,0 @@
/*
* Copyright 2024 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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JToolTip;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.LabelView;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.StyleSheet;
/**
* @author Karl Tauber
* @since 3.5
*/
public class FlatHTML
{
private FlatHTML() {}
/**
* Adds CSS rule BASE_SIZE to the style sheet of the HTML view,
* which re-calculates font sizes based on current component font size.
* This is necessary for "absolute-size" keywords (e.g. "x-large")
* for "font-size" attributes in default style sheet (see javax/swing/text/html/default.css).
* See also <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values">CSS font-size</a>.
* <p>
* This method should be invoked after {@link BasicHTML#updateRenderer(JComponent, String)}.
*/
public static void updateRendererCSSFontBaseSize( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view == null )
return;
// dumpViews( view, 0 );
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) )
return;
// add BASE_SIZE rule if necessary
// - if point size at index 7 is not 36, then probably HTML text contains BASE_SIZE rule
// - if point size at index 4 is equal to given font size, then it is not necessary to add BASE_SIZE rule
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
/*debug
for( int i = 1; i <= 7; i++ )
System.out.println( i+": "+ styleSheet.getPointSize( i ) );
debug*/
int fontBaseSize = c.getFont().getSize();
if( styleSheet.getPointSize( 7 ) != 36f ||
styleSheet.getPointSize( 4 ) == fontBaseSize )
return;
// check whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
if( !usesAbsoluteSizeKeywordForFontSize( view ) )
return;
// get HTML text from component
String text;
if( c instanceof JLabel )
text = ((JLabel)c).getText();
else if( c instanceof AbstractButton )
text = ((AbstractButton)c).getText();
else if( c instanceof JToolTip )
text = ((JToolTip)c).getTipText();
else
return;
if( text == null )
return;
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String openTag = "";
String closeTag = "";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
insertIndex = "<html>".length();
openTag = "<head>";
closeTag = "</head>";
}
String newText = text.substring( 0, insertIndex )
+ openTag + style + closeTag
+ text.substring( insertIndex );
BasicHTML.updateRenderer( c, newText );
}
private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList(
"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" ) );
/**
* Checks whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
* (see javax/swing/text/html/default.css).
*/
private static boolean usesAbsoluteSizeKeywordForFontSize( View view ) {
AttributeSet attributes = view.getAttributes();
if( attributes != null ) {
Object fontSize = attributes.getAttribute( CSS.Attribute.FONT_SIZE );
if( fontSize != null ) {
if( absoluteSizeKeywordsSet.contains( fontSize.toString() ) )
return true;
}
}
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
if( usesAbsoluteSizeKeywordForFontSize( view.getView( i ) ) )
return true;
}
return false;
}
/**
* Updates foreground in style sheet of the HTML view.
* Adds "body { color: #&lt;foreground-hex&gt;; }"
*/
public static void updateRendererCSSForeground( View view, Color foreground ) {
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) || foreground == null )
return;
// add foreground rule if necessary
// - use tag 'body' because BasicHTML.createHTMLView() also uses this tag
// to set font and color styles to component font/color
// see: SwingUtilities2.displayPropertiesToCSS()
// - this color is not used if component is disabled;
// JTextComponent.getDisabledTextColor() is used for disabled text components;
// UIManager.getColor("textInactiveText") is used for other disabled components
// see: javax.swing.text.GlyphView.paint()
Style bodyStyle = ((HTMLDocument)doc).getStyle( "body" );
if( bodyStyle == null ) {
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
styleSheet.addRule( String.format( "body { color: #%06x; }", foreground.getRGB() & 0xffffff ) );
clearViewCaches( view );
} else if( !foreground.equals( bodyStyle.getAttribute( StyleConstants.Foreground ) ) ) {
bodyStyle.addAttribute( StyleConstants.Foreground, foreground );
clearViewCaches( view );
}
}
/**
* Clears cached values in view so that CSS changes take effect.
*/
private static void clearViewCaches( View view ) {
if( view instanceof LabelView )
((LabelView)view).changedUpdate( null, null, null );
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ )
clearViewCaches( view.getView( i ) );
}
public static PropertyChangeListener createPropertyChangeListener( PropertyChangeListener superListener ) {
return e -> {
if( superListener != null )
superListener.propertyChange( e );
propertyChange( e );
};
}
/**
* Invokes {@link #updateRendererCSSFontBaseSize(JComponent)}
* for {@link BasicHTML#propertyKey} property change events,
* which are fired when {@link BasicHTML#updateRenderer(JComponent, String)}
* updates the HTML view.
*/
public static void propertyChange( PropertyChangeEvent e ) {
if( BasicHTML.propertyKey.equals( e.getPropertyName() ) && e.getNewValue() instanceof View )
updateRendererCSSFontBaseSize( (JComponent) e.getSource() );
}
/*debug
public static void dumpView( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view != null )
dumpViews( view, 0 );
}
public static void dumpViews( View view, int indent ) {
for( int i = 0; i < indent; i++ )
System.out.print( " " );
System.out.printf( "%s @%-8x %3d,%2d",
view.getClass().isAnonymousClass() ? view.getClass().getName() : view.getClass().getSimpleName(),
System.identityHashCode( view ),
(int) view.getPreferredSpan( View.X_AXIS ),
(int) view.getPreferredSpan( View.Y_AXIS ) );
AttributeSet attrs = view.getAttributes();
if( attrs != null ) {
Object fontSize = attrs.getAttribute( CSS.Attribute.FONT_SIZE );
System.out.printf( " %-8s", fontSize );
}
if( view instanceof javax.swing.text.GlyphView ) {
javax.swing.text.GlyphView gview = ((javax.swing.text.GlyphView)view);
java.awt.Font font = gview.getFont();
System.out.printf( " %3d-%-3d %s %2d (@%x) #%06x '%s'",
gview.getStartOffset(), gview.getEndOffset() - 1,
font.getName(), font.getSize(), System.identityHashCode( font ),
gview.getForeground().getRGB() & 0xffffff,
gview.getText( gview.getStartOffset(), gview.getEndOffset() ) );
}
System.out.println();
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
View child = view.getView( i );
dumpViews( child, indent + 1 );
}
}
debug*/
}

View File

@@ -22,7 +22,11 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel; import javax.swing.JLabel;
@@ -60,9 +64,6 @@ public class FlatLabelUI
{ {
@Styleable protected Color disabledForeground; @Styleable protected Color disabledForeground;
// only used via styling (not in UI defaults)
/** @since 3.5 */ @Styleable protected int arc = -1;
private final boolean shared; private final boolean shared;
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
@@ -109,13 +110,16 @@ public class FlatLabelUI
super.installComponents( c ); super.installComponents( c );
// update HTML renderer if necessary // update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( c ); updateHTMLRenderer( c, c.getText(), false );
} }
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName(); String name = e.getPropertyName();
if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) { if( name == "text" || name == "font" || name == "foreground" ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource(); JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) { if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary // unshare component UI if necessary
@@ -124,11 +128,9 @@ public class FlatLabelUI
} else } else
installStyle( label ); installStyle( label );
label.revalidate(); label.revalidate();
HiDPIUtils.repaint( label ); label.repaint();
} } else
super.propertyChange( e ); super.propertyChange( e );
FlatHTML.propertyChange( e );
} }
/** @since 2 */ /** @since 2 */
@@ -163,10 +165,83 @@ public class FlatLabelUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key ); return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
} }
@Override /**
public void update( Graphics g, JComponent c ) { * Checks whether text contains HTML tags that use "absolute-size" keywords
FlatPanelUI.fillRoundedBackground( g, c, arc ); * (e.g. "x-large") for font-size in default style sheet
paint( g, c ); * (see javax/swing/text/html/default.css).
* If yes, adds a special CSS rule (BASE_SIZE) to the HTML text, which
* re-calculates font sizes based on current component font size.
*/
static void updateHTMLRenderer( JComponent c, String text, boolean always ) {
if( BasicHTML.isHTMLString( text ) &&
c.getClientProperty( "html.disable" ) != Boolean.TRUE &&
needsFontBaseSize( text ) )
{
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
style = "<head>" + style + "</head>";
insertIndex = "<html>".length();
}
text = text.substring( 0, insertIndex )
+ style
+ text.substring( insertIndex );
} else if( !always )
return; // not necessary to invoke BasicHTML.updateRenderer()
BasicHTML.updateRenderer( c, text );
}
private static Set<String> tagsUseFontSizeSet;
private static boolean needsFontBaseSize( String text ) {
if( tagsUseFontSizeSet == null ) {
// tags that use font-size in javax/swing/text/html/default.css
tagsUseFontSizeSet = new HashSet<>( Arrays.asList(
"h1", "h2", "h3", "h4", "h5", "h6", "code", "kbd", "big", "small", "samp" ) );
}
// search for tags in HTML text
int textLength = text.length();
for( int i = 6; i < textLength - 1; i++ ) {
if( text.charAt( i ) == '<' ) {
switch( text.charAt( i + 1 ) ) {
// first letters of tags in tagsUseFontSizeSet
case 'b': case 'B':
case 'c': case 'C':
case 'h': case 'H':
case 'k': case 'K':
case 's': case 'S':
int tagBegin = i + 1;
for( i += 2; i < textLength; i++ ) {
if( !Character.isLetterOrDigit( text.charAt( i ) ) ) {
String tag = text.substring( tagBegin, i ).toLowerCase( Locale.ENGLISH );
if( tagsUseFontSizeSet.contains( tag ) )
return true;
break;
}
}
break;
}
}
}
return false;
} }
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) { static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {

View File

@@ -22,21 +22,13 @@ import java.awt.Component;
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 javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI;
/** /**
* Line border for various components. * Line border for various components.
* <p> *
* Paints a scaled (usually 1px thick) line around the component. * Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not added to the border insets. * The line thickness is not added to the border insets.
* The insets should be at least have line thickness (usually 1,1,1,1). * The insets should be at least have line thickness (usually 1,1,1,1).
* <p>
* For {@link javax.swing.JPanel} and {@link javax.swing.JLabel}, this border
* can be used paint rounded background (if line color is {@code null}) or
* paint rounded line border with rounded background.
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -48,7 +40,7 @@ public class FlatLineBorder
/** @since 2 */ private final int arc; /** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) { public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f, -1 ); this( insets, lineColor, 1f, 0 );
} }
/** @since 2 */ /** @since 2 */
@@ -59,62 +51,26 @@ public class FlatLineBorder
this.arc = arc; this.arc = arc;
} }
/** @since 3.5 */
public FlatLineBorder( Insets insets, int arc ) {
this( insets, null, 0, arc );
}
public Color getLineColor() { public Color getLineColor() {
return lineColor; return lineColor;
} }
/**
* Returns the (unscaled) line thickness used to paint the border.
* The line thickness does not affect the border insets.
*/
public float getLineThickness() { public float getLineThickness() {
return lineThickness; return lineThickness;
} }
/** /** @since 2 */
* Returns the (unscaled) arc diameter of the border corners.
*
* @since 2
*/
public int getArc() { public int getArc() {
return arc; return arc;
} }
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return;
Color lineColor = getLineColor();
float lineThickness = getLineThickness();
if( lineColor == null || lineThickness <= 0 )
return;
int arc = getArc();
if( arc < 0 ) {
// get arc from label or panel
ComponentUI ui = (c instanceof JLabel)
? ((JLabel)c).getUI()
: (c instanceof JPanel ? ((JPanel)c).getUI() : null);
if( ui instanceof FlatLabelUI )
arc = ((FlatLabelUI)ui).arc;
else if( ui instanceof FlatPanelUI )
arc = ((FlatPanelUI)ui).arc;
if( arc < 0 )
arc = 0;
}
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( lineThickness ), scale( arc ), null, lineColor, null ); 0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -43,7 +43,6 @@ import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -183,7 +182,7 @@ public class FlatListUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
list.revalidate(); list.revalidate();
HiDPIUtils.repaint( list ); list.repaint();
break; break;
} }
}; };
@@ -206,7 +205,7 @@ public class FlatListUI
Rectangle r = getCellBounds( list, firstIndex, lastIndex ); Rectangle r = getCellBounds( list, firstIndex, lastIndex );
if( r != null ) { if( r != null ) {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
HiDPIUtils.repaint( list, r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); list.repaint( r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) );
} }
} }
}; };
@@ -325,7 +324,8 @@ public class FlatListUI
(rendererComponent instanceof DefaultListCellRenderer || (rendererComponent instanceof DefaultListCellRenderer ||
rendererComponent instanceof BasicComboBoxRenderer) && rendererComponent instanceof BasicComboBoxRenderer) &&
(selectionArc > 0 || (selectionArc > 0 ||
(selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets ))) ) (selectionInsets != null &&
(selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) )
{ {
// Because selection painting is done in the cell renderer, it would be // Because selection painting is done in the cell renderer, it would be
// necessary to require a FlatLaf specific renderer to implement rounded selection. // necessary to require a FlatLaf specific renderer to implement rounded selection.
@@ -374,15 +374,7 @@ public class FlatListUI
rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true ); rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true );
} }
/** /** @since 3 */
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3
*/
protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) { protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) {
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
@@ -419,7 +411,7 @@ public class FlatListUI
int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) ); int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) );
int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) ); int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) );
// special handling for the case that last column contains fewer cells than the other columns // special handling for the case that last column contains less cells than the other columns
boolean ltr = list.getComponentOrientation().isLeftToRight(); boolean ltr = list.getComponentOrientation().isLeftToRight();
if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) ) if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) )
leftIndex = -1; leftIndex = -1;
@@ -448,8 +440,7 @@ public class FlatListUI
* Paints a cell selection at the given coordinates. * Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context. * The selection color must be set on the graphics context.
* <p> * <p>
* This method is intended for use in custom cell renderers * This method is intended for use in custom cell renderers.
* to support {@link #selectionArc} and {@link #selectionInsets}.
* *
* @since 3 * @since 3
*/ */

View File

@@ -38,6 +38,7 @@ import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager; import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ActionMapUIResource;
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.BasicMenuBarUI; import javax.swing.plaf.basic.BasicMenuBarUI;
@@ -142,10 +143,12 @@ public class FlatMenuBarUI
protected void installKeyboardActions() { protected void installKeyboardActions() {
super.installKeyboardActions(); super.installKeyboardActions();
// get shared action map, used for all menu bars
ActionMap map = SwingUtilities.getUIActionMap( menuBar ); ActionMap map = SwingUtilities.getUIActionMap( menuBar );
if( map != null && !(map.get( "takeFocus" ) instanceof TakeFocusAction) ) if( map == null ) {
map.put( "takeFocus", new TakeFocusAction( "takeFocus" ) ); map = new ActionMapUIResource();
SwingUtilities.replaceUIActionMap( menuBar, map );
}
map.put( "takeFocus", new TakeFocus( "takeFocus" ) );
} }
/** @since 2 */ /** @since 2 */
@@ -361,17 +364,17 @@ public class FlatMenuBarUI
} }
} }
//---- class TakeFocusAction ---------------------------------------------- //---- class TakeFocus ----------------------------------------------------
/** /**
* Activates the menu bar and shows mnemonics. * Activates the menu bar and shows mnemonics.
* On Windows, the popup of the first menu is not shown. * On Windows, the popup of the first menu is not shown.
* On other platforms, the popup of the first menu is shown. * On other platforms, the popup of the first menu is shown.
*/ */
private static class TakeFocusAction private static class TakeFocus
extends FlatUIAction extends FlatUIAction
{ {
TakeFocusAction( String name ) { public TakeFocus( String name ) {
super( name ); super( name );
} }

View File

@@ -25,9 +25,7 @@ import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets; import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
@@ -37,7 +35,6 @@ import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@@ -225,7 +222,7 @@ public class FlatMenuItemRenderer
} }
// arrow size // arrow size
if( arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem )) ) { if( !isTopLevelMenu && arrowIcon != null ) {
// gap between text and arrow // gap between text and arrow
if( accelText == null ) if( accelText == null )
width += scale( textNoAcceleratorGap ); width += scale( textNoAcceleratorGap );
@@ -257,8 +254,7 @@ public class FlatMenuItemRenderer
boolean isTopLevelMenu = isTopLevelMenu( menuItem ); boolean isTopLevelMenu = isTopLevelMenu( menuItem );
// layout arrow // layout arrow
boolean showArrowIcon = (arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem ))); if( !isTopLevelMenu && arrowIcon != null ) {
if( showArrowIcon ) {
arrowRect.width = arrowIcon.getIconWidth(); arrowRect.width = arrowIcon.getIconWidth();
arrowRect.height = arrowIcon.getIconHeight(); arrowRect.height = arrowIcon.getIconHeight();
} else } else
@@ -292,7 +288,7 @@ public class FlatMenuItemRenderer
int accelArrowWidth = accelRect.width + arrowRect.width; int accelArrowWidth = accelRect.width + arrowRect.width;
if( accelText != null ) if( accelText != null )
accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() ); accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() );
if( showArrowIcon ) { if( !isTopLevelMenu && arrowIcon != null ) {
if( accelText == null ) if( accelText == null )
accelArrowWidth += scale( textNoAcceleratorGap ); accelArrowWidth += scale( textNoAcceleratorGap );
accelArrowWidth += scale( acceleratorArrowGap ); accelArrowWidth += scale( acceleratorArrowGap );
@@ -359,7 +355,7 @@ debug*/
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground ); paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground ); paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground ); paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( arrowIcon != null && (!isTopLevelMenu( menuItem ) || isInVerticalMenuBar( menuItem )) ) if( !isTopLevelMenu( menuItem ) )
paintArrowIcon( g, arrowRect, arrowIcon ); paintArrowIcon( g, arrowRect, arrowIcon );
} }
@@ -524,15 +520,6 @@ debug*/
return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu(); return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu();
} }
/** @since 3.5 */
public static boolean isInVerticalMenuBar( JMenuItem menuItem ) {
if( !(menuItem instanceof JMenu) || !(menuItem.getParent() instanceof JMenuBar) )
return false;
LayoutManager layout = menuItem.getParent().getLayout();
return layout instanceof GridLayout && ((GridLayout)layout).getRows() != 1;
}
protected boolean isUnderlineSelection() { protected boolean isUnderlineSelection() {
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) ); return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
} }

View File

@@ -103,23 +103,13 @@ public class FlatMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener( return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -17,7 +17,6 @@
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.Dimension; import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
@@ -47,7 +46,6 @@ import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -137,14 +135,6 @@ public class FlatMenuUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@@ -168,7 +158,7 @@ public class FlatMenuUI
JMenu menu = (JMenu) e.getSource(); JMenu menu = (JMenu) e.getSource();
if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) { if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) {
menu.getModel().setRollover( rollover ); menu.getModel().setRollover( rollover );
HiDPIUtils.repaint( menu ); menu.repaint();
} }
} }
}; };
@@ -176,9 +166,7 @@ public class FlatMenuUI
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener( return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */
@@ -283,7 +271,7 @@ public class FlatMenuUI
if( !isHover() ) if( !isHover() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground ); selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground );
Container menuBar = menuItem.getParent(); JMenuBar menuBar = (JMenuBar) menuItem.getParent();
JRootPane rootPane = SwingUtilities.getRootPane( menuBar ); JRootPane rootPane = SwingUtilities.getRootPane( menuBar );
if( rootPane != null && rootPane.getParent() instanceof Window && if( rootPane != null && rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == menuBar && rootPane.getJMenuBar() == menuBar &&
@@ -333,17 +321,12 @@ public class FlatMenuUI
} }
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) { private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
Container menuItemParent = menuItem.getParent(); MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( menuItemParent instanceof JMenuBar ) { if( !(ui instanceof FlatMenuBarUI) )
MenuBarUI ui = ((JMenuBar) menuItemParent).getUI();
if( ui instanceof FlatMenuBarUI ) {
T value = f.apply( (FlatMenuBarUI) ui );
if( value != null ) {
return value;
}
}
}
return defaultValue; return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
} }
} }
} }

View File

@@ -22,7 +22,6 @@ import java.security.CodeSource;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.NativeLibrary; import com.formdev.flatlaf.util.NativeLibrary;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
/** /**
@@ -37,18 +36,16 @@ class FlatNativeLibrary
private static boolean initialized; private static boolean initialized;
private static NativeLibrary nativeLibrary; private static NativeLibrary nativeLibrary;
private native static int getApiVersion();
/** /**
* Loads native library (if available) and returns whether loaded successfully. * Loads native library (if available) and returns whether loaded successfully.
* Returns {@code false} if no native library is available. * Returns {@code false} if no native library is available.
*/ */
static synchronized boolean isLoaded( int apiVersion ) { static synchronized boolean isLoaded() {
initialize( apiVersion ); initialize();
return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false; return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false;
} }
private static void initialize( int apiVersion ) { private static void initialize() {
if( initialized ) if( initialized )
return; return;
initialized = true; initialized = true;
@@ -81,15 +78,9 @@ class FlatNativeLibrary
// //
// To avoid this, flatlaf.dll is not linked to jawt.dll, // To avoid this, flatlaf.dll is not linked to jawt.dll,
// which avoids loading jawt.dll when flatlaf.dll is loaded. // which avoids loading jawt.dll when flatlaf.dll is loaded.
// Instead, flatlaf.dll dynamically loads jawt.dll when first used, // Instead flatlaf.dll dynamically loads jawt.dll when first used,
// which is guaranteed after AWT initialization. // which is guaranteed after AWT initialization.
} else if( SystemInfo.isMacOS_10_14_Mojave_orLater && (SystemInfo.isAARCH64 || SystemInfo.isX86_64) ) {
// macOS: requires macOS 10.14 or later (arm64 or x86_64)
classifier = SystemInfo.isAARCH64 ? "macos-arm64" : "macos-x86_64";
ext = "dylib";
} else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) { } else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) {
// Linux: requires x86_64 // Linux: requires x86_64
@@ -106,26 +97,7 @@ class FlatNativeLibrary
return; // no native library available for current OS or CPU architecture return; // no native library available for current OS or CPU architecture
// load native library // load native library
NativeLibrary nativeLibrary = createNativeLibrary( classifier, ext ); nativeLibrary = createNativeLibrary( classifier, ext );
if( !nativeLibrary.isLoaded() )
return;
// check API version (and check whether library works)
try {
int actualApiVersion = getApiVersion();
if( actualApiVersion != apiVersion ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Wrong API version in native library (expected "
+ apiVersion + ", actual " + actualApiVersion + "). Ignoring native library.", null );
return;
}
} catch( Throwable ex ) {
// could be a UnsatisfiedLinkError in case that loading native library
// from temp directory was blocked by some OS security mechanism
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to get API version of native library. Ignoring native library.", ex );
return;
}
FlatNativeLibrary.nativeLibrary = nativeLibrary;
} }
private static NativeLibrary createNativeLibrary( String classifier, String ext ) { private static NativeLibrary createNativeLibrary( String classifier, String ext ) {
@@ -139,32 +111,13 @@ class FlatNativeLibrary
if( library.isLoaded() ) if( library.isLoaded() )
return library; return library;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '" + System.mapLibraryName( libraryName ) LoggingFacade.INSTANCE.logSevere( "Did not find library " + libraryName + " in java.library.path, using extracted library instead", null );
+ "' not found in java.library.path '" + System.getProperty( "java.library.path" )
+ "'. Using extracted native library instead.", null );
} else { } else {
// try standard library naming scheme
// (same as in flatlaf.jar in package 'com/formdev/flatlaf/natives')
File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) ); File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) );
if( libraryFile.exists() ) if( libraryFile.exists() )
return new NativeLibrary( libraryFile, true ); return new NativeLibrary( libraryFile, true );
// try Maven naming scheme LoggingFacade.INSTANCE.logSevere( "Did not find external library " + libraryFile + ", using extracted library instead", null );
// (see https://www.formdev.com/flatlaf/native-libraries/)
String libraryName2 = null;
File jarFile = getJarFile();
if( jarFile != null ) {
libraryName2 = buildLibraryName( jarFile, classifier, ext );
File libraryFile2 = new File( libraryPath, libraryName2 );
if( libraryFile2.exists() )
return new NativeLibrary( libraryFile2, true );
}
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '"
+ libraryFile.getName()
+ (libraryName2 != null ? ("' or '" + libraryName2) : "")
+ "' not found in '" + libraryFile.getParentFile().getAbsolutePath()
+ "'. Using extracted native library instead.", null );
} }
} }
@@ -192,51 +145,6 @@ class FlatNativeLibrary
* flatlaf-3.1-linux-x86_64.so * flatlaf-3.1-linux-x86_64.so
*/ */
private static File findLibraryBesideJar( String classifier, String ext ) { private static File findLibraryBesideJar( String classifier, String ext ) {
// get location of FlatLaf jar (or fat/uber application jar)
File jarFile = getJarFile();
if( jarFile == null )
return null;
// build library file
String libraryName = buildLibraryName( jarFile, classifier, ext );
File jarDir = jarFile.getParentFile();
// check whether native library exists in same directory as jar
File libraryFile = new File( jarDir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether native library exists
// in "../bin" directory
if( jarDir.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( jarDir.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
// special case: support Gradle cache when running in development environment
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-1>/flatlaf-<version>.jar
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-2>/flatlaf-<version>-windows-x86_64.dll
String path = jarDir.getAbsolutePath().replace( '\\', '/' );
if( path.contains( "/.gradle/caches/" ) ) {
File versionDir = jarDir.getParentFile();
if( libraryName.contains( versionDir.getName() ) ) {
File[] dirs = versionDir.listFiles();
if( dirs != null ) {
for( File dir : dirs ) {
libraryFile = new File( dir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
}
}
}
// native library not found
return null;
}
private static File getJarFile() {
try { try {
// get location of FlatLaf jar // get location of FlatLaf jar
CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource(); CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource();
@@ -254,23 +162,31 @@ class FlatNativeLibrary
if( !jarFile.isFile() ) if( !jarFile.isFile() )
return null; return null;
return jarFile; // build library file
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), ex );
return null;
}
}
private static String buildLibraryName( File jarFile, String classifier, String ext ) {
String jarName = jarFile.getName(); String jarName = jarFile.getName();
String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) ); String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) );
File parent = jarFile.getParentFile();
// remove classifier "no-natives" (if used) String libraryName = jarBasename
jarBasename = StringUtils.removeTrailing( jarBasename, "-no-natives" );
return jarBasename
+ (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf") + (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf")
+ '-' + classifier + '.' + ext; + '-' + classifier + '.' + ext;
// check whether native library exists in same directory as jar
File libraryFile = new File( parent, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether library exists
// in "../bin" directory
if( parent.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( parent.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), ex );
}
return null;
} }
private static void loadJAWT() { private static void loadJAWT() {

View File

@@ -35,16 +35,8 @@ import com.formdev.flatlaf.util.SystemInfo;
*/ */
class FlatNativeLinuxLibrary class FlatNativeLinuxLibrary
{ {
private static int API_VERSION_LINUX = 3001;
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
static boolean isLoaded() { static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX ); return SystemInfo.isLinux && FlatNativeLibrary.isLoaded();
} }
// direction for _NET_WM_MOVERESIZE message // direction for _NET_WM_MOVERESIZE message

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2023 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.Rectangle;
import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Native methods for macOS.
* <p>
* <b>Note</b>: This is private API. Do not use!
*
* <h2>Methods that use windows as parameter</h2>
*
* For all methods that accept a {@link java.awt.Window} as parameter,
* the underlying macOS window must be already created,
* otherwise the method fails. You can use following to ensure this:
* <pre>{@code
* if( !window.isDisplayable() )
* window.addNotify();
* }</pre>
* or invoke the method after packing the window. E.g.
* <pre>{@code
* window.pack();
* }</pre>
*
* @author Karl Tauber
* @since 3.3
*/
public class FlatNativeMacLibrary
{
private static int API_VERSION_MACOS = 2001;
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
public static boolean isLoaded() {
return SystemInfo.isMacOS && FlatNativeLibrary.isLoaded( API_VERSION_MACOS );
}
public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor );
/** @since 3.4 */
public static final int
BUTTONS_SPACING_DEFAULT = 0,
BUTTONS_SPACING_MEDIUM = 1,
BUTTONS_SPACING_LARGE = 2;
/** @since 3.4 */ public native static boolean setWindowButtonsSpacing( Window window, int buttonsSpacing );
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
}

View File

@@ -17,27 +17,20 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.function.Predicate; import java.util.List;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JRootPane; import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import javax.swing.plaf.BorderUIResource;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
/** /**
@@ -61,15 +54,27 @@ public class FlatNativeWindowBorder
!SystemInfo.isWinPE && !SystemInfo.isWinPE &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true ); FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true );
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
private static final boolean canUseJBRCustomDecorations =
canUseWindowDecorations &&
SystemInfo.isJetBrainsJVM_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false );
private static Boolean supported; private static Boolean supported;
private static Provider nativeProvider; private static Provider nativeProvider;
public static boolean isSupported() { public static boolean isSupported() {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.isSupported();
initialize(); initialize();
return supported; return supported;
} }
static Object install( JRootPane rootPane ) { static Object install( JRootPane rootPane ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.install( rootPane );
if( !isSupported() ) if( !isSupported() )
return null; return null;
@@ -158,6 +163,11 @@ public class FlatNativeWindowBorder
} }
static void uninstall( JRootPane rootPane, Object data ) { static void uninstall( JRootPane rootPane, Object data ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.uninstall( rootPane, data );
return;
}
if( !isSupported() ) if( !isSupported() )
return; return;
@@ -205,6 +215,9 @@ public class FlatNativeWindowBorder
} }
public static boolean hasCustomDecoration( Window window ) { public static boolean hasCustomDecoration( Window window ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.hasCustomDecoration( window );
if( !isSupported() ) if( !isSupported() )
return false; return false;
@@ -212,6 +225,11 @@ public class FlatNativeWindowBorder
} }
public static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) { public static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setHasCustomDecoration( window, hasCustomDecoration );
return;
}
if( !isSupported() ) if( !isSupported() )
return; return;
@@ -219,18 +237,23 @@ public class FlatNativeWindowBorder
} }
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
Predicate<Point> captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds, List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds ) Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{ {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
return;
}
if( !isSupported() ) if( !isSupported() )
return; return;
nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback, nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
} }
static boolean showWindow( Window window, int cmd ) { static boolean showWindow( Window window, int cmd ) {
if( !isSupported() ) if( canUseJBRCustomDecorations || !isSupported() )
return false; return false;
return nativeProvider.showWindow( window, cmd ); return nativeProvider.showWindow( window, cmd );
@@ -271,7 +294,7 @@ public class FlatNativeWindowBorder
{ {
boolean hasCustomDecoration( Window window ); boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration ); void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback, void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds, Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds ); Rectangle closeButtonBounds );
@@ -297,36 +320,20 @@ public class FlatNativeWindowBorder
* No longer needed since Windows 11. * No longer needed since Windows 11.
*/ */
static class WindowTopBorder static class WindowTopBorder
extends BorderUIResource.EmptyBorderUIResource extends JBRCustomDecorations.JBRWindowTopBorder
{ {
private static WindowTopBorder instance; private static WindowTopBorder instance;
private final Color activeLightColor = new Color( 0x707070 ); static JBRWindowTopBorder getInstance() {
private final Color activeDarkColor = new Color( 0x2D2E2F ); if( canUseJBRCustomDecorations )
private final Color inactiveLightColor = new Color( 0xaaaaaa ); return JBRWindowTopBorder.getInstance();
private final Color inactiveDarkColor = new Color( 0x494A4B );
private boolean colorizationAffectsBorders;
private Color activeColor;
static WindowTopBorder getInstance() {
if( instance == null ) if( instance == null )
instance = new WindowTopBorder(); instance = new WindowTopBorder();
return instance; return instance;
} }
WindowTopBorder() { @Override
super( 1, 0, 0, 0 );
update();
installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
void installListeners() { void installListeners() {
nativeProvider.addChangeListener( e -> { nativeProvider.addChangeListener( e -> {
update(); update();
@@ -339,69 +346,19 @@ public class FlatNativeWindowBorder
} ); } );
} }
@Override
boolean isColorizationColorAffectsBorders() { boolean isColorizationColorAffectsBorders() {
return nativeProvider.isColorizationColorAffectsBorders(); return nativeProvider.isColorizationColorAffectsBorders();
} }
@Override
Color getColorizationColor() { Color getColorizationColor() {
return nativeProvider.getColorizationColor(); return nativeProvider.getColorizationColor();
} }
@Override
int getColorizationColorBalance() { int getColorizationColorBalance() {
return nativeProvider.getColorizationColorBalance(); return nativeProvider.getColorizationColorBalance();
} }
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return null;
Color colorizationColor = getColorizationColor();
if( colorizationColor != null ) {
int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
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 );
// avoid potential IllegalArgumentException in Color constructor
r = Math.min( Math.max( r, 0 ), 255 );
g = Math.min( Math.max( g, 0 ), 255 );
b = Math.min( Math.max( b, 0 ), 255 );
return new Color( r, g, b );
}
Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().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();
boolean dark = FlatLaf.isLafDark();
g.setColor( active
? (activeColor != null ? activeColor : (dark ? activeDarkColor : activeLightColor))
: (dark ? 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.fillRect( x, y, width, 1 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
} }
} }

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Window; import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -30,18 +29,10 @@ import com.formdev.flatlaf.util.SystemInfo;
*/ */
public class FlatNativeWindowsLibrary public class FlatNativeWindowsLibrary
{ {
private static int API_VERSION_WINDOWS = 1001;
private static long osBuildNumber = Long.MIN_VALUE; private static long osBuildNumber = Long.MIN_VALUE;
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
public static boolean isLoaded() { public static boolean isLoaded() {
return SystemInfo.isWindows && FlatNativeLibrary.isLoaded( API_VERSION_WINDOWS ); return SystemInfo.isWindows && FlatNativeLibrary.isLoaded();
} }
/** /**
@@ -102,60 +93,15 @@ public class FlatNativeWindowsLibrary
public native static boolean setWindowCornerPreference( long hwnd, int cornerPreference ); public native static boolean setWindowCornerPreference( long hwnd, int cornerPreference );
/** /**
* DWMWINDOWATTRIBUTE * Sets the color of the window border.
* see https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute * The red/green/blue values must be in range {@code 0 - 255}.
* * If red is {@code -1}, then the system default border color is used (useful to reset the border color).
* @since 3.3 * If red is {@code -2}, then no border is painted.
*/ * <p>
public static final int * Invokes Win32 API method {@code DwmSetWindowAttribute(DWMWA_BORDER_COLOR)}.
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
DWMWA_BORDER_COLOR = 34,
DWMWA_CAPTION_COLOR = 35,
DWMWA_TEXT_COLOR = 36;
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code BOOL} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeBOOL( long hwnd, int attribute, boolean value );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code DWORD} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeDWORD( long hwnd, int attribute, int value );
/** @since 3.3 */
public static final int
// use this constant to reset any window part colors to the system default behavior
DWMWA_COLOR_DEFAULT = 0xFFFFFFFF,
// use this constant to specify that a window part should not be rendered
DWMWA_COLOR_NONE = 0xFFFFFFFE;
/** @since 3.3 */
public static final Color COLOR_NONE = new Color( 0, true );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code COLORREF} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute * See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
* <p> * <p>
* Supported since Windows 11 Build 22000. * Supported since Windows 11 Build 22000.
*
* @since 3.3
*/ */
public static boolean dwmSetWindowAttributeCOLORREF( long hwnd, int attribute, Color color ) { public native static boolean setWindowBorderColor( long hwnd, int red, int green, int blue );
// convert color to Windows RGB value
int rgb = (color == COLOR_NONE)
? DWMWA_COLOR_NONE
: (color != null
? (color.getRed() | (color.getGreen() << 8) | (color.getBlue() << 16))
: DWMWA_COLOR_DEFAULT);
// DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD
return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb );
}
} }

View File

@@ -30,7 +30,9 @@ import javax.swing.JPanel;
import javax.swing.JRootPane; import javax.swing.JRootPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI; import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -113,6 +115,13 @@ public class FlatOptionPaneUI
sameSizeButtons = FlatUIUtils.getUIBoolean( "OptionPane.sameSizeButtons", true ); sameSizeButtons = FlatUIUtils.getUIBoolean( "OptionPane.sameSizeButtons", true );
} }
@Override
protected void installComponents() {
super.installComponents();
updateChildPanels( optionPane );
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener() { protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener(); PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -146,13 +155,6 @@ public class FlatOptionPaneUI
protected Container createMessageArea() { protected Container createMessageArea() {
Container messageArea = super.createMessageArea(); Container messageArea = super.createMessageArea();
// use non-UIResource OptionPane.messageAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( messageArea );
// make known sub-panels non-opaque for OptionPane.background
updateKnownChildPanels( messageArea );
// set icon-message gap // set icon-message gap
if( iconMessageGap > 0 ) { if( iconMessageGap > 0 ) {
Component iconMessageSeparator = SwingUtils.getComponentByName( messageArea, "OptionPane.separator" ); Component iconMessageSeparator = SwingUtils.getComponentByName( messageArea, "OptionPane.separator" );
@@ -167,10 +169,6 @@ public class FlatOptionPaneUI
protected Container createButtonArea() { protected Container createButtonArea() {
Container buttonArea = super.createButtonArea(); Container buttonArea = super.createButtonArea();
// use non-UIResource OptionPane.buttonAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( buttonArea );
// scale button padding and subtract focusWidth // scale button padding and subtract focusWidth
if( buttonArea.getLayout() instanceof ButtonAreaLayout ) { if( buttonArea.getLayout() instanceof ButtonAreaLayout ) {
ButtonAreaLayout layout = (ButtonAreaLayout) buttonArea.getLayout(); ButtonAreaLayout layout = (ButtonAreaLayout) buttonArea.getLayout();
@@ -220,33 +218,22 @@ public class FlatOptionPaneUI
super.addMessageComponents( container, cons, msg, maxll, internallyCreated ); super.addMessageComponents( container, cons, msg, maxll, internallyCreated );
} }
private void updateAreaPanel( Container area ) { private void updateChildPanels( Container c ) {
if( !(area instanceof JPanel) )
return;
// use non-UIResource border to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
JPanel panel = (JPanel) area;
panel.setBorder( FlatUIUtils.nonUIResource( panel.getBorder() ) );
panel.setOpaque( false );
}
private void updateKnownChildPanels( Container c ) {
for( Component child : c.getComponents() ) { for( Component child : c.getComponents() ) {
if( child instanceof JPanel && child.getName() != null ) { if( child.getClass() == JPanel.class ) {
switch( child.getName() ) { JPanel panel = (JPanel)child;
case "OptionPane.realBody":
case "OptionPane.body": // make sub-panel non-opaque for OptionPane.background
case "OptionPane.separator": panel.setOpaque( false );
case "OptionPane.break":
// make known sub-panels non-opaque for OptionPane.background // use non-UIResource borders to avoid that they are replaced when switching LaF
((JPanel)child).setOpaque( false ); Border border = panel.getBorder();
break; if( border instanceof UIResource )
} panel.setBorder( FlatUIUtils.nonUIResource( border ) );
} }
if( child instanceof Container ) if( child instanceof Container )
updateKnownChildPanels( (Container) child ); updateChildPanels( (Container) child );
} }
} }

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
@@ -24,14 +23,11 @@ import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI; import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -73,8 +69,6 @@ public class FlatPanelUI
super.installUI( c ); super.installUI( c );
c.addPropertyChangeListener( this ); c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c ); installStyle( (JPanel) c );
} }
@@ -84,20 +78,10 @@ public class FlatPanelUI
super.uninstallUI( c ); super.uninstallUI( c );
c.removePropertyChangeListener( this ); c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installDefaults( JPanel p ) {
super.installDefaults( p );
if( p.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
LookAndFeel.installProperty( p, "opaque", false );
}
/** @since 2.0.1 */ /** @since 2.0.1 */
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
@@ -112,18 +96,7 @@ public class FlatPanelUI
} else } else
installStyle( c ); installStyle( c );
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER:
JPanel p = (JPanel) e.getSource();
if( e.getOldValue() != null )
FullWindowContentSupport.unregisterPlaceholder( p );
if( e.getNewValue() != null )
FullWindowContentSupport.registerPlaceholder( p );
// make panel non-opaque for placeholders
LookAndFeel.installProperty( p, "opaque", e.getNewValue() == null );
break; break;
} }
} }
@@ -162,52 +135,31 @@ public class FlatPanelUI
@Override @Override
public void update( Graphics g, JComponent c ) { public void update( Graphics g, JComponent c ) {
fillRoundedBackground( g, c, arc );
paint( g, c );
}
/** @since 3.5 */
public static void fillRoundedBackground( Graphics g, JComponent c, int arc ) {
if( arc < 0 ) {
Border border = c.getBorder();
arc = ((border instanceof FlatLineBorder)
? ((FlatLineBorder)border).getArc()
: 0);
}
// fill background // fill background
if( c.isOpaque() ) { if( c.isOpaque() ) {
if( arc > 0 ) { int width = c.getWidth();
// fill background with parent color to avoid garbage in rounded corners int height = c.getHeight();
FlatUIUtils.paintParentBackground( g, c ); int arc = (this.arc >= 0)
} else { ? this.arc
g.setColor( c.getBackground() ); : ((c.getBorder() instanceof FlatLineBorder)
g.fillRect( 0, 0, c.getWidth(), c.getHeight() ); ? ((FlatLineBorder)c.getBorder()).getArc()
} : 0);
}
// fill background with parent color to avoid garbage in rounded corners
if( arc > 0 )
FlatUIUtils.paintParentBackground( g, c );
// fill rounded rectangle if having rounded corners
if( arc > 0 ) {
g.setColor( c.getBackground() ); g.setColor( c.getBackground() );
if( arc > 0 ) {
// fill rounded rectangle if having rounded corners
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(), FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height,
0, UIScale.scale( arc ) ); 0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} } else
g.fillRect( 0, 0, width, height );
} }
@Override paint( g, c );
public Dimension getPreferredSize( JComponent c ) {
Object value = c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER );
if( value != null )
return FullWindowContentSupport.getPlaceholderPreferredSize( c, (String) value );
return super.getPreferredSize( c );
}
@Override
public void paint( Graphics g, JComponent c ) {
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.debugPaint( g, c );
} }
} }

View File

@@ -43,7 +43,6 @@ import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCapsLockIcon; import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -164,7 +163,7 @@ public class FlatPasswordFieldUI
} }
private void repaint( KeyEvent e ) { private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) { if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) {
HiDPIUtils.repaint( e.getComponent() ); e.getComponent().repaint();
scrollCaretToVisible(); scrollCaretToVisible();
} }
} }
@@ -327,7 +326,7 @@ public class FlatPasswordFieldUI
if( visible != revealButton.isVisible() ) { if( visible != revealButton.isVisible() ) {
revealButton.setVisible( visible ); revealButton.setVisible( visible );
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
if( !visible ) { if( !visible ) {
revealButton.setSelected( false ); revealButton.setSelected( false );

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.AWTEvent; import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
@@ -37,12 +36,10 @@ import java.awt.Window;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.WindowFocusListener;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -73,13 +70,9 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatPopupFactory public class FlatPopupFactory
extends PopupFactory extends PopupFactory
{ {
static final String KEY_POPUP_USES_NATIVE_BORDER = "FlatLaf.internal.FlatPopupFactory.popupUsesNativeBorder";
private MethodHandle java8getPopupMethod; private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod; private MethodHandle java9getPopupMethod;
private final ArrayList<NonFlashingPopup> stillShownHeavyWeightPopups = new ArrayList<>();
@Override @Override
public Popup getPopup( Component owner, Component contents, int x, int y ) public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException throws IllegalArgumentException
@@ -90,59 +83,28 @@ public class FlatPopupFactory
y = pt.y; y = pt.y;
} }
fixLinuxWaylandJava21focusIssue( owner );
// reuse a heavy weight popup window, which is still shown on screen,
// to avoid flicker when popup (e.g. tooltip) is moving while mouse is moved
for( NonFlashingPopup popup : stillShownHeavyWeightPopups ) {
if( popup.delegate != null &&
popup.owner == owner &&
(popup.contents == contents ||
(popup.contents instanceof JToolTip && contents instanceof JToolTip)) )
{
stillShownHeavyWeightPopups.remove( popup );
return reuseStillShownHeavyWeightPopups( popup, contents, x, y );
}
}
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" ); boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing ) if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents ); return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), contents );
// macOS and Linux adds drop shadow to heavy weight popups // macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) { if( SystemInfo.isMacOS || SystemInfo.isLinux )
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents ); return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null && SystemInfo.isMacOS &&
// do not use rounded border on macOS 14.4+ because it may freeze the application
// and crash the macOS WindowServer process (reports vary from Finder restarts to OS restarts)
// https://github.com/apache/netbeans/issues/7560#issuecomment-2226439215
// https://github.com/apache/netbeans/issues/6647#issuecomment-2070124442
SystemInfo.osVersion < SystemInfo.toVersion( 14, 4, 0, 0 ) &&
FlatNativeMacLibrary.isLoaded() )
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
// Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups // Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups
int borderCornerRadius;
if( isWindows11BorderSupported() && if( isWindows11BorderSupported() &&
getBorderCornerRadius( owner, contents ) > 0 ) (borderCornerRadius = getBorderCornerRadius( owner, contents )) > 0 )
{ {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents ); NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null ) if( popup.popupWindow != null )
setupRoundedBorder( popup.popupWindow, owner, contents ); setupWindows11Border( popup.popupWindow, contents, borderCornerRadius );
return popup; return popup;
} }
// check whether popup overlaps a heavy weight component
if( !forceHeavyWeight && overlapsHeavyWeightComponent( owner, contents, x, y ) )
forceHeavyWeight = true;
// create drop shadow popup // create drop shadow popup
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ); return new DropShadowPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
return owner.getGraphicsConfiguration().isTranslucencyCapable()
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
} }
/** /**
@@ -193,6 +155,67 @@ public class FlatPopupFactory
} }
} }
/**
* 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 a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
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 isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
private int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/** /**
* There is no API in Java 8 to force creation of heavy weight popups, * 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. * but it is possible with reflection. Java 9 provides a new method.
@@ -228,51 +251,6 @@ public class FlatPopupFactory
} }
} }
private static boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private static Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* Reuse a heavy weight popup window, which is still shown on screen,
* by updating window location and contents.
* This avoid flicker when popup (e.g. a tooltip) is moving while mouse is moved.
* E.g. overridden JComponent.getToolTipLocation(MouseEvent).
* See ToolTipManager.checkForTipChange(MouseEvent).
*/
private static NonFlashingPopup reuseStillShownHeavyWeightPopups(
NonFlashingPopup reusePopup, Component contents, int ownerX, int ownerY )
{
// clone popup because PopupFactory.getPopup() should not return old instance
NonFlashingPopup popup = reusePopup.cloneForReuse();
// update popup location, size and contents
popup.reset( contents, ownerX, ownerY );
return popup;
}
//---- tooltips -----------------------------------------------------------
/** /**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20). * Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen, * In case that the tooltip would be partly outside of the screen,
@@ -357,40 +335,16 @@ public class FlatPopupFactory
((JComponent)owner).getToolTipLocation( me ) != null; ((JComponent)owner).getToolTipLocation( me ) != null;
} }
//---- native rounded border ----------------------------------------------
private static boolean isWindows11BorderSupported() { private static boolean isWindows11BorderSupported() {
return SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded(); return SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded();
} }
private static void setupRoundedBorder( Window popupWindow, Component owner, Component contents ) { private static void setupWindows11Border( Window popupWindow, Component contents, int borderCornerRadius ) {
// make sure that the native window is created // make sure that the Windows 11 window is created
if( !popupWindow.isDisplayable() ) if( !popupWindow.isDisplayable() )
popupWindow.addNotify(); popupWindow.addNotify();
int borderCornerRadius = getBorderCornerRadius( owner, contents ); // get window handle
float borderWidth = getRoundedBorderWidth( owner, contents );
// get Swing border color
Color borderColor = null; // use system default color
if( contents instanceof JComponent ) {
Border border = ((JComponent)contents).getBorder();
border = FlatUIUtils.unwrapNonUIResourceBorder( border );
// get color from border of contents (e.g. JPopupMenu or JToolTip)
if( border instanceof FlatLineBorder )
borderColor = ((FlatLineBorder)border).getLineColor();
else if( border instanceof LineBorder )
borderColor = ((LineBorder)border).getLineColor();
else if( border instanceof EmptyBorder )
borderColor = FlatNativeWindowsLibrary.COLOR_NONE; // do not paint border
// avoid that FlatLineBorder paints the Swing border
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, true );
}
if( SystemInfo.isWindows ) {
// get native window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow ); long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
// set corner preference // set corner preference
@@ -400,16 +354,30 @@ public class FlatPopupFactory
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference ); FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color // set border color
FlatNativeWindowsLibrary.dwmSetWindowAttributeCOLORREF( hwnd, FlatNativeWindowsLibrary.DWMWA_BORDER_COLOR, borderColor ); int red = -1; // use system default color
} else if( SystemInfo.isMacOS ) { int green = 0;
if( borderColor == null || borderColor == FlatNativeWindowsLibrary.COLOR_NONE ) int blue = 0;
borderWidth = 0; if( contents instanceof JComponent ) {
Border border = ((JComponent)contents).getBorder();
border = FlatUIUtils.unwrapNonUIResourceBorder( border );
// set corner radius, border width and color // get color from border of contents (e.g. JPopupMenu or JToolTip)
FlatNativeMacLibrary.setWindowRoundedBorder( popupWindow, borderCornerRadius, Color borderColor = null;
borderWidth, (borderColor != null) ? borderColor.getRGB() : 0 ); if( border instanceof FlatLineBorder )
borderColor = ((FlatLineBorder)border).getLineColor();
else if( border instanceof LineBorder )
borderColor = ((LineBorder)border).getLineColor();
else if( border instanceof EmptyBorder )
red = -2; // do not paint border
if( borderColor != null ) {
red = borderColor.getRed();
green = borderColor.getGreen();
blue = borderColor.getBlue();
} }
} }
FlatNativeWindowsLibrary.setWindowBorderColor( hwnd, red, green, blue );
}
private static void resetWindows11Border( Window popupWindow ) { private static void resetWindows11Border( Window popupWindow ) {
// get window handle // get window handle
@@ -421,144 +389,20 @@ public class FlatPopupFactory
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, FlatNativeWindowsLibrary.DWMWCP_DONOTROUND ); FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, FlatNativeWindowsLibrary.DWMWCP_DONOTROUND );
} }
private static int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
private static float getRoundedBorderWidth( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.roundedBorderWidth" :
(contents instanceof JPopupMenu) ? "PopupMenu.roundedBorderWidth" :
(contents instanceof JToolTip) ? "ToolTip.roundedBorderWidth" :
"Popup.roundedBorderWidth";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_ROUNDED_BORDER_WIDTH, uiKey );
return (value instanceof Number) ? ((Number)value).floatValue() : 0;
}
//---- fixes --------------------------------------------------------------
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
if( owner == null )
return false;
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return false;
Rectangle r = new Rectangle( new Point( x, y ), contents.getPreferredSize() );
return overlapsHeavyWeightComponent( window, r );
}
private static boolean overlapsHeavyWeightComponent( Component parent, Rectangle r ) {
if( !parent.isVisible() || !r.intersects( parent.getBounds() ) )
return false;
if( !parent.isLightweight() && !(parent instanceof Window) )
return true;
if( parent instanceof Container ) {
Rectangle r2 = new Rectangle( r.x - parent.getX(), r.y - parent.getY(), r.width, r.height );
for( Component c : ((Container)parent).getComponents() ) {
if( overlapsHeavyWeightComponent( c, r2 ) )
return true;
}
}
return false;
}
/**
* On Linux with Wayland, since Java 21, Swing adds a window focus listener to popup owner/invoker window,
* which hides the popup as soon as the owner/invoker window looses focus.
* This works fine for light-weight popups.
* It also works for heavy-weight popups if they do not request focus.
* Because FlatLaf always uses heavy-weight popups, all popups that request focus
* are broken since Java 21.
*
* This method removes the problematic window focus listener.
*
* https://bugs.openjdk.org/browse/JDK-8280993
* https://github.com/openjdk/jdk/pull/13830
*/
private static void fixLinuxWaylandJava21focusIssue( Component owner ) {
// only necessary on Linux when running in Java 21+
if( owner == null || !SystemInfo.isLinux || SystemInfo.javaVersion < SystemInfo.toVersion( 21, 0, 0, 0 ) )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return;
// remove window focus listener, which was added from class sun.awt.UNIXToolkit since Java 21
for( WindowFocusListener l : window.getWindowFocusListeners() ) {
if( "sun.awt.UNIXToolkit$1".equals( l.getClass().getName() ) ) {
window.removeWindowFocusListener( l );
break;
}
}
}
/**
* 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 a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
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();
}
//---- class NonFlashingPopup --------------------------------------------- //---- class NonFlashingPopup ---------------------------------------------
/** private static class NonFlashingPopup
* Fixes popup background flashing effect when using dark theme on light platform theme,
* where the light popup background is shown for a fraction of a second before
* the dark popup content is shown.
* This is fixed by setting popup background to content background.
* <p>
* Defers hiding of heavy weight popup window for an event cycle,
* which allows reusing popup window to avoid flicker when "moving" popup.
*/
private class NonFlashingPopup
extends Popup extends Popup
{ {
private Popup delegate; private Popup delegate;
Component owner;
private Component contents; private Component contents;
// heavy weight // heavy weight
Window popupWindow; protected Window popupWindow;
private Color oldPopupWindowBackground; private Color oldPopupWindowBackground;
private boolean disposed; NonFlashingPopup( Popup delegate, Component contents ) {
NonFlashingPopup( Popup delegate, Component owner, Component contents ) {
this.delegate = delegate; this.delegate = delegate;
this.owner = owner;
this.contents = contents; this.contents = contents;
popupWindow = SwingUtilities.windowForComponent( contents ); popupWindow = SwingUtilities.windowForComponent( contents );
@@ -572,27 +416,8 @@ public class FlatPopupFactory
} }
} }
private NonFlashingPopup( NonFlashingPopup reusePopup ) {
delegate = reusePopup.delegate;
owner = reusePopup.owner;
contents = reusePopup.contents;
popupWindow = reusePopup.popupWindow;
oldPopupWindowBackground = reusePopup.oldPopupWindowBackground;
}
NonFlashingPopup cloneForReuse() {
return new NonFlashingPopup( this );
}
@Override @Override
public final void show() { public void show() {
if( disposed )
return;
showImpl();
}
void showImpl() {
if( delegate != null ) { if( delegate != null ) {
showPopupAndFixLocation( delegate, popupWindow ); showPopupAndFixLocation( delegate, popupWindow );
@@ -616,36 +441,10 @@ public class FlatPopupFactory
} }
@Override @Override
public final void hide() { public void hide() {
if( disposed )
return;
disposed = true;
// immediately hide non-heavy weight popups or combobox popups
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) {
hideImpl();
return;
}
// defer hiding of heavy weight popup window for an event cycle,
// which allows reusing popup window to avoid flicker when "moving" popup
((JWindow)popupWindow).getContentPane().removeAll();
stillShownHeavyWeightPopups.add( this );
EventQueue.invokeLater( () -> {
// hide popup if it was not reused
if( stillShownHeavyWeightPopups.remove( this ) )
hideImpl();
} );
}
void hideImpl() {
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
if( delegate != null ) { if( delegate != null ) {
delegate.hide(); delegate.hide();
delegate = null; delegate = null;
owner = null;
contents = null; contents = null;
} }
@@ -656,30 +455,6 @@ public class FlatPopupFactory
popupWindow = null; popupWindow = null;
} }
} }
void reset( Component contents, int ownerX, int ownerY ) {
// update popup window location
popupWindow.setLocation( ownerX, ownerY );
// replace component in content pane
Container contentPane = ((JWindow)popupWindow).getContentPane();
contentPane.removeAll();
contentPane.add( contents, BorderLayout.CENTER );
popupWindow.invalidate();
popupWindow.validate();
popupWindow.pack();
// update client property on contents
if( this.contents != contents ) {
Object old = (this.contents instanceof JComponent)
? ((JComponent)this.contents).getClientProperty( KEY_POPUP_USES_NATIVE_BORDER )
: null;
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, old );
this.contents = contents;
}
}
} }
//---- class DropShadowPopup ---------------------------------------------- //---- class DropShadowPopup ----------------------------------------------
@@ -687,6 +462,8 @@ public class FlatPopupFactory
private class DropShadowPopup private class DropShadowPopup
extends NonFlashingPopup extends NonFlashingPopup
{ {
private final Component owner;
// light weight // light weight
private JComponent lightComp; private JComponent lightComp;
private Border oldBorder; private Border oldBorder;
@@ -701,11 +478,11 @@ public class FlatPopupFactory
// heavy weight // heavy weight
private Popup dropShadowDelegate; private Popup dropShadowDelegate;
private Window dropShadowWindow; private Window dropShadowWindow;
private JPanel dropShadowPanel2;
private Color oldDropShadowWindowBackground; private Color oldDropShadowWindowBackground;
DropShadowPopup( Popup delegate, Component owner, Component contents ) { DropShadowPopup( Popup delegate, Component owner, Component contents ) {
super( delegate, owner, contents ); super( delegate, contents );
this.owner = owner;
Dimension size = contents.getPreferredSize(); Dimension size = contents.getPreferredSize();
if( size.width <= 0 || size.height <= 0 ) if( size.width <= 0 || size.height <= 0 )
@@ -721,24 +498,24 @@ public class FlatPopupFactory
// the drop shadow and is positioned behind the popup window. // the drop shadow and is positioned behind the popup window.
// create panel that paints the drop shadow // create panel that paints the drop shadow
dropShadowPanel2 = new JPanel(); JPanel dropShadowPanel = new JPanel();
dropShadowPanel2.setBorder( createDropShadowBorder() ); dropShadowPanel.setBorder( createDropShadowBorder() );
dropShadowPanel2.setOpaque( false ); dropShadowPanel.setOpaque( false );
// set preferred size of drop shadow panel // set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize(); Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel2.getInsets(); Insets insets = dropShadowPanel.getInsets();
dropShadowPanel2.setPreferredSize( new Dimension( dropShadowPanel.setPreferredSize( new Dimension(
prefSize.width + insets.left + insets.right, prefSize.width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom ) ); prefSize.height + insets.top + insets.bottom ) );
// create heavy weight popup for drop shadow // create heavy weight popup for drop shadow
int x = popupWindow.getX() - insets.left; int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top; int y = popupWindow.getY() - insets.top;
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel2, x, y, true ); dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel, x, y, true );
// make drop shadow popup window translucent // make drop shadow popup window translucent
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel2 ); dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel );
if( dropShadowWindow != null ) { if( dropShadowWindow != null ) {
oldDropShadowWindowBackground = dropShadowWindow.getBackground(); oldDropShadowWindowBackground = dropShadowWindow.getBackground();
dropShadowWindow.setBackground( new Color( 0, true ) ); dropShadowWindow.setBackground( new Color( 0, true ) );
@@ -775,23 +552,6 @@ public class FlatPopupFactory
} }
} }
private DropShadowPopup( DropShadowPopup reusePopup ) {
super( reusePopup );
// not necessary to clone fields used for light/medium weight popups
// heavy weight
dropShadowDelegate = reusePopup.dropShadowDelegate;
dropShadowWindow = reusePopup.dropShadowWindow;
dropShadowPanel2 = reusePopup.dropShadowPanel2;
oldDropShadowWindowBackground = reusePopup.oldDropShadowWindowBackground;
}
@Override
NonFlashingPopup cloneForReuse() {
return new DropShadowPopup( this );
}
private Border createDropShadowBorder() { private Border createDropShadowBorder() {
return new FlatDropShadowBorder( return new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ), UIManager.getColor( "Popup.dropShadowColor" ),
@@ -800,14 +560,14 @@ public class FlatPopupFactory
} }
@Override @Override
void showImpl() { public void show() {
if( dropShadowDelegate != null ) if( dropShadowDelegate != null )
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow ); showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
if( mediumWeightPanel != null ) if( mediumWeightPanel != null )
showMediumWeightDropShadow(); showMediumWeightDropShadow();
super.showImpl(); super.show();
// fix location of light weight popup in case it has left or top drop shadow // fix location of light weight popup in case it has left or top drop shadow
if( lightComp != null ) { if( lightComp != null ) {
@@ -818,11 +578,10 @@ public class FlatPopupFactory
} }
@Override @Override
void hideImpl() { public void hide() {
if( dropShadowDelegate != null ) { if( dropShadowDelegate != null ) {
dropShadowDelegate.hide(); dropShadowDelegate.hide();
dropShadowDelegate = null; dropShadowDelegate = null;
dropShadowPanel2 = null;
} }
if( mediumWeightPanel != null ) { if( mediumWeightPanel != null ) {
@@ -831,7 +590,7 @@ public class FlatPopupFactory
mediumWeightPanel = null; mediumWeightPanel = null;
} }
super.hideImpl(); super.hide();
if( dropShadowWindow != null ) { if( dropShadowWindow != null ) {
dropShadowWindow.setBackground( oldDropShadowWindowBackground ); dropShadowWindow.setBackground( oldDropShadowWindowBackground );
@@ -915,25 +674,5 @@ public class FlatPopupFactory
if( dropShadowPanel != null && mediumWeightPanel != null ) if( dropShadowPanel != null && mediumWeightPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) ); dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
} }
@Override
void reset( Component contents, int ownerX, int ownerY ) {
super.reset( contents, ownerX, ownerY );
if( dropShadowWindow != null ) {
// set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel2.getInsets();
int w = prefSize.width + insets.left + insets.right;
int h = prefSize.height + insets.top + insets.bottom;
dropShadowPanel2.setPreferredSize( new Dimension( w, h ) );
// update drop shadow popup window location and size
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setBounds( x, y, w, h );
dropShadowWindow.pack();
}
}
} }
} }

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@@ -87,17 +86,6 @@ public class FlatProgressBarUI
installStyle(); installStyle();
} }
@Override
public void uninstallUI( JComponent c ) {
if( !EventQueue.isDispatchThread() && progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Uninstalling indeterminate progress bar UI not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
super.uninstallUI( c );
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
@@ -122,25 +110,17 @@ public class FlatProgressBarUI
propertyChangeListener = e -> { propertyChangeListener = e -> {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case "indeterminate":
if( !EventQueue.isDispatchThread() && !progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Using JProgressBar.setIndeterminate(false) not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
break;
case PROGRESS_BAR_LARGE_HEIGHT: case PROGRESS_BAR_LARGE_HEIGHT:
case PROGRESS_BAR_SQUARE: case PROGRESS_BAR_SQUARE:
progressBar.revalidate(); progressBar.revalidate();
HiDPIUtils.repaint( progressBar ); progressBar.repaint();
break; break;
case STYLE: case STYLE:
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
progressBar.revalidate(); progressBar.revalidate();
HiDPIUtils.repaint( progressBar ); progressBar.repaint();
break; break;
} }
}; };
@@ -294,6 +274,6 @@ public class FlatProgressBarUI
// Only solution is to repaint whole progress bar. // Only solution is to repaint whole progress bar.
double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() ); double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() );
if( (int) systemScaleFactor != systemScaleFactor ) if( (int) systemScaleFactor != systemScaleFactor )
HiDPIUtils.repaint( progressBar ); progressBar.repaint();
} }
} }

View File

@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -103,23 +102,13 @@ public class FlatRadioButtonMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener( return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -35,19 +35,16 @@ import javax.swing.CellRendererPane;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicRadioButtonUI; import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -161,10 +158,6 @@ public class FlatRadioButtonUI
/** @since 2 */ /** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) { if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
@@ -174,7 +167,7 @@ public class FlatRadioButtonUI
} else } else
installStyle( b ); installStyle( b );
b.revalidate(); b.revalidate();
HiDPIUtils.repaint( b ); b.repaint();
break; break;
} }
} }
@@ -269,7 +262,7 @@ public class FlatRadioButtonUI
@Override @Override
public void paint( Graphics g, JComponent c ) { public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque and if: // fill background even if not opaque if
// - contentAreaFilled is true and // - contentAreaFilled is true and
// - if background color is different to default background color // - if background color is different to default background color
// (this paints selection if using the component as cell renderer) // (this paints selection if using the component as cell renderer)
@@ -285,27 +278,20 @@ public class FlatRadioButtonUI
int focusWidth = getIconFocusWidth( c ); int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) { if( focusWidth > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight(); boolean ltr = c.getComponentOrientation().isLeftToRight();
int halign = ((AbstractButton)c).getHorizontalAlignment();
if( halign == SwingConstants.LEADING )
halign = ltr ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = ltr ? SwingConstants.RIGHT : SwingConstants.LEFT;
Insets insets = c.getInsets( tempInsets ); Insets insets = c.getInsets( tempInsets );
if( (focusWidth > insets.left || focusWidth > insets.right) && int leftOrRightInset = ltr ? insets.left : insets.right;
(halign == SwingConstants.LEFT || halign == SwingConstants.RIGHT) ) if( focusWidth > leftOrRightInset ) {
{
// The left (or right) inset is smaller than the focus width, which may be // The left (or right) inset is smaller than the focus width, which may be
// the case if insets were explicitly reduced (e.g. with an EmptyBorder). // the case if insets were explicitly reduced (e.g. with an EmptyBorder).
// In this case the width has been increased in getPreferredSize() and // In this case the width has been increased in getPreferredSize() and
// here it is necessary to fix icon and text painting location. // here it is necessary to fix icon and text painting location.
int offset = (halign == SwingConstants.LEFT) int offset = focusWidth - leftOrRightInset;
? Math.max( focusWidth - insets.left, 0 ) if( !ltr )
: -Math.max( focusWidth - insets.right, 0 ); offset = -offset;
// move the graphics origin to the left (or right) // move the graphics origin to the left (or right)
g.translate( offset, 0 ); g.translate( offset, 0 );
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c ); super.paint( g, c );
g.translate( -offset, 0 ); g.translate( -offset, 0 );
return; return;
} }
@@ -342,11 +328,6 @@ public class FlatRadioButtonUI
: 0; : 0;
} }
@Override
public int getBaseline( JComponent c, int width, int height ) {
return FlatButtonUI.getBaselineImpl( c, width, height );
}
//---- class FlatRadioButtonListener -------------------------------------- //---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */ /** @since 2 */

View File

@@ -28,6 +28,8 @@ import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.LayoutManager2; import java.awt.LayoutManager2;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@@ -39,6 +41,7 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JRootPane; import javax.swing.JRootPane;
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.Border;
import javax.swing.plaf.BorderUIResource; import javax.swing.plaf.BorderUIResource;
@@ -85,8 +88,8 @@ public class FlatRootPaneUI
private Object nativeWindowBorderData; private Object nativeWindowBorderData;
private LayoutManager oldLayout; private LayoutManager oldLayout;
private ComponentListener macFullWindowContentListener; private PropertyChangeListener ancestorListener;
private PropertyChangeListener macWindowBackgroundListener; private ComponentListener componentListener;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI(); return new FlatRootPaneUI();
@@ -104,7 +107,6 @@ public class FlatRootPaneUI
installBorder(); installBorder();
installNativeWindowBorder(); installNativeWindowBorder();
macInstallFullWindowContentSupport();
} }
protected void installBorder() { protected void installBorder() {
@@ -121,7 +123,6 @@ public class FlatRootPaneUI
uninstallNativeWindowBorder(); uninstallNativeWindowBorder();
uninstallClientDecorations(); uninstallClientDecorations();
macUninstallFullWindowContentSupport();
rootPane = null; rootPane = null;
} }
@@ -156,7 +157,9 @@ public class FlatRootPaneUI
parent.setBackground( UIManager.getColor( "control" ) ); parent.setBackground( UIManager.getColor( "control" ) );
} }
macClearBackgroundForTranslucentWindow( c ); // 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() );
} }
@Override @Override
@@ -176,20 +179,55 @@ public class FlatRootPaneUI
protected void installListeners( JRootPane root ) { protected void installListeners( JRootPane root ) {
super.installListeners( root ); super.installListeners( root );
if( SystemInfo.isMacFullWindowContentSupported ) if( SystemInfo.isJava_9_orLater ) {
macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root ); // On HiDPI screens, where scaling is used, there may be white lines on the
macInstallWindowBackgroundListener( root ); // bottom and on the right side of the window when it is initially shown.
// This is very disturbing in dark themes, but hard to notice in light themes.
// Seems to be a rounding issue when Swing adds dirty region of window
// using RepaintManager.nativeAddDirtyRegion().
//
// Note: Not using a HierarchyListener here, which would be much easier,
// because this causes problems with mouse clicks in heavy-weight popups.
// Instead, add a listener to the root pane that waits until it is added
// to a window, then add a component listener to the window.
// See: https://github.com/JFormDesigner/FlatLaf/issues/371
ancestorListener = e -> {
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if( newValue instanceof Window ) {
if( componentListener == null ) {
componentListener = new ComponentAdapter() {
@Override
public void componentShown( ComponentEvent e ) {
// add whole root pane to dirty regions when window is initially shown
root.getParent().repaint( root.getX(), root.getY(), root.getWidth(), root.getHeight() );
}
};
}
((Window)newValue).addComponentListener( componentListener );
} else if( newValue == null && oldValue instanceof Window ) {
if( componentListener != null )
((Window)oldValue).removeComponentListener( componentListener );
}
};
root.addPropertyChangeListener( "ancestor", ancestorListener );
}
} }
@Override @Override
protected void uninstallListeners( JRootPane root ) { protected void uninstallListeners( JRootPane root ) {
super.uninstallListeners( root ); super.uninstallListeners( root );
if( SystemInfo.isMacFullWindowContentSupported ) { if( SystemInfo.isJava_9_orLater ) {
FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener ); if( componentListener != null ) {
macFullWindowContentListener = null; Window window = SwingUtilities.windowForComponent( root );
if( window != null )
window.removeComponentListener( componentListener );
componentListener = null;
}
root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
} }
macUninstallWindowBackgroundListener( root );
} }
/** @since 1.1.2 */ /** @since 1.1.2 */
@@ -269,141 +307,19 @@ public class FlatRootPaneUI
// layer title pane under frame content layer to allow placing menu bar over title pane // 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 final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
private final static Integer TITLE_PANE_MOUSE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 2;
private final static Integer WINDOW_TOP_BORDER_LAYER = Integer.MAX_VALUE;
// for fullWindowContent mode, layer title pane over frame content layer to allow placing title bar buttons over content
/** @since 3.4 */
protected final static Integer TITLE_PANE_FULL_WINDOW_CONTENT_LAYER = JLayeredPane.FRAME_CONTENT_LAYER + 1;
private Integer getLayerForTitlePane() {
return isFullWindowContent( rootPane ) ? TITLE_PANE_FULL_WINDOW_CONTENT_LAYER : TITLE_PANE_LAYER;
}
protected void setTitlePane( FlatTitlePane newTitlePane ) { protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane(); JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane != null ) { if( titlePane != null )
layeredPane.remove( titlePane ); layeredPane.remove( titlePane );
layeredPane.remove( titlePane.mouseLayer );
if( titlePane.windowTopBorderLayer != null )
layeredPane.remove( titlePane.windowTopBorderLayer );
}
if( newTitlePane != null ) { if( newTitlePane != null )
layeredPane.add( newTitlePane, getLayerForTitlePane() ); layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
if( newTitlePane.windowTopBorderLayer != null && newTitlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( newTitlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
}
titlePane = newTitlePane; titlePane = newTitlePane;
} }
private void macInstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// set window buttons spacing
if( isMacButtonsSpacingSupported() && rootPane.isDisplayable() ) {
int buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT;
String value = (String) rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING );
if( value != null ) {
switch( value ) {
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_MEDIUM;
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_LARGE;
break;
}
}
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), buttonsSpacing );
}
// update buttons bounds client property
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
}
private void macUninstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// do not uninstall when switching to another FlatLaf theme
if( UIManager.getLookAndFeel() instanceof FlatLaf )
return;
// reset window buttons spacing
if( isMacButtonsSpacingSupported() )
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT );
// remove buttons bounds client property
FullWindowContentSupport.macUninstallFullWindowContentButtonsBoundsProperty( rootPane );
}
private boolean isMacButtonsSpacingSupported() {
return SystemInfo.isMacOS && SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded();
}
private void macInstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener == null ) {
macWindowBackgroundListener = e -> macClearBackgroundForTranslucentWindow( c );
window.addPropertyChangeListener( "background", macWindowBackgroundListener );
}
}
private void macUninstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener != null ) {
window.removePropertyChangeListener( "background", macWindowBackgroundListener );
macWindowBackgroundListener = null;
}
}
/**
* When setting window background to translucent color (alpha < 255),
* Swing paints that window translucent on Windows and Linux, but not on macOS.
* The reason for this is that FlatLaf sets the background color of the root pane,
* and Swing behaves a bit differently on macOS than on other platforms in that case.
* Other L&Fs do not set root pane background, which is {@code null} by default.
* <p>
* To fix this problem, set the root pane background to {@code null}
* if windows uses a translucent background.
*/
private void macClearBackgroundForTranslucentWindow( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null ) {
Color windowBackground = window.getBackground();
if( windowBackground != null &&
windowBackground.getAlpha() < 255 &&
c.getBackground() instanceof UIResource )
{
// clear root pane background
c.setBackground( null );
}
}
}
private Window getParentWindow( JRootPane c ) {
// not using SwingUtilities.windowForComponent() or SwingUtilities.getWindowAncestor()
// here because root panes may be nested and used anywhere (e.g. in JInternalFrame)
// but we're only interested in the "root" root pane, which is a direct child of the window
Container parent = c.getParent();
return (parent instanceof Window) ? (Window) parent : null;
}
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e ); super.propertyChange( e );
@@ -448,28 +364,6 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged(); titlePane.titleBarColorsChanged();
break; break;
case FlatClientProperties.FULL_WINDOW_CONTENT:
if( titlePane != null ) {
rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
if( titlePane.windowTopBorderLayer != null ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( titlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
else
layeredPane.remove( titlePane.windowTopBorderLayer );
}
titlePane.updateIcon();
titlePane.updateVisibility();
titlePane.updateFullWindowContentButtonsBoundsProperty();
}
FullWindowContentSupport.revalidatePlaceholders( rootPane );
rootPane.revalidate();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
case FlatClientProperties.GLASS_PANE_FULL_HEIGHT: case FlatClientProperties.GLASS_PANE_FULL_HEIGHT:
rootPane.revalidate(); rootPane.revalidate();
break; break;
@@ -478,43 +372,14 @@ public class FlatRootPaneUI
if( rootPane.isDisplayable() ) if( rootPane.isDisplayable() )
throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." );
break; break;
case "ancestor":
if( e.getNewValue() instanceof Window )
macClearBackgroundForTranslucentWindow( rootPane );
macUninstallWindowBackgroundListener( rootPane );
macInstallWindowBackgroundListener( rootPane );
// FlatNativeMacLibrary.setWindowButtonsSpacing() and
// FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty()
// require a native window, but setting the client properties
// "apple.awt.fullWindowContent" or FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING
// is usually done before the native window is created
// --> try again when native window is created
if( e.getNewValue() instanceof Window )
macInstallFullWindowContentSupport();
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING:
macInstallFullWindowContentSupport();
break;
case "apple.awt.fullWindowContent":
if( SystemInfo.isMacFullWindowContentSupported )
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
break;
} }
} }
/** @since 3.4 */
protected static boolean isFullWindowContent( JRootPane rootPane ) {
return FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.FULL_WINDOW_CONTENT, false );
}
protected static boolean isMenuBarEmbedded( JRootPane rootPane ) { protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
FlatTitlePane titlePane = getTitlePane( rootPane ); RootPaneUI ui = rootPane.getUI();
return titlePane != null && titlePane.isMenuBarEmbedded(); return ui instanceof FlatRootPaneUI &&
((FlatRootPaneUI)ui).titlePane != null &&
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
} }
/** @since 2.4 */ /** @since 2.4 */
@@ -550,22 +415,24 @@ public class FlatRootPaneUI
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) { private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent; JRootPane rootPane = (JRootPane) parent;
Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null) Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() ) ? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize(); // same as in JRootPane.RootLayout.preferredLayoutSize() : rootPane.getSize();
int width = contentSize.width; // title pane width is not considered here int width = contentSize.width; // title pane width is not considered here
int height = contentSize.height; int height = titlePaneSize.height + contentSize.height;
if( titlePane != null && !isFullWindowContent( rootPane ) )
height += getSizeFunc.apply( titlePane ).height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) { if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar(); JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) { Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
Dimension menuBarSize = getSizeFunc.apply( menuBar ); ? getSizeFunc.apply( menuBar )
: new Dimension();
width = Math.max( width, menuBarSize.width ); width = Math.max( width, menuBarSize.width );
height += menuBarSize.height; height += menuBarSize.height;
} }
}
Insets insets = rootPane.getInsets(); Insets insets = rootPane.getInsets();
@@ -589,28 +456,11 @@ public class FlatRootPaneUI
if( rootPane.getLayeredPane() != null ) if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height ); rootPane.getLayeredPane().setBounds( x, y, width, height );
// title pane (is a child of layered pane) // title pane
int nextY = 0; int nextY = 0;
if( titlePane != null ) { if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0; int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
boolean isFullWindowContent = isFullWindowContent( rootPane );
if( isFullWindowContent && !UIManager.getBoolean( FlatTitlePane.KEY_DEBUG_SHOW_RECTANGLES ) ) {
// place title bar into top-right corner
int tw = Math.min( titlePane.getPreferredSize().width, width );
int tx = titlePane.getComponentOrientation().isLeftToRight() ? width - tw : 0;
titlePane.setBounds( tx, 0, tw, prefHeight );
} else
titlePane.setBounds( 0, 0, width, prefHeight ); titlePane.setBounds( 0, 0, width, prefHeight );
titlePane.mouseLayer.setBounds( 0, 0, width, prefHeight );
if( titlePane.windowTopBorderLayer != null ) {
boolean show = isFullWindowContent && !titlePane.isWindowMaximized() && !isFullScreen;
if( show )
titlePane.windowTopBorderLayer.setBounds( 0, 0, width, 1 );
titlePane.windowTopBorderLayer.setVisible( show );
}
if( !isFullWindowContent )
nextY += prefHeight; nextY += prefHeight;
} }
@@ -622,7 +472,7 @@ public class FlatRootPaneUI
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset ); rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
} }
// menu bar (is a child of layered pane) // menu bar
JMenuBar menuBar = rootPane.getJMenuBar(); JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) { if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded(); boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
@@ -630,23 +480,13 @@ public class FlatRootPaneUI
titlePane.validate(); titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() ); menuBar.setBounds( titlePane.getMenuBarBounds() );
} else { } else {
int mx = 0;
int mw = width;
if( titlePane != null && isFullWindowContent( rootPane ) ) {
// make menu bar width smaller to avoid that it overlaps title bar buttons
int tw = Math.min( titlePane.getPreferredSize().width, width );
mw -= tw;
if( !titlePane.getComponentOrientation().isLeftToRight() )
mx = tw;
}
Dimension prefSize = menuBar.getPreferredSize(); Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( mx, nextY, mw, prefSize.height ); menuBar.setBounds( 0, nextY, width, prefSize.height );
nextY += prefSize.height; nextY += prefSize.height;
} }
} }
// content pane (is a child of layered pane) // content pane
Container contentPane = rootPane.getContentPane(); Container contentPane = rootPane.getContentPane();
if( contentPane != null ) if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) ); contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
@@ -659,7 +499,7 @@ public class FlatRootPaneUI
@Override @Override
public void invalidateLayout( Container parent ) { public void invalidateLayout( Container parent ) {
if( titlePane != null ) if( titlePane != null )
titlePane.menuBarInvalidate(); titlePane.menuBarChanged();
} }
@Override @Override

View File

@@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@@ -39,11 +40,13 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI; import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -204,6 +207,16 @@ public class FlatScrollBarUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected TrackListener createTrackListener() {
return new FlatTrackListener();
}
@Override
protected ScrollListener createScrollListener() {
return new FlatScrollListener();
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener() { protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener(); PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -213,14 +226,14 @@ public class FlatScrollBarUI
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS: case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
scrollbar.revalidate(); scrollbar.revalidate();
HiDPIUtils.repaint( scrollbar ); scrollbar.repaint();
break; break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
scrollbar.revalidate(); scrollbar.revalidate();
HiDPIUtils.repaint( scrollbar ); scrollbar.repaint();
break; break;
case "componentOrientation": case "componentOrientation":
@@ -246,7 +259,7 @@ public class FlatScrollBarUI
// because scroll bars do not receive mouse exited event. // because scroll bars do not receive mouse exited event.
// The scroll pane, including its scroll bars, is not part // The scroll pane, including its scroll bars, is not part
// of the component hierarchy and does not receive mouse events // of the component hierarchy and does not receive mouse events
// directly. Instead, LWComponentPeer receives mouse events // directly. Instead LWComponentPeer receives mouse events
// and delegates them to peers, but entered/exited events // and delegates them to peers, but entered/exited events
// are sent only for the whole scroll pane. // are sent only for the whole scroll pane.
// Exited event is only sent when mouse leaves scroll pane. // Exited event is only sent when mouse leaves scroll pane.
@@ -432,6 +445,197 @@ public class FlatScrollBarUI
return allowsAbsolutePositioning; return allowsAbsolutePositioning;
} }
@Override
protected void scrollByBlock( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByBlock( direction );
} );
}
@Override
protected void scrollByUnit( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByUnit( direction );
} );
}
/**
* Runs the given runnable, which should modify the scroll bar value,
* and then animate scroll bar value from old value to new value.
*/
public void runAndSetValueAnimated( Runnable r ) {
if( inRunAndSetValueAnimated || !isSmoothScrollingEnabled() ) {
r.run();
return;
}
inRunAndSetValueAnimated = true;
if( animator != null )
animator.cancel();
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );
// remember current scrollbar value so that we can start scroll animation from there
int oldValue = scrollbar.getValue();
// run given runnable, which computes and sets the new scrollbar value
FlatScrollPaneUI.runWithoutBlitting( scrollbar.getParent(), () ->{
// if invoked while animation is running, calculation of new value
// should start at the previous target value
if( targetValue != Integer.MIN_VALUE )
scrollbar.setValue( targetValue );
r.run();
} );
// do not use animation if started dragging thumb
if( isDragging ) {
// do not clear valueIsAdjusting here
inRunAndSetValueAnimated = false;
return;
}
int newValue = scrollbar.getValue();
if( newValue != oldValue ) {
// start scroll animation if value has changed
setValueAnimated( oldValue, newValue );
} else {
// clear valueIsAdjusting if value has not changed
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( false );
}
inRunAndSetValueAnimated = false;
}
private boolean inRunAndSetValueAnimated;
private Animator animator;
private int startValue = Integer.MIN_VALUE;
private int targetValue = Integer.MIN_VALUE;
private boolean useValueIsAdjusting = true;
int getTargetValue() {
return targetValue;
}
public void setValueAnimated( int initialValue, int value ) {
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );
// (always) set scrollbar value to initial value
scrollbar.setValue( initialValue );
// do some check if animation already running
if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) {
// Ignore requests if animation still running and scroll direction is the same
// and new value is within currently running animation.
// Without this check, repeating-scrolling via keyboard would become
// very slow when reaching the top/bottom/left/right of the viewport,
// because it would start a new 200ms animation to scroll a few pixels.
if( value == targetValue ||
(value > startValue && value < targetValue) || // scroll down/right
(value < startValue && value > targetValue) ) // scroll up/left
return;
}
startValue = initialValue;
targetValue = value;
// create animator
if( animator == null ) {
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
Object interpolator = UIManager.get( "ScrollPane.smoothScrolling.interpolator" );
animator = new Animator( duration, fraction -> {
if( scrollbar == null || !scrollbar.isShowing() ) {
animator.stop();
return;
}
// re-enable valueIsAdjusting if disabled while animation is running
// (e.g. in mouse released listener)
if( useValueIsAdjusting && !scrollbar.getValueIsAdjusting() )
scrollbar.setValueIsAdjusting( true );
scrollbar.setValue( startValue + Math.round( (targetValue - startValue) * fraction ) );
}, () -> {
startValue = targetValue = Integer.MIN_VALUE;
if( useValueIsAdjusting && scrollbar != null )
scrollbar.setValueIsAdjusting( false );
});
animator.setResolution( resolution );
animator.setInterpolator( (interpolator instanceof Animator.Interpolator)
? (Animator.Interpolator) interpolator
: new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) );
}
// restart animator
animator.cancel();
animator.start();
}
protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
return false;
// if scroll bar is child of scroll pane, check only client property of scroll pane
Container parent = scrollbar.getParent();
JComponent c = (parent instanceof JScrollPane) ? (JScrollPane) parent : scrollbar;
Object smoothScrolling = c.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" );
}
//---- class FlatTrackListener --------------------------------------------
protected class FlatTrackListener
extends TrackListener
{
@Override
public void mousePressed( MouseEvent e ) {
// Do not use valueIsAdjusting here (in runAndSetValueAnimated())
// for smooth scrolling because super.mousePressed() enables this itself
// and super.mouseRelease() disables it later.
// If we would disable valueIsAdjusting here (in runAndSetValueAnimated())
// and move the thumb with the mouse, then the thumb location is not updated
// if later scrolled with a key (e.g. HOME key).
useValueIsAdjusting = false;
runAndSetValueAnimated( () -> {
super.mousePressed( e );
} );
}
@Override
public void mouseReleased( MouseEvent e ) {
super.mouseReleased( e );
useValueIsAdjusting = true;
}
}
//---- class FlatScrollListener -------------------------------------------
protected class FlatScrollListener
extends ScrollListener
{
@Override
public void actionPerformed( ActionEvent e ) {
runAndSetValueAnimated( () -> {
super.actionPerformed( e );
} );
}
}
//---- class ScrollBarHoverListener --------------------------------------- //---- class ScrollBarHoverListener ---------------------------------------
// using static field to disabling hover for other scroll bars // using static field to disabling hover for other scroll bars
@@ -493,7 +697,7 @@ public class FlatScrollBarUI
private void repaint() { private void repaint() {
if( scrollbar.isEnabled() ) if( scrollbar.isEnabled() )
HiDPIUtils.repaint( scrollbar ); scrollbar.repaint();
} }
} }

View File

@@ -1,118 +0,0 @@
/*
* Copyright 2023 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Border for {@link javax.swing.JScrollPane}.
*
* @uiDefault ScrollPane.arc int
* @uiDefault ScrollPane.List.arc int
* @uiDefault ScrollPane.Table.arc int
* @uiDefault ScrollPane.TextComponent.arc int
* @uiDefault ScrollPane.Tree.arc int
* @author Karl Tauber
* @since 3.3
*/
public class FlatScrollPaneBorder
extends FlatBorder
{
@Styleable protected int arc = UIManager.getInt( "ScrollPane.arc" );
private boolean isArcStyled;
private final int listArc = FlatUIUtils.getUIInt( "ScrollPane.List.arc", -1 );
private final int tableArc = FlatUIUtils.getUIInt( "ScrollPane.Table.arc", -1 );
private final int textComponentArc = FlatUIUtils.getUIInt( "ScrollPane.TextComponent.arc", -1 );
private final int treeArc = FlatUIUtils.getUIInt( "ScrollPane.Tree.arc", -1 );
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = super.applyStyleProperty( key, value );
if( "arc".equals( key ) )
isArcStyled = true;
return oldValue;
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets );
// if view is rounded, increase left and right insets to avoid that the viewport
// is painted over the rounded border on the corners
int padding = getLeftRightPadding( c );
if( padding > 0 ) {
insets.left += padding;
insets.right += padding;
}
return insets;
}
@Override
protected int getArc( Component c ) {
if( isCellEditor( c ) )
return 0;
if( isArcStyled )
return arc;
if( c instanceof JScrollPane ) {
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
if( listArc >= 0 && view instanceof JList )
return listArc;
if( tableArc >= 0 && view instanceof JTable )
return tableArc;
if( textComponentArc >= 0&& view instanceof JTextComponent )
return textComponentArc;
if( treeArc >= 0 && view instanceof JTree )
return treeArc;
}
return arc;
}
/**
* Returns the scaled left/right padding used when arc is larger than zero.
* <p>
* This is the distance from the inside of the left border to the left side of the view component.
* On the right side, this is the distance between the right side of the view component and
* the vertical scrollbar. Or the inside of the right border if the scrollbar is hidden.
*/
public int getLeftRightPadding( Component c ) {
// Subtract lineWidth from radius because radius is given for the outside
// of the painted line, but insets from super already include lineWidth.
// Reduce padding by 10% to make padding slightly smaller because it is not recognizable
// when the view is minimally painted over the beginning of the border curve.
int arc = getArc( c );
return (arc > 0)
? Math.max( Math.round( UIScale.scale( ((arc / 2f) - getLineWidth( c )) * 0.9f ) ), 0 )
: 0;
}
}

View File

@@ -19,11 +19,10 @@ package com.formdev.flatlaf.ui;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ContainerEvent; import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener; import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
@@ -34,6 +33,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
@@ -44,20 +45,18 @@ import javax.swing.JTree;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneConstants;
import javax.swing.ScrollPaneLayout;
import javax.swing.Scrollable; import javax.swing.Scrollable;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
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.basic.BasicScrollPaneUI; import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -104,13 +103,7 @@ public class FlatScrollPaneUI
super.installUI( c ); super.installUI( c );
int focusWidth = UIManager.getInt( "Component.focusWidth" ); int focusWidth = UIManager.getInt( "Component.focusWidth" );
int arc = UIManager.getInt( "ScrollPane.arc" ); LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 && arc == 0 );
// install layout manager
LayoutManager layout = c.getLayout();
if( layout != null && layout.getClass() == ScrollPaneLayout.UIResource.class )
c.setLayout( createScrollPaneLayout() );
installStyle(); installStyle();
@@ -121,10 +114,6 @@ public class FlatScrollPaneUI
public void uninstallUI( JComponent c ) { public void uninstallUI( JComponent c ) {
MigLayoutVisualPadding.uninstall( scrollpane ); MigLayoutVisualPadding.uninstall( scrollpane );
// uninstall layout manager
if( c.getLayout() instanceof FlatScrollPaneLayout )
c.setLayout( new ScrollPaneLayout.UIResource() );
super.uninstallUI( c ); super.uninstallUI( c );
oldStyleValues = null; oldStyleValues = null;
@@ -147,30 +136,38 @@ public class FlatScrollPaneUI
handler = null; handler = null;
} }
/**
* @since 3.3
*/
protected FlatScrollPaneLayout createScrollPaneLayout() {
return new FlatScrollPaneLayout();
}
@Override @Override
protected MouseWheelListener createMouseWheelListener() { protected MouseWheelListener createMouseWheelListener() {
MouseWheelListener superListener = super.createMouseWheelListener(); MouseWheelListener superListener = super.createMouseWheelListener();
return e -> { return e -> {
if( isSmoothScrollingEnabled() && if( isSmoothScrollingEnabled() &&
scrollpane.isWheelScrollingEnabled() && scrollpane.isWheelScrollingEnabled() )
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
e.getPreciseWheelRotation() != 0 &&
e.getPreciseWheelRotation() != e.getWheelRotation() )
{ {
mouseWheelMovedSmooth( e ); if( e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
isPreciseWheelEvent( e ) )
{
// precise scrolling
mouseWheelMovedPrecise( e );
} else {
// smooth scrolling
JScrollBar scrollBar = findScrollBarToScroll( e );
if( scrollBar != null && scrollBar.getUI() instanceof FlatScrollBarUI ) {
FlatScrollBarUI ui = (FlatScrollBarUI) scrollBar.getUI();
ui.runAndSetValueAnimated( () -> {
superListener.mouseWheelMoved( e );
} );
} else
superListener.mouseWheelMoved( e );
}
} else } else
superListener.mouseWheelMoved( e ); superListener.mouseWheelMoved( e );
}; };
} }
protected boolean isSmoothScrollingEnabled() { protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
return false;
Object smoothScrolling = scrollpane.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING ); Object smoothScrolling = scrollpane.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
if( smoothScrolling instanceof Boolean ) if( smoothScrolling instanceof Boolean )
return (Boolean) smoothScrolling; return (Boolean) smoothScrolling;
@@ -181,19 +178,40 @@ public class FlatScrollPaneUI
return UIManager.getBoolean( "ScrollPane.smoothScrolling" ); return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
} }
private void mouseWheelMovedSmooth( MouseWheelEvent e ) { private long lastPreciseWheelWhen;
private boolean isPreciseWheelEvent( MouseWheelEvent e ) {
double preciseWheelRotation = e.getPreciseWheelRotation();
if( preciseWheelRotation != 0 && preciseWheelRotation != e.getWheelRotation() ) {
// precise wheel event
lastPreciseWheelWhen = e.getWhen();
return true;
}
// If a non-precise wheel event occurs shortly after a precise wheel event,
// then it is probably still a precise wheel but the precise value
// is by chance an integer value (e.g. 1.0 or 2.0).
// Not handling this special case, would start an animation for smooth scrolling,
// which would be interrupted soon when the next precise wheel event occurs.
// This would result in jittery scrolling. E.g. on a MacBook using Trackpad or Magic Mouse.
if( e.getWhen() - lastPreciseWheelWhen < 1000 )
return true;
// non-precise wheel event
lastPreciseWheelWhen = 0;
return false;
}
private void mouseWheelMovedPrecise( MouseWheelEvent e ) {
// return if there is no viewport // return if there is no viewport
JViewport viewport = scrollpane.getViewport(); JViewport viewport = scrollpane.getViewport();
if( viewport == null ) if( viewport == null )
return; return;
// find scrollbar to scroll // find scrollbar to scroll
JScrollBar scrollbar = scrollpane.getVerticalScrollBar(); JScrollBar scrollbar = findScrollBarToScroll( e );
if( scrollbar == null || !scrollbar.isVisible() || e.isShiftDown() ) { if( scrollbar == null )
scrollbar = scrollpane.getHorizontalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() )
return; return;
}
// consume event // consume event
e.consume(); e.consume();
@@ -286,6 +304,16 @@ public class FlatScrollPaneUI
*/ */
} }
private JScrollBar findScrollBarToScroll( MouseWheelEvent e ) {
JScrollBar scrollBar = scrollpane.getVerticalScrollBar();
if( scrollBar == null || !scrollBar.isVisible() || e.isShiftDown() ) {
scrollBar = scrollpane.getHorizontalScrollBar();
if( scrollBar == null || !scrollBar.isVisible() )
return null;
}
return scrollBar;
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener() { protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener(); PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -298,11 +326,11 @@ public class FlatScrollPaneUI
JScrollBar hsb = scrollpane.getHorizontalScrollBar(); JScrollBar hsb = scrollpane.getHorizontalScrollBar();
if( vsb != null ) { if( vsb != null ) {
vsb.revalidate(); vsb.revalidate();
HiDPIUtils.repaint( vsb ); vsb.repaint();
} }
if( hsb != null ) { if( hsb != null ) {
hsb.revalidate(); hsb.revalidate();
HiDPIUtils.repaint( hsb ); hsb.repaint();
} }
break; break;
@@ -314,7 +342,8 @@ public class FlatScrollPaneUI
Object corner = e.getNewValue(); Object corner = e.getNewValue();
if( corner instanceof JButton && if( corner instanceof JButton &&
((JButton)corner).getBorder() instanceof FlatButtonBorder && ((JButton)corner).getBorder() instanceof FlatButtonBorder &&
getView( scrollpane ) instanceof JTable ) scrollpane.getViewport() != null &&
scrollpane.getViewport().getView() instanceof JTable )
{ {
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() ); ((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false ); ((JButton)corner).setFocusable( false );
@@ -322,26 +351,14 @@ public class FlatScrollPaneUI
break; break;
case FlatClientProperties.OUTLINE: case FlatClientProperties.OUTLINE:
HiDPIUtils.repaint( scrollpane ); scrollpane.repaint();
break; break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
scrollpane.revalidate(); scrollpane.revalidate();
HiDPIUtils.repaint( scrollpane ); scrollpane.repaint();
break;
case "border":
Object newBorder = e.getNewValue();
if( newBorder != null && newBorder == UIManager.getBorder( "Table.scrollPaneBorder" ) ) {
// JTable.configureEnclosingScrollPaneUI() replaces the scrollpane border
// with another one --> re-apply style on new border
borderShared = null;
installStyle();
scrollpane.revalidate();
HiDPIUtils.repaint( scrollpane );
}
break; break;
} }
}; };
@@ -369,10 +386,9 @@ public class FlatScrollPaneUI
/** @since 2 */ /** @since 2 */
protected Object applyStyleProperty( String key, Object value ) { protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "focusWidth" ) || key.equals( "arc" ) ) { if( key.equals( "focusWidth" ) ) {
int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" ); int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" );
int arc = (value instanceof Integer) ? (int) value : UIManager.getInt( "ScrollPane.arc" ); LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 && arc == 0 );
} }
if( borderShared == null ) if( borderShared == null )
@@ -438,46 +454,13 @@ public class FlatScrollPaneUI
c.getHeight() - insets.top - insets.bottom ); c.getHeight() - insets.top - insets.bottom );
} }
// if view is rounded, paint rounded background with view background color
// to ensure that free areas at left and right have same color as view
Component view;
float arc = getBorderArc( scrollpane );
if( arc > 0 && (view = getView( scrollpane )) != null ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
g.setColor( view.getBackground() );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
paint( g, c ); paint( g, c );
} }
@Override
public void paint( Graphics g, JComponent c ) {
Border viewportBorder = scrollpane.getViewportBorder();
if( viewportBorder != null ) {
Rectangle r = scrollpane.getViewportBorderBounds();
int padding = getBorderLeftRightPadding( scrollpane );
JScrollBar vsb = scrollpane.getVerticalScrollBar();
if( padding > 0 &&
vsb != null && vsb.isVisible() &&
scrollpane.getLayout() instanceof FlatScrollPaneLayout &&
((FlatScrollPaneLayout)scrollpane.getLayout()).canIncreaseViewportWidth( scrollpane ) )
{
boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();
int extraWidth = Math.min( padding, vsb.getWidth() );
viewportBorder.paintBorder( scrollpane, g, r.x - (ltr ? 0 : extraWidth), r.y, r.width + extraWidth, r.height );
} else
viewportBorder.paintBorder( scrollpane, g, r.x, r.y, r.width, r.height );
}
}
/** @since 1.3 */ /** @since 1.3 */
public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) { public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) {
Component view = getView( scrollPane ); JViewport viewport = scrollPane.getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view == null ) if( view == null )
return false; return false;
@@ -497,23 +480,117 @@ public class FlatScrollPaneUI
return false; return false;
} }
static Component getView( JScrollPane scrollPane ) { @Override
JViewport viewport = scrollPane.getViewport(); protected void syncScrollPaneWithViewport() {
return (viewport != null) ? viewport.getView() : null; // if the viewport has been scrolled by using JComponent.scrollRectToVisible()
// (e.g. by moving selection), then it is necessary to update the scroll bar values
if( isSmoothScrollingEnabled() ) {
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, false, () -> {
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, false, () -> {
super.syncScrollPaneWithViewport();
} );
} );
} else
super.syncScrollPaneWithViewport();
} }
private static float getBorderArc( JScrollPane scrollPane ) { /**
Border border = scrollPane.getBorder(); * Runs the given runnable, if smooth scrolling is enabled, with disabled
return (border instanceof FlatScrollPaneBorder) * viewport blitting mode and with scroll bar value set to "target" value.
? UIScale.scale( (float) ((FlatScrollPaneBorder)border).getArc( scrollPane ) ) * This is necessary when calculating new view position during animation.
: 0; * Otherwise calculation would use wrong view position and (repeating) scrolling
* would be much slower than without smooth scrolling.
*/
private void runWithScrollBarsTargetValues( boolean blittingOnly, Runnable r ) {
if( isSmoothScrollingEnabled() ) {
runWithoutBlitting( scrollpane, () -> {
if( blittingOnly )
r.run();
else {
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, true, () -> {
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, true, r );
} );
}
} );
} else
r.run();
} }
private static int getBorderLeftRightPadding( JScrollPane scrollPane ) { private void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, boolean useTargetValue, Runnable r ) {
Border border = scrollPane.getBorder(); if( inRunAndSyncValueAnimated[i] || sb == null || !(sb.getUI() instanceof FlatScrollBarUI) ) {
return (border instanceof FlatScrollPaneBorder) r.run();
? ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane ) return;
: 0; }
inRunAndSyncValueAnimated[i] = true;
int oldValue = sb.getValue();
int oldVisibleAmount = sb.getVisibleAmount();
int oldMinimum = sb.getMinimum();
int oldMaximum = sb.getMaximum();
FlatScrollBarUI ui = (FlatScrollBarUI) sb.getUI();
if( useTargetValue && ui.getTargetValue() != Integer.MIN_VALUE )
sb.setValue( ui.getTargetValue() );
r.run();
int newValue = sb.getValue();
if( newValue != oldValue &&
sb.getVisibleAmount() == oldVisibleAmount &&
sb.getMinimum() == oldMinimum &&
sb.getMaximum() == oldMaximum &&
sb.getUI() instanceof FlatScrollBarUI )
{
ui.setValueAnimated( oldValue, newValue );
}
inRunAndSyncValueAnimated[i] = false;
}
private final boolean[] inRunAndSyncValueAnimated = new boolean[2];
/**
* Runs the given runnable with disabled viewport blitting mode.
* If blitting mode is enabled, the viewport immediately repaints parts of the
* view if the view position is changed via JViewport.setViewPosition().
* This causes scrolling artifacts if smooth scrolling is enabled and the view position
* is "temporary" changed to its new target position, changed back to its old position
* and again moved animated to the target position.
*/
static void runWithoutBlitting( Container scrollPane, Runnable r ) {
// prevent the viewport to immediately repaint using blitting
JViewport viewport = (scrollPane instanceof JScrollPane) ? ((JScrollPane)scrollPane).getViewport() : null;
boolean isBlitScrollMode = (viewport != null) ? viewport.getScrollMode() == JViewport.BLIT_SCROLL_MODE : false;
if( isBlitScrollMode )
viewport.setScrollMode( JViewport.SIMPLE_SCROLL_MODE );
try {
r.run();
} finally {
if( isBlitScrollMode )
viewport.setScrollMode( JViewport.BLIT_SCROLL_MODE );
}
}
public static void installSmoothScrollingDelegateActions( JComponent c, boolean blittingOnly, String... actionKeys ) {
// get shared action map, used for all components of same type
ActionMap map = SwingUtilities.getUIActionMap( c );
if( map == null )
return;
// install actions, but only if not already installed
for( String actionKey : actionKeys )
installSmoothScrollingDelegateAction( map, blittingOnly, actionKey );
}
private static void installSmoothScrollingDelegateAction( ActionMap map, boolean blittingOnly, String actionKey ) {
Action oldAction = map.get( actionKey );
if( oldAction == null || oldAction instanceof SmoothScrollingDelegateAction )
return; // not found or already installed
map.put( actionKey, new SmoothScrollingDelegateAction( oldAction, blittingOnly ) );
} }
//---- class Handler ------------------------------------------------------ //---- class Handler ------------------------------------------------------
@@ -538,71 +615,43 @@ public class FlatScrollPaneUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder ) scrollpane.repaint();
HiDPIUtils.repaint( scrollpane );
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder ) scrollpane.repaint();
HiDPIUtils.repaint( scrollpane );
} }
} }
//---- class FlatScrollPaneLayout ----------------------------------------- //---- class SmoothScrollingDelegateAction --------------------------------
/** /**
* @since 3.3 * Used to run component actions with disabled blitting mode and
* with scroll bar target values.
*/ */
protected static class FlatScrollPaneLayout private static class SmoothScrollingDelegateAction
extends ScrollPaneLayout.UIResource extends FlatUIAction
{ {
private final boolean blittingOnly;
private SmoothScrollingDelegateAction( Action delegate, boolean blittingOnly ) {
super( delegate );
this.blittingOnly = blittingOnly;
}
@Override @Override
public void layoutContainer( Container parent ) { public void actionPerformed( ActionEvent e ) {
super.layoutContainer( parent ); Object source = e.getSource();
JScrollPane scrollPane = (source instanceof Component)
JScrollPane scrollPane = (JScrollPane) parent; ? (JScrollPane) SwingUtilities.getAncestorOfClass( JScrollPane.class, (Component) source )
int padding = getBorderLeftRightPadding( scrollPane ); : null;
if( padding > 0 && vsb != null && vsb.isVisible() ) { if( scrollPane != null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
// move vertical scrollbar to trailing edge ((FlatScrollPaneUI)scrollPane.getUI()).runWithScrollBarsTargetValues( blittingOnly,
Insets insets = scrollPane.getInsets(); () -> delegate.actionPerformed( e ) );
Rectangle r = vsb.getBounds(); } else
int y = Math.max( r.y, insets.top + padding ); delegate.actionPerformed( e );
int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding );
boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y );
// increase width of viewport, column header and horizontal scrollbar
if( canIncreaseViewportWidth( scrollPane ) ) {
int extraWidth = Math.min( padding, vsb.getWidth() );
resizeViewport( viewport, extraWidth, ltr );
resizeViewport( colHead, extraWidth, ltr );
resizeViewport( hsb, extraWidth, ltr );
}
}
}
boolean canIncreaseViewportWidth( JScrollPane scrollPane ) {
return scrollPane.getComponentOrientation().isLeftToRight()
? !isCornerVisible( upperRight ) && !isCornerVisible( lowerRight )
: !isCornerVisible( upperLeft ) && !isCornerVisible( lowerLeft );
}
private static boolean isCornerVisible( Component corner ) {
return corner != null &&
corner.getWidth() > 0 &&
corner.getHeight() > 0 &&
corner.isVisible();
}
private static void resizeViewport( Component c, int extraWidth, boolean ltr ) {
if( c == null )
return;
Rectangle vr = c.getBounds();
c.setBounds( vr.x - (ltr ? 0 : extraWidth), vr.y, vr.width + extraWidth, vr.height );
} }
} }
} }

View File

@@ -32,7 +32,6 @@ import javax.swing.plaf.basic.BasicSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -135,7 +134,7 @@ public class FlatSeparatorUI
} else } else
installStyle( s ); installStyle( s );
s.revalidate(); s.revalidate();
HiDPIUtils.repaint( s ); s.repaint();
break; break;
} }
} }

View File

@@ -25,8 +25,6 @@ import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
@@ -193,23 +191,6 @@ public class FlatSliderUI
return new FlatTrackListener(); return new FlatTrackListener();
} }
@Override
protected FocusListener createFocusListener( JSlider slider ) {
return new BasicSliderUI.FocusHandler() {
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( slider );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( slider );
}
};
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) { protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle, return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle,
@@ -441,7 +422,7 @@ debug*/
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth ) Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{ {
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
if( systemScaleFactor != (int) systemScaleFactor ) { 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%
HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -598,15 +579,15 @@ debug*/
@Override @Override
public void setThumbLocation( int x, int y ) { public void setThumbLocation( int x, int y ) {
if( !isRoundThumb() ) {
// the needle of the directional thumb is painted outside of thumbRect
// --> must increase repaint rectangle
// set new thumb location and compute union of old and new thumb bounds // set new thumb location and compute union of old and new thumb bounds
Rectangle r = new Rectangle( thumbRect ); Rectangle r = new Rectangle( thumbRect );
thumbRect.setLocation( x, y ); thumbRect.setLocation( x, y );
SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r ); SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r );
if( !isRoundThumb() ) {
// the needle of the directional thumb is painted outside of thumbRect
// --> must increase repaint rectangle
// increase union rectangle for repaint // increase union rectangle for repaint
int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f ); int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f );
if( slider.getOrientation() == JSlider.HORIZONTAL ) if( slider.getOrientation() == JSlider.HORIZONTAL )
@@ -616,9 +597,10 @@ debug*/
if( !slider.getComponentOrientation().isLeftToRight() ) if( !slider.getComponentOrientation().isLeftToRight() )
r.x -= extra; r.x -= extra;
} }
}
HiDPIUtils.repaint( slider, r ); slider.repaint( r );
} else
super.setThumbLocation( x, y );
} }
//---- class FlatTrackListener -------------------------------------------- //---- class FlatTrackListener --------------------------------------------
@@ -706,21 +688,21 @@ debug*/
!UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) ) !UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) )
{ {
calculateThumbLocation(); calculateThumbLocation();
HiDPIUtils.repaint( slider ); slider.repaint();
} }
} }
protected void setThumbHover( boolean hover ) { protected void setThumbHover( boolean hover ) {
if( hover != thumbHover ) { if( hover != thumbHover ) {
thumbHover = hover; thumbHover = hover;
HiDPIUtils.repaint( slider, thumbRect ); slider.repaint( thumbRect );
} }
} }
protected void setThumbPressed( boolean pressed ) { protected void setThumbPressed( boolean pressed ) {
if( pressed != thumbPressed ) { if( pressed != thumbPressed ) {
thumbPressed = pressed; thumbPressed = pressed;
HiDPIUtils.repaint( slider, thumbRect ); slider.repaint( thumbRect );
} }
} }

View File

@@ -47,7 +47,6 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -587,7 +586,7 @@ public class FlatSpinnerUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
HiDPIUtils.repaint( spinner ); spinner.repaint();
// if spinner gained focus, transfer it to the editor text field // if spinner gained focus, transfer it to the editor text field
if( e.getComponent() == spinner ) { if( e.getComponent() == spinner ) {
@@ -600,7 +599,7 @@ public class FlatSpinnerUI
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
HiDPIUtils.repaint( spinner ); spinner.repaint();
} }
//---- interface PropertyChangeListener ---- //---- interface PropertyChangeListener ----
@@ -615,7 +614,7 @@ public class FlatSpinnerUI
case FlatClientProperties.COMPONENT_ROUND_RECT: case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.OUTLINE: case FlatClientProperties.OUTLINE:
HiDPIUtils.repaint( spinner ); spinner.repaint();
break; break;
case FlatClientProperties.MINIMUM_WIDTH: case FlatClientProperties.MINIMUM_WIDTH:
@@ -626,7 +625,7 @@ public class FlatSpinnerUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
spinner.revalidate(); spinner.revalidate();
HiDPIUtils.repaint( spinner ); spinner.repaint();
break; break;
} }
} }

View File

@@ -16,9 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Canvas;
import java.awt.Color; import java.awt.Color;
import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Graphics; import java.awt.Graphics;
@@ -69,8 +67,6 @@ import com.formdev.flatlaf.util.UIScale;
* <!-- FlatSplitPaneUI --> * <!-- FlatSplitPaneUI -->
* *
* @uiDefault Component.arrowType String chevron (default) or triangle * @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault SplitPaneDivider.hoverColor Color optional
* @uiDefault SplitPaneDivider.pressedColor Color optional
* @uiDefault SplitPaneDivider.oneTouchArrowColor Color * @uiDefault SplitPaneDivider.oneTouchArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchHoverArrowColor Color * @uiDefault SplitPaneDivider.oneTouchHoverArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchPressedArrowColor Color * @uiDefault SplitPaneDivider.oneTouchPressedArrowColor Color
@@ -84,10 +80,9 @@ import com.formdev.flatlaf.util.UIScale;
*/ */
public class FlatSplitPaneUI public class FlatSplitPaneUI
extends BasicSplitPaneUI extends BasicSplitPaneUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest implements StyleableUI
{ {
@Styleable protected String arrowType; @Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor;
@Styleable protected Color oneTouchArrowColor; @Styleable protected Color oneTouchArrowColor;
@Styleable protected Color oneTouchHoverArrowColor; @Styleable protected Color oneTouchHoverArrowColor;
@Styleable protected Color oneTouchPressedArrowColor; @Styleable protected Color oneTouchPressedArrowColor;
@@ -109,8 +104,6 @@ public class FlatSplitPaneUI
protected void installDefaults() { protected void installDefaults() {
arrowType = UIManager.getString( "Component.arrowType" ); arrowType = UIManager.getString( "Component.arrowType" );
draggingColor = UIManager.getColor( "SplitPaneDivider.draggingColor" );
// get one-touch colors before invoking super.installDefaults() because they are // get one-touch colors before invoking super.installDefaults() because they are
// used in there on LaF switching // used in there on LaF switching
oneTouchArrowColor = UIManager.getColor( "SplitPaneDivider.oneTouchArrowColor" ); oneTouchArrowColor = UIManager.getColor( "SplitPaneDivider.oneTouchArrowColor" );
@@ -124,8 +117,6 @@ public class FlatSplitPaneUI
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
draggingColor = null;
oneTouchArrowColor = null; oneTouchArrowColor = null;
oneTouchHoverArrowColor = null; oneTouchHoverArrowColor = null;
oneTouchPressedArrowColor = null; oneTouchPressedArrowColor = null;
@@ -192,58 +183,12 @@ public class FlatSplitPaneUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key ); return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
} }
@Override
protected Component createDefaultNonContinuousLayoutDivider() {
// only used for non-continuous layout if left or right component is heavy weight
return new Canvas() {
@Override
public void paint( Graphics g ) {
if( !isContinuousLayout() && getLastDragLocation() != -1 )
paintDragDivider( g, 0 );
}
};
}
@Override
public void finishedPaintingChildren( JSplitPane sp, Graphics g ) {
if( sp == splitPane && getLastDragLocation() != -1 && !isContinuousLayout() && !draggingHW )
paintDragDivider( g, getLastDragLocation() );
}
private void paintDragDivider( Graphics g, int dividerLocation ) {
// divider bounds
boolean horizontal = (getOrientation() == JSplitPane.HORIZONTAL_SPLIT);
int x = horizontal ? dividerLocation : 0;
int y = !horizontal ? dividerLocation : 0;
int width = horizontal ? dividerSize : splitPane.getWidth();
int height = !horizontal ? dividerSize : splitPane.getHeight();
// paint background
g.setColor( FlatUIUtils.deriveColor( draggingColor, splitPane.getBackground() ) );
g.fillRect( x, y, width, height );
// paint divider style (e.g. grip)
if( divider instanceof FlatSplitPaneDivider )
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height );
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicSplitPaneDivider adds some mouse listeners for dragging divider
return null; // check children
}
//---- class FlatSplitPaneDivider ----------------------------------------- //---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider protected class FlatSplitPaneDivider
extends BasicSplitPaneDivider extends BasicSplitPaneDivider
{ {
@Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" ); @Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" );
/** @since 3.3 */ @Styleable protected Color hoverColor = UIManager.getColor( "SplitPaneDivider.hoverColor" );
/** @since 3.3 */ @Styleable protected Color pressedColor = UIManager.getColor( "SplitPaneDivider.pressedColor" );
@Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" ); @Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" );
@Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 ); @Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 );
@Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 ); @Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 );
@@ -301,40 +246,20 @@ public class FlatSplitPaneUI
// necessary to show/hide one-touch buttons on expand/collapse // necessary to show/hide one-touch buttons on expand/collapse
doLayout(); doLayout();
break; break;
case FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE:
revalidate();
break;
} }
} }
@Override @Override
public void paint( Graphics g ) { public void paint( Graphics g ) {
// paint hover or pressed background
Color hoverOrPressedColor = (isContinuousLayout() && dragger != null)
? pressedColor
: (isMouseOver() && dragger == null
? hoverColor
: null);
if( hoverOrPressedColor != null ) {
g.setColor( FlatUIUtils.deriveColor( hoverOrPressedColor, splitPane.getBackground() ) );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
super.paint( g ); super.paint( g );
paintStyle( g, 0, 0, getWidth(), getHeight() );
}
/** @since 3.3 */
protected void paintStyle( Graphics g, int x, int y, int width, int height ) {
if( "plain".equals( style ) ) if( "plain".equals( style ) )
return; return;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( gripColor ); g.setColor( gripColor );
paintGrip( g, x, y, width, height ); paintGrip( g, 0, 0, getWidth(), getHeight() );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} }
@@ -361,29 +286,6 @@ public class FlatSplitPaneUI
: location == (splitPane.getWidth() - getWidth() - insets.right); : location == (splitPane.getWidth() - getWidth() - insets.right);
} }
@Override
protected void setMouseOver( boolean mouseOver ) {
super.setMouseOver( mouseOver );
repaintIfNecessary();
}
@Override
protected void prepareForDragging() {
super.prepareForDragging();
repaintIfNecessary();
}
@Override
protected void finishDraggingTo( int location ) {
super.finishDraggingTo( location );
repaintIfNecessary();
}
private void repaintIfNecessary() {
if( hoverColor != null || pressedColor != null )
repaint();
}
//---- class FlatOneTouchButton --------------------------------------- //---- class FlatOneTouchButton ---------------------------------------
protected class FlatOneTouchButton protected class FlatOneTouchButton

View File

@@ -40,7 +40,6 @@ import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -710,7 +709,7 @@ public class FlatStylingSupport
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle.run(); installStyle.run();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
} }
}; };

View File

@@ -65,28 +65,8 @@ public class FlatTableCellBorder
return super.getLineColor(); return super.getLineColor();
} }
@Override
public int getArc() {
if( c != null ) {
Integer selectionArc = getStyleFromTableUI( c, ui -> ui.selectionArc );
if( selectionArc != null )
return selectionArc;
}
return super.getArc();
}
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c != null ) {
Insets selectionInsets = getStyleFromTableUI( c, ui -> ui.selectionInsets );
if( selectionInsets != null ) {
x += selectionInsets.left;
y += selectionInsets.top;
width -= selectionInsets.left + selectionInsets.right;
height -= selectionInsets.top + selectionInsets.bottom;
}
}
this.c = c; this.c = c;
super.paintBorder( c, g, x, y, width, height ); super.paintBorder( c, g, x, y, width, height );
this.c = null; this.c = null;

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
@@ -29,15 +28,16 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.CellRendererPane;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener; import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer; import javax.swing.table.TableCellRenderer;
@@ -45,7 +45,6 @@ import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -115,11 +114,6 @@ public class FlatTableHeaderUI
public void installUI( JComponent c ) { public void installUI( JComponent c ) {
super.installUI( c ); super.installUI( c );
// replace cell renderer pane
header.remove( rendererPane );
rendererPane = new FlatTableHeaderCellRendererPane();
header.add( rendererPane );
installStyle(); installStyle();
} }
@@ -235,8 +229,8 @@ public class FlatTableHeaderUI
@Override @Override
protected void rolloverColumnUpdated( int oldColumn, int newColumn ) { protected void rolloverColumnUpdated( int oldColumn, int newColumn ) {
HiDPIUtils.repaint( header, header.getHeaderRect( oldColumn ) ); header.repaint( header.getHeaderRect( oldColumn ) );
HiDPIUtils.repaint( header, header.getHeaderRect( newColumn ) ); header.repaint( header.getHeaderRect( newColumn ) );
} }
@Override @Override
@@ -271,8 +265,16 @@ public class FlatTableHeaderUI
} }
} }
// temporary use own default renderer
FlatTableCellHeaderRenderer tempRenderer = new FlatTableCellHeaderRenderer( header.getDefaultRenderer() );
header.setDefaultRenderer( tempRenderer );
// paint header // paint header
super.paint( g, c ); super.paint( g, c );
// restore default renderer
tempRenderer.reset();
header.setDefaultRenderer( tempRenderer.delegate );
} }
private boolean isSystemDefaultRenderer( Object headerRenderer ) { private boolean isSystemDefaultRenderer( Object headerRenderer ) {
@@ -330,129 +332,119 @@ public class FlatTableHeaderUI
return false; return false;
} }
//---- class FlatTableHeaderCellRendererPane ------------------------------ //---- class FlatTableCellHeaderRenderer ----------------------------------
/** /**
* Cell renderer pane that is used to paint hover and pressed background/foreground * A delegating header renderer that is only used to paint hover and pressed
* and to paint sort arrows at top, bottom or left position. * background/foreground and to paint sort arrows at top, bottom or left position.
*/ */
private class FlatTableHeaderCellRendererPane private class FlatTableCellHeaderRenderer
extends CellRendererPane implements TableCellRenderer, Border, UIResource
{ {
private final Icon ascendingSortIcon; private final TableCellRenderer delegate;
private final Icon descendingSortIcon;
public FlatTableHeaderCellRendererPane() { private JLabel l;
ascendingSortIcon = UIManager.getIcon( "Table.ascendingSortIcon" ); private Color oldBackground;
descendingSortIcon = UIManager.getIcon( "Table.descendingSortIcon" ); private Color oldForeground;
private Boolean oldOpaque;
private int oldHorizontalTextPosition = -1;
private Border origBorder;
private Icon sortIcon;
FlatTableCellHeaderRenderer( TableCellRenderer delegate ) {
this.delegate = delegate;
} }
@Override @Override
public void paintComponent( Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate ) { public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected,
if( !(c instanceof JLabel) ) { boolean hasFocus, int row, int column )
super.paintComponent( g, c, p, x, y, w, h, shouldValidate ); {
return; Component c = delegate.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
} if( !(c instanceof JLabel) )
return c;
JLabel l = (JLabel) c; l = (JLabel) c;
Color oldBackground = null;
Color oldForeground = null;
boolean oldOpaque = false;
Icon oldIcon = null;
int oldHorizontalTextPosition = -1;
// hover and pressed background/foreground // hover and pressed background/foreground
TableColumn draggedColumn = header.getDraggedColumn(); TableColumn draggedColumn = header.getDraggedColumn();
Color background = null; Color background = null;
Color foreground = null; Color foreground = null;
if( draggedColumn != null && if( draggedColumn != null && header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() ) == column ) {
header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() )
== getColumn( x - header.getDraggedDistance(), w ) )
{
background = pressedBackground; background = pressedBackground;
foreground = pressedForeground; foreground = pressedForeground;
} else if( getRolloverColumn() >= 0 && getRolloverColumn() == getColumn( x, w ) ) { } else if( getRolloverColumn() == column ) {
background = hoverBackground; background = hoverBackground;
foreground = hoverForeground; foreground = hoverForeground;
} }
if( background != null ) { if( background != null ) {
if( oldBackground == null )
oldBackground = l.getBackground(); oldBackground = l.getBackground();
if( oldOpaque == null )
oldOpaque = l.isOpaque(); oldOpaque = l.isOpaque();
l.setBackground( FlatUIUtils.deriveColor( background, header.getBackground() ) ); l.setBackground( FlatUIUtils.deriveColor( background, header.getBackground() ) );
l.setOpaque( true ); l.setOpaque( true );
} }
if( foreground != null ) { if( foreground != null ) {
if( oldForeground == null )
oldForeground = l.getForeground(); oldForeground = l.getForeground();
l.setForeground( FlatUIUtils.deriveColor( foreground, header.getForeground() ) ); l.setForeground( FlatUIUtils.deriveColor( foreground, header.getForeground() ) );
} }
// sort icon position // sort icon
Icon icon = l.getIcon();
boolean isSortIcon = (icon != null && (icon == ascendingSortIcon || icon == descendingSortIcon));
if( isSortIcon ) {
if( sortIconPosition == SwingConstants.LEFT ) { if( sortIconPosition == SwingConstants.LEFT ) {
// left // left
if( oldHorizontalTextPosition < 0 )
oldHorizontalTextPosition = l.getHorizontalTextPosition(); oldHorizontalTextPosition = l.getHorizontalTextPosition();
l.setHorizontalTextPosition( SwingConstants.RIGHT ); l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) { } else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom // top or bottom
oldIcon = icon; sortIcon = l.getIcon();
origBorder = l.getBorder();
l.setIcon( null ); l.setIcon( null );
} l.setBorder( this );
} }
// paint renderer component return l;
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
// paint top or bottom sort icon
if( isSortIcon && (sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM) ) {
int xi = x + ((w - icon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - icon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
icon.paintIcon( c, g, xi, yi );
} }
// restore modified renderer component properties void reset() {
if( background != null ) { if( l == null )
return;
if( oldBackground != null )
l.setBackground( oldBackground ); l.setBackground( oldBackground );
l.setOpaque( oldOpaque ); if( oldForeground != null )
}
if( foreground != null )
l.setForeground( oldForeground ); l.setForeground( oldForeground );
if( oldIcon != null ) if( oldOpaque != null )
l.setIcon( oldIcon ); l.setOpaque( oldOpaque );
if( oldHorizontalTextPosition >= 0 ) if( oldHorizontalTextPosition >= 0 )
l.setHorizontalTextPosition( oldHorizontalTextPosition ); l.setHorizontalTextPosition( oldHorizontalTextPosition );
} }
/** @Override
* Get column index for given coordinates. public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
*/ if( origBorder != null )
private int getColumn( int x, int width ) { origBorder.paintBorder( c, g, x, y, width, height );
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
boolean ltr = header.getComponentOrientation().isLeftToRight();
int cx = ltr ? 0 : getWidthInRightToLef();
for( int i = 0; i < columnCount; i++ ) { if( sortIcon != null ) {
int cw = columnModel.getColumn( i ).getWidth(); int xi = x + ((width - sortIcon.getIconWidth()) / 2);
if( x == cx - (ltr ? 0 : cw) && width == cw ) int yi = (sortIconPosition == SwingConstants.TOP)
return i; ? y + UIScale.scale( 1 )
: y + height - sortIcon.getIconHeight()
cx += ltr ? cw : -cw; - 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
sortIcon.paintIcon( c, g, xi, yi );
} }
return -1;
} }
// similar to JTableHeader.getWidthInRightToLeft() @Override
private int getWidthInRightToLef() { public Insets getBorderInsets( Component c ) {
JTable table = header.getTable(); return (origBorder != null) ? origBorder.getBorderInsets( c ) : new Insets( 0, 0, 0, 0 );
return (table != null && table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF) }
? table.getWidth()
: header.getWidth(); @Override
public boolean isBorderOpaque() {
return (origBorder != null) ? origBorder.isBorderOpaque() : false;
} }
} }

View File

@@ -17,56 +17,30 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue; import java.awt.EventQueue;
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.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableUI; import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -106,10 +80,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.intercellSpacing Dimension * @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color * @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color * @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.selectionInsets Insets
* @uiDefault Table.selectionArc int
* @uiDefault Table.paintOutsideAlternateRows boolean * @uiDefault Table.paintOutsideAlternateRows boolean
* @uiDefault Table.editorSelectAllOnStartEditing boolean
* *
* <!-- FlatTableCellBorder --> * <!-- FlatTableCellBorder -->
* *
@@ -136,8 +107,6 @@ public class FlatTableUI
@Styleable protected Color selectionForeground; @Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground; @Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground; @Styleable protected Color selectionInactiveForeground;
/** @since 3.5 */ @Styleable protected Insets selectionInsets;
/** @since 3.5 */ @Styleable protected int selectionArc;
// for FlatTableCellBorder // for FlatTableCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins; /** @since 2 */ @Styleable protected Insets cellMargins;
@@ -147,12 +116,8 @@ public class FlatTableUI
private boolean oldShowHorizontalLines; private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines; private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing; private Dimension oldIntercellSpacing;
private TableCellRenderer oldBooleanRenderer;
private PropertyChangeListener propertyChangeListener; private PropertyChangeListener propertyChangeListener;
private ComponentListener outsideAlternateRowsListener;
private ListSelectionListener rowSelectionListener;
private TableColumnModelListener columnSelectionListener;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
@@ -179,8 +144,6 @@ public class FlatTableUI
selectionForeground = UIManager.getColor( "Table.selectionForeground" ); selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
selectionInsets = UIManager.getInsets( "Table.selectionInsets" );
selectionArc = UIManager.getInt( "Table.selectionArc" );
toggleSelectionColors(); toggleSelectionColors();
@@ -188,35 +151,19 @@ public class FlatTableUI
if( rowHeight > 0 ) if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) ); LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table ); if( !showHorizontalLines ) {
if( watcher != null )
watcher.enabled = false;
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
oldShowHorizontalLines = table.getShowHorizontalLines(); oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false ); table.setShowHorizontalLines( false );
} }
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) { if( !showVerticalLines ) {
oldShowVerticalLines = table.getShowVerticalLines(); oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false ); table.setShowVerticalLines( false );
} }
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) { if( intercellSpacing != null ) {
oldIntercellSpacing = table.getIntercellSpacing(); oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing ); table.setIntercellSpacing( intercellSpacing );
} }
if( watcher != null )
watcher.enabled = true;
else
table.addPropertyChangeListener( new FlatTablePropertyWatcher() );
// install boolean renderer
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
if( oldBooleanRenderer instanceof UIResource )
table.setDefaultRenderer( Boolean.class, new FlatBooleanRenderer() );
else
oldBooleanRenderer = null;
} }
@Override @Override
@@ -230,36 +177,15 @@ public class FlatTableUI
oldStyleValues = null; oldStyleValues = null;
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
// restore old show horizontal/vertical lines (if not modified) // restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() && if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
(watcher == null || !watcher.showHorizontalLinesChanged) )
table.setShowHorizontalLines( true ); table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() && if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() )
(watcher == null || !watcher.showVerticalLinesChanged) )
table.setShowVerticalLines( true ); table.setShowVerticalLines( true );
// restore old intercell spacing (if not modified) // restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) && if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) )
(watcher == null || !watcher.intercellSpacingChanged) )
table.setIntercellSpacing( oldIntercellSpacing ); table.setIntercellSpacing( oldIntercellSpacing );
if( watcher != null )
watcher.enabled = true;
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
if( oldBooleanRenderer instanceof Component ) {
// because the old renderer component was not attached to any component hierarchy,
// its UI was not yet updated, and it is necessary to do it here
SwingUtilities.updateComponentTreeUI( (Component) oldBooleanRenderer );
}
table.setDefaultRenderer( Boolean.class, oldBooleanRenderer );
}
oldBooleanRenderer = null;
} }
@Override @Override
@@ -268,28 +194,6 @@ public class FlatTableUI
propertyChangeListener = e -> { propertyChangeListener = e -> {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case "selectionModel":
if( rowSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((ListSelectionModel)oldModel).removeListSelectionListener( rowSelectionListener );
if( newModel != null )
((ListSelectionModel)newModel).addListSelectionListener( rowSelectionListener );
}
break;
case "columnModel":
if( columnSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((TableColumnModel)oldModel).removeColumnModelListener( columnSelectionListener );
if( newModel != null )
((TableColumnModel)newModel).addColumnModelListener( columnSelectionListener );
}
break;
case FlatClientProperties.COMPONENT_FOCUS_OWNER: case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors(); toggleSelectionColors();
break; break;
@@ -298,14 +202,11 @@ public class FlatTableUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
table.revalidate(); table.revalidate();
HiDPIUtils.repaint( table ); table.repaint();
break; break;
} }
}; };
table.addPropertyChangeListener( propertyChangeListener ); table.addPropertyChangeListener( propertyChangeListener );
if( selectionArc > 0 )
installRepaintRoundedSelectionListeners();
} }
@Override @Override
@@ -314,19 +215,6 @@ public class FlatTableUI
table.removePropertyChangeListener( propertyChangeListener ); table.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null; propertyChangeListener = null;
if( outsideAlternateRowsListener != null ) {
table.removeComponentListener( outsideAlternateRowsListener );
outsideAlternateRowsListener = null;
}
if( rowSelectionListener != null ) {
table.getSelectionModel().removeListSelectionListener( rowSelectionListener );
rowSelectionListener = null;
}
if( columnSelectionListener != null ) {
table.getColumnModel().removeColumnModelListener( columnSelectionListener );
columnSelectionListener = null;
}
} }
@Override @Override
@@ -350,18 +238,6 @@ public class FlatTableUI
}; };
} }
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
if( UIManager.getBoolean( "Table.editorSelectAllOnStartEditing" ) ) {
// get shared action map, used for all tables
ActionMap map = SwingUtilities.getUIActionMap( table );
if( map != null )
StartEditingAction.install( map, "startEditing" );
}
}
/** @since 2 */ /** @since 2 */
protected void installStyle() { protected void installStyle() {
try { try {
@@ -403,8 +279,6 @@ public class FlatTableUI
protected Object applyStyleProperty( String key, Object value ) { protected Object applyStyleProperty( String key, Object value ) {
if( "rowHeight".equals( key ) && value instanceof Integer ) if( "rowHeight".equals( key ) && value instanceof Integer )
value = UIScale.scale( (Integer) value ); value = UIScale.scale( (Integer) value );
else if( "selectionArc".equals( key ) && value instanceof Integer && (Integer) value > 0 )
installRepaintRoundedSelectionListeners();
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value ); return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
} }
@@ -467,7 +341,6 @@ public class FlatTableUI
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor; double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor;
double lineOffset = (1. - lineThickness) + 0.05; // adding 0.05 to fix line location in some cases
// Java 8 uses drawLine() to paint grid lines // Java 8 uses drawLine() to paint grid lines
// Java 9+ uses fillRect() to paint grid lines (except for dragged column) // Java 9+ uses fillRect() to paint grid lines (except for dragged column)
@@ -510,11 +383,11 @@ public class FlatTableUI
// reduce line thickness to avoid unstable painted line thickness // reduce line thickness to avoid unstable painted line thickness
if( lineThickness != 1 ) { if( lineThickness != 1 ) {
if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) { if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x, y + lineOffset, width, lineThickness ) ); super.fill( new Rectangle2D.Double( x, y, width, lineThickness ) );
return; return;
} }
if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) { if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x + lineOffset, y, lineThickness, height ) ); super.fill( new Rectangle2D.Double( x, y, lineThickness, height ) );
return; return;
} }
} }
@@ -532,10 +405,6 @@ public class FlatTableUI
}; };
} }
// rounded selection or selection insets
if( selectionArc > 0 || (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets )) )
g = new RoundedSelectionGraphics( g, UIManager.getColor( "Table.alternateRowColor" ) );
super.paint( g, c ); super.paint( g, c );
} }
@@ -581,6 +450,8 @@ public class FlatTableUI
boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" ); boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" );
Color alternateColor; Color alternateColor;
if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) { if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) {
g.setColor( alternateColor );
int rowCount = table.getRowCount(); int rowCount = table.getRowCount();
// paint alternating empty rows below the table // paint alternating empty rows below the table
@@ -589,449 +460,11 @@ public class FlatTableUI
int tableWidth = table.getWidth(); int tableWidth = table.getWidth();
int rowHeight = table.getRowHeight(); int rowHeight = table.getRowHeight();
g.setColor( alternateColor );
int x = viewport.getComponentOrientation().isLeftToRight() ? 0 : viewportWidth - tableWidth;
for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) { for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) {
if( row % 2 != 0 ) if( row % 2 != 0 )
paintAlternateRowBackground( g, -1, -1, x, y, tableWidth, rowHeight ); g.fillRect( 0, y, tableWidth, rowHeight );
}
// add listener on demand
if( outsideAlternateRowsListener == null && table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF ) {
outsideAlternateRowsListener = new FlatOutsideAlternateRowsListener();
table.addComponentListener( outsideAlternateRowsListener );
} }
} }
} }
} }
/**
* Paints (rounded) alternate row background.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintAlternateRowBackground( Graphics g, int row, int column, int x, int y, int width, int height ) {
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( column >= 0 ) {
// selection insets
// selection arc
if( column > 0 ) {
if( insets != null )
insets.left = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopLeft = arcBottomLeft = 0;
else
arcTopRight = arcBottomRight = 0;
}
if( column < table.getColumnCount() - 1 ) {
if( insets != null )
insets.right = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopRight = arcBottomRight = 0;
else
arcTopLeft = arcBottomLeft = 0;
}
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintCellSelection( Graphics g, int row, int column, int x, int y, int width, int height ) {
boolean rowSelAllowed = table.getRowSelectionAllowed();
boolean colSelAllowed = table.getColumnSelectionAllowed();
boolean rowSelOnly = rowSelAllowed && !colSelAllowed;
boolean colSelOnly = colSelAllowed && !rowSelAllowed;
boolean cellOnlySel = rowSelAllowed && colSelAllowed;
// get selection state of surrounding cells
boolean leftSelected = (column > 0 && (rowSelOnly || table.isCellSelected( row, column - 1 )));
boolean topSelected = (row > 0 && (colSelOnly || table.isCellSelected( row - 1, column )));
boolean rightSelected = (column < table.getColumnCount() - 1 && (rowSelOnly || table.isCellSelected( row, column + 1 )));
boolean bottomSelected = (row < table.getRowCount() - 1 && (colSelOnly || table.isCellSelected( row + 1, column )));
if( !table.getComponentOrientation().isLeftToRight() ) {
boolean temp = leftSelected;
leftSelected = rightSelected;
rightSelected = temp;
}
// selection insets
// (insets are applied to whole row if row-only selection is used,
// or to whole column if column-only selection is used,
// or to cell if cell selection is used)
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
if( insets != null ) {
if( rowSelOnly && leftSelected )
insets.left = 0;
if( rowSelOnly && rightSelected )
insets.right = 0;
if( colSelOnly && topSelected )
insets.top = 0;
if( colSelOnly && bottomSelected )
insets.bottom = 0;
}
// selection arc
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( selectionArc > 0 ) {
// note that intercellSpacing is not considered as a gap because
// grid lines are usually painted to intercell space
boolean hasRowGap = (rowSelOnly || cellOnlySel) && insets != null && (insets.top != 0 || insets.bottom != 0);
boolean hasColGap = (colSelOnly || cellOnlySel) && insets != null && (insets.left != 0 || insets.right != 0);
if( leftSelected && !hasColGap )
arcTopLeft = arcBottomLeft = 0;
if( rightSelected && !hasColGap )
arcTopRight = arcBottomRight = 0;
if( topSelected && !hasRowGap )
arcTopLeft = arcTopRight = 0;
if( bottomSelected && !hasRowGap )
arcBottomLeft = arcBottomRight = 0;
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context.
* <p>
* This method is intended for use in custom cell renderers to support
* {@link #selectionArc} and {@link #selectionInsets}.
*
* @since 3.5
*/
public static void paintCellSelection( JTable table, Graphics g, int row, int column, int x, int y, int width, int height ) {
if( !(table.getUI() instanceof FlatTableUI) )
return;
FlatTableUI ui = (FlatTableUI) table.getUI();
ui.paintCellSelection( g, row, column, x, y, width, height );
}
private void installRepaintRoundedSelectionListeners() {
if( rowSelectionListener == null ) {
rowSelectionListener = this::repaintRoundedRowSelection;
table.getSelectionModel().addListSelectionListener( rowSelectionListener );
}
if( columnSelectionListener == null ) {
columnSelectionListener = new TableColumnModelListener() {
@Override
public void columnSelectionChanged( ListSelectionEvent e ) {
repaintRoundedColumnSelection( e );
}
@Override public void columnRemoved( TableColumnModelEvent e ) {}
@Override public void columnMoved( TableColumnModelEvent e ) {}
@Override public void columnMarginChanged( ChangeEvent e ) {}
@Override public void columnAdded( TableColumnModelEvent e ) {}
};
table.getColumnModel().addColumnModelListener( columnSelectionListener );
}
}
private void repaintRoundedRowSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getRowSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// repaint including rows before and after changed selection
int firstRow = Math.max( 0, Math.min( e.getFirstIndex() - 1, rowCount - 1 ) );
int lastRow = Math.max( 0, Math.min( e.getLastIndex() + 1, rowCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, 0, false );
Rectangle lastRect = table.getCellRect( lastRow, columnCount - 1, false );
table.repaint( firstRect.union( lastRect ) );
}
private void repaintRoundedColumnSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getColumnSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// limit to selected rows for cell selection
int firstRow = 0;
int lastRow = rowCount - 1;
if( table.getRowSelectionAllowed() ) {
firstRow = table.getSelectionModel().getMinSelectionIndex();
lastRow = table.getSelectionModel().getMaxSelectionIndex();
}
// repaint including columns before and after changed selection
int firstColumn = Math.max( 0, Math.min( e.getFirstIndex() - 1, columnCount - 1 ) );
int lastColumn = Math.max( 0, Math.min( e.getLastIndex() + 1, columnCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, firstColumn, false );
Rectangle lastRect = table.getCellRect( lastRow, lastColumn, false );
table.repaint( firstRect.union( lastRect ) );
}
//---- class RoundedSelectionGraphics -------------------------------------
/**
* Because selection painting is done in the cell renderer, it would be
* necessary to require a FlatLaf specific renderer to implement rounded selection.
* Using a LaF specific renderer was avoided because often a custom renderer is
* already used in applications. Then either the rounded selection is not used,
* or the application has to be changed to extend a FlatLaf renderer.
* <p>
* To solve this, a graphics proxy is used that paints rounded selection
* if row/column/cell is selected and the renderer wants to fill the background.
*/
private class RoundedSelectionGraphics
extends Graphics2DProxy
{
private final Color alternateRowColor;
// used to avoid endless loop in case that paintCellSelection() invokes
// g.fillRect() with full bounds (selectionInsets is 0,0,0,0)
private boolean inPaintSelection;
RoundedSelectionGraphics( Graphics delegate, Color alternateRowColor ) {
super( (Graphics2D) delegate );
this.alternateRowColor = alternateRowColor;
}
@Override
public Graphics create() {
return new RoundedSelectionGraphics( super.create(), alternateRowColor );
}
@Override
public Graphics create( int x, int y, int width, int height ) {
return new RoundedSelectionGraphics( super.create( x, y, width, height ), alternateRowColor );
}
@Override
public void fillRect( int x, int y, int width, int height ) {
if( fillCellSelection( x, y, width, height ) )
return;
super.fillRect( x, y, width, height );
}
@Override
public void fill( Shape shape ) {
if( shape instanceof Rectangle2D ) {
Rectangle2D r = (Rectangle2D) shape;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
if( x == (int) x && y == (int) y && width == (int) width && height == (int) height ) {
if( fillCellSelection( (int) x, (int) y, (int) width, (int) height ) )
return;
}
}
super.fill( shape );
}
private boolean fillCellSelection( int x, int y, int width, int height ) {
if( inPaintSelection )
return false;
Color color;
Component rendererComponent;
if( x == 0 && y == 0 &&
((color = getColor()) == table.getSelectionBackground() ||
(alternateRowColor != null && color == alternateRowColor)) &&
(rendererComponent = findActiveRendererComponent()) != null &&
width == rendererComponent.getWidth() &&
height == rendererComponent.getHeight() )
{
Point location = rendererComponent.getLocation();
int row = table.rowAtPoint( location );
int column = table.columnAtPoint( location );
if( row >= 0 && column >= 0 ) {
inPaintSelection = true;
if( color == table.getSelectionBackground() )
paintCellSelection( this, row, column, x, y, width, height );
else
paintAlternateRowBackground( this, row, column, x, y, width, height );
inPaintSelection = false;
return true;
}
}
return false;
}
/**
* A CellRendererPane may contain multiple components, if multiple renderers
* are used. Inactive renderer components have size {@code 0x0}.
*/
private Component findActiveRendererComponent() {
int count = rendererPane.getComponentCount();
for( int i = 0; i < count; i++ ) {
Component c = rendererPane.getComponent( i );
if( c.getWidth() > 0 && c.getHeight() > 0 )
return c;
}
return null;
}
}
//---- class OutsideAlternateRowsListener ---------------------------------
/**
* Used if table auto-resize-mode is off to repaint outside alternate rows
* when table width changed (column resized) or component orientation changed.
*/
private class FlatOutsideAlternateRowsListener
extends ComponentAdapter
{
@Override
public void componentHidden( ComponentEvent e ) {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport )
HiDPIUtils.repaint( viewport );
}
@Override
public void componentMoved( ComponentEvent e ) {
repaintAreaBelowTable();
}
@Override
public void componentResized( ComponentEvent e ) {
repaintAreaBelowTable();
}
private void repaintAreaBelowTable() {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport ) {
int viewportHeight = viewport.getHeight();
int tableHeight = table.getHeight();
if( tableHeight < viewportHeight )
HiDPIUtils.repaint( viewport, 0, tableHeight, viewport.getWidth(), viewportHeight - tableHeight );
}
}
}
//---- class FlatTablePropertyWatcher -------------------------------------
/**
* Listener that watches for change of some table properties from application code.
* This information is used in {@link FlatTableUI#installDefaults()} and
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
* If they are modified in application code, FlatLaf no longer changes them.
*
* The listener is added once for each table, but never removed.
* So switching Laf/theme reuses existing listener.
*/
private static class FlatTablePropertyWatcher
implements PropertyChangeListener
{
boolean enabled = true;
boolean showHorizontalLinesChanged;
boolean showVerticalLinesChanged;
boolean intercellSpacingChanged;
static FlatTablePropertyWatcher get( JTable table ) {
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
if( l instanceof FlatTablePropertyWatcher )
return (FlatTablePropertyWatcher) l;
}
return null;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( !enabled )
return;
switch( e.getPropertyName() ) {
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
case "showVerticalLines": showVerticalLinesChanged = true; break;
case "rowMargin": intercellSpacingChanged = true; break;
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer
extends DefaultTableCellRenderer
implements UIResource
{
private boolean selected;
FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER );
setIcon( new FlatCheckBoxIcon() {
@Override
protected boolean isSelected( Component c ) {
return selected;
}
} );
}
@Override
protected void setValue( Object value ) {
selected = (value != null && (Boolean) value);
}
}
//---- class StartEditingAction -------------------------------------------
private static class StartEditingAction
extends FlatUIAction
{
static void install( ActionMap map, String key ) {
Action oldAction = map.get( key );
if( oldAction == null || oldAction instanceof StartEditingAction )
return; // not found or already installed
map.put( key, new StartEditingAction( oldAction ) );
}
private StartEditingAction( Action delegate ) {
super( delegate );
}
@Override
public void actionPerformed( ActionEvent e ) {
JTable table = (JTable) e.getSource();
Component oldEditorComp = table.getEditorComponent();
delegate.actionPerformed( e );
// select all text in editor if editing starts with F2 key
Component editorComp = table.getEditorComponent();
if( oldEditorComp == null && editorComp instanceof JTextField )
((JTextField)editorComp).selectAll();
}
}
} }

View File

@@ -141,6 +141,12 @@ public class FlatTextAreaUI
focusListener = null; focusListener = null;
} }
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
FlatEditorPaneUI.installKeyboardActions( getComponent() );
}
@Override @Override
protected Caret createCaret() { protected Caret createCaret() {
return new FlatCaret( null, false ); return new FlatCaret( null, false );

View File

@@ -45,7 +45,6 @@ import javax.swing.JTextField;
import javax.swing.JToggleButton; import javax.swing.JToggleButton;
import javax.swing.JToolBar; import javax.swing.JToolBar;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
@@ -239,7 +238,7 @@ public class FlatTextFieldUI
case COMPONENT_ROUND_RECT: case COMPONENT_ROUND_RECT:
case OUTLINE: case OUTLINE:
case TEXT_FIELD_PADDING: case TEXT_FIELD_PADDING:
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
@@ -250,38 +249,38 @@ public class FlatTextFieldUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case TEXT_FIELD_LEADING_ICON: case TEXT_FIELD_LEADING_ICON:
leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case TEXT_FIELD_TRAILING_ICON: case TEXT_FIELD_TRAILING_ICON:
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case TEXT_FIELD_LEADING_COMPONENT: case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent(); uninstallLeadingComponent();
installLeadingComponent(); installLeadingComponent();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case TEXT_FIELD_TRAILING_COMPONENT: case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent(); uninstallTrailingComponent();
installTrailingComponent(); installTrailingComponent();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON: case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton(); uninstallClearButton();
installClearButton(); installClearButton();
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
break; break;
case "enabled": case "enabled":
@@ -481,21 +480,9 @@ debug*/
// compute placeholder location // compute placeholder location
Rectangle r = getVisibleEditorRect(); Rectangle r = getVisibleEditorRect();
FontMetrics fm = c.getFontMetrics( c.getFont() ); FontMetrics fm = c.getFontMetrics( c.getFont() );
int x = r.x;
int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
// apply horizontal alignment to x location
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width ); String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width );
int stringWidth = fm.stringWidth( clippedPlaceholder ); int x = r.x + (isLeftToRight() ? 0 : r.width - fm.stringWidth( clippedPlaceholder ));
int halign = (c instanceof JTextField) ? ((JTextField)c).getHorizontalAlignment() : SwingConstants.LEADING; int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
if( halign == SwingConstants.LEADING )
halign = isLeftToRight() ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = isLeftToRight() ? SwingConstants.RIGHT : SwingConstants.LEFT;
if( halign == SwingConstants.RIGHT )
x += r.width - stringWidth;
else if( halign == SwingConstants.CENTER )
x = Math.max( 0, x + (r.width / 2) - (stringWidth / 2) );
// paint placeholder // paint placeholder
g.setColor( placeholderForeground ); g.setColor( placeholderForeground );
@@ -815,7 +802,7 @@ debug*/
if( visible != clearButton.isVisible() ) { if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible ); clearButton.setVisible( visible );
c.revalidate(); c.revalidate();
HiDPIUtils.repaint( c ); c.repaint();
} }
} }

View File

@@ -142,6 +142,12 @@ public class FlatTextPaneUI
focusListener = null; focusListener = null;
} }
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
FlatEditorPaneUI.installKeyboardActions( getComponent() );
}
@Override @Override
protected Caret createCaret() { protected Caret createCaret() {
return new FlatCaret( null, false ); return new FlatCaret( null, false );
@@ -156,6 +162,11 @@ public class FlatTextPaneUI
super.propertyChange( e ); super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle ); FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
// which removed our delegate actions
if( "editorKit".equals( propertyName ) )
FlatEditorPaneUI.installKeyboardActions( getComponent() );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -36,7 +36,6 @@ import java.awt.Rectangle;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@@ -47,9 +46,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
@@ -58,6 +57,7 @@ import javax.swing.Icon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -66,7 +66,6 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.AbstractBorder; import javax.swing.border.AbstractBorder;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder; import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -99,6 +98,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.showIconBesideTitle boolean * @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int * @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int * @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon * @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon * @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon * @uiDefault TitlePane.maximizeIcon Icon
@@ -109,8 +109,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane public class FlatTitlePane
extends JComponent extends JComponent
{ {
static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles"; private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
private static final boolean isWindows_10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
/** @since 2.5 */ protected final Font titleFont; /** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground; protected final Color activeBackground;
@@ -132,6 +131,7 @@ public class FlatTitlePane
/** @since 2.4 */ protected final boolean showIconBesideTitle; /** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap; protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap; /** @since 2.4 */ protected final int menuBarTitleMinimumGap;
/** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane; protected final JRootPane rootPane;
protected final String windowStyle; protected final String windowStyle;
@@ -150,33 +150,6 @@ public class FlatTitlePane
private final Handler handler; private final Handler handler;
/**
* This panel handles mouse events if FlatLaf window decorations are used
* without native window border. E.g. on Linux.
* <p>
* This panel usually has same bounds as the title pane,
* except if fullWindowContent mode is enabled.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer
* under the title pane and under the frame content.
* The separation is necessary for fullWindowContent mode, where the title pane
* is layered over the frame content (for title pane buttons), but the mousePanel
* needs to be layered under the frame content so that components on content pane
* can receive mouse events when located in title area.
*/
final JPanel mouseLayer;
/**
* This panel paint a border at the top of the window in fullWindowContent mode,
* if FlatLaf window decorations are enabled.
* Only used on Windows 10.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer over all other layers.
*/
final JPanel windowTopBorderLayer;
public FlatTitlePane( JRootPane rootPane ) { public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane; this.rootPane = rootPane;
@@ -205,6 +178,7 @@ public class FlatTitlePane
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false ); showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 ); menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 ); menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler(); handler = createHandler();
@@ -213,18 +187,11 @@ public class FlatTitlePane
addSubComponents(); addSubComponents();
activeChanged( true ); activeChanged( true );
mouseLayer = new JPanel(); addMouseListener( handler );
mouseLayer.setOpaque( false ); addMouseMotionListener( handler );
mouseLayer.addMouseListener( handler );
mouseLayer.addMouseMotionListener( handler );
if( isWindows_10 && FlatNativeWindowBorder.isSupported() ) { // necessary for closing window with double-click on icon
windowTopBorderLayer = new JPanel(); iconLabel.addMouseListener( handler );
windowTopBorderLayer.setVisible( false );
windowTopBorderLayer.setOpaque( false );
windowTopBorderLayer.setBorder( FlatUIUtils.nonUIResource( WindowTopBorder.getInstance() ) );
} else
windowTopBorderLayer = null;
applyComponentOrientation( rootPane.getComponentOrientation() ); applyComponentOrientation( rootPane.getComponentOrientation() );
} }
@@ -267,11 +234,6 @@ public class FlatTitlePane
setLayout( new BorderLayout() { setLayout( new BorderLayout() {
@Override @Override
public void layoutContainer( Container target ) { public void layoutContainer( Container target ) {
if( isFullWindowContent() ) {
super.layoutContainer( target );
return;
}
// compute available bounds // compute available bounds
Insets insets = target.getInsets(); Insets insets = target.getInsets();
int x = insets.left; int x = insets.left;
@@ -285,7 +247,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth; int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth ); int minTitleWidth = UIScale.scale( titleMinimumWidth );
// increase minimum width if icon is shown besides the title // increase minimum width if icon is show besides the title
Icon icon = titleLabel.getIcon(); Icon icon = titleLabel.getIcon();
if( icon != null ) { if( icon != null ) {
Insets iconInsets = iconLabel.getInsets(); Insets iconInsets = iconLabel.getInsets();
@@ -333,9 +295,6 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() ); horizontalGlue.getWidth(), titleLabel.getHeight() );
} }
} }
// clear hit-test cache
lastCaptionHitTestTime = 0;
} }
} ); } );
@@ -379,13 +338,6 @@ public class FlatTitlePane
buttonPanel.add( restoreButton ); buttonPanel.add( restoreButton );
} }
buttonPanel.add( closeButton ); buttonPanel.add( closeButton );
ComponentListener l = new ComponentAdapter() {
@Override public void componentResized( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
@Override public void componentMoved( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
};
buttonPanel.addComponentListener( l );
addComponentListener( l );
} }
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) { protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -465,9 +417,7 @@ public class FlatTitlePane
/** @since 3 */ /** @since 3 */
protected void updateVisibility() { protected void updateVisibility() {
boolean isFullWindowContent = isFullWindowContent(); titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) );
leftPanel.setVisible( !isFullWindowContent );
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) && !isFullWindowContent );
closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) ); closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) { if( window instanceof Frame ) {
@@ -493,7 +443,7 @@ public class FlatTitlePane
// get window images // get window images
List<Image> images = null; List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) { if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
images = window.getIconImages(); images = window.getIconImages();
if( images.isEmpty() ) { if( images.isEmpty() ) {
// search in owners // search in owners
@@ -518,13 +468,6 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater(); updateNativeTitleBarHeightAndHitTestSpotsLater();
} }
void updateFullWindowContentButtonsBoundsProperty() {
Rectangle bounds = isFullWindowContent()
? new Rectangle( SwingUtilities.convertPoint( buttonPanel, 0, 0, rootPane ), buttonPanel.getSize() )
: null;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
@Override @Override
public void addNotify() { public void addNotify() {
super.addNotify(); super.addNotify();
@@ -579,11 +522,6 @@ public class FlatTitlePane
window.removeComponentListener( handler ); window.removeComponentListener( handler );
} }
/** @since 3.4 */
protected boolean isFullWindowContent() {
return FlatRootPaneUI.isFullWindowContent( rootPane );
}
/** /**
* Returns whether this title pane currently has a visible and embedded menubar. * Returns whether this title pane currently has a visible and embedded menubar.
*/ */
@@ -595,9 +533,6 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane. * Returns whether the menubar should be embedded into the title pane.
*/ */
protected boolean isMenuBarEmbedded() { protected boolean isMenuBarEmbedded() {
if( isFullWindowContent() )
return false;
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime // not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane, return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED, FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -673,10 +608,6 @@ public class FlatTitlePane
doLayout(); doLayout();
} }
void menuBarInvalidate() {
menuBarPlaceholder.invalidate();
}
@Override @Override
public void paint( Graphics g ) { public void paint( Graphics g ) {
super.paint( g ); super.paint( g );
@@ -685,45 +616,21 @@ public class FlatTitlePane
return; return;
if( debugTitleBarHeight > 0 ) { if( debugTitleBarHeight > 0 ) {
// title bar height is measured from window top edge
int y = SwingUtilities.convertPoint( window, 0, debugTitleBarHeight, this ).y;
g.setColor( Color.green ); g.setColor( Color.green );
g.drawLine( 0, y, getWidth(), y ); g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
}
if( debugHitTestSpots != null ) {
for( Rectangle r : debugHitTestSpots )
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
} }
g.setColor( Color.red ); private void paintRect( Graphics g, Color color, Rectangle r ) {
debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
debugPaintRect( g, Color.blue, debugAppIconBounds );
debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void debugPaintComponentWithMouseListener( Graphics g, Color color, Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || c == mouseLayer ||
c == iconifyButton || c == maximizeButton || c == restoreButton || c == closeButton )
return;
if( c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0 ||
c.getMouseWheelListeners().length > 0 )
{
g.drawRect( x, y, c.getWidth(), c.getHeight() );
return;
}
if( c instanceof Container ) {
Rectangle titlePaneBoundsOnWindow = SwingUtilities.convertRectangle( this, new Rectangle( getSize() ), window );
for( Component child : ((Container)c).getComponents() ) {
Rectangle compBoundsOnWindow = SwingUtilities.convertRectangle( c, new Rectangle( c.getSize() ), window );
if( compBoundsOnWindow.intersects( titlePaneBoundsOnWindow ) )
debugPaintComponentWithMouseListener( g, color, child, x + child.getX(), y + child.getY() );
}
}
}
private void debugPaintRect( Graphics g, Color color, Rectangle r ) {
if( r == null ) if( r == null )
return; return;
@@ -734,9 +641,6 @@ public class FlatTitlePane
@Override @Override
protected void paintComponent( Graphics g ) { protected void paintComponent( Graphics g ) {
if( isFullWindowContent() )
return;
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime // not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) && g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null) clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
@@ -745,6 +649,16 @@ public class FlatTitlePane
g.fillRect( 0, 0, getWidth(), getHeight() ); 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. * Iconifies the window.
*/ */
@@ -921,6 +835,10 @@ public class FlatTitlePane
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) ); window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
} }
private boolean hasJBRCustomDecoration() {
return window != null && JBRCustomDecorations.hasCustomDecoration( window );
}
/** /**
* Returns whether windows uses native window border and has custom decorations enabled. * Returns whether windows uses native window border and has custom decorations enabled.
*/ */
@@ -928,10 +846,6 @@ public class FlatTitlePane
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window ); return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
} }
boolean isWindowTopBorderNeeded() {
return isWindows_10 && hasNativeCustomDecoration();
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater() // used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter; private int laterCounter;
@@ -952,14 +866,11 @@ public class FlatTitlePane
return; return;
int titleBarHeight = getHeight(); int titleBarHeight = getHeight();
// title bar height must be measured from window top edge
// (when window is maximized, window y location is e.g. -11 and window top inset is 11)
for( Component c = this; c != window && c != null; c = c.getParent() )
titleBarHeight += c.getY();
// slightly reduce height so that component receives mouseExit events // slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 ) if( titleBarHeight > 0 )
titleBarHeight--; titleBarHeight--;
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null; Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) { if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -985,6 +896,9 @@ public class FlatTitlePane
iconBounds.width += iconInsets.right; iconBounds.width += iconInsets.right;
} }
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconBounds );
else
appIconBounds = iconBounds; appIconBounds = iconBounds;
} else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) { } else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) {
FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI(); FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI();
@@ -1013,21 +927,78 @@ public class FlatTitlePane
iconR.width += 2; iconR.width += 2;
iconR.height += 2; iconR.height += 2;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconR );
else
appIconBounds = iconR; appIconBounds = iconR;
} }
} }
Rectangle r = getNativeHitTestSpot( buttonPanel );
if( r != null )
hitTestSpots.add( r );
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
// if frame is resizable and not maximized, make menu bar hit test spot smaller at top
// to have a small area above the menu bar to resize the window
if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
// limit to 8, because Windows does not use a larger height
int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
r.y += resizeHeight;
r.height -= resizeHeight;
}
int count = menuBar.getComponentCount();
for( int i = count - 1; i >= 0; i-- ) {
Component c = menuBar.getComponent( i );
if( c instanceof Box.Filler ||
(c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
{
// If menu bar is embedded and contains a horizontal glue or caption component,
// then split the hit test spot so that
// the glue/caption component area can be used to move the window.
Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
int x2 = glueLocation.x + c.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
if( r2.width > 0 )
hitTestSpots.add( r2 );
}
}
hitTestSpots.add( r );
}
}
// allow internal frames in layered pane to be moved/resized when placed over title bar
for( Component c : rootPane.getLayeredPane().getComponents() ) {
r = (c instanceof JInternalFrame) ? getNativeHitTestSpot( (JInternalFrame) c ) : null;
if( r != null )
hitTestSpots.add( r );
}
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton ); Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton ); Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton ); Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight; debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds; debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds; debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds; debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1042,101 +1013,18 @@ public class FlatTitlePane
: null; : null;
} }
/** protected Rectangle getNativeHitTestSpot( JComponent c ) {
* Returns whether there is a component at the given location, that processes Dimension size = c.getSize();
* mouse events. E.g. buttons, menus, etc. if( size.width <= 0 || size.height <= 0 )
* <p> return null;
* Note:
* <ul> Point location = SwingUtilities.convertPoint( c, 0, 0, window );
* <li>This method is invoked often when mouse is moved over title bar Rectangle r = new Rectangle( location, size );
* and should therefore return quickly. return r;
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* </ul>
*/
private boolean captionHitTest( Point pt ) {
// Windows invokes this method every ~200ms, even if the mouse has not moved
long time = System.currentTimeMillis();
if( pt.x == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
lastCaptionHitTestTime = time;
return lastCaptionHitTestResult;
} }
// convert pt from window coordinates to layeredPane coordinates
Component layeredPane = rootPane.getLayeredPane();
int x = pt.x;
int y = pt.y;
for( Component c = layeredPane; c != window && c != null; c = c.getParent() ) {
x -= c.getX();
y -= c.getY();
}
lastCaptionHitTestX = pt.x;
lastCaptionHitTestY = pt.y;
lastCaptionHitTestTime = time;
lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
return lastCaptionHitTestResult;
}
private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || !c.contains( x, y ) || c == mouseLayer )
return true; // continue checking with next component
if( c.isEnabled() &&
(c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0) )
{
if( !(c instanceof JComponent) )
return false; // assume that this is not a caption because the component has mouse listeners
// check client property boolean value
Object caption = ((JComponent)c).getClientProperty( COMPONENT_TITLE_BAR_CAPTION );
if( caption instanceof Boolean )
return (boolean) caption;
// if component is not fully layouted, do not invoke function
// because it is too dangerous that the function tries to layout the component,
// which could cause a dead lock
if( !c.isValid() )
return false; // assume that this is not a caption because the component has mouse listeners
if( caption instanceof Function ) {
// check client property function value
@SuppressWarnings( "unchecked" )
Function<Point, Boolean> hitTest = (Function<Point, Boolean>) caption;
Boolean result = hitTest.apply( new Point( x, y ) );
if( result != null )
return result;
} else {
// check component UI
ComponentUI ui = JavaCompatibility2.getUI( (JComponent) c );
if( !(ui instanceof TitleBarCaptionHitTest) )
return false; // assume that this is not a caption because the component has mouse listeners
Boolean result = ((TitleBarCaptionHitTest)ui).isTitleBarCaptionAt( x, y );
if( result != null )
return result;
}
// else continue checking children
}
// check children
if( c instanceof Container ) {
for( Component child : ((Container)c).getComponents() ) {
if( !isTitleBarCaptionAt( child, x - child.getX(), y - child.getY() ) )
return false;
}
}
return true;
}
private int lastCaptionHitTestX;
private int lastCaptionHitTestY;
private long lastCaptionHitTestTime;
private boolean lastCaptionHitTestResult;
private int debugTitleBarHeight; private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds; private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds; private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds; private Rectangle debugMaximizeButtonBounds;
@@ -1159,7 +1047,7 @@ public class FlatTitlePane
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) } else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 ); insets.bottom += UIScale.scale( 1 );
if( isWindowTopBorderNeeded() && !isWindowMaximized() ) if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() ); insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets; return insets;
@@ -1178,7 +1066,7 @@ public class FlatTitlePane
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight ); FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
} }
if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() ) if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height ); WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
} }
@@ -1234,7 +1122,7 @@ public class FlatTitlePane
} }
} }
// compute icon width and gap (if icon is shown besides the title) // compute icon width and gap (if icon is show besides the title)
int iconTextGap = 0; int iconTextGap = 0;
int iconWidthAndGap = 0; int iconWidthAndGap = 0;
if( icon != null ) { if( icon != null ) {
@@ -1243,7 +1131,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap; iconWidthAndGap = icon.getIconWidth() + iconTextGap;
} }
// layout title and icon (if shown besides the title) // layout title and icon (if show besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon, String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1342,7 +1230,10 @@ public class FlatTitlePane
activeChanged( true ); activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
repaintBorder(); if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
} }
@Override @Override
@@ -1350,22 +1241,10 @@ public class FlatTitlePane
activeChanged( false ); activeChanged( false );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
repaintBorder(); if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
}
private void repaintBorder() {
// Windows 10 top border
if( windowTopBorderLayer != null && windowTopBorderLayer.isShowing())
WindowTopBorder.getInstance().repaintBorder( windowTopBorderLayer );
else if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
// Window border used for non-native window decorations repaintWindowBorder();
if( rootPane.getBorder() instanceof FlatRootPaneUI.FlatWindowBorder ) {
// not repainting four areas on the four sides because RepaintManager
// unions dirty regions, which also results in repaint of whole rootpane
rootPane.repaint();
}
} }
@Override @Override
@@ -1391,7 +1270,7 @@ debug*/
public void mouseClicked( MouseEvent e ) { public void mouseClicked( MouseEvent e ) {
// on Linux, when using native library, the mouse clicked event // on Linux, when using native library, the mouse clicked event
// is usually not sent and maximize/restore is done in mouse pressed event // is usually not sent and maximize/restore is done in mouse pressed event
// this check is here for the case that a mouse clicked event comes through for some reason // this check is here for the case that a mouse clicked event comes thru for some reason
if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) { if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed() // see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) { if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
@@ -1402,7 +1281,7 @@ debug*/
} }
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) { if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) { if( e.getSource() == iconLabel ) {
// double-click on icon closes window // double-click on icon closes window
close(); close();
} else if( !hasNativeCustomDecoration() ) { } else if( !hasNativeCustomDecoration() ) {
@@ -1429,7 +1308,7 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) ) if( !SwingUtilities.isLeftMouseButton( e ) )
return; return;
dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window ); dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
linuxNativeMove = false; linuxNativeMove = false;
// on Linux, move or maximize/restore window // on Linux, move or maximize/restore window
@@ -1542,27 +1421,4 @@ debug*/
@Override public void componentMoved( ComponentEvent e ) {} @Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {} @Override public void componentHidden( ComponentEvent e ) {}
} }
//---- interface TitleBarCaptionHitTest -----------------------------------
/**
* For custom components use {@link FlatClientProperties#COMPONENT_TITLE_BAR_CAPTION}
* instead of this interface.
*
* @since 3.4
*/
public interface TitleBarCaptionHitTest {
/**
* Invoked for a component that is enabled and has mouse listeners,
* to check whether it processes mouse input at the given x/y location.
* Useful for components that do not use mouse input on whole component bounds.
* E.g. a tabbed pane with a few tabs has some empty space beside the tabs
* that can be used to move the window.
*
* @return {@code true} if the component is not interested in mouse input at the given location
* {@code false} if the component wants process mouse input at the given location
* {@code null} if the component children should be checked
*/
Boolean isTitleBarCaptionAt( int x, int y );
}
} }

View File

@@ -26,7 +26,6 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -160,14 +159,14 @@ public class FlatToggleButtonUI
b.revalidate(); b.revalidate();
} }
HiDPIUtils.repaint( b ); b.repaint();
break; break;
case TAB_BUTTON_UNDERLINE_PLACEMENT: case TAB_BUTTON_UNDERLINE_PLACEMENT:
case TAB_BUTTON_UNDERLINE_HEIGHT: case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR: case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND: case TAB_BUTTON_SELECTED_BACKGROUND:
HiDPIUtils.repaint( b ); b.repaint();
break; break;
} }
} }

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.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;
@@ -36,7 +35,6 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -132,7 +130,7 @@ public class FlatToolBarSeparatorUI
} else } else
installStyle( s ); installStyle( s );
s.revalidate(); s.revalidate();
HiDPIUtils.repaint( s ); s.repaint();
break; break;
} }
} }
@@ -175,12 +173,6 @@ public class FlatToolBarSeparatorUI
if( size != null ) if( size != null )
return scale( size ); return scale( size );
// get separator width
int separatorWidth = this.separatorWidth;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorWidth != null )
separatorWidth = toolBarUI.separatorWidth;
// make sure that gap on left and right side of line have same size // make sure that gap on left and right side of line have same size
int sepWidth = (scale( (separatorWidth - LINE_WIDTH) / 2 ) * 2) + scale( LINE_WIDTH ); int sepWidth = (scale( (separatorWidth - LINE_WIDTH) / 2 ) * 2) + scale( LINE_WIDTH );
@@ -204,12 +196,6 @@ public class FlatToolBarSeparatorUI
float lineWidth = scale( 1f ); float lineWidth = scale( 1f );
float offset = scale( 2f ); float offset = scale( 2f );
// get separator color
Color separatorColor = this.separatorColor;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorColor != null )
separatorColor = toolBarUI.separatorColor;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( separatorColor ); g.setColor( separatorColor );
@@ -224,11 +210,4 @@ public class FlatToolBarSeparatorUI
private boolean isVertical( JComponent c ) { private boolean isVertical( JComponent c ) {
return ((JToolBar.Separator)c).getOrientation() == SwingConstants.VERTICAL; return ((JToolBar.Separator)c).getOrientation() == SwingConstants.VERTICAL;
} }
private FlatToolBarUI getToolBarUI( JComponent c ) {
Container parent = c.getParent();
return (parent instanceof JToolBar && ((JToolBar)parent).getUI() instanceof FlatToolBarUI)
? (FlatToolBarUI) ((JToolBar)parent).getUI()
: null;
}
} }

View File

@@ -47,7 +47,6 @@ import javax.swing.plaf.basic.BasicToolBarUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -83,7 +82,7 @@ import com.formdev.flatlaf.util.UIScale;
*/ */
public class FlatToolBarUI public class FlatToolBarUI
extends BasicToolBarUI extends BasicToolBarUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest implements StyleableUI
{ {
/** @since 1.4 */ @Styleable protected boolean focusableButtons; /** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation; /** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -94,10 +93,6 @@ public class FlatToolBarUI
@Styleable protected Insets borderMargins; @Styleable protected Insets borderMargins;
@Styleable protected Color gripColor; @Styleable protected Color gripColor;
// for FlatToolBarSeparatorUI
/** @since 3.3 */ @Styleable protected Integer separatorWidth;
/** @since 3.3 */ @Styleable protected Color separatorColor;
private FocusTraversalPolicy focusTraversalPolicy; private FocusTraversalPolicy focusTraversalPolicy;
private Boolean oldFloatable; private Boolean oldFloatable;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
@@ -444,7 +439,7 @@ public class FlatToolBarUI
// repaint button group // repaint button group
if( gr != null ) if( gr != null )
HiDPIUtils.repaint(toolBar, gr ); toolBar.repaint( gr );
} }
private ButtonGroup getButtonGroup( AbstractButton b ) { private ButtonGroup getButtonGroup( AbstractButton b ) {
@@ -454,15 +449,6 @@ public class FlatToolBarUI
: null; : null;
} }
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicToolBarUI adds some mouse listeners for dragging when toolbar is floatable
return null; // check children
}
//---- class FlatToolBarFocusTraversalPolicy ------------------------------ //---- class FlatToolBarFocusTraversalPolicy ------------------------------
/** /**

View File

@@ -61,7 +61,7 @@ public class FlatToolTipUI
super.installUI( c ); super.installUI( c );
// update HTML renderer if necessary // update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( c ); FlatLabelUI.updateHTMLRenderer( c, ((JToolTip)c).getTipText(), false );
} }
@Override @Override
@@ -81,7 +81,11 @@ public class FlatToolTipUI
/** @since 2.0.1 */ /** @since 2.0.1 */
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
FlatHTML.propertyChange( e ); String name = e.getPropertyName();
if( name == "tiptext" || name == "font" || name == "foreground" ) {
JToolTip toolTip = (JToolTip) e.getSource();
FlatLabelUI.updateHTMLRenderer( toolTip, toolTip.getTipText(), false );
}
} }
@Override @Override

View File

@@ -47,7 +47,6 @@ import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -239,6 +238,22 @@ public class FlatTreeUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
FlatScrollPaneUI.installSmoothScrollingDelegateActions( tree, false,
"scrollDownChangeSelection", // PAGE_DOWN
"scrollUpChangeSelection", // PAGE_UP
"scrollDownChangeLead", // ctrl PAGE_DOWN
"scrollUpChangeLead", // ctrl PAGE_UP
"scrollDownExtendSelection", // shift PAGE_DOWN, shift ctrl PAGE_DOWN
"scrollUpExtendSelection", // shift PAGE_UP, shift ctrl PAGE_UP
"selectNextChangeLead", // ctrl DOWN
"selectPreviousChangeLead" // ctrl UP
);
}
@Override @Override
protected void updateRenderer() { protected void updateRenderer() {
super.updateRenderer(); super.updateRenderer();
@@ -311,7 +326,7 @@ public class FlatTreeUI
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case TREE_WIDE_SELECTION: case TREE_WIDE_SELECTION:
case TREE_PAINT_SELECTION: case TREE_PAINT_SELECTION:
HiDPIUtils.repaint( tree ); tree.repaint();
break; break;
case "dropLocation": case "dropLocation":
@@ -326,7 +341,7 @@ public class FlatTreeUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
tree.revalidate(); tree.revalidate();
HiDPIUtils.repaint( tree ); tree.repaint();
break; break;
case "enabled": case "enabled":
@@ -354,7 +369,7 @@ public class FlatTreeUI
Rectangle r = tree.getPathBounds( loc.getPath() ); Rectangle r = tree.getPathBounds( loc.getPath() );
if( r != null ) if( r != null )
HiDPIUtils.repaint( tree, 0, r.y, tree.getWidth(), r.height ); tree.repaint( 0, r.y, tree.getWidth(), r.height );
} }
@Override @Override
@@ -371,14 +386,14 @@ public class FlatTreeUI
{ {
if( changedPaths.length > 4 ) { if( changedPaths.length > 4 ) {
// same is done in BasicTreeUI.Handler.valueChanged() // same is done in BasicTreeUI.Handler.valueChanged()
HiDPIUtils.repaint( tree ); tree.repaint();
} else { } else {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
for( TreePath path : changedPaths ) { for( TreePath path : changedPaths ) {
Rectangle r = getPathBounds( tree, path ); Rectangle r = getPathBounds( tree, path );
if( r != null ) if( r != null )
HiDPIUtils.repaint( tree, r.x, r.y - arc, r.width, r.height + (arc * 2) ); tree.repaint( r.x, r.y - arc, r.width, r.height + (arc * 2) );
} }
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2024 FormDev Software GmbH * Copyright 2023 FormDev Software GmbH
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import javax.swing.Action;
* (similar to class sun.swing.UIAction) * (similar to class sun.swing.UIAction)
* *
* @author Karl Tauber * @author Karl Tauber
* @since 3.4 * @since 3.3
*/ */
public abstract class FlatUIAction public abstract class FlatUIAction
implements Action implements Action

View File

@@ -60,7 +60,6 @@ import javax.swing.border.Border;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.tree.DefaultTreeCellEditor;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatIntelliJLaf; import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
@@ -124,11 +123,6 @@ public class FlatUIUtils
dest.right = src.right; dest.right = src.right;
} }
/** @since 3.5 */
public static boolean isInsetsEmpty( Insets insets ) {
return insets.top == 0 && insets.left == 0 && insets.bottom == 0 && insets.right == 0;
}
public static Color getUIColor( String key, int defaultColorRGB ) { public static Color getUIColor( String key, int defaultColorRGB ) {
Color color = UIManager.getColor( key ); Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB ); return (color != null) ? color : new Color( defaultColorRGB );
@@ -305,14 +299,15 @@ public class FlatUIUtils
if( c == null ) if( c == null )
return false; return false;
// check whether used as table cell editor // check whether used in cell editor (check 3 levels up)
Container parent = c.getParent(); Component c2 = c;
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c ) for( int i = 0; i <= 2 && c2 != null; i++ ) {
Container parent = c2.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c2 )
return true; return true;
// check whether used as tree cell editor c2 = parent;
if( parent instanceof DefaultTreeCellEditor.EditorContainer ) }
return true;
// check whether used as cell editor // check whether used as cell editor
// Table.editor is set in JTable.GenericEditor constructor // Table.editor is set in JTable.GenericEditor constructor
@@ -606,55 +601,28 @@ public class FlatUIUtils
public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height, public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc, float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background ) Paint focusColor, Paint borderColor, Paint background )
{
paintOutlinedComponent( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background, false );
}
static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background, boolean scrollPane )
{ {
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( (int) systemScaleFactor != systemScaleFactor ) { 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%
HiDPIUtils.paintAtScale1x( g, x, y, width, height, HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
paintOutlinedComponentImpl( g2d, x2, y2, width2, height2, paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor), (float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
(float) (borderWidth * scaleFactor), (float) (arc * scaleFactor), (float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
focusColor, borderColor, background, scrollPane, scaleFactor ); focusColor, borderColor, background );
} ); } );
return; return;
} }
paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth, paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background, scrollPane, systemScaleFactor ); borderWidth, arc, focusColor, borderColor, background );
} }
@SuppressWarnings( "SelfAssignment" ) // Error Prone
private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height, private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc, float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background, boolean scrollPane, double scaleFactor ) Paint focusColor, Paint borderColor, Paint background )
{ {
// Special handling for scrollpane and fractional scale factors (e.g. 1.25 - 1.75),
// where Swing scales one "logical" pixel (border insets) to either one or two physical pixels.
// Antialiasing is used to paint the border, which usually needs two physical pixels
// at small scale factors. 1px for the solid border and another 1px for antialiasing.
// But scrollpane view is painted over the border, which results in a painted border
// that is 1px thick at some sides and 2px thick at other sides.
if( scrollPane && scaleFactor != (int) scaleFactor ) {
if( focusWidth > 0 ) {
// reduce outer border thickness (focusWidth) so that inner side of
// component border (focusWidth + borderWidth) is at a full pixel
int totalWidth = (int) (focusWidth + borderWidth);
focusWidth = totalWidth - borderWidth;
} else {// if( scaleFactor > 1 && scaleFactor < 2 ) {
// reduce component border thickness (borderWidth) to full pixels
borderWidth = (int) borderWidth;
}
}
// outside bounds of the border and the background // outside bounds of the border and the background
float x1 = x + focusWidth; float x1 = x + focusWidth;
float y1 = y + focusWidth; float y1 = y + focusWidth;
@@ -766,7 +734,7 @@ public class FlatUIUtils
} }
/** /**
* Creates a (rounded) rectangle used to paint components (border, background, etc.). * Creates a (rounded) rectangle used to paint components (border, background, etc).
* The given arc diameter is limited to min(width,height). * The given arc diameter is limited to min(width,height).
*/ */
public static Shape createComponentRectangle( float x, float y, float w, float h, float arc ) { public static Shape createComponentRectangle( float x, float y, float w, float h, float arc ) {
@@ -812,7 +780,7 @@ public class FlatUIUtils
if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) { if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) {
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != (int) systemScaleFactor ) { 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%
HiDPIUtils.paintAtScale1x( g, x, y, width, height, HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -1347,13 +1315,13 @@ debug*/
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
HiDPIUtils.repaint( repaintComponent ); repaintComponent.repaint();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
HiDPIUtils.repaint( repaintComponent ); repaintComponent.repaint();
} }
} }

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.lang.reflect.Method;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
@@ -47,9 +48,14 @@ public class FlatViewportUI
Component view = ((JViewport)c).getView(); Component view = ((JViewport)c).getView();
if( view instanceof JComponent ) { if( view instanceof JComponent ) {
ComponentUI ui = JavaCompatibility2.getUI( (JComponent) view ); try {
Method m = view.getClass().getMethod( "getUI" );
Object ui = m.invoke( view );
if( ui instanceof ViewportPainter ) if( ui instanceof ViewportPainter )
((ViewportPainter)ui).paintViewport( g, (JComponent) view, (JViewport) c ); ((ViewportPainter)ui).paintViewport( g, (JComponent) view, (JViewport) c );
} catch( Exception ex ) {
// ignore
}
} }
} }

View File

@@ -29,8 +29,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Collections; import java.util.Collections;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.Timer; import javax.swing.Timer;
@@ -89,7 +89,7 @@ class FlatWindowsNativeWindowBorder
return null; return null;
// check whether native library was successfully loaded // check whether native library was successfully loaded
if( !FlatNativeWindowsLibrary.isLoaded() ) if( !FlatNativeLibrary.isLoaded() )
return null; return null;
// create new instance // create new instance
@@ -159,7 +159,7 @@ class FlatWindowsNativeWindowBorder
} }
@Override @Override
public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback, public void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds, Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds ) Rectangle closeButtonBounds )
{ {
@@ -168,7 +168,7 @@ class FlatWindowsNativeWindowBorder
return; return;
wndProc.titleBarHeight = titleBarHeight; wndProc.titleBarHeight = titleBarHeight;
wndProc.captionHitTestCallback = captionHitTestCallback; wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
wndProc.appIconBounds = cloneRectange( appIconBounds ); wndProc.appIconBounds = cloneRectange( appIconBounds );
wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds ); wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds );
wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds ); wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds );
@@ -288,8 +288,8 @@ class FlatWindowsNativeWindowBorder
private final long hwnd; private final long hwnd;
// Swing coordinates/values may be scaled on a HiDPI screen // Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight; // measured from window top edge, which may be out-of-screen if maximized private int titleBarHeight;
private Predicate<Point> captionHitTestCallback; private Rectangle[] hitTestSpots;
private Rectangle appIconBounds; private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds; private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds; private Rectangle maximizeButtonBounds;
@@ -340,61 +340,50 @@ class FlatWindowsNativeWindowBorder
private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) { private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen // scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y ); Point pt = scaleDown( x, y );
int sx = pt.x;
int sy = pt.y;
// return HTSYSMENU if mouse is over application icon // return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu // - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE // - double-left-click sends WM_CLOSE
if( contains( appIconBounds, pt ) ) if( contains( appIconBounds, sx, sy ) )
return HTSYSMENU; return HTSYSMENU;
// return HTMINBUTTON if mouse is over minimize button // return HTMINBUTTON if mouse is over minimize button
// - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11 // - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11
if( contains( minimizeButtonBounds, pt ) ) if( contains( minimizeButtonBounds, sx, sy ) )
return HTMINBUTTON; return HTMINBUTTON;
// return HTMAXBUTTON if mouse is over maximize/restore button // return HTMAXBUTTON if mouse is over maximize/restore button
// - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10 // - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10
// - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11 // - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu // https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
if( contains( maximizeButtonBounds, pt ) ) if( contains( maximizeButtonBounds, sx, sy ) )
return HTMAXBUTTON; return HTMAXBUTTON;
// return HTCLOSE if mouse is over close button // return HTCLOSE if mouse is over close button
// - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11 // - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11
if( contains( closeButtonBounds, pt ) ) if( contains( closeButtonBounds, sx, sy ) )
return HTCLOSE; return HTCLOSE;
// return HTTOP if mouse is over top resize border boolean isOnTitleBar = (sy < titleBarHeight);
// - hovering mouse shows vertical resize cursor
// - left-click and drag vertically resizes window
if( isOnResizeBorder )
return HTTOP;
boolean isOnTitleBar = (pt.y < titleBarHeight);
if( isOnTitleBar ) { if( isOnTitleBar ) {
// return HTCLIENT if mouse is over any Swing component in title bar // use a second reference to the array to avoid that it can be changed
// that processes mouse events (e.g. buttons, menus, etc) // in another thread while processing the array
// - Windows ignores mouse events in this area Rectangle[] hitTestSpots2 = hitTestSpots;
try { for( Rectangle spot : hitTestSpots2 ) {
if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) ) if( spot.contains( sx, sy ) )
return HTCLIENT;
} catch( Throwable ex ) {
// ignore
}
// return HTCAPTION if mouse is over title bar
// - right-click shows system menu
// - double-left-click maximizes/restores window size
return HTCAPTION;
}
// return HTCLIENT
// - Windows ignores mouse events in this area
return HTCLIENT; return HTCLIENT;
} }
return isOnResizeBorder ? HTTOP : HTCAPTION;
}
private boolean contains( Rectangle rect, Point pt ) { return isOnResizeBorder ? HTTOP : HTCLIENT;
return (rect != null && rect.contains( pt ) ); }
private boolean contains( Rectangle rect, int x, int y ) {
return (rect != null && rect.contains( x, y ) );
} }
/** /**

View File

@@ -1,219 +0,0 @@
/*
* Copyright 2024 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.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
/**
* @author Karl Tauber
*/
class FullWindowContentSupport
{
private static final String KEY_DEBUG_SHOW_PLACEHOLDERS = "FlatLaf.debug.panel.showPlaceholders";
private static ArrayList<WeakReference<JComponent>> placeholders = new ArrayList<>();
static Dimension getPlaceholderPreferredSize( JComponent c, String options ) {
JRootPane rootPane;
Rectangle bounds;
if( !options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) ||
!c.isDisplayable() ||
(rootPane = SwingUtilities.getRootPane( c )) == null ||
(bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) == null )
return new Dimension( 0, 0 );
if( options.length() > 3 ) {
if( (options.contains( "leftToRight" ) && !c.getComponentOrientation().isLeftToRight()) ||
(options.contains( "rightToLeft" ) && c.getComponentOrientation().isLeftToRight()) )
return new Dimension( 0, 0 );
}
// On macOS, the client property is updated very late when toggling full screen,
// which results in "jumping" layout after full screen toggle finished.
// To avoid that, get up-to-date buttons bounds from macOS.
if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) {
Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) );
if( r != null )
bounds = r;
}
int width = bounds.width;
int height = bounds.height;
if( options.length() > 3 ) {
if( width == 0 && options.contains( "zeroInFullScreen" ) )
height = 0;
if( options.contains( "horizontal" ) )
height = 0;
if( options.contains( "vertical" ) )
width = 0;
}
return new Dimension( width, height );
}
static void registerPlaceholder( JComponent c ) {
synchronized( placeholders ) {
if( indexOfPlaceholder( c ) < 0 )
placeholders.add( new WeakReference<>( c ) );
}
}
static void unregisterPlaceholder( JComponent c ) {
synchronized( placeholders ) {
int index = indexOfPlaceholder( c );
if( index >= 0 )
placeholders.remove( index );
}
}
private static int indexOfPlaceholder( JComponent c ) {
int size = placeholders.size();
for( int i = 0; i < size; i++ ) {
if( placeholders.get( i ).get() == c )
return i;
}
return -1;
}
static void revalidatePlaceholders( Component container ) {
synchronized( placeholders ) {
if( placeholders.isEmpty() )
return;
for( Iterator<WeakReference<JComponent>> it = placeholders.iterator(); it.hasNext(); ) {
WeakReference<JComponent> ref = it.next();
JComponent c = ref.get();
// remove already released placeholder
if( c == null ) {
it.remove();
continue;
}
// revalidate placeholder if is in given container
if( SwingUtilities.isDescendingFrom( c, container ) )
c.revalidate();
}
}
}
static ComponentListener macInstallListeners( JRootPane rootPane ) {
ComponentListener l = new ComponentAdapter() {
boolean lastFullScreen;
@Override
public void componentResized( ComponentEvent e ) {
Window window = SwingUtilities.windowForComponent( rootPane );
if( window == null )
return;
boolean fullScreen = FlatNativeMacLibrary.isLoaded() && FlatNativeMacLibrary.isWindowFullScreen( window );
if( fullScreen == lastFullScreen )
return;
lastFullScreen = fullScreen;
macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
}
};
rootPane.addComponentListener( l );
return l;
}
static void macUninstallListeners( JRootPane rootPane, ComponentListener l ) {
if( l != null )
rootPane.removeComponentListener( l );
}
static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) {
if( !SystemInfo.isMacFullWindowContentSupported || !rootPane.isDisplayable() )
return;
Rectangle bounds = null;
if( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) {
bounds = FlatNativeMacLibrary.isLoaded()
? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) )
: new Rectangle( 68, 28 ); // default size
}
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
static void macUninstallFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) {
if( !SystemInfo.isMacFullWindowContentSupported )
return;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, null );
}
static void debugPaint( Graphics g, JComponent c ) {
if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) )
return;
int width = c.getWidth();
int height = c.getHeight();
if( width <= 0 || height <= 0 )
return;
// draw red figure
g.setColor( Color.red );
debugPaintRect( g, new Rectangle( width, height ) );
// draw magenta figure if buttons bounds are not equal to placeholder bounds
JRootPane rootPane;
Rectangle bounds;
if( (rootPane = SwingUtilities.getRootPane( c )) != null &&
(bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null &&
(bounds.width != width || bounds.height != height) )
{
g.setColor( Color.magenta );
debugPaintRect( g, SwingUtilities.convertRectangle( rootPane, bounds, c ) );
}
}
private static void debugPaintRect( Graphics g, Rectangle r ) {
// draw rectangle
g.drawRect( r.x, r.y, r.width - 1, r.height - 1 );
// draw diagonal cross
int x2 = r.x + r.width - 1;
int y2 = r.y + r.height - 1;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.drawLine( r.x, r.y, x2, y2 );
g.drawLine( r.x, y2, x2, r.y );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
}

View File

@@ -0,0 +1,306 @@
/*
* 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.Collections;
import java.util.List;
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.util.LoggingFacade;
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 supported;
private static Method Window_hasCustomDecoration;
private static Method Window_setHasCustomDecoration;
private static Method WWindowPeer_setCustomDecorationTitleBarHeight;
private static Method WWindowPeer_setCustomDecorationHitTestSpots;
private static Method AWTAccessor_getComponentAccessor;
private static Method AWTAccessor_ComponentAccessor_getPeer;
public static boolean isSupported() {
initialize();
return supported;
}
static Object install( JRootPane rootPane ) {
if( !isSupported() )
return null;
// check whether root pane already has a parent, which is the case when switching LaF
Container parent = rootPane.getParent();
if( parent != null ) {
if( parent instanceof Window )
FlatNativeWindowBorder.install( (Window) parent );
return null;
}
// 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 )
FlatNativeWindowBorder.install( (Window) parent );
// remove listener since it is actually not possible to uninstall JBR decorations
// use invokeLater to remove listener to avoid that listener
// is removed while listener queue is processed
EventQueue.invokeLater( () -> {
rootPane.removeHierarchyListener( this );
} );
}
};
rootPane.addHierarchyListener( addListener );
return addListener;
}
static void uninstall( JRootPane rootPane, Object data ) {
// remove listener (if not yet done)
if( data instanceof HierarchyListener )
rootPane.removeHierarchyListener( (HierarchyListener) data );
// since it is actually not possible to uninstall JBR decorations,
// simply reduce titleBarHeight so that it is still possible to resize window
// and remove hitTestSpots
Container parent = rootPane.getParent();
if( parent instanceof Window )
setHasCustomDecoration( (Window) parent, false );
}
static boolean hasCustomDecoration( Window window ) {
if( !isSupported() )
return false;
try {
return (Boolean) Window_hasCustomDecoration.invoke( window );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
return false;
}
}
static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
if( !isSupported() )
return;
try {
if( hasCustomDecoration )
Window_setHasCustomDecoration.invoke( window );
else
setTitleBarHeightAndHitTestSpots( window, 4, Collections.emptyList() );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, List<Rectangle> hitTestSpots ) {
if( !isSupported() )
return;
try {
Object compAccessor = AWTAccessor_getComponentAccessor.invoke( null );
Object peer = AWTAccessor_ComponentAccessor_getPeer.invoke( compAccessor, window );
WWindowPeer_setCustomDecorationTitleBarHeight.invoke( peer, titleBarHeight );
WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
private static void initialize() {
if( supported != null )
return;
supported = false;
// requires JetBrains Runtime 11 and Windows 10
if( !SystemInfo.isJetBrainsJVM_11_orLater || !SystemInfo.isWindows_10_orLater )
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_setCustomDecorationTitleBarHeight = peerClass.getDeclaredMethod( "setCustomDecorationTitleBarHeight", int.class );
WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
WWindowPeer_setCustomDecorationTitleBarHeight.setAccessible( true );
WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
Window_hasCustomDecoration = Window.class.getDeclaredMethod( "hasCustomDecoration" );
Window_setHasCustomDecoration = Window.class.getDeclaredMethod( "setHasCustomDecoration" );
Window_hasCustomDecoration.setAccessible( true );
Window_setHasCustomDecoration.setAccessible( true );
supported = true;
} catch( Exception ex ) {
// ignore
}
}
//---- class JBRWindowTopBorder -------------------------------------------
static class JBRWindowTopBorder
extends BorderUIResource.EmptyBorderUIResource
{
private static JBRWindowTopBorder instance;
private final Color activeLightColor = new Color( 0x707070 );
private final Color activeDarkColor = new Color( 0x2D2E2F );
private final Color inactiveLightColor = new Color( 0xaaaaaa );
private final Color inactiveDarkColor = new Color( 0x494A4B );
private boolean colorizationAffectsBorders;
private Color activeColor;
static JBRWindowTopBorder getInstance() {
if( instance == null )
instance = new JBRWindowTopBorder();
return instance;
}
JBRWindowTopBorder() {
super( 1, 0, 0, 0 );
update();
installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
void installListeners() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
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 );
}
boolean isColorizationColorAffectsBorders() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" );
return (value instanceof Boolean) ? (Boolean) value : true;
}
Color getColorizationColor() {
return (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor" );
}
int getColorizationColorBalance() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColorBalance" );
return (value instanceof Integer) ? (Integer) value : -1;
}
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return null;
Color colorizationColor = getColorizationColor();
if( colorizationColor != null ) {
int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
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 );
// avoid potential IllegalArgumentException in Color constructor
r = Math.min( Math.max( r, 0 ), 255 );
g = Math.min( Math.max( g, 0 ), 255 );
b = Math.min( Math.max( b, 0 ), 255 );
return new Color( r, g, b );
}
Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().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();
boolean dark = FlatLaf.isLafDark();
g.setColor( active
? (activeColor != null ? activeColor : (dark ? activeDarkColor : activeLightColor))
: (dark ? 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.fillRect( x, y, width, 1 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
}
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Provides Java version compatibility methods.
* <p>
* WARNING: This is private API and may change.
*
* @author Karl Tauber
* @since 3.3
*/
public class JavaCompatibility2
{
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/**
* Java 8: getUI() method on various components (e.g. JButton, JList, etc)
* <br>
* Java 9: javax.swing.JComponent.getUI()
*/
public static ComponentUI getUI( JComponent c ) {
try {
// Java 9+
if( SystemInfo.isJava_9_orLater ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
try {
MethodType mt = MethodType.methodType( ComponentUI.class, new Class[0] );
getUIMethod = MethodHandles.publicLookup().findVirtual( JComponent.class, "getUI", mt );
} catch( Exception ex ) {
// ignore
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
if( getUIMethod != null )
return (ComponentUI) getUIMethod.invoke( c );
}
// components often used (e.g. as view in scroll panes)
if( c instanceof JPanel )
return ((JPanel)c).getUI();
if( c instanceof JList )
return ((JList<?>)c).getUI();
if( c instanceof JTable )
return ((JTable)c).getUI();
if( c instanceof JTree )
return ((JTree)c).getUI();
if( c instanceof JTextComponent )
return ((JTextComponent)c).getUI();
// Java 8 and fallback
Method m = c.getClass().getMethod( "getUI" );
return (ComponentUI) m.invoke( c );
} catch( Throwable ex ) {
// ignore
return null;
}
}
/**
* Java 8 - 11 on Windows: sun.awt.shell.ShellFolder.get( "fileChooserShortcutPanelFolders" )
* <br>
* Java 12: javax.swing.filechooser.FileSystemView.getChooserShortcutPanelFiles()
*
* @since 3.4
*/
public static File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
/**
* Java 8: sun.awt.shell.ShellFolder.get( "fileChooserComboBoxFolders" )
* <br>
* Java 9: javax.swing.filechooser.FileSystemView.getChooserComboBoxFiles()
*
* @since 3.4
*/
public static File[] getChooserComboBoxFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_9_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserComboBoxFiles" );
return (File[]) m.invoke( fsv );
} else {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserComboBoxFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
}

View File

@@ -80,7 +80,7 @@ public class FontUtils
/** /**
* Loads a font family previously registered via {@link #registerFontFamilyLoader(String, Runnable)}. * Loads a font family previously registered via {@link #registerFontFamilyLoader(String, Runnable)}.
* If the family is already loaded or no loader is registered for that family, nothing happens. * If the family is already loaded or no londer is registered for that family, nothing happens.
*/ */
public static void loadFontFamily( String family ) { public static void loadFontFamily( String family ) {
if( !hasLoaders() ) if( !hasLoaders() )
@@ -109,7 +109,7 @@ public class FontUtils
} }
/** /**
* Returns all font family names available in the graphics environment. * Returns all font familiy names available in the graphics environment.
* This invokes {@link GraphicsEnvironment#getAvailableFontFamilyNames()} and * This invokes {@link GraphicsEnvironment#getAvailableFontFamilyNames()} and
* appends families registered for lazy loading via {@link #registerFontFamilyLoader(String, Runnable)} * appends families registered for lazy loading via {@link #registerFontFamilyLoader(String, Runnable)}
* to the result. * to the result.

View File

@@ -16,17 +16,14 @@
package com.formdev.flatlaf.util; package com.formdev.flatlaf.util;
import java.awt.Component;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.GlyphVector; import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.RepaintManager;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
/** /**
@@ -195,8 +192,7 @@ public class HiDPIUtils
case "Inter": case "Inter":
case "Inter Light": case "Inter Light":
case "Inter Semi Bold": // Inter v3 case "Inter Semi Bold":
case "Inter SemiBold": // Inter v4
case "Roboto": case "Roboto":
case "Roboto Light": case "Roboto Light":
case "Roboto Medium": case "Roboto Medium":
@@ -325,243 +321,4 @@ public class HiDPIUtils
} }
}; };
} }
/**
* Repaints the given component.
* <p>
* See {@link #repaint(Component, int, int, int, int)} for more details.
*
* @since 3.5
*/
public static void repaint( Component c ) {
repaint( c, 0, 0, c.getWidth(), c.getHeight() );
}
/**
* Repaints the given component area.
* <p>
* See {@link #repaint(Component, int, int, int, int)} for more details.
*
* @since 3.5
*/
public static void repaint( Component c, Rectangle r ) {
repaint( c, r.x, r.y, r.width, r.height );
}
/**
* Repaints the given component area.
* <p>
* Invokes {@link Component#repaint(int, int, int, int)} on the given component,
* <p>
* Use this method, instead of {@code Component.repaint(...)},
* to fix a problem in Swing when using scale factors that end on .25 or .75
* (e.g. 1.25, 1.75, 2.25, etc) and repainting single components, which may not
* repaint right and/or bottom 1px edge of component.
* <p>
* The problem may occur under following conditions:
* <ul>
* <li>using Java 9 or later
* <li>system scale factor is 125%, 175%, 225%, ...
* (Windows only; Java on macOS and Linux does not support fractional scale factors)
* <li>repaint whole component or right/bottom area of component
* <li>component is opaque; or component is contained in a opaque container
* that has same right/bottom bounds as component
* <li>component has bounds that Java/Swing scales different when repainting components
* </ul>
*
* @since 3.5
*/
public static void repaint( Component c, int x, int y, int width, int height ) {
// repaint given component area
// Always invoke repaint() on given component, even if also invoked (below)
// on one of its ancestors, for the case that component overrides that method.
// Also RepaintManager "merges" the two repaints into one.
c.repaint( x, y, width, height );
if( RepaintManager.currentManager( c ) instanceof HiDPIRepaintManager )
return;
// if necessary, also repaint given area in first ancestor that is larger than component
// to avoid clipping issue (see needsSpecialRepaint())
if( needsSpecialRepaint( c, x, y, width, height ) ) {
int x2 = x + c.getX();
int y2 = y + c.getY();
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
x2 += p.getX();
y2 += p.getY();
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() ) {
p.repaint( x2, y2, width, height );
break;
}
}
}
}
/**
* There is a problem in Swing, when using scale factors that end on .25 or .75
* (e.g. 1.25, 1.75, 2.25, etc) and repainting single components, which may not
* repaint right and/or bottom 1px edge of component.
* <p>
* The component is first painted to an in-memory image,
* and then that image is copied to the screen.
* See {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}.
* <p>
* There are two clipping rectangles involved when copying the image to the screen:
* {@code sun.java2d.SunGraphics2D#devClip} and
* {@code sun.java2d.SunGraphics2D#usrClip}.
* <p>
* {@code devClip} is the device clipping in physical pixels.
* It gets the bounds of the painting component, which is either the passed component,
* or if it is non-opaque, then the first opaque ancestor of the passed component.
* It is calculated in {@code sun.java2d.SunGraphics2D#constrain()} while
* getting a graphics context via {@link JComponent#getGraphics()}.
* <p>
* {@code usrClip} is the user clipping, which is set via {@link Graphics} clipping methods.
* This is done in {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}.
* <p>
* The intersection of {@code devClip} and {@code usrClip}
* (computed in {@code sun.java2d.SunGraphics2D#validateCompClip()})
* is used to copy the image to the screen.
* <p>
* Unfortunately different scaling/rounding strategies are used to calculate
* the two clipping rectangles, which is the reason of the issue.
* <p>
* {@code devClip} (see {@code sun.java2d.SunGraphics2D#constrain()}):
* <pre>{@code
* int devX = (int) (x * scale);
* int devWidth = Math.round( width * scale )
* }</pre>
* {@code usrClip} (see {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}):
* <pre>{@code
* int usrX = (int) Math.ceil( (x * scale) - 0.5 );
* int usrWidth = ((int) Math.ceil( ((x + width) * scale) - 0.5 )) - usrX;
* }</pre>
* X/Y coordinates are always rounded down for {@code devClip}, but rounded up for {@code usrClip}.
* Width/height calculation is also different.
*/
private static boolean needsSpecialRepaint( Component c, int x, int y, int width, int height ) {
// no special repaint necessary for Java 8 or for macOS and Linux
// (Java on those platforms does not support fractional scale factors)
if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
return false;
// check whether repaint area is empty or no component given
// (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
if( width <= 0 || height <= 0 || c == null )
return false;
// check whether component has zero size
// (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
int compWidth = c.getWidth();
int compHeight = c.getHeight();
if( compWidth <= 0 || compHeight <= 0 )
return false;
// check whether repaint area does span to right or bottom component edges
// (in this case, {@code devClip} is always larger than {@code usrClip})
if( x + width < compWidth && y + height < compHeight )
return false;
// if component is not opaque, Swing uses the first opaque ancestor for painting
if( !c.isOpaque() ) {
int x2 = x;
int y2 = y;
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
x2 += p.getX();
y2 += p.getY();
if( p.isOpaque() ) {
// check whether repaint area does span to right or bottom edges
// of the opaque ancestor component
// (in this case, {@code devClip} is always larger than {@code usrClip})
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() )
return false;
break;
}
}
}
// check whether Special repaint is necessary for current scale factor
// (doing this check late because it temporary allocates some memory)
double scaleFactor = UIScale.getSystemScaleFactor( c.getGraphicsConfiguration() );
double fraction = scaleFactor - (int) scaleFactor;
if( fraction == 0 || fraction == 0.5 )
return false;
return true;
}
/**
* Installs a {@link HiDPIRepaintManager} on Windows when running in Java 9+,
* but only if default repaint manager is currently installed.
* <p>
* Invoke once on application startup.
* Compatible with all/other LaFs.
*
* @since 3.5
*/
public static void installHiDPIRepaintManager() {
if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
return;
RepaintManager manager = RepaintManager.currentManager( (Component) null );
if( manager.getClass() == RepaintManager.class )
RepaintManager.setCurrentManager( new HiDPIRepaintManager() );
}
/**
* Similar to {@link #repaint(Component, int, int, int, int)},
* but invokes callback instead of invoking {@link Component#repaint(int, int, int, int)}.
* <p>
* For use in custom repaint managers.
*
* @since 3.5
*/
public static void addDirtyRegion( JComponent c, int x, int y, int width, int height, DirtyRegionCallback callback ) {
if( needsSpecialRepaint( c, x, y, width, height ) ) {
int x2 = x + c.getX();
int y2 = y + c.getY();
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() && p instanceof JComponent ) {
callback.addDirtyRegion( (JComponent) p, x2, y2, width, height );
return;
}
x2 += p.getX();
y2 += p.getY();
}
}
callback.addDirtyRegion( c, x, y, width, height );
}
//---- interface DirtyRegionCallback --------------------------------------
/**
* For {@link HiDPIUtils#addDirtyRegion(JComponent, int, int, int, int, DirtyRegionCallback)}.
*
* @since 3.5
*/
public interface DirtyRegionCallback {
void addDirtyRegion( JComponent c, int x, int y, int w, int h );
}
//---- class HiDPIRepaintManager ------------------------------------------
/**
* A repaint manager that fixes a problem in Swing when repainting components
* at some scale factors (e.g. 125%, 175%, etc) on Windows.
* <p>
* Use {@link HiDPIUtils#installHiDPIRepaintManager()} to install it.
* <p>
* See {@link HiDPIUtils#repaint(Component, int, int, int, int)} for details.
*
* @since 3.5
*/
public static class HiDPIRepaintManager
extends RepaintManager
{
@Override
public void addDirtyRegion( JComponent c, int x, int y, int w, int h ) {
HiDPIUtils.addDirtyRegion( c, x, y, w, h, super::addDirtyRegion );
}
}
} }

View File

@@ -116,11 +116,7 @@ public class NativeLibrary
try { try {
// for development environment // for development environment
if( "file".equals( libraryUrl.getProtocol() ) ) { if( "file".equals( libraryUrl.getProtocol() ) ) {
String binPath = libraryUrl.getPath(); File libraryFile = new File( libraryUrl.getPath() );
String srcPath = binPath.replace( "flatlaf-core/bin/main/", "flatlaf-core/src/main/resources/" );
File libraryFile = new File( srcPath ); // use from 'src' folder if available
if( !libraryFile.isFile() )
libraryFile = new File( binPath ); // use from 'bin' or 'output' folder if available
if( libraryFile.isFile() ) { if( libraryFile.isFile() ) {
// load library without copying // load library without copying
System.load( libraryFile.getCanonicalPath() ); System.load( libraryFile.getCanonicalPath() );

View File

@@ -204,7 +204,7 @@ public class UIScale
if( SystemInfo.isWindows ) { if( SystemInfo.isWindows ) {
// Special handling for Windows to be compatible with OS scaling, // Special handling for Windows to be compatible with OS scaling,
// which distinguish between "screen scaling" and "text scaling". // which distinguish between "screen scaling" and "text scaling".
// - Windows "screen scaling" scales everything (text, icon, gaps, etc.) // - Windows "screen scaling" scales everything (text, icon, gaps, etc)
// and may have different scaling factors for each screen. // and may have different scaling factors for each screen.
// - Windows "text scaling" increases only the font size, but on all screens. // - Windows "text scaling" increases only the font size, but on all screens.
// //

View File

@@ -20,12 +20,9 @@ import java.awt.Dimension;
import java.awt.Image; import java.awt.Image;
import java.awt.image.AbstractMultiResolutionImage; import java.awt.image.AbstractMultiResolutionImage;
import java.awt.image.BaseMultiResolutionImage; import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.MultiResolutionImage; import java.awt.image.MultiResolutionImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@@ -119,26 +116,6 @@ public class MultiResolutionImageSupport
return mapAndCacheImage( mrImage ); return mapAndCacheImage( mrImage );
} }
@Override
public int getWidth( ImageObserver observer ) {
return mrImage.getWidth( observer );
}
@Override
public int getHeight( ImageObserver observer ) {
return mrImage.getHeight( observer );
}
@Override
public ImageProducer getSource() {
return mrImage.getSource();
}
@Override
public Object getProperty( String name, ImageObserver observer ) {
return mrImage.getProperty( name, observer );
}
private Image mapAndCacheImage( Image image ) { private Image mapAndCacheImage( Image image ) {
return cache.computeIfAbsent( image, img -> { return cache.computeIfAbsent( image, img -> {
// using ImageIcon here makes sure that the image is loaded // using ImageIcon here makes sure that the image is loaded
@@ -157,7 +134,7 @@ public class MultiResolutionImageSupport
{ {
private final Dimension[] dimensions; private final Dimension[] dimensions;
private final Function<Dimension, Image> producer; private final Function<Dimension, Image> producer;
private final HashMap<Dimension, Image> cache = new HashMap<>(); private final IdentityHashMap<Dimension, Image> cache = new IdentityHashMap<>();
ProducerMultiResolutionImage( Dimension[] dimensions, Function<Dimension, Image> producer ) { ProducerMultiResolutionImage( Dimension[] dimensions, Function<Dimension, Image> producer ) {
this.dimensions = dimensions; this.dimensions = dimensions;
@@ -182,16 +159,6 @@ public class MultiResolutionImageSupport
return produceAndCacheImage( dimensions[0] ); return produceAndCacheImage( dimensions[0] );
} }
@Override
public int getWidth( ImageObserver observer ) {
return dimensions[0].width;
}
@Override
public int getHeight( ImageObserver observer ) {
return dimensions[0].height;
}
private Image produceAndCacheImage( Dimension size ) { private Image produceAndCacheImage( Dimension size ) {
return cache.computeIfAbsent( size, size2 -> { return cache.computeIfAbsent( size, size2 -> {
// using ImageIcon here makes sure that the image is loaded // using ImageIcon here makes sure that the image is loaded

View File

@@ -246,7 +246,6 @@ PasswordField.revealIconColor = @foreground
#---- Popup ---- #---- Popup ----
[mac]Popup.roundedBorderWidth = 1
Popup.dropShadowColor = #000 Popup.dropShadowColor = #000
Popup.dropShadowOpacity = 0.25 Popup.dropShadowOpacity = 0.25

View File

@@ -289,7 +289,6 @@ ComboBox.popupInsets = 0,0,0,0
ComboBox.selectionInsets = 0,0,0,0 ComboBox.selectionInsets = 0,0,0,0
ComboBox.selectionArc = 0 ComboBox.selectionArc = 0
ComboBox.borderCornerRadius = $Popup.borderCornerRadius ComboBox.borderCornerRadius = $Popup.borderCornerRadius
[mac]ComboBox.roundedBorderWidth = $Popup.roundedBorderWidth
#---- Component ---- #---- Component ----
@@ -506,7 +505,6 @@ PasswordField.revealIcon = com.formdev.flatlaf.icons.FlatRevealIcon
#---- Popup ---- #---- Popup ----
Popup.borderCornerRadius = 4 Popup.borderCornerRadius = 4
[mac]Popup.roundedBorderWidth = 0
Popup.dropShadowPainted = true Popup.dropShadowPainted = true
Popup.dropShadowInsets = -4,-4,4,4 Popup.dropShadowInsets = -4,-4,4,4
@@ -516,7 +514,6 @@ Popup.dropShadowInsets = -4,-4,4,4
PopupMenu.border = com.formdev.flatlaf.ui.FlatPopupMenuBorder PopupMenu.border = com.formdev.flatlaf.ui.FlatPopupMenuBorder
PopupMenu.borderInsets = 4,1,4,1 PopupMenu.borderInsets = 4,1,4,1
PopupMenu.borderCornerRadius = $Popup.borderCornerRadius PopupMenu.borderCornerRadius = $Popup.borderCornerRadius
[mac]PopupMenu.roundedBorderWidth = $Popup.roundedBorderWidth
PopupMenu.background = @menuBackground PopupMenu.background = @menuBackground
PopupMenu.scrollArrowColor = @buttonArrowColor PopupMenu.scrollArrowColor = @buttonArrowColor
@@ -600,15 +597,10 @@ ScrollBar.allowsAbsolutePositioning = true
#---- ScrollPane ---- #---- ScrollPane ----
ScrollPane.border = com.formdev.flatlaf.ui.FlatScrollPaneBorder ScrollPane.border = com.formdev.flatlaf.ui.FlatBorder
ScrollPane.background = $ScrollBar.track ScrollPane.background = $ScrollBar.track
ScrollPane.fillUpperCorner = true ScrollPane.fillUpperCorner = true
ScrollPane.smoothScrolling = true ScrollPane.smoothScrolling = true
ScrollPane.arc = 0
#ScrollPane.List.arc = -1
#ScrollPane.Table.arc = -1
#ScrollPane.TextComponent.arc = -1
#ScrollPane.Tree.arc = -1
#---- SearchField ---- #---- SearchField ----
@@ -705,8 +697,6 @@ TabbedPane.tabAreaAlignment = leading
TabbedPane.tabAlignment = center TabbedPane.tabAlignment = center
# allowed values: preferred, equal or compact # allowed values: preferred, equal or compact
TabbedPane.tabWidthMode = preferred TabbedPane.tabWidthMode = preferred
# allowed values: none, auto, left or right
TabbedPane.tabRotation = none
# allowed values: underlined or card # allowed values: underlined or card
TabbedPane.tabType = underlined TabbedPane.tabType = underlined
@@ -739,11 +729,9 @@ Table.rowHeight = 20
Table.showHorizontalLines = false Table.showHorizontalLines = false
Table.showVerticalLines = false Table.showVerticalLines = false
Table.showTrailingVerticalLine = false Table.showTrailingVerticalLine = false
Table.paintOutsideAlternateRows = false
Table.editorSelectAllOnStartEditing = true
Table.consistentHomeEndKeyBehavior = true Table.consistentHomeEndKeyBehavior = true
Table.intercellSpacing = 0,0 Table.intercellSpacing = 0,0
Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatScrollPaneBorder Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatBorder
Table.ascendingSortIcon = com.formdev.flatlaf.icons.FlatAscendingSortIcon Table.ascendingSortIcon = com.formdev.flatlaf.icons.FlatAscendingSortIcon
Table.descendingSortIcon = com.formdev.flatlaf.icons.FlatDescendingSortIcon Table.descendingSortIcon = com.formdev.flatlaf.icons.FlatDescendingSortIcon
Table.sortIconColor = @icon Table.sortIconColor = @icon
@@ -831,6 +819,7 @@ TitlePane.centerTitleIfMenuBarEmbedded = true
TitlePane.showIconBesideTitle = false TitlePane.showIconBesideTitle = false
TitlePane.menuBarTitleGap = 40 TitlePane.menuBarTitleGap = 40
TitlePane.menuBarTitleMinimumGap = 12 TitlePane.menuBarTitleMinimumGap = 12
TitlePane.menuBarResizeHeight = 4
TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon
TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon
TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon
@@ -913,7 +902,6 @@ ToolTipManager.enableToolTipMode = activeApplication
#---- ToolTip ---- #---- ToolTip ----
ToolTip.borderCornerRadius = $Popup.borderCornerRadius ToolTip.borderCornerRadius = $Popup.borderCornerRadius
[mac]ToolTip.roundedBorderWidth = $Popup.roundedBorderWidth
#---- Tree ---- #---- Tree ----

View File

@@ -295,8 +295,3 @@ ToggleButton.disabledBackground = $Button.disabledBackground
ToggleButton.selectedBackground = lighten($ToggleButton.background,20%,derived) ToggleButton.selectedBackground = lighten($ToggleButton.background,20%,derived)
ToggleButton.toolbar.selectedBackground = #fff3 ToggleButton.toolbar.selectedBackground = #fff3
#---- ToolBar ----
ToolBar.hoverButtonGroupArc = 14

View File

@@ -291,8 +291,3 @@ TextPane.selectionForeground = @textSelectionForeground
#---- ToggleButton ---- #---- ToggleButton ----
ToggleButton.disabledBackground = $Button.disabledBackground ToggleButton.disabledBackground = $Button.disabledBackground
#---- ToolBar ----
ToolBar.hoverButtonGroupArc = 14

View File

@@ -84,12 +84,10 @@ public class TestUIDefaultsLoader
void parseBorders() { void parseBorders() {
Insets insets = new Insets( 1,2,3,4 ); Insets insets = new Insets( 1,2,3,4 );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" ); assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4,,," );
assertBorderEquals( new FlatLineBorder( insets, Color.red ), "1,2,3,4,#f00" ); assertBorderEquals( new FlatLineBorder( insets, Color.red ), "1,2,3,4,#f00" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, -1 ), "1,2,3,4,#f00,2.5" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 0 ), "1,2,3,4,#f00,2.5" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 6 ), "1,2,3,4,#f00,2.5,6" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 6 ), "1,2,3,4,#f00,2.5,6" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" );
assertBorderEquals( new FlatLineBorder( insets, null, 1, 6 ), "1,2,3,4,,,6" );
} }
private void assertBorderEquals( Border expected, String actualStyle ) { private void assertBorderEquals( Border expected, String actualStyle ) {

View File

@@ -253,8 +253,7 @@ public class TestFlatStyleableInfo
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
Map<String, Class<?>> expected = expectedMap( Map<String, Class<?>> expected = expectedMap(
"disabledForeground", Color.class, "disabledForeground", Color.class
"arc", int.class
); );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );
@@ -602,7 +601,7 @@ public class TestFlatStyleableInfo
); );
// border // border
flatScrollPaneBorder( expected ); flatBorder( expected );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );
} }
@@ -690,9 +689,6 @@ public class TestFlatStyleableInfo
Map<String, Class<?>> expected = expectedMap( Map<String, Class<?>> expected = expectedMap(
"arrowType", String.class, "arrowType", String.class,
"draggingColor", Color.class,
"hoverColor", Color.class,
"pressedColor", Color.class,
"oneTouchArrowColor", Color.class, "oneTouchArrowColor", Color.class,
"oneTouchHoverArrowColor", Color.class, "oneTouchHoverArrowColor", Color.class,
"oneTouchPressedArrowColor", Color.class, "oneTouchPressedArrowColor", Color.class,
@@ -756,7 +752,6 @@ public class TestFlatStyleableInfo
"tabAreaAlignment", String.class, "tabAreaAlignment", String.class,
"tabAlignment", String.class, "tabAlignment", String.class,
"tabWidthMode", String.class, "tabWidthMode", String.class,
"tabRotation", String.class,
"arrowType", String.class, "arrowType", String.class,
"buttonInsets", Insets.class, "buttonInsets", Insets.class,
@@ -800,8 +795,6 @@ public class TestFlatStyleableInfo
"selectionForeground", Color.class, "selectionForeground", Color.class,
"selectionInactiveBackground", Color.class, "selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class, "selectionInactiveForeground", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
// FlatTableCellBorder // FlatTableCellBorder
"cellMargins", Insets.class, "cellMargins", Insets.class,
@@ -932,10 +925,7 @@ public class TestFlatStyleableInfo
"hoverButtonGroupBackground", Color.class, "hoverButtonGroupBackground", Color.class,
"borderMargins", Insets.class, "borderMargins", Insets.class,
"gripColor", Color.class, "gripColor", Color.class
"separatorWidth", Integer.class,
"separatorColor", Color.class
); );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );
@@ -996,20 +986,12 @@ public class TestFlatStyleableInfo
"disabledBorderColor", Color.class, "disabledBorderColor", Color.class,
"focusedBorderColor", Color.class, "focusedBorderColor", Color.class,
"hoverBorderColor", Color.class, "hoverBorderColor", Color.class,
"pressedBorderColor", Color.class,
"selectedBorderColor", Color.class,
"disabledSelectedBorderColor", Color.class,
"focusedSelectedBorderColor", Color.class,
"hoverSelectedBorderColor", Color.class,
"pressedSelectedBorderColor", Color.class,
"default.borderWidth", float.class, "default.borderWidth", float.class,
"default.borderColor", Color.class, "default.borderColor", Color.class,
"default.focusedBorderColor", Color.class, "default.focusedBorderColor", Color.class,
"default.focusColor", Color.class, "default.focusColor", Color.class,
"default.hoverBorderColor", Color.class, "default.hoverBorderColor", Color.class,
"default.pressedBorderColor", Color.class,
"toolbar.focusWidth", float.class, "toolbar.focusWidth", float.class,
"toolbar.focusColor", Color.class, "toolbar.focusColor", Color.class,
@@ -1023,23 +1005,17 @@ public class TestFlatStyleableInfo
expectedMap( expected, expectedMap( expected,
"arc", int.class, "arc", int.class,
"roundRect", Boolean.class "roundRect", Boolean.class
); );
} }
private void flatScrollPaneBorder( Map<String, Class<?>> expected ) {
flatBorder( expected );
expectedMap( expected,
"arc", int.class
);
}
private void flatTextBorder( Map<String, Class<?>> expected ) { private void flatTextBorder( Map<String, Class<?>> expected ) {
flatBorder( expected ); flatBorder( expected );
expectedMap( expected, expectedMap( expected,
"arc", int.class, "arc", int.class,
"roundRect", Boolean.class "roundRect", Boolean.class
); );
} }

View File

@@ -305,9 +305,6 @@ public class TestFlatStyleableValue
testColor( c, ui, "buttonPressedArrowColor", 0x123456 ); testColor( c, ui, "buttonPressedArrowColor", 0x123456 );
testColor( c, ui, "popupBackground", 0x123456 ); testColor( c, ui, "popupBackground", 0x123456 );
testInsets( c, ui, "popupInsets", 1,2,3,4 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// border // border
flatRoundBorder( c, ui ); flatRoundBorder( c, ui );
@@ -358,7 +355,6 @@ public class TestFlatStyleableValue
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
testColor( c, ui, "disabledForeground", 0x123456 ); testColor( c, ui, "disabledForeground", 0x123456 );
testInteger( c, ui, "arc", 123 );
} }
@Test @Test
@@ -370,8 +366,6 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionForeground", 0x123456 );
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// FlatListCellBorder // FlatListCellBorder
testInsets( c, ui, "cellMargins", 1,2,3,4 ); testInsets( c, ui, "cellMargins", 1,2,3,4 );
@@ -631,7 +625,7 @@ public class TestFlatStyleableValue
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border // border
flatScrollPaneBorder( c, ui ); flatBorder( c, ui );
testBoolean( c, ui, "showButtons", true ); testBoolean( c, ui, "showButtons", true );
} }
@@ -705,9 +699,6 @@ public class TestFlatStyleableValue
FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI(); FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI();
testString( c, ui, "arrowType", "chevron" ); testString( c, ui, "arrowType", "chevron" );
testColor( c, ui, "draggingColor", 0x123456 );
testColor( c, ui, "hoverColor", 0x123456 );
testColor( c, ui, "pressedColor", 0x123456 );
testColor( c, ui, "oneTouchArrowColor", 0x123456 ); testColor( c, ui, "oneTouchArrowColor", 0x123456 );
testColor( c, ui, "oneTouchHoverArrowColor", 0x123456 ); testColor( c, ui, "oneTouchHoverArrowColor", 0x123456 );
testColor( c, ui, "oneTouchPressedArrowColor", 0x123456 ); testColor( c, ui, "oneTouchPressedArrowColor", 0x123456 );
@@ -767,7 +758,6 @@ public class TestFlatStyleableValue
testString( c, ui, "tabAreaAlignment", "leading" ); testString( c, ui, "tabAreaAlignment", "leading" );
testString( c, ui, "tabAlignment", "center" ); testString( c, ui, "tabAlignment", "center" );
testString( c, ui, "tabWidthMode", "preferred" ); testString( c, ui, "tabWidthMode", "preferred" );
testString( c, ui, "tabRotation", "none" );
testString( c, ui, "arrowType", "chevron" ); testString( c, ui, "arrowType", "chevron" );
testInsets( c, ui, "buttonInsets", 1,2,3,4 ); testInsets( c, ui, "buttonInsets", 1,2,3,4 );
@@ -807,8 +797,6 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionForeground", 0x123456 );
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x901324 ); testColor( c, ui, "selectionInactiveForeground", 0x901324 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// FlatTableCellBorder // FlatTableCellBorder
testInsets( c, ui, "cellMargins", 1,2,3,4 ); testInsets( c, ui, "cellMargins", 1,2,3,4 );
@@ -914,9 +902,6 @@ public class TestFlatStyleableValue
testInsets( c, ui, "borderMargins", 1,2,3,4 ); testInsets( c, ui, "borderMargins", 1,2,3,4 );
testColor( c, ui, "gripColor", 0x123456 ); testColor( c, ui, "gripColor", 0x123456 );
testInteger( c, ui, "separatorWidth", 123 );
testColor( c, ui, "separatorColor", 0x123456 );
} }
@Test @Test
@@ -938,8 +923,6 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 );
testColor( c, ui, "selectionBorderColor", 0x123456 ); testColor( c, ui, "selectionBorderColor", 0x123456 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
testBoolean( c, ui, "wideSelection", true ); testBoolean( c, ui, "wideSelection", true );
testBoolean( c, ui, "showCellFocusIndicator", true ); testBoolean( c, ui, "showCellFocusIndicator", true );
@@ -965,20 +948,12 @@ public class TestFlatStyleableValue
testColor( c, ui, "disabledBorderColor", 0x123456 ); testColor( c, ui, "disabledBorderColor", 0x123456 );
testColor( c, ui, "focusedBorderColor", 0x123456 ); testColor( c, ui, "focusedBorderColor", 0x123456 );
testColor( c, ui, "hoverBorderColor", 0x123456 ); testColor( c, ui, "hoverBorderColor", 0x123456 );
testColor( c, ui, "pressedBorderColor", 0x123456 );
testColor( c, ui, "selectedBorderColor", 0x123456 );
testColor( c, ui, "disabledSelectedBorderColor", 0x123456 );
testColor( c, ui, "focusedSelectedBorderColor", 0x123456 );
testColor( c, ui, "hoverSelectedBorderColor", 0x123456 );
testColor( c, ui, "pressedSelectedBorderColor", 0x123456 );
testFloat( c, ui, "default.borderWidth", 1.23f ); testFloat( c, ui, "default.borderWidth", 1.23f );
testColor( c, ui, "default.borderColor", 0x123456 ); testColor( c, ui, "default.borderColor", 0x123456 );
testColor( c, ui, "default.focusedBorderColor", 0x123456 ); testColor( c, ui, "default.focusedBorderColor", 0x123456 );
testColor( c, ui, "default.focusColor", 0x123456 ); testColor( c, ui, "default.focusColor", 0x123456 );
testColor( c, ui, "default.hoverBorderColor", 0x123456 ); testColor( c, ui, "default.hoverBorderColor", 0x123456 );
testColor( c, ui, "default.pressedBorderColor", 0x123456 );
testFloat( c, ui, "toolbar.focusWidth", 1.23f ); testFloat( c, ui, "toolbar.focusWidth", 1.23f );
testColor( c, ui, "toolbar.focusColor", 0x123456 ); testColor( c, ui, "toolbar.focusColor", 0x123456 );
@@ -990,19 +965,15 @@ public class TestFlatStyleableValue
flatBorder( c, ui ); flatBorder( c, ui );
testInteger( c, ui, "arc", 123 ); testInteger( c, ui, "arc", 123 );
testBoolean( c, ui, "roundRect", true ); testBoolean( c, ui, "roundRect", true );
} }
private void flatScrollPaneBorder( JComponent c, StyleableUI ui ) {
flatBorder( c, ui );
testInteger( c, ui, "arc", 123 );
}
private void flatTextBorder( JComponent c, StyleableUI ui ) { private void flatTextBorder( JComponent c, StyleableUI ui ) {
flatBorder( c, ui ); flatBorder( c, ui );
testInteger( c, ui, "arc", 123 ); testInteger( c, ui, "arc", 123 );
testBoolean( c, ui, "roundRect", true ); testBoolean( c, ui, "roundRect", true );
} }
@@ -1043,20 +1014,12 @@ public class TestFlatStyleableValue
testValue( border, "disabledBorderColor", Color.WHITE ); testValue( border, "disabledBorderColor", Color.WHITE );
testValue( border, "focusedBorderColor", Color.WHITE ); testValue( border, "focusedBorderColor", Color.WHITE );
testValue( border, "hoverBorderColor", Color.WHITE ); testValue( border, "hoverBorderColor", Color.WHITE );
testValue( border, "pressedBorderColor", Color.WHITE );
testValue( border, "selectedBorderColor", Color.WHITE );
testValue( border, "disabledSelectedBorderColor", Color.WHITE );
testValue( border, "focusedSelectedBorderColor", Color.WHITE );
testValue( border, "hoverSelectedBorderColor", Color.WHITE );
testValue( border, "pressedSelectedBorderColor", Color.WHITE );
testValue( border, "default.borderWidth", 2f ); testValue( border, "default.borderWidth", 2f );
testValue( border, "default.borderColor", Color.WHITE ); testValue( border, "default.borderColor", Color.WHITE );
testValue( border, "default.focusedBorderColor", Color.WHITE ); testValue( border, "default.focusedBorderColor", Color.WHITE );
testValue( border, "default.focusColor", Color.WHITE ); testValue( border, "default.focusColor", Color.WHITE );
testValue( border, "default.hoverBorderColor", Color.WHITE ); testValue( border, "default.hoverBorderColor", Color.WHITE );
testValue( border, "default.pressedBorderColor", Color.WHITE );
testValue( border, "toolbar.focusWidth", 1.5f ); testValue( border, "toolbar.focusWidth", 1.5f );
testValue( border, "toolbar.focusColor", Color.WHITE ); testValue( border, "toolbar.focusColor", Color.WHITE );
@@ -1071,17 +1034,6 @@ public class TestFlatStyleableValue
// FlatRoundBorder extends FlatBorder // FlatRoundBorder extends FlatBorder
flatBorder( border ); flatBorder( border );
testValue( border, "arc", 6 );
testValue( border, "roundRect", true );
}
@Test
void flatScrollPaneBorder() {
FlatScrollPaneBorder border = new FlatScrollPaneBorder();
// FlatScrollPaneBorder extends FlatBorder
flatBorder( border );
testValue( border, "arc", 6 ); testValue( border, "arc", 6 );
} }
@@ -1093,7 +1045,6 @@ public class TestFlatStyleableValue
flatBorder( border ); flatBorder( border );
testValue( border, "arc", 6 ); testValue( border, "arc", 6 );
testValue( border, "roundRect", true );
} }
@Test @Test

View File

@@ -412,7 +412,6 @@ public class TestFlatStyling
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
ui.applyStyle( c, "disabledForeground: #fff" ); ui.applyStyle( c, "disabledForeground: #fff" );
ui.applyStyle( c, "arc: 8" );
// JComponent properties // JComponent properties
ui.applyStyle( c, "background: #fff" ); ui.applyStyle( c, "background: #fff" );
@@ -761,7 +760,7 @@ public class TestFlatStyling
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border // border
flatScrollPaneBorder( style -> ui.applyStyle( style ) ); flatBorder( style -> ui.applyStyle( style ) );
ui.applyStyle( "showButtons: true" ); ui.applyStyle( "showButtons: true" );
@@ -871,9 +870,6 @@ public class TestFlatStyling
FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI(); FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI();
ui.applyStyle( "arrowType: chevron" ); ui.applyStyle( "arrowType: chevron" );
ui.applyStyle( "draggingColor: #fff" );
ui.applyStyle( "hoverColor: #fff" );
ui.applyStyle( "pressedColor: #fff" );
ui.applyStyle( "oneTouchArrowColor: #fff" ); ui.applyStyle( "oneTouchArrowColor: #fff" );
ui.applyStyle( "oneTouchHoverArrowColor: #fff" ); ui.applyStyle( "oneTouchHoverArrowColor: #fff" );
ui.applyStyle( "oneTouchPressedArrowColor: #fff" ); ui.applyStyle( "oneTouchPressedArrowColor: #fff" );
@@ -941,7 +937,6 @@ public class TestFlatStyling
ui.applyStyle( "tabAreaAlignment: leading" ); ui.applyStyle( "tabAreaAlignment: leading" );
ui.applyStyle( "tabAlignment: center" ); ui.applyStyle( "tabAlignment: center" );
ui.applyStyle( "tabWidthMode: preferred" ); ui.applyStyle( "tabWidthMode: preferred" );
ui.applyStyle( "tabRotation: none" );
ui.applyStyle( "arrowType: chevron" ); ui.applyStyle( "arrowType: chevron" );
ui.applyStyle( "buttonInsets: 1,2,3,4" ); ui.applyStyle( "buttonInsets: 1,2,3,4" );
@@ -987,8 +982,6 @@ public class TestFlatStyling
ui.applyStyle( "selectionForeground: #fff" ); ui.applyStyle( "selectionForeground: #fff" );
ui.applyStyle( "selectionInactiveBackground: #fff" ); ui.applyStyle( "selectionInactiveBackground: #fff" );
ui.applyStyle( "selectionInactiveForeground: #fff" ); ui.applyStyle( "selectionInactiveForeground: #fff" );
ui.applyStyle( "selectionInsets: 1,2,3,4" );
ui.applyStyle( "selectionArc: 8" );
// FlatTableCellBorder // FlatTableCellBorder
ui.applyStyle( "cellMargins: 1,2,3,4" ); ui.applyStyle( "cellMargins: 1,2,3,4" );
@@ -1153,9 +1146,6 @@ public class TestFlatStyling
ui.applyStyle( "borderMargins: 1,2,3,4" ); ui.applyStyle( "borderMargins: 1,2,3,4" );
ui.applyStyle( "gripColor: #fff" ); ui.applyStyle( "gripColor: #fff" );
ui.applyStyle( "separatorWidth: 6" );
ui.applyStyle( "separatorColor: #fff" );
// JComponent properties // JComponent properties
ui.applyStyle( "background: #fff" ); ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" ); ui.applyStyle( "foreground: #fff" );
@@ -1244,19 +1234,15 @@ public class TestFlatStyling
flatBorder( applyStyle ); flatBorder( applyStyle );
applyStyle.accept( "arc: 6" ); applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" ); applyStyle.accept( "roundRect: true" );
} }
private void flatScrollPaneBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
}
private void flatTextBorder( Consumer<String> applyStyle ) { private void flatTextBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle ); flatBorder( applyStyle );
applyStyle.accept( "arc: 6" ); applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" ); applyStyle.accept( "roundRect: true" );
} }
@@ -1297,20 +1283,12 @@ public class TestFlatStyling
border.applyStyleProperty( "disabledBorderColor", Color.WHITE ); border.applyStyleProperty( "disabledBorderColor", Color.WHITE );
border.applyStyleProperty( "focusedBorderColor", Color.WHITE ); border.applyStyleProperty( "focusedBorderColor", Color.WHITE );
border.applyStyleProperty( "hoverBorderColor", Color.WHITE ); border.applyStyleProperty( "hoverBorderColor", Color.WHITE );
border.applyStyleProperty( "pressedBorderColor", Color.WHITE );
border.applyStyleProperty( "selectedBorderColor", Color.WHITE );
border.applyStyleProperty( "disabledSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "focusedSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "hoverSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "pressedSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "default.borderWidth", 2 ); border.applyStyleProperty( "default.borderWidth", 2 );
border.applyStyleProperty( "default.borderColor", Color.WHITE ); border.applyStyleProperty( "default.borderColor", Color.WHITE );
border.applyStyleProperty( "default.focusedBorderColor", Color.WHITE ); border.applyStyleProperty( "default.focusedBorderColor", Color.WHITE );
border.applyStyleProperty( "default.focusColor", Color.WHITE ); border.applyStyleProperty( "default.focusColor", Color.WHITE );
border.applyStyleProperty( "default.hoverBorderColor", Color.WHITE ); border.applyStyleProperty( "default.hoverBorderColor", Color.WHITE );
border.applyStyleProperty( "default.pressedBorderColor", Color.WHITE );
border.applyStyleProperty( "toolbar.focusWidth", 1.5f ); border.applyStyleProperty( "toolbar.focusWidth", 1.5f );
border.applyStyleProperty( "toolbar.focusColor", Color.WHITE ); border.applyStyleProperty( "toolbar.focusColor", Color.WHITE );

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