mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 06:27:13 -06:00
Compare commits
39 Commits
3.2
...
smooth-scr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a04cca2c | ||
|
|
b32b8db97a | ||
|
|
c529dcb747 | ||
|
|
04658c2ef0 | ||
|
|
5cdef5409b | ||
|
|
6dfc204e40 | ||
|
|
542e7d5f60 | ||
|
|
3628a03c9d | ||
|
|
6ce2198cd6 | ||
|
|
e2e3fd31e9 | ||
|
|
cf70cfb50c | ||
|
|
29f6c5fae9 | ||
|
|
419a689ca4 | ||
|
|
865a56875f | ||
|
|
3573188025 | ||
|
|
1f2622819a | ||
|
|
305e9e602e | ||
|
|
1ae31588c4 | ||
|
|
d64a8e93e1 | ||
|
|
e603bd81a1 | ||
|
|
522ebb6fa3 | ||
|
|
7a582c2d1f | ||
|
|
762fe89867 | ||
|
|
1ebfe00f3c | ||
|
|
fdabca99b2 | ||
|
|
736305849a | ||
|
|
889b5ea56a | ||
|
|
82514ccbfc | ||
|
|
b67b701d1e | ||
|
|
7f226a2742 | ||
|
|
c953ff84d0 | ||
|
|
96cd207df3 | ||
|
|
7a75f62a6a | ||
|
|
61e5fe58c2 | ||
|
|
1a3baba702 | ||
|
|
58dc14bb46 | ||
|
|
a5b7e04943 | ||
|
|
22f2aa5475 | ||
|
|
d4e9cb12be |
277
README.md
277
README.md
@@ -6,7 +6,7 @@ Swing desktop applications.
|
||||
|
||||
It looks almost flat (no shadows or gradients), clean, simple and elegant.
|
||||
FlatLaf comes with **Light**, **Dark**, **IntelliJ** and **Darcula** themes,
|
||||
scales on **HiDPI** displays and runs on Java 8 or newer.
|
||||
scales on **HiDPI** displays and runs on Java 8 or newer (LTS and latest).
|
||||
|
||||
The look is heavily inspired by **Darcula** and **IntelliJ** themes from
|
||||
IntelliJ IDEA 2019.2+ and uses almost the same colors and icons.
|
||||
@@ -142,6 +142,7 @@ 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 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/)
|
||||
|
||||
@@ -149,125 +150,193 @@ Buzz
|
||||
Applications using FlatLaf
|
||||
--------------------------
|
||||
|
||||
- 
|
||||
[Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client optimized for businesses and organizations.
|
||||
- 
|
||||
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) Kafka GUI client
|
||||
- 
|
||||
### Featured
|
||||
|
||||
-  [JFormDesigner](https://www.formdev.com/)
|
||||
(**commercial**) - Java/Swing GUI Designer (from the FlatLaf creators)
|
||||
- 
|
||||
[JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html)
|
||||
12 (**commercial**) - the award-winning all-in-one Java profiler
|
||||
-  [JFormDesigner](https://www.formdev.com/) 8
|
||||
(**commercial**) - Java/Swing GUI Designer
|
||||
-  [Jeyla Studio](https://www.jeylastudio.com/) - Salon
|
||||
Software
|
||||
-  [Fanurio](https://www.fanuriotimetracking.com/) 3.3.2
|
||||
(**commercial**) - time tracking and billing for freelancers and teams
|
||||
-  [Antares](https://www.antarescircuit.io/) - a free,
|
||||
powerful platform for designing, simulating and explaining digital circuits
|
||||
- 
|
||||
[Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution)
|
||||
3.6 - Digital logic design tool and simulator
|
||||
-  [Cinecred](https://loadingbyte.com/cinecred/) - create
|
||||
beautiful film credit sequences
|
||||
-  [tinyMediaManager](https://www.tinymediamanager.org/)
|
||||
v4 (**commercial**) - a media management tool
|
||||
-  [Weasis](https://nroduit.github.io/) - medical DICOM
|
||||
viewer used in healthcare by hospitals, health networks, etc
|
||||
- 
|
||||
[Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software)
|
||||
7.3.0 - for plotters, especially the wall-hanging polargraph
|
||||
-  [Ultorg](https://www.ultorg.com/) (**commercial**) - a
|
||||
visual query system for relational databases
|
||||
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
|
||||
OSHI, to view information about the system and hardware
|
||||
- [Jailer](https://github.com/Wisser/Jailer) 11.2 - database subsetting and
|
||||
relational data browsing tool
|
||||
-  [Apache NetBeans](https://netbeans.apache.org/) 11.3 -
|
||||
IDE for Java, PHP, HTML and much more
|
||||
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
|
||||
- [KeyStore Explorer](https://keystore-explorer.org/) 5.4.3
|
||||
- 
|
||||
(**commercial**) - the award-winning all-in-one Java profiler
|
||||
- 
|
||||
[install4j](https://www.ej-technologies.com/products/install4j/overview.html)
|
||||
9.0 (**commercial**) - the powerful multi-platform Java installer builder
|
||||
-  [DbVisualizer](https://www.dbvis.com/) 12.0
|
||||
(**commercial**) - the powerful multi-platform Java installer builder
|
||||
-  [DbVisualizer](https://www.dbvis.com/)
|
||||
(**commercial**) - the universal database tool for developers, analysts and
|
||||
DBAs
|
||||
-  [MagicPlot](https://magicplot.com/) 3.0
|
||||
(**commercial**) - Software for nonlinear fitting, plotting and data analysis
|
||||
- 
|
||||
[Thermo-Calc](https://thermocalc.com/products/thermo-calc/) 2021a
|
||||
(**commercial**) - Thermodynamics and Properties Software
|
||||
-  [ZAP](https://www.zaproxy.org/) 2.10 - the world's
|
||||
most widely used web app scanner
|
||||
-  [Apache NetBeans](https://netbeans.apache.org/) - IDE
|
||||
for Java, PHP, HTML and much more
|
||||
- 
|
||||
[Thermo-Calc](https://thermocalc.com/products/thermo-calc/) (**commercial**) -
|
||||
Thermodynamics and Properties Software
|
||||
|
||||
### Data
|
||||
|
||||
-  [Ultorg](https://www.ultorg.com/) (**commercial**) - a
|
||||
visual query system for relational databases
|
||||
- [Jailer](https://github.com/Wisser/Jailer) - database subsetting and
|
||||
relational data browsing tool
|
||||
-  [MagicPlot](https://magicplot.com/) (**commercial**) -
|
||||
Software for nonlinear fitting, plotting and data analysis
|
||||
-  [Constellation](https://www.constellation-app.com/) -
|
||||
Data Visualization and Analytics (based on NetBeans platform)
|
||||
- 
|
||||
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
|
||||
client
|
||||
|
||||
### Security
|
||||
|
||||
-  [ZAP](https://www.zaproxy.org/) - the world's most
|
||||
widely used web app scanner
|
||||
- 
|
||||
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
|
||||
2020.11.2 (**commercial**) - the leading software for web security testing
|
||||
(**commercial**) - the leading software for web security testing
|
||||
- 
|
||||
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
|
||||
reverse engineering (SRE) framework
|
||||
-  [jadx](https://github.com/skylot/jadx) - Dex to Java
|
||||
decompiler
|
||||
- [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more
|
||||
FlatLaf themes to Burp Suite
|
||||
- [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks
|
||||
your website
|
||||
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
|
||||
encryption
|
||||
|
||||
### Software Development
|
||||
|
||||
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib)
|
||||
- [KeyStore Explorer](https://keystore-explorer.org/)
|
||||
- 
|
||||
[muCommander](https://github.com/mucommander/mucommander) - lightweight
|
||||
cross-platform file manager
|
||||
-  [Guiffy](https://www.guiffy.com/) (**commercial**) -
|
||||
advanced cross-platform Diff/Merge
|
||||
-  [HashGarten](https://github.com/jonelo/HashGarten) -
|
||||
cross-platform Swing GUI for Jacksum
|
||||
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
|
||||
IDE for Pseudo-Assembler
|
||||
- [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming
|
||||
language created to learn programming
|
||||
- [lsfusion platform](https://github.com/lsfusion/platform) - information
|
||||
systems development platform
|
||||
|
||||
### Electrical
|
||||
|
||||
- [Antares](https://www.antarescircuit.io/) - a free, powerful platform for
|
||||
designing, simulating and explaining digital circuits
|
||||
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
|
||||
Digital logic design tool and simulator
|
||||
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
|
||||
for plotters, especially the wall-hanging polargraph
|
||||
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
|
||||
builder for [GUIslice](https://github.com/ImpulseAdventure/GUIslice), a
|
||||
lightweight GUI framework for embedded displays
|
||||
- [ThunderFocus](https://github.com/marcocipriani01/ThunderFocus) -
|
||||
Arduino-based telescope focuser
|
||||
- [RemoteLight](https://github.com/Drumber/RemoteLight) - multifunctional LED
|
||||
control software
|
||||
|
||||
### Media
|
||||
|
||||
-  [jAlbum](https://jalbum.net/) (**commercial**) -
|
||||
creates photo album websites
|
||||
-  [MediathekView](https://mediathekview.de/) - search in
|
||||
media libraries of various German broadcasters
|
||||
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
|
||||
sequences
|
||||
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
|
||||
media management tool
|
||||
- [Weasis](https://nroduit.github.io/) - medical DICOM viewer used in healthcare
|
||||
by hospitals, health networks, etc
|
||||
- [Shutter Encoder](https://www.shutterencoder.com/)
|
||||
([source code](https://github.com/paulpacifico/shutter-encoder)) -
|
||||
professional video converter and compression tool
|
||||
- [Sound Analysis](https://github.com/tomasz-herman/SoundAnalysis) - analyze
|
||||
sound files in time or frequency domain
|
||||
- [Novel-Grabber](https://github.com/Flameish/Novel-Grabber) - download novels
|
||||
from any webnovel and lightnovel site
|
||||
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
|
||||
ease
|
||||
|
||||
### Modelling
|
||||
|
||||
-  [Astah](https://astah.net/) (**commercial**) - create
|
||||
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML
|
||||
diagrams and more
|
||||
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
|
||||
Application System
|
||||
|
||||
### Documents
|
||||
|
||||
-  [Big Faceless (BFO) PDF Viewer](https://bfo.com/)
|
||||
(**commercial**) - Swing PDF Viewer
|
||||
- [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create,
|
||||
review and edit PDF documents
|
||||
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**)
|
||||
|
||||
### Geo
|
||||
|
||||
-  [JOSM](https://josm.openstreetmap.de/) - an extensible
|
||||
editor for [OpenStreetMap](https://www.openstreetmap.org/) (requires FlatLaf
|
||||
JOSM plugin)
|
||||
-  [jAlbum](https://jalbum.net/) 21 (**commercial**) -
|
||||
creates photo album websites
|
||||
- [PDF Studio](https://www.qoppa.com/pdfstudio/) 2021 (**commercial**) - create,
|
||||
review and edit PDF documents
|
||||
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (**commercial**)
|
||||
- [Total Validator](https://www.totalvalidator.com/) 15 (**commercial**) -
|
||||
checks your website
|
||||
- [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
|
||||
- [MegaMek](https://github.com/MegaMek/megamek),
|
||||
[MegaMekLab](https://github.com/MegaMek/megameklab) and
|
||||
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5+ - a sci-fi tabletop
|
||||
BattleTech simulator suite handling battles, unit building, and campaigns
|
||||
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder)
|
||||
0.13.b024 - GUI builder for
|
||||
[GUIslice](https://github.com/ImpulseAdventure/GUIslice), a lightweight GUI
|
||||
framework for embedded displays
|
||||
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
|
||||
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy) - advanced
|
||||
gamepad mapping software
|
||||
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
|
||||
connections manager
|
||||
- [jEnTunnel](https://github.com/ggrandes/jentunnel) - manage SSH Tunnels made
|
||||
easy
|
||||
- [Mapton](https://mapton.org/)
|
||||
([source code](https://github.com/trixon/mapton)) - some kind of map
|
||||
application (based on NetBeans platform)
|
||||
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) - GIS and scientific
|
||||
computation environment for meteorological community
|
||||
|
||||
### Business / Legal
|
||||
|
||||
- 
|
||||
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
|
||||
-  [Jeyla Studio](https://www.jeylastudio.com/) -
|
||||
Salon Software
|
||||
- [Fanurio](https://www.fanuriotimetracking.com/) (**commercial**) - time
|
||||
tracking and billing for freelancers and teams
|
||||
- [Jes](https://www.jes-eur.de) - Die Java-EÜR
|
||||
- [mendelson AS2](https://sourceforge.net/projects/mec-as2/),
|
||||
[AS4](https://sourceforge.net/projects/mendelson-as4/) and
|
||||
[OFTP2](https://sourceforge.net/projects/mendelson-oftp2/) (open-source) and
|
||||
[mendelson AS2](https://mendelson-e-c.com/as2/),
|
||||
[AS4](https://mendelson-e-c.com/as4/) and
|
||||
[OFTP2](https://mendelson-e-c.com/oftp2) (**commercial**)
|
||||
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
|
||||
Application System
|
||||
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) 2.2 - GIS and scientific
|
||||
computation environment for meteorological community
|
||||
- [lsfusion platform](https://github.com/lsfusion/platform) 4 - information
|
||||
systems development platform
|
||||
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
|
||||
encryption
|
||||
- [Jes - Die Java-EÜR](https://www.jes-eur.de)
|
||||
- [Mapton](https://mapton.org/) 2.0
|
||||
([source code](https://github.com/trixon/mapton)) - some kind of map
|
||||
application (based on NetBeans platform)
|
||||
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
|
||||
IDE for Pseudo-Assembler
|
||||
- [Linotte](https://github.com/cpc6128/LangageLinotte) 3.1 - French programming
|
||||
language created to learn programming
|
||||
- [MEKA](https://github.com/Waikato/meka) 1.9.3 - multi-label classifiers and
|
||||
evaluation procedures using the Weka machine learning framework
|
||||
- [Shutter Encoder](https://www.shutterencoder.com/) 14.2
|
||||
([source code](https://github.com/paulpacifico/shutter-encoder)) -
|
||||
professional video converter and compression tool (screenshots show **old**
|
||||
look)
|
||||
- [Sound Analysis](https://github.com/tomasz-herman/SoundAnalysis) - analyze
|
||||
sound files in time or frequency domain
|
||||
- [RemoteLight](https://github.com/Drumber/RemoteLight) - multifunctional LED
|
||||
control software
|
||||
- [ThunderFocus](https://github.com/marcocipriani01/ThunderFocus) -
|
||||
Arduino-based telescope focuser
|
||||
- [Novel-Grabber](https://github.com/Flameish/Novel-Grabber) - download novels
|
||||
from any webnovel and lightnovel site
|
||||
- [lectureStudio](https://www.lecturestudio.org/) 4.3.1060 - digitize your
|
||||
lectures with ease
|
||||
|
||||
### Messaging
|
||||
|
||||
-  [Spark](https://github.com/igniterealtime/Spark) -
|
||||
cross-platform IM client optimized for businesses and organizations
|
||||
-  [Chatty](https://github.com/chatty/chatty) - Twitch
|
||||
Chat Client
|
||||
|
||||
### Gaming
|
||||
|
||||
-  
|
||||
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon
|
||||
-  [MapTool](https://github.com/RPTools/maptool) - virtual
|
||||
Tabletop for playing role-playing games
|
||||
- [MegaMek](https://github.com/MegaMek/megamek),
|
||||
[MegaMekLab](https://github.com/MegaMek/megameklab) and
|
||||
[MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech
|
||||
simulator suite handling battles, unit building, and campaigns
|
||||
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy) - advanced
|
||||
gamepad mapping software
|
||||
|
||||
### Utilities
|
||||
|
||||
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
|
||||
OSHI, to view information about the system and hardware
|
||||
- 
|
||||
[Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
|
||||
GUI for monitoring and managing various aspects of a Linux system
|
||||
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
|
||||
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
|
||||
connections manager
|
||||
- [jEnTunnel](https://github.com/ggrandes/jentunnel) - manage SSH Tunnels made
|
||||
easy
|
||||
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
|
||||
and fastboot commands easier to use
|
||||
- and more...
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- [MEKA](https://github.com/Waikato/meka) - multi-label classifiers and
|
||||
evaluation procedures using the Weka machine learning framework
|
||||
|
||||
@@ -122,3 +122,11 @@ signing {
|
||||
tasks.withType<Sign>().configureEach {
|
||||
onlyIf { rootProject.hasProperty( "release" ) }
|
||||
}
|
||||
|
||||
// check whether parallel build is enabled
|
||||
tasks.withType<PublishToMavenRepository>().configureEach {
|
||||
doFirst {
|
||||
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'." )
|
||||
}
|
||||
}
|
||||
|
||||
2
flatlaf-core/.settings/org.eclipse.core.resources.prefs
Normal file
2
flatlaf-core/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -1,5 +1,5 @@
|
||||
#Signature file v4.1
|
||||
#Version 3.1
|
||||
#Version 3.2
|
||||
|
||||
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
|
||||
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
|
||||
@@ -96,6 +96,8 @@ fld public final static java.lang.String TITLE_BAR_SHOW_TITLE = "JRootPane.title
|
||||
fld public final static java.lang.String TREE_PAINT_SELECTION = "JTree.paintSelection"
|
||||
fld public final static java.lang.String TREE_WIDE_SELECTION = "JTree.wideSelection"
|
||||
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "JRootPane.useWindowDecorations"
|
||||
fld public final static java.lang.String WINDOW_STYLE = "Window.style"
|
||||
fld public final static java.lang.String WINDOW_STYLE_SMALL = "small"
|
||||
meth public static <%0 extends java.lang.Object> {%%0} clientProperty(javax.swing.JComponent,java.lang.String,{%%0},java.lang.Class<{%%0}>)
|
||||
meth public static boolean clientPropertyBoolean(javax.swing.JComponent,java.lang.String,boolean)
|
||||
meth public static boolean clientPropertyEquals(javax.swing.JComponent,java.lang.String,java.lang.Object)
|
||||
@@ -280,6 +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 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_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
|
||||
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_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
|
||||
@@ -298,7 +301,7 @@ meth public static boolean setup(java.io.InputStream)
|
||||
meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme)
|
||||
meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException
|
||||
supr java.lang.Object
|
||||
hfds checkboxDuplicateColors,checkboxKeyMapping,colors,icons,isMaterialUILite,namedColors,ui,uiKeyCopying,uiKeyInverseMapping,uiKeyMapping
|
||||
hfds checkboxDuplicateColors,checkboxKeyMapping,colors,icons,isMaterialUILite,namedColors,ui,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping
|
||||
|
||||
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
|
||||
outer com.formdev.flatlaf.IntelliJTheme
|
||||
|
||||
@@ -132,6 +132,14 @@ public interface FlatSystemProperties
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
|
||||
@@ -31,6 +31,7 @@ import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.basic.BasicEditorPaneUI;
|
||||
import javax.swing.text.Caret;
|
||||
import javax.swing.text.DefaultEditorKit;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
@@ -145,6 +146,21 @@ public class FlatEditorPaneUI
|
||||
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
|
||||
protected Caret createCaret() {
|
||||
return new FlatCaret( null, false );
|
||||
@@ -159,6 +175,11 @@ public class FlatEditorPaneUI
|
||||
|
||||
super.propertyChange( e );
|
||||
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 ) {
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JComponent;
|
||||
@@ -149,7 +148,7 @@ public class FlatMenuBarUI
|
||||
map = new ActionMapUIResource();
|
||||
SwingUtilities.replaceUIActionMap( menuBar, map );
|
||||
}
|
||||
map.put( "takeFocus", new TakeFocus() );
|
||||
map.put( "takeFocus", new TakeFocus( "takeFocus" ) );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
@@ -373,8 +372,12 @@ public class FlatMenuBarUI
|
||||
* On other platforms, the popup of the first menu is shown.
|
||||
*/
|
||||
private static class TakeFocus
|
||||
extends AbstractAction
|
||||
extends FlatUIAction
|
||||
{
|
||||
public TakeFocus( String name ) {
|
||||
super( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e ) {
|
||||
JMenuBar menuBar = (JMenuBar) e.getSource();
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
@@ -39,10 +40,13 @@ import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.basic.BasicScrollBarUI;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
import com.formdev.flatlaf.util.CubicBezierEasing;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
@@ -203,6 +207,16 @@ public class FlatScrollBarUI
|
||||
oldStyleValues = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackListener createTrackListener() {
|
||||
return new FlatTrackListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScrollListener createScrollListener() {
|
||||
return new FlatScrollListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PropertyChangeListener createPropertyChangeListener() {
|
||||
PropertyChangeListener superListener = super.createPropertyChangeListener();
|
||||
@@ -431,6 +445,197 @@ public class FlatScrollBarUI
|
||||
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 ---------------------------------------
|
||||
|
||||
// using static field to disabling hover for other scroll bars
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ContainerEvent;
|
||||
import java.awt.event.ContainerListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
@@ -31,6 +33,8 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
@@ -48,8 +52,10 @@ import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.basic.BasicScrollPaneUI;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
|
||||
/**
|
||||
@@ -135,18 +141,33 @@ public class FlatScrollPaneUI
|
||||
MouseWheelListener superListener = super.createMouseWheelListener();
|
||||
return e -> {
|
||||
if( isSmoothScrollingEnabled() &&
|
||||
scrollpane.isWheelScrollingEnabled() &&
|
||||
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
|
||||
e.getPreciseWheelRotation() != 0 &&
|
||||
e.getPreciseWheelRotation() != e.getWheelRotation() )
|
||||
scrollpane.isWheelScrollingEnabled() )
|
||||
{
|
||||
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
|
||||
superListener.mouseWheelMoved( e );
|
||||
};
|
||||
}
|
||||
|
||||
protected boolean isSmoothScrollingEnabled() {
|
||||
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
|
||||
return false;
|
||||
|
||||
Object smoothScrolling = scrollpane.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
|
||||
if( smoothScrolling instanceof Boolean )
|
||||
return (Boolean) smoothScrolling;
|
||||
@@ -157,19 +178,40 @@ public class FlatScrollPaneUI
|
||||
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
|
||||
JViewport viewport = scrollpane.getViewport();
|
||||
if( viewport == null )
|
||||
return;
|
||||
|
||||
// find scrollbar to scroll
|
||||
JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
|
||||
if( scrollbar == null || !scrollbar.isVisible() || e.isShiftDown() ) {
|
||||
scrollbar = scrollpane.getHorizontalScrollBar();
|
||||
if( scrollbar == null || !scrollbar.isVisible() )
|
||||
return;
|
||||
}
|
||||
JScrollBar scrollbar = findScrollBarToScroll( e );
|
||||
if( scrollbar == null )
|
||||
return;
|
||||
|
||||
// consume event
|
||||
e.consume();
|
||||
@@ -262,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
|
||||
protected PropertyChangeListener createPropertyChangeListener() {
|
||||
PropertyChangeListener superListener = super.createPropertyChangeListener();
|
||||
@@ -428,6 +480,119 @@ public class FlatScrollPaneUI
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void syncScrollPaneWithViewport() {
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given runnable, if smooth scrolling is enabled, with disabled
|
||||
* viewport blitting mode and with scroll bar value set to "target" value.
|
||||
* This is necessary when calculating new view position during animation.
|
||||
* 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 void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, boolean useTargetValue, Runnable r ) {
|
||||
if( inRunAndSyncValueAnimated[i] || sb == null || !(sb.getUI() instanceof FlatScrollBarUI) ) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
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 ------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -459,4 +624,34 @@ public class FlatScrollPaneUI
|
||||
scrollpane.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//---- class SmoothScrollingDelegateAction --------------------------------
|
||||
|
||||
/**
|
||||
* Used to run component actions with disabled blitting mode and
|
||||
* with scroll bar target values.
|
||||
*/
|
||||
private static class SmoothScrollingDelegateAction
|
||||
extends FlatUIAction
|
||||
{
|
||||
private final boolean blittingOnly;
|
||||
|
||||
private SmoothScrollingDelegateAction( Action delegate, boolean blittingOnly ) {
|
||||
super( delegate );
|
||||
this.blittingOnly = blittingOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e ) {
|
||||
Object source = e.getSource();
|
||||
JScrollPane scrollPane = (source instanceof Component)
|
||||
? (JScrollPane) SwingUtilities.getAncestorOfClass( JScrollPane.class, (Component) source )
|
||||
: null;
|
||||
if( scrollPane != null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
|
||||
((FlatScrollPaneUI)scrollPane.getUI()).runWithScrollBarsTargetValues( blittingOnly,
|
||||
() -> delegate.actionPerformed( e ) );
|
||||
} else
|
||||
delegate.actionPerformed( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3520,10 +3520,8 @@ public class FlatTabbedPaneUI
|
||||
//---- class RunWithOriginalLayoutManagerDelegateAction -------------------
|
||||
|
||||
private static class RunWithOriginalLayoutManagerDelegateAction
|
||||
implements Action
|
||||
extends FlatUIAction
|
||||
{
|
||||
private final Action delegate;
|
||||
|
||||
static void install( ActionMap map, String key ) {
|
||||
Action oldAction = map.get( key );
|
||||
if( oldAction == null || oldAction instanceof RunWithOriginalLayoutManagerDelegateAction )
|
||||
@@ -3533,24 +3531,9 @@ public class FlatTabbedPaneUI
|
||||
}
|
||||
|
||||
private RunWithOriginalLayoutManagerDelegateAction( Action delegate ) {
|
||||
this.delegate = delegate;
|
||||
super( delegate );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue( String key ) {
|
||||
return delegate.getValue( key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return delegate.isEnabled();
|
||||
}
|
||||
|
||||
@Override public void putValue( String key, Object value ) {}
|
||||
@Override public void setEnabled( boolean b ) {}
|
||||
@Override public void addPropertyChangeListener( PropertyChangeListener listener ) {}
|
||||
@Override public void removePropertyChangeListener( PropertyChangeListener listener ) {}
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e ) {
|
||||
JTabbedPane tabbedPane = (JTabbedPane) e.getSource();
|
||||
|
||||
@@ -141,6 +141,12 @@ public class FlatTextAreaUI
|
||||
focusListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installKeyboardActions() {
|
||||
super.installKeyboardActions();
|
||||
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Caret createCaret() {
|
||||
return new FlatCaret( null, false );
|
||||
|
||||
@@ -142,6 +142,12 @@ public class FlatTextPaneUI
|
||||
focusListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installKeyboardActions() {
|
||||
super.installKeyboardActions();
|
||||
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Caret createCaret() {
|
||||
return new FlatCaret( null, false );
|
||||
@@ -156,6 +162,11 @@ public class FlatTextPaneUI
|
||||
|
||||
super.propertyChange( e );
|
||||
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 */
|
||||
|
||||
@@ -238,6 +238,22 @@ public class FlatTreeUI
|
||||
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
|
||||
protected void updateRenderer() {
|
||||
super.updateRenderer();
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.beans.PropertyChangeListener;
|
||||
import javax.swing.Action;
|
||||
|
||||
/**
|
||||
* Base class for UI actions used in ActionMap.
|
||||
* (similar to class sun.swing.UIAction)
|
||||
*
|
||||
* @author Karl Tauber
|
||||
* @since 3.3
|
||||
*/
|
||||
public abstract class FlatUIAction
|
||||
implements Action
|
||||
{
|
||||
protected final String name;
|
||||
protected final Action delegate;
|
||||
|
||||
protected FlatUIAction( String name ) {
|
||||
this.name = name;
|
||||
this.delegate = null;
|
||||
}
|
||||
|
||||
protected FlatUIAction( Action delegate ) {
|
||||
this.name = null;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue( String key ) {
|
||||
if( key == NAME && delegate == null )
|
||||
return name;
|
||||
return (delegate != null) ? delegate.getValue( key ) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return (delegate != null) ? delegate.isEnabled() : true;
|
||||
}
|
||||
|
||||
// do nothing in following methods because this class is immutable
|
||||
@Override public void putValue( String key, Object value ) {}
|
||||
@Override public void setEnabled( boolean b ) {}
|
||||
@Override public void addPropertyChangeListener( PropertyChangeListener listener ) {}
|
||||
@Override public void removePropertyChangeListener( PropertyChangeListener listener ) {}
|
||||
}
|
||||
2
flatlaf-demo/.settings/org.eclipse.core.resources.prefs
Normal file
2
flatlaf-demo/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -33,6 +33,7 @@ import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import com.formdev.flatlaf.FlatIntelliJLaf;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
import com.formdev.flatlaf.demo.HintManager.Hint;
|
||||
import com.formdev.flatlaf.demo.extras.*;
|
||||
import com.formdev.flatlaf.demo.intellijthemes.*;
|
||||
@@ -266,6 +267,18 @@ class DemoFrame
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void animationChanged() {
|
||||
boolean enabled = animationMenuItem.isSelected();
|
||||
System.setProperty( FlatSystemProperties.ANIMATION, Boolean.toString( enabled ) );
|
||||
|
||||
smoothScrollingMenuItem.setEnabled( enabled );
|
||||
animatedLafChangeMenuItem.setEnabled( enabled );
|
||||
}
|
||||
|
||||
private void smoothScrollingChanged() {
|
||||
UIManager.put( "ScrollPane.smoothScrolling", smoothScrollingMenuItem.isSelected() );
|
||||
}
|
||||
|
||||
private void animatedLafChangeChanged() {
|
||||
System.setProperty( "flatlaf.animatedLafChange", String.valueOf( animatedLafChangeMenuItem.isSelected() ) );
|
||||
}
|
||||
@@ -505,6 +518,8 @@ class DemoFrame
|
||||
showTitleBarIconMenuItem = new JCheckBoxMenuItem();
|
||||
underlineMenuSelectionMenuItem = new JCheckBoxMenuItem();
|
||||
alwaysShowMnemonicsMenuItem = new JCheckBoxMenuItem();
|
||||
animationMenuItem = new JCheckBoxMenuItem();
|
||||
smoothScrollingMenuItem = new JCheckBoxMenuItem();
|
||||
animatedLafChangeMenuItem = new JCheckBoxMenuItem();
|
||||
JMenuItem showHintsMenuItem = new JMenuItem();
|
||||
JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem();
|
||||
@@ -792,6 +807,19 @@ class DemoFrame
|
||||
alwaysShowMnemonicsMenuItem.setText("Always show mnemonics");
|
||||
alwaysShowMnemonicsMenuItem.addActionListener(e -> alwaysShowMnemonics());
|
||||
optionsMenu.add(alwaysShowMnemonicsMenuItem);
|
||||
optionsMenu.addSeparator();
|
||||
|
||||
//---- animationMenuItem ----
|
||||
animationMenuItem.setText("Animation");
|
||||
animationMenuItem.setSelected(true);
|
||||
animationMenuItem.addActionListener(e -> animationChanged());
|
||||
optionsMenu.add(animationMenuItem);
|
||||
|
||||
//---- smoothScrollingMenuItem ----
|
||||
smoothScrollingMenuItem.setText("Smooth Scrolling");
|
||||
smoothScrollingMenuItem.setSelected(true);
|
||||
smoothScrollingMenuItem.addActionListener(e -> smoothScrollingChanged());
|
||||
optionsMenu.add(smoothScrollingMenuItem);
|
||||
|
||||
//---- animatedLafChangeMenuItem ----
|
||||
animatedLafChangeMenuItem.setText("Animated Laf Change");
|
||||
@@ -981,6 +1009,8 @@ class DemoFrame
|
||||
private JCheckBoxMenuItem showTitleBarIconMenuItem;
|
||||
private JCheckBoxMenuItem underlineMenuSelectionMenuItem;
|
||||
private JCheckBoxMenuItem alwaysShowMnemonicsMenuItem;
|
||||
private JCheckBoxMenuItem animationMenuItem;
|
||||
private JCheckBoxMenuItem smoothScrollingMenuItem;
|
||||
private JCheckBoxMenuItem animatedLafChangeMenuItem;
|
||||
private JMenuItem aboutMenuItem;
|
||||
private JToolBar toolBar;
|
||||
|
||||
@@ -418,6 +418,27 @@ new FormModel {
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "alwaysShowMnemonics", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
|
||||
name: "separator9"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
|
||||
name: "animationMenuItem"
|
||||
"text": "Animation"
|
||||
"selected": true
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "animationChanged", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
|
||||
name: "smoothScrollingMenuItem"
|
||||
"text": "Smooth Scrolling"
|
||||
"selected": true
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smoothScrollingChanged", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
|
||||
name: "animatedLafChangeMenuItem"
|
||||
"text": "Animated Laf Change"
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.awt.Window;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.beans.Beans;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
@@ -156,6 +157,9 @@ public class IJThemesPanel
|
||||
}
|
||||
|
||||
private void updateThemesList() {
|
||||
if( Beans.isDesignTime() )
|
||||
return; // disable if running in GUI builder
|
||||
|
||||
int filterLightDark = filterComboBox.getSelectedIndex();
|
||||
boolean showLight = (filterLightDark != 2);
|
||||
boolean showDark = (filterLightDark != 1);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -67,7 +67,7 @@ public class FlatAnimatedLafChange
|
||||
* Invoke before setting new look and feel.
|
||||
*/
|
||||
public static void showSnapshot() {
|
||||
if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
|
||||
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
|
||||
return;
|
||||
|
||||
// stop already running animation
|
||||
@@ -138,7 +138,7 @@ public class FlatAnimatedLafChange
|
||||
* Invoke after updating UI.
|
||||
*/
|
||||
public static void hideSnapshotWithAnimation() {
|
||||
if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
|
||||
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
|
||||
return;
|
||||
|
||||
if( oldUIsnapshots.isEmpty() )
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -1,6 +1,6 @@
|
||||
FlatLaf Native Libraries
|
||||
========================
|
||||
|
||||
- [Windows 10 Native Library](flatlaf-natives-windows)
|
||||
- [Windows 10/11 Native Library](flatlaf-natives-windows)
|
||||
- [Linux Native Library](flatlaf-natives-linux)
|
||||
- [Natives using JNA](flatlaf-natives-jna) (for development only)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -1,17 +1,21 @@
|
||||
FlatLaf Windows 10 Native Library
|
||||
=================================
|
||||
FlatLaf Windows 10/11 Native Library
|
||||
====================================
|
||||
|
||||
This sub-project contains the source code for the FlatLaf Windows 10 native
|
||||
This sub-project contains the source code for the FlatLaf Windows 10/11 native
|
||||
library (DLL).
|
||||
|
||||
The native library can be built only on Windows and requires a C++ compiler.
|
||||
Tested only with Microsoft Visual C++ 2019 (comes with Visual Studio 2019).
|
||||
Tested with Microsoft Visual C++ (MSVC) 2019 and 2022 (comes with Visual Studio
|
||||
2019 and 2022).
|
||||
|
||||
The native library is available for folloging CPU architectures: `x86_64` (or
|
||||
`amd64`), `x86` and `arm64`.
|
||||
|
||||
To be able to build FlatLaf on any platform, and without C++ compiler, the
|
||||
pre-built DLL is checked into Git at
|
||||
pre-built DLLs are checked into Git at
|
||||
[flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/](https://github.com/JFormDesigner/FlatLaf/tree/main/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives).
|
||||
|
||||
The DLL was built on a GitHub server with the help of GitHub Actions. See:
|
||||
The DLLs were built on a GitHub server with the help of GitHub Actions. See:
|
||||
[Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml)
|
||||
workflow. Then the produced Artifacts ZIP was downloaded and the DLL checked
|
||||
into Git.
|
||||
workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with
|
||||
FormDev Software code signing certificate and committed the DLLs to Git.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
@@ -17,7 +17,11 @@
|
||||
package com.formdev.flatlaf.testing;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
import com.formdev.flatlaf.util.CubicBezierEasing;
|
||||
import net.miginfocom.swing.*;
|
||||
@@ -40,6 +44,8 @@ public class FlatAnimatorTest
|
||||
|
||||
FlatAnimatorTest() {
|
||||
initComponents();
|
||||
|
||||
mouseWheelTestPanel.lineChartPanel = lineChartPanel;
|
||||
}
|
||||
|
||||
private void start() {
|
||||
@@ -74,11 +80,14 @@ public class FlatAnimatorTest
|
||||
|
||||
private void initComponents() {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
JLabel label1 = new JLabel();
|
||||
JLabel linearLabel = new JLabel();
|
||||
linearScrollBar = new JScrollBar();
|
||||
JLabel label2 = new JLabel();
|
||||
JLabel easeInOutLabel = new JLabel();
|
||||
easeInOutScrollBar = new JScrollBar();
|
||||
startButton = new JButton();
|
||||
JLabel mouseWheelTestLabel = new JLabel();
|
||||
mouseWheelTestPanel = new FlatAnimatorTest.MouseWheelTestPanel();
|
||||
lineChartPanel = new LineChartPanel();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new MigLayout(
|
||||
@@ -89,20 +98,22 @@ public class FlatAnimatorTest
|
||||
// rows
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[]"));
|
||||
"[]para" +
|
||||
"[top]" +
|
||||
"[400,grow,fill]"));
|
||||
|
||||
//---- label1 ----
|
||||
label1.setText("Linear:");
|
||||
add(label1, "cell 0 0");
|
||||
//---- linearLabel ----
|
||||
linearLabel.setText("Linear:");
|
||||
add(linearLabel, "cell 0 0");
|
||||
|
||||
//---- linearScrollBar ----
|
||||
linearScrollBar.setOrientation(Adjustable.HORIZONTAL);
|
||||
linearScrollBar.setBlockIncrement(1);
|
||||
add(linearScrollBar, "cell 1 0");
|
||||
|
||||
//---- label2 ----
|
||||
label2.setText("Ease in out:");
|
||||
add(label2, "cell 0 1");
|
||||
//---- easeInOutLabel ----
|
||||
easeInOutLabel.setText("Ease in out:");
|
||||
add(easeInOutLabel, "cell 0 1");
|
||||
|
||||
//---- easeInOutScrollBar ----
|
||||
easeInOutScrollBar.setOrientation(Adjustable.HORIZONTAL);
|
||||
@@ -113,6 +124,18 @@ public class FlatAnimatorTest
|
||||
startButton.setText("Start");
|
||||
startButton.addActionListener(e -> start());
|
||||
add(startButton, "cell 0 2");
|
||||
|
||||
//---- mouseWheelTestLabel ----
|
||||
mouseWheelTestLabel.setText("Mouse wheel test:");
|
||||
add(mouseWheelTestLabel, "cell 0 3");
|
||||
|
||||
//---- mouseWheelTestPanel ----
|
||||
mouseWheelTestPanel.setBorder(new LineBorder(Color.red));
|
||||
add(mouseWheelTestPanel, "cell 1 3,height 100");
|
||||
|
||||
//---- lineChartPanel ----
|
||||
lineChartPanel.setUpdateChartDelayed(false);
|
||||
add(lineChartPanel, "cell 0 4 2 1");
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
}
|
||||
|
||||
@@ -120,5 +143,89 @@ public class FlatAnimatorTest
|
||||
private JScrollBar linearScrollBar;
|
||||
private JScrollBar easeInOutScrollBar;
|
||||
private JButton startButton;
|
||||
private FlatAnimatorTest.MouseWheelTestPanel mouseWheelTestPanel;
|
||||
private LineChartPanel lineChartPanel;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
|
||||
//---- class MouseWheelTestPanel ------------------------------------------
|
||||
|
||||
static class MouseWheelTestPanel
|
||||
extends JPanel
|
||||
implements MouseWheelListener
|
||||
{
|
||||
private static final int MAX_VALUE = 1000;
|
||||
private static final int STEP = 100;
|
||||
|
||||
private final JLabel valueLabel;
|
||||
private final Animator animator;
|
||||
|
||||
LineChartPanel lineChartPanel;
|
||||
|
||||
private int value;
|
||||
private int startValue;
|
||||
private int targetValue = -1;
|
||||
|
||||
MouseWheelTestPanel() {
|
||||
super( new BorderLayout() );
|
||||
valueLabel = new JLabel( String.valueOf( value ), SwingConstants.CENTER );
|
||||
valueLabel.setFont( valueLabel.getFont().deriveFont( (float) valueLabel.getFont().getSize() * 2 ) );
|
||||
add( valueLabel, BorderLayout.CENTER );
|
||||
add( new JLabel( " " ), BorderLayout.NORTH );
|
||||
add( new JLabel( "(move mouse into rectangle and rotate mouse wheel)", SwingConstants.CENTER ), BorderLayout.SOUTH );
|
||||
|
||||
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
|
||||
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
|
||||
|
||||
animator = new Animator( duration, fraction -> {
|
||||
value = startValue + Math.round( (targetValue - startValue) * fraction );
|
||||
valueLabel.setText( String.valueOf( value ) );
|
||||
|
||||
lineChartPanel.addValue( Color.red, value / (double) MAX_VALUE, value, null );
|
||||
}, () -> {
|
||||
targetValue = -1;
|
||||
} );
|
||||
animator.setResolution( resolution );
|
||||
animator.setInterpolator( new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) );
|
||||
|
||||
addMouseWheelListener( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved( MouseWheelEvent e ) {
|
||||
double preciseWheelRotation = e.getPreciseWheelRotation();
|
||||
|
||||
// add a dot in the middle of the chart for the wheel rotation
|
||||
// for unprecise wheels the rotation value is usually -1 or +1
|
||||
// for precise wheels the rotation value is in range ca. -10 to +10,
|
||||
// depending how fast the wheel is rotated
|
||||
lineChartPanel.addDot( Color.red, 0.5 + (preciseWheelRotation / 20.), (int) (preciseWheelRotation * 1000), null, null );
|
||||
|
||||
// increase/decrease target value if animation is in progress
|
||||
int newValue = (int) ((targetValue < 0 ? value : targetValue) + (STEP * preciseWheelRotation));
|
||||
newValue = Math.min( Math.max( newValue, 0 ), MAX_VALUE );
|
||||
|
||||
if( preciseWheelRotation != 0 &&
|
||||
preciseWheelRotation != e.getWheelRotation() )
|
||||
{
|
||||
// do not use animation for precise scrolling (e.g. with trackpad)
|
||||
|
||||
// stop running animation (if any)
|
||||
animator.stop();
|
||||
|
||||
value = newValue;
|
||||
valueLabel.setText( String.valueOf( value ) );
|
||||
|
||||
lineChartPanel.addValue( Color.red, value / (double) MAX_VALUE, value, null );
|
||||
return;
|
||||
}
|
||||
|
||||
// start next animation at the current value
|
||||
startValue = value;
|
||||
targetValue = newValue;
|
||||
|
||||
// restart animator
|
||||
animator.cancel();
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
JFDML JFormDesigner: "7.0.2.0.298" Java: "14.0.2" encoding: "UTF-8"
|
||||
JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
@@ -9,11 +9,11 @@ new FormModel {
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
||||
"$columnConstraints": "[fill][grow,fill]"
|
||||
"$rowConstraints": "[][][]"
|
||||
"$rowConstraints": "[][][]para[top][400,grow,fill]"
|
||||
} ) {
|
||||
name: "this"
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "label1"
|
||||
name: "linearLabel"
|
||||
"text": "Linear:"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
@@ -29,7 +29,7 @@ new FormModel {
|
||||
"value": "cell 1 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "label2"
|
||||
name: "easeInOutLabel"
|
||||
"text": "Ease in out:"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
@@ -54,9 +54,33 @@ new FormModel {
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 2"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "mouseWheelTestLabel"
|
||||
"text": "Mouse wheel test:"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3"
|
||||
} )
|
||||
add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$MouseWheelTestPanel" ) {
|
||||
name: "mouseWheelTestPanel"
|
||||
"border": new javax.swing.border.LineBorder( sfield java.awt.Color red, 1, false )
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 3,height 100"
|
||||
} )
|
||||
add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) {
|
||||
name: "lineChartPanel"
|
||||
"updateChartDelayed": false
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 2 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( null ) {
|
||||
"location": new java.awt.Point( 0, 0 )
|
||||
"size": new java.awt.Dimension( 415, 350 )
|
||||
"size": new java.awt.Dimension( 625, 625 )
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
* 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.testing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.tree.*;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
import com.formdev.flatlaf.util.ColorFunctions;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import net.miginfocom.swing.*;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class FlatSmoothScrollingTest
|
||||
extends FlatTestPanel
|
||||
{
|
||||
public static void main( String[] args ) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
FlatTestFrame frame = FlatTestFrame.create( args, "FlatSmoothScrollingTest" );
|
||||
UIManager.put( "ScrollBar.showButtons", true );
|
||||
frame.showFrame( FlatSmoothScrollingTest::new );
|
||||
} );
|
||||
}
|
||||
|
||||
FlatSmoothScrollingTest() {
|
||||
initComponents();
|
||||
|
||||
initializeDurationAndResolution();
|
||||
|
||||
// allow enabling/disabling smooth scrolling with Alt+S without moving focus to checkbox
|
||||
registerKeyboardAction(
|
||||
e -> {
|
||||
smoothScrollingCheckBox.setSelected( !smoothScrollingCheckBox.isSelected() );
|
||||
smoothScrollingChanged();
|
||||
},
|
||||
KeyStroke.getKeyStroke( "alt " + (char) smoothScrollingCheckBox.getMnemonic() ),
|
||||
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||
|
||||
listLabel.setIcon( new ColorIcon( Color.red.darker() ) );
|
||||
treeLabel.setIcon( new ColorIcon( Color.blue.darker() ) );
|
||||
tableLabel.setIcon( new ColorIcon( Color.green.darker() ) );
|
||||
textAreaLabel.setIcon( new ColorIcon( Color.magenta.darker() ) );
|
||||
textPaneLabel.setIcon( new ColorIcon( Color.cyan.darker() ) );
|
||||
editorPaneLabel.setIcon( new ColorIcon( Color.orange.darker() ) );
|
||||
customLabel.setIcon( new ColorIcon( Color.pink ) );
|
||||
|
||||
listScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, true, "list vert", Color.red.darker() ) );
|
||||
listScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, false, "list horz", Color.red ) );
|
||||
|
||||
treeScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, true, "tree vert", Color.blue.darker() ) );
|
||||
treeScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, false, "tree horz", Color.blue ) );
|
||||
|
||||
tableScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, true, "table vert", Color.green.darker() ) );
|
||||
tableScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, false, "table horz", Color.green ) );
|
||||
|
||||
textAreaScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, true, "textArea vert", Color.magenta.darker() ) );
|
||||
textAreaScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, false, "textArea horz", Color.magenta ) );
|
||||
|
||||
textPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, true, "textPane vert", Color.cyan.darker() ) );
|
||||
textPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, false, "textPane horz", Color.cyan ) );
|
||||
|
||||
editorPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, true, "editorPane vert", Color.orange.darker() ) );
|
||||
editorPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, false, "editorPane horz", Color.orange ) );
|
||||
|
||||
customScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, true, "custom vert", Color.pink ) );
|
||||
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, false, "custom horz", Color.pink.darker() ) );
|
||||
|
||||
ArrayList<String> items = new ArrayList<>();
|
||||
for( int i = 0; i < 10; i++ ) {
|
||||
for( int j = 0; j < 10; j++ ) {
|
||||
char[] chars = new char[i*10 + j + 1];
|
||||
Arrays.fill( chars, ' ' );
|
||||
chars[chars.length - 1] = (char) ('0' + j);
|
||||
if( i >= 5 )
|
||||
chars[50 - 1 - ((i-5)*10) - j] = (char) ('0' + j);
|
||||
items.add( new String( chars ) );
|
||||
}
|
||||
}
|
||||
|
||||
// list model
|
||||
list.setModel( new AbstractListModel<String>() {
|
||||
@Override
|
||||
public int getSize() {
|
||||
return items.size();
|
||||
}
|
||||
@Override
|
||||
public String getElementAt( int index ) {
|
||||
return items.get( index );
|
||||
}
|
||||
} );
|
||||
|
||||
// tree model
|
||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode( items.get( 0 ) );
|
||||
DefaultMutableTreeNode last = null;
|
||||
for( int i = 1; i < items.size(); i++ ) {
|
||||
DefaultMutableTreeNode node = new DefaultMutableTreeNode( items.get( i ) );
|
||||
if( i % 5 == 1 ) {
|
||||
root.add( node );
|
||||
last = node;
|
||||
} else
|
||||
last.add( node );
|
||||
}
|
||||
tree.setModel( new DefaultTreeModel( root ) );
|
||||
for( int row = tree.getRowCount() - 1; row >= 0; row-- )
|
||||
tree.expandRow( row );
|
||||
|
||||
// table model
|
||||
table.setModel( new AbstractTableModel() {
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return items.size();
|
||||
}
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return (table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF) ? 100 : 2;
|
||||
}
|
||||
@Override
|
||||
public Object getValueAt( int rowIndex, int columnIndex ) {
|
||||
return items.get( rowIndex );
|
||||
}
|
||||
} );
|
||||
|
||||
// select some rows to better see smooth scrolling issues
|
||||
for( int i = 5; i < items.size(); i += 10 ) {
|
||||
list.addSelectionInterval( i, i );
|
||||
tree.addSelectionInterval( i, i );
|
||||
table.addRowSelectionInterval( i, i );
|
||||
}
|
||||
|
||||
// text components
|
||||
String longText = "";
|
||||
for( int i = 0; i < 100; i++ )
|
||||
longText += String.format( "%-5d ", i );
|
||||
longText += "100";
|
||||
String text = items.stream().collect( Collectors.joining( "\n" ) ) + '\n';
|
||||
textArea.setText( longText + '\n' + text );
|
||||
textPane.setText( text );
|
||||
editorPane.setText( text );
|
||||
|
||||
// move selection to beginning in text components
|
||||
textArea.select( 0, 0 );
|
||||
textPane.select( 0, 0 );
|
||||
editorPane.select( 0, 0 );
|
||||
|
||||
// custom scrollable
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.append( "<html>" )
|
||||
.append( customButton.getText() );
|
||||
for( String item : items ) {
|
||||
buf.append( "<br>" );
|
||||
for( int i = 0; i < item.length(); i++ ) {
|
||||
char ch = item.charAt( i );
|
||||
if( ch == ' ' )
|
||||
buf.append( " " );
|
||||
else
|
||||
buf.append( ch );
|
||||
}
|
||||
}
|
||||
buf.append( "</html>" );
|
||||
customButton.setText( buf.toString() );
|
||||
|
||||
// line chart
|
||||
lineChartPanel.addMethodHighlight( JViewport.class.getName() + ".setViewPosition", "#0000bb" );
|
||||
lineChartPanel.addMethodHighlight( JViewport.class.getName() + ".scrollRectToVisible", "#00bbbb" );
|
||||
lineChartPanel.addMethodHighlight( JScrollBar.class.getName() + ".setValue", "#00aa00" );
|
||||
lineChartPanel.addMethodHighlight( Animator.class.getName() + ".timingEvent", "#bb0000" );
|
||||
lineChartPanel.addMethodHighlight( JComponent.class.getName() + ".processKeyBinding", "#bb0000" );
|
||||
lineChartPanel.addMethodHighlight( Component.class.getName() + ".processMouseEvent", "#bb0000" );
|
||||
lineChartPanel.addMethodHighlight( Component.class.getName() + ".processMouseWheelEvent", "#bb0000" );
|
||||
lineChartPanel.addMethodHighlight( Component.class.getName() + ".processMouseMotionEvent", "#bb0000" );
|
||||
lineChartPanel.addMethodHighlight( "actionPerformed", "#bbbb00" );
|
||||
|
||||
// request focus for list
|
||||
EventQueue.invokeLater( () -> {
|
||||
EventQueue.invokeLater( () -> {
|
||||
list.requestFocusInWindow();
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
private void smoothScrollingChanged() {
|
||||
UIManager.put( "ScrollPane.smoothScrolling", smoothScrollingCheckBox.isSelected() );
|
||||
}
|
||||
|
||||
private void initializeDurationAndResolution() {
|
||||
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
|
||||
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
|
||||
|
||||
durationSlider.setValue( duration );
|
||||
resolutionSlider.setValue( resolution );
|
||||
|
||||
updateDurationAndResolutionLabels( duration, resolution );
|
||||
}
|
||||
|
||||
private void durationOrResolutionChanged() {
|
||||
int duration = durationSlider.getValue();
|
||||
int resolution = resolutionSlider.getValue();
|
||||
|
||||
updateDurationAndResolutionLabels( duration, resolution );
|
||||
|
||||
UIManager.put( "ScrollPane.smoothScrolling.duration", duration );
|
||||
UIManager.put( "ScrollPane.smoothScrolling.resolution", resolution );
|
||||
|
||||
// update UI of scroll bars to force re-creation of animator
|
||||
JScrollPane[] scrollPanes = { listScrollPane, treeScrollPane, tableScrollPane,
|
||||
textAreaScrollPane, textPaneScrollPane, editorPaneScrollPane, customScrollPane };
|
||||
for( JScrollPane scrollPane : scrollPanes ) {
|
||||
scrollPane.getVerticalScrollBar().updateUI();
|
||||
scrollPane.getHorizontalScrollBar().updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDurationAndResolutionLabels( int duration, int resolution ) {
|
||||
durationValueLabel.setText( duration + " ms" );
|
||||
resolutionValueLabel.setText( resolution + " ms" );
|
||||
}
|
||||
|
||||
private void scrollToChanged() {
|
||||
if( scrollToSlider.getValueIsAdjusting() )
|
||||
return;
|
||||
|
||||
int value = scrollToSlider.getValue();
|
||||
JComponent[] comps = { list, tree, table, textArea, textPane, editorPane, customButton };
|
||||
for( JComponent c : comps ) {
|
||||
int x = (c.getWidth() * value) / 100;
|
||||
int y = (c.getHeight() * value) / 100;
|
||||
c.scrollRectToVisible( new Rectangle( x, y, 1, 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void rowHeaderChanged() {
|
||||
JTable rowHeader = null;
|
||||
if( rowHeaderCheckBox.isSelected() ) {
|
||||
rowHeader = new JTable();
|
||||
rowHeader.setPreferredScrollableViewportSize( new Dimension( UIScale.scale( 50 ), 100 ) );
|
||||
rowHeader.setModel( new AbstractTableModel() {
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return table.getRowCount();
|
||||
}
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 1;
|
||||
}
|
||||
@Override
|
||||
public Object getValueAt( int rowIndex, int columnIndex ) {
|
||||
char[] chars = new char[10];
|
||||
Arrays.fill( chars, ' ' );
|
||||
int i = rowIndex % 10;
|
||||
if( (rowIndex / 10) % 2 == 0 )
|
||||
chars[i] = (char) ('0' + i);
|
||||
else
|
||||
chars[9 - i] = (char) ('A' + i);
|
||||
return new String( chars );
|
||||
}
|
||||
} );
|
||||
}
|
||||
tableScrollPane.setRowHeaderView( rowHeader );
|
||||
}
|
||||
|
||||
private void showTableGridChanged() {
|
||||
boolean showGrid = showTableGridCheckBox.isSelected();
|
||||
table.setShowHorizontalLines( showGrid );
|
||||
table.setShowVerticalLines( showGrid );
|
||||
table.setIntercellSpacing( showGrid ? new Dimension( 1, 1 ) : new Dimension() );
|
||||
table.setGridColor( Color.gray );
|
||||
}
|
||||
|
||||
private void autoResizeModeChanged() {
|
||||
table.setAutoResizeMode( autoResizeModeCheckBox.isSelected() ? JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS : JTable.AUTO_RESIZE_OFF );
|
||||
((AbstractTableModel)table.getModel()).fireTableStructureChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUI() {
|
||||
super.updateUI();
|
||||
|
||||
EventQueue.invokeLater( () -> {
|
||||
showTableGridChanged();
|
||||
} );
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
smoothScrollingCheckBox = new JCheckBox();
|
||||
JPanel hSpacer1 = new JPanel(null);
|
||||
JLabel durationLabel = new JLabel();
|
||||
durationSlider = new JSlider();
|
||||
durationValueLabel = new JLabel();
|
||||
JLabel resolutionLabel = new JLabel();
|
||||
resolutionSlider = new JSlider();
|
||||
resolutionValueLabel = new JLabel();
|
||||
splitPane1 = new JSplitPane();
|
||||
splitPane2 = new JSplitPane();
|
||||
panel1 = new JPanel();
|
||||
listLabel = new JLabel();
|
||||
treeLabel = new JLabel();
|
||||
tableLabel = new JLabel();
|
||||
rowHeaderCheckBox = new JCheckBox();
|
||||
showTableGridCheckBox = new JCheckBox();
|
||||
autoResizeModeCheckBox = new JCheckBox();
|
||||
listScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
list = new JList<>();
|
||||
treeScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
tree = new JTree();
|
||||
tableScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
table = new JTable();
|
||||
scrollToSlider = new JSlider();
|
||||
panel2 = new JPanel();
|
||||
textAreaLabel = new JLabel();
|
||||
textPaneLabel = new JLabel();
|
||||
editorPaneLabel = new JLabel();
|
||||
customLabel = new JLabel();
|
||||
textAreaScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
textArea = new JTextArea();
|
||||
textPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
textPane = new JTextPane();
|
||||
editorPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
editorPane = new JEditorPane();
|
||||
customScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||
customButton = new JButton();
|
||||
lineChartPanel = new LineChartPanel();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new MigLayout(
|
||||
"ltr,insets dialog,hidemode 3",
|
||||
// columns
|
||||
"[200,grow,fill]",
|
||||
// rows
|
||||
"[]" +
|
||||
"[grow,fill]"));
|
||||
|
||||
//---- smoothScrollingCheckBox ----
|
||||
smoothScrollingCheckBox.setText("Smooth scrolling");
|
||||
smoothScrollingCheckBox.setSelected(true);
|
||||
smoothScrollingCheckBox.setMnemonic('S');
|
||||
smoothScrollingCheckBox.addActionListener(e -> smoothScrollingChanged());
|
||||
add(smoothScrollingCheckBox, "cell 0 0,alignx left,growx 0");
|
||||
add(hSpacer1, "cell 0 0,growx");
|
||||
|
||||
//---- durationLabel ----
|
||||
durationLabel.setText("Duration:");
|
||||
add(durationLabel, "cell 0 0");
|
||||
|
||||
//---- durationSlider ----
|
||||
durationSlider.setMaximum(5000);
|
||||
durationSlider.setValue(200);
|
||||
durationSlider.setSnapToTicks(true);
|
||||
durationSlider.setMinimum(100);
|
||||
durationSlider.setMinorTickSpacing(50);
|
||||
durationSlider.addChangeListener(e -> durationOrResolutionChanged());
|
||||
add(durationSlider, "cell 0 0");
|
||||
|
||||
//---- durationValueLabel ----
|
||||
durationValueLabel.setText("0000 ms");
|
||||
add(durationValueLabel, "cell 0 0,width 50");
|
||||
|
||||
//---- resolutionLabel ----
|
||||
resolutionLabel.setText("Resolution:");
|
||||
add(resolutionLabel, "cell 0 0");
|
||||
|
||||
//---- resolutionSlider ----
|
||||
resolutionSlider.setMaximum(1000);
|
||||
resolutionSlider.setMinimum(10);
|
||||
resolutionSlider.setValue(10);
|
||||
resolutionSlider.setMinorTickSpacing(10);
|
||||
resolutionSlider.setSnapToTicks(true);
|
||||
resolutionSlider.addChangeListener(e -> durationOrResolutionChanged());
|
||||
add(resolutionSlider, "cell 0 0");
|
||||
|
||||
//---- resolutionValueLabel ----
|
||||
resolutionValueLabel.setText("0000 ms");
|
||||
add(resolutionValueLabel, "cell 0 0,width 50");
|
||||
|
||||
//======== splitPane1 ========
|
||||
{
|
||||
splitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
|
||||
splitPane1.setResizeWeight(1.0);
|
||||
|
||||
//======== splitPane2 ========
|
||||
{
|
||||
splitPane2.setOrientation(JSplitPane.VERTICAL_SPLIT);
|
||||
splitPane2.setResizeWeight(0.5);
|
||||
|
||||
//======== panel1 ========
|
||||
{
|
||||
panel1.setLayout(new MigLayout(
|
||||
"ltr,insets 3,hidemode 3",
|
||||
// columns
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]" +
|
||||
"[fill]",
|
||||
// rows
|
||||
"[]0" +
|
||||
"[200,grow,fill]"));
|
||||
|
||||
//---- listLabel ----
|
||||
listLabel.setText("JList:");
|
||||
listLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel1.add(listLabel, "cell 0 0,aligny top,growy 0");
|
||||
|
||||
//---- treeLabel ----
|
||||
treeLabel.setText("JTree:");
|
||||
treeLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel1.add(treeLabel, "cell 1 0");
|
||||
|
||||
//---- tableLabel ----
|
||||
tableLabel.setText("JTable:");
|
||||
tableLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel1.add(tableLabel, "cell 2 0 2 1");
|
||||
|
||||
//---- rowHeaderCheckBox ----
|
||||
rowHeaderCheckBox.setText("Row header");
|
||||
rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged());
|
||||
panel1.add(rowHeaderCheckBox, "cell 2 0 2 1,alignx right,growx 0");
|
||||
|
||||
//---- showTableGridCheckBox ----
|
||||
showTableGridCheckBox.setText("Show table grid");
|
||||
showTableGridCheckBox.setMnemonic('G');
|
||||
showTableGridCheckBox.addActionListener(e -> showTableGridChanged());
|
||||
panel1.add(showTableGridCheckBox, "cell 2 0 2 1,alignx right,growx 0");
|
||||
|
||||
//---- autoResizeModeCheckBox ----
|
||||
autoResizeModeCheckBox.setText("Auto-resize mode");
|
||||
autoResizeModeCheckBox.setSelected(true);
|
||||
autoResizeModeCheckBox.addActionListener(e -> autoResizeModeChanged());
|
||||
panel1.add(autoResizeModeCheckBox, "cell 2 0 2 1,alignx right,growx 0");
|
||||
|
||||
//======== listScrollPane ========
|
||||
{
|
||||
listScrollPane.setViewportView(list);
|
||||
}
|
||||
panel1.add(listScrollPane, "cell 0 1,growx");
|
||||
|
||||
//======== treeScrollPane ========
|
||||
{
|
||||
treeScrollPane.setViewportView(tree);
|
||||
}
|
||||
panel1.add(treeScrollPane, "cell 1 1");
|
||||
|
||||
//======== tableScrollPane ========
|
||||
{
|
||||
tableScrollPane.setViewportView(table);
|
||||
}
|
||||
panel1.add(tableScrollPane, "cell 2 1 2 1,width 100,height 100");
|
||||
|
||||
//---- scrollToSlider ----
|
||||
scrollToSlider.setOrientation(SwingConstants.VERTICAL);
|
||||
scrollToSlider.setValue(0);
|
||||
scrollToSlider.setInverted(true);
|
||||
scrollToSlider.addChangeListener(e -> scrollToChanged());
|
||||
panel1.add(scrollToSlider, "cell 4 1");
|
||||
}
|
||||
splitPane2.setTopComponent(panel1);
|
||||
|
||||
//======== panel2 ========
|
||||
{
|
||||
panel2.setLayout(new MigLayout(
|
||||
"ltr,insets 3,hidemode 3",
|
||||
// columns
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]" +
|
||||
"[200,grow,fill]",
|
||||
// rows
|
||||
"[]0" +
|
||||
"[200,grow,fill]"));
|
||||
|
||||
//---- textAreaLabel ----
|
||||
textAreaLabel.setText("JTextArea:");
|
||||
textAreaLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel2.add(textAreaLabel, "cell 0 0");
|
||||
|
||||
//---- textPaneLabel ----
|
||||
textPaneLabel.setText("JTextPane:");
|
||||
textPaneLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel2.add(textPaneLabel, "cell 1 0");
|
||||
|
||||
//---- editorPaneLabel ----
|
||||
editorPaneLabel.setText("JEditorPane:");
|
||||
editorPaneLabel.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
panel2.add(editorPaneLabel, "cell 2 0");
|
||||
|
||||
//---- customLabel ----
|
||||
customLabel.setText("Custom:");
|
||||
panel2.add(customLabel, "cell 3 0");
|
||||
|
||||
//======== textAreaScrollPane ========
|
||||
{
|
||||
textAreaScrollPane.setViewportView(textArea);
|
||||
}
|
||||
panel2.add(textAreaScrollPane, "cell 0 1");
|
||||
|
||||
//======== textPaneScrollPane ========
|
||||
{
|
||||
textPaneScrollPane.setViewportView(textPane);
|
||||
}
|
||||
panel2.add(textPaneScrollPane, "cell 1 1");
|
||||
|
||||
//======== editorPaneScrollPane ========
|
||||
{
|
||||
editorPaneScrollPane.setViewportView(editorPane);
|
||||
}
|
||||
panel2.add(editorPaneScrollPane, "cell 2 1");
|
||||
|
||||
//======== customScrollPane ========
|
||||
{
|
||||
|
||||
//---- customButton ----
|
||||
customButton.setText("I'm a large button, but do not implement Scrollable interface");
|
||||
customButton.setHorizontalAlignment(SwingConstants.LEADING);
|
||||
customButton.setVerticalAlignment(SwingConstants.TOP);
|
||||
customScrollPane.setViewportView(customButton);
|
||||
}
|
||||
panel2.add(customScrollPane, "cell 3 1");
|
||||
}
|
||||
splitPane2.setBottomComponent(panel2);
|
||||
}
|
||||
splitPane1.setTopComponent(splitPane2);
|
||||
|
||||
//---- lineChartPanel ----
|
||||
lineChartPanel.setLegend1Text("Rectangles: scrollbar values (mouse hover shows stack)");
|
||||
lineChartPanel.setLegend2Text("Dots: disabled blitting mode in JViewport");
|
||||
lineChartPanel.setLegendYValueText("scroll bar value");
|
||||
splitPane1.setBottomComponent(lineChartPanel);
|
||||
}
|
||||
add(splitPane1, "cell 0 1");
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
private JCheckBox smoothScrollingCheckBox;
|
||||
private JSlider durationSlider;
|
||||
private JLabel durationValueLabel;
|
||||
private JSlider resolutionSlider;
|
||||
private JLabel resolutionValueLabel;
|
||||
private JSplitPane splitPane1;
|
||||
private JSplitPane splitPane2;
|
||||
private JPanel panel1;
|
||||
private JLabel listLabel;
|
||||
private JLabel treeLabel;
|
||||
private JLabel tableLabel;
|
||||
private JCheckBox rowHeaderCheckBox;
|
||||
private JCheckBox showTableGridCheckBox;
|
||||
private JCheckBox autoResizeModeCheckBox;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane listScrollPane;
|
||||
private JList<String> list;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane treeScrollPane;
|
||||
private JTree tree;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane tableScrollPane;
|
||||
private JTable table;
|
||||
private JSlider scrollToSlider;
|
||||
private JPanel panel2;
|
||||
private JLabel textAreaLabel;
|
||||
private JLabel textPaneLabel;
|
||||
private JLabel editorPaneLabel;
|
||||
private JLabel customLabel;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane textAreaScrollPane;
|
||||
private JTextArea textArea;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane textPaneScrollPane;
|
||||
private JTextPane textPane;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane editorPaneScrollPane;
|
||||
private JEditorPane editorPane;
|
||||
private FlatSmoothScrollingTest.DebugScrollPane customScrollPane;
|
||||
private JButton customButton;
|
||||
private LineChartPanel lineChartPanel;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
|
||||
//---- class ScrollBarChangeHandler ---------------------------------------
|
||||
|
||||
private class ScrollBarChangeHandler
|
||||
implements ChangeListener
|
||||
{
|
||||
private final String name;
|
||||
private final Color chartColor; // for smooth scrolling
|
||||
private final Color chartColor2; // for non-smooth scrolling
|
||||
private int count;
|
||||
private int lastValue;
|
||||
private long lastTime;
|
||||
|
||||
ScrollBarChangeHandler( DebugScrollPane scrollPane, boolean vertical, String name, Color chartColor ) {
|
||||
this.name = name;
|
||||
this.chartColor = chartColor;
|
||||
this.chartColor2 = ColorFunctions.lighten( chartColor, 0.1f );
|
||||
|
||||
// add change listener to viewport that is invoked from JViewport.setViewPosition()
|
||||
scrollPane.getViewport().addChangeListener( e -> {
|
||||
JViewport viewport = scrollPane.getViewport();
|
||||
Point viewPosition = viewport.getViewPosition();
|
||||
|
||||
if( (vertical && viewPosition.y != scrollPane.previousViewPosition.y) ||
|
||||
(!vertical && viewPosition.x != scrollPane.previousViewPosition.x) )
|
||||
{
|
||||
// calculate value from view position because scrollbar value is not yet up-to-date
|
||||
Dimension viewSize = viewport.getViewSize();
|
||||
double value = vertical
|
||||
? ((double) viewPosition.y) / (viewSize.height - viewport.getHeight())
|
||||
: ((double) viewPosition.x) / (viewSize.width - viewport.getWidth());
|
||||
int ivalue = vertical ? viewPosition.y : viewPosition.x;
|
||||
|
||||
// add dot to chart if blit scroll mode is disabled
|
||||
boolean dot = (scrollPane.getViewport().getScrollMode() != JViewport.BLIT_SCROLL_MODE);
|
||||
|
||||
Color color = smoothScrollingCheckBox.isSelected() ? this.chartColor : chartColor2;
|
||||
if( dot )
|
||||
lineChartPanel.addValueWithDot( color, value, ivalue, null, name );
|
||||
else
|
||||
lineChartPanel.addValue( color, value, ivalue, name );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent e ) {
|
||||
DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource();
|
||||
int value = m.getValue();
|
||||
/*
|
||||
double chartValue = (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
|
||||
lineChartPanel.addValue( chartValue, value, false, false,
|
||||
smoothScrollingCheckBox.isSelected() ? chartColor : chartColor2, name );
|
||||
*/
|
||||
long t = System.nanoTime() / 1000000;
|
||||
|
||||
System.out.printf( "%s (%d): %4d --> %4d %3d ms %-5b %s%n",
|
||||
name, ++count,
|
||||
lastValue,
|
||||
value,
|
||||
t - lastTime,
|
||||
m.getValueIsAdjusting(),
|
||||
value > lastValue ? "down" : value < lastValue ? "up" : "" );
|
||||
|
||||
lastValue = value;
|
||||
lastTime = t;
|
||||
}
|
||||
}
|
||||
|
||||
//---- class DebugViewport ------------------------------------------------
|
||||
|
||||
private static class DebugScrollPane
|
||||
extends JScrollPane
|
||||
{
|
||||
Point previousViewPosition = new Point();
|
||||
|
||||
@Override
|
||||
protected JViewport createViewport() {
|
||||
return new JViewport() {
|
||||
@Override
|
||||
public Point getViewPosition() {
|
||||
Point viewPosition = super.getViewPosition();
|
||||
// System.out.println( " viewPosition " + viewPosition.x + "," + viewPosition.y );
|
||||
return viewPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setViewPosition( Point p ) {
|
||||
// remember previous view position
|
||||
previousViewPosition = getViewPosition();
|
||||
|
||||
super.setViewPosition( p );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//---- class ColorIcon ----------------------------------------------------
|
||||
|
||||
private static class ColorIcon
|
||||
implements Icon
|
||||
{
|
||||
private final Color color;
|
||||
|
||||
ColorIcon( Color color ) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon( Component c, Graphics g, int x, int y ) {
|
||||
int width = getIconWidth();
|
||||
int height = getIconHeight();
|
||||
|
||||
g.setColor( color );
|
||||
g.fillRect( x, y, width, height );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return UIScale.scale( 24 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return UIScale.scale( 12 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
root: new FormRoot {
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
||||
"$columnConstraints": "[200,grow,fill]"
|
||||
"$rowConstraints": "[][grow,fill]"
|
||||
} ) {
|
||||
name: "this"
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "smoothScrollingCheckBox"
|
||||
"text": "Smooth scrolling"
|
||||
"selected": true
|
||||
"mnemonic": 83
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smoothScrollingChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0,alignx left,growx 0"
|
||||
} )
|
||||
add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
|
||||
name: "hSpacer1"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0,growx"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "durationLabel"
|
||||
"text": "Duration:"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JSlider" ) {
|
||||
name: "durationSlider"
|
||||
"maximum": 5000
|
||||
"value": 200
|
||||
"snapToTicks": true
|
||||
"minimum": 100
|
||||
"minorTickSpacing": 50
|
||||
addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "durationOrResolutionChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "durationValueLabel"
|
||||
"text": "0000 ms"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0,width 50"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "resolutionLabel"
|
||||
"text": "Resolution:"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JSlider" ) {
|
||||
name: "resolutionSlider"
|
||||
"maximum": 1000
|
||||
"minimum": 10
|
||||
"value": 10
|
||||
"minorTickSpacing": 10
|
||||
"snapToTicks": true
|
||||
addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "durationOrResolutionChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "resolutionValueLabel"
|
||||
"text": "0000 ms"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0,width 50"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JSplitPane", new FormLayoutManager( class javax.swing.JSplitPane ) ) {
|
||||
name: "splitPane1"
|
||||
"orientation": 0
|
||||
"resizeWeight": 1.0
|
||||
add( new FormContainer( "javax.swing.JSplitPane", new FormLayoutManager( class javax.swing.JSplitPane ) ) {
|
||||
name: "splitPane2"
|
||||
"orientation": 0
|
||||
"resizeWeight": 0.5
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$columnConstraints": "[200,grow,fill][200,grow,fill][200,grow,fill][200,grow,fill][fill]"
|
||||
"$rowConstraints": "[]0[200,grow,fill]"
|
||||
"$layoutConstraints": "ltr,insets 3,hidemode 3"
|
||||
} ) {
|
||||
name: "panel1"
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "listLabel"
|
||||
"text": "JList:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0,aligny top,growy 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "treeLabel"
|
||||
"text": "JTree:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "tableLabel"
|
||||
"text": "JTable:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0 2 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "rowHeaderCheckBox"
|
||||
"text": "Row header"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowHeaderChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0 2 1,alignx right,growx 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "showTableGridCheckBox"
|
||||
"text": "Show table grid"
|
||||
"mnemonic": 71
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTableGridChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0 2 1,alignx right,growx 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "autoResizeModeCheckBox"
|
||||
"text": "Auto-resize mode"
|
||||
"selected": true
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "autoResizeModeChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0 2 1,alignx right,growx 0"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "listScrollPane"
|
||||
add( new FormComponent( "javax.swing.JList" ) {
|
||||
name: "list"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.typeParameters": "String"
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,growx"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "treeScrollPane"
|
||||
add( new FormComponent( "javax.swing.JTree" ) {
|
||||
name: "tree"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 1"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "tableScrollPane"
|
||||
add( new FormComponent( "javax.swing.JTable" ) {
|
||||
name: "table"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 1 2 1,width 100,height 100"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JSlider" ) {
|
||||
name: "scrollToSlider"
|
||||
"orientation": 1
|
||||
"value": 0
|
||||
"inverted": true
|
||||
addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "scrollToChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 4 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class java.lang.String ) {
|
||||
"value": "left"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$columnConstraints": "[200,grow,fill][200,grow,fill][200,grow,fill][200,grow,fill]"
|
||||
"$rowConstraints": "[]0[200,grow,fill]"
|
||||
"$layoutConstraints": "ltr,insets 3,hidemode 3"
|
||||
} ) {
|
||||
name: "panel2"
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "textAreaLabel"
|
||||
"text": "JTextArea:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "textPaneLabel"
|
||||
"text": "JTextPane:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "editorPaneLabel"
|
||||
"text": "JEditorPane:"
|
||||
"horizontalTextPosition": 10
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "customLabel"
|
||||
"text": "Custom:"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 3 0"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "textAreaScrollPane"
|
||||
add( new FormComponent( "javax.swing.JTextArea" ) {
|
||||
name: "textArea"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "textPaneScrollPane"
|
||||
add( new FormComponent( "javax.swing.JTextPane" ) {
|
||||
name: "textPane"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 1"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "editorPaneScrollPane"
|
||||
add( new FormComponent( "javax.swing.JEditorPane" ) {
|
||||
name: "editorPane"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 1"
|
||||
} )
|
||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "customScrollPane"
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "customButton"
|
||||
"text": "I'm a large button, but do not implement Scrollable interface"
|
||||
"horizontalAlignment": 10
|
||||
"verticalAlignment": 1
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 3 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class java.lang.String ) {
|
||||
"value": "right"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class java.lang.String ) {
|
||||
"value": "left"
|
||||
} )
|
||||
add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) {
|
||||
name: "lineChartPanel"
|
||||
"legend1Text": "Rectangles: scrollbar values (mouse hover shows stack)"
|
||||
"legend2Text": "Dots: disabled blitting mode in JViewport"
|
||||
"legendYValueText": "scroll bar value"
|
||||
}, new FormLayoutConstraints( class java.lang.String ) {
|
||||
"value": "right"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( null ) {
|
||||
"location": new java.awt.Point( 0, 0 )
|
||||
"size": new java.awt.Dimension( 875, 715 )
|
||||
} )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
/*
|
||||
* 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.testing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.swing.*;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
import com.formdev.flatlaf.util.ColorFunctions;
|
||||
import com.formdev.flatlaf.util.HSLColor;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import net.miginfocom.swing.*;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
class LineChartPanel
|
||||
extends JPanel
|
||||
{
|
||||
LineChartPanel() {
|
||||
initComponents();
|
||||
|
||||
lineChartScrollPane.putClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, false );
|
||||
|
||||
oneSecondWidthChanged();
|
||||
updateChartDelayedChanged();
|
||||
|
||||
// clear chart on startup
|
||||
addHierarchyListener( e -> {
|
||||
if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && isShowing() )
|
||||
EventQueue.invokeLater( this::clearChart );
|
||||
} );
|
||||
|
||||
// show chart tooltips immediately and forever
|
||||
ToolTipManager.sharedInstance().setInitialDelay( 0 );
|
||||
ToolTipManager.sharedInstance().setDismissDelay( Integer.MAX_VALUE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
|
||||
// allow clearing chart with Alt+C without moving focus to button
|
||||
getRootPane().registerKeyboardAction(
|
||||
e -> clearChart(),
|
||||
KeyStroke.getKeyStroke( "alt " + (char) clearChartButton.getMnemonic() ),
|
||||
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||
}
|
||||
|
||||
public String getLegendYValueText() {
|
||||
return yValueLabel.getText();
|
||||
}
|
||||
|
||||
public void setLegendYValueText( String s ) {
|
||||
yValueLabel.setText( s );
|
||||
}
|
||||
|
||||
public String getLegend1Text() {
|
||||
return legend1Label.getText();
|
||||
}
|
||||
|
||||
public void setLegend1Text( String s ) {
|
||||
legend1Label.setText( s );
|
||||
}
|
||||
|
||||
public String getLegend2Text() {
|
||||
return legend2Label.getText();
|
||||
}
|
||||
|
||||
public void setLegend2Text( String s ) {
|
||||
legend2Label.setText( s );
|
||||
}
|
||||
|
||||
public boolean isUpdateChartDelayed() {
|
||||
return updateChartDelayedCheckBox.isSelected();
|
||||
}
|
||||
|
||||
public void setUpdateChartDelayed( boolean updateChartDelayed ) {
|
||||
updateChartDelayedCheckBox.setSelected( updateChartDelayed );
|
||||
}
|
||||
|
||||
void addValue( Color chartColor, double value, int ivalue, String name ) {
|
||||
lineChart.addValue( chartColor, value, ivalue, null, false, name );
|
||||
}
|
||||
|
||||
void addValueWithDot( Color chartColor, double value, int ivalue, Color dotColor, String name ) {
|
||||
if( dotColor == null )
|
||||
dotColor = chartColor;
|
||||
lineChart.addValue( chartColor, value, ivalue, dotColor, false, name );
|
||||
}
|
||||
|
||||
void addDot( Color chartColor, double value, int ivalue, Color dotColor, String name ) {
|
||||
if( dotColor == null )
|
||||
dotColor = chartColor;
|
||||
lineChart.addValue( chartColor, value, ivalue, dotColor, true, name );
|
||||
}
|
||||
|
||||
void addMethodHighlight( String classAndMethod, String highlightColor ) {
|
||||
lineChart.methodHighlightMap.put( classAndMethod, highlightColor );
|
||||
}
|
||||
|
||||
private void oneSecondWidthChanged() {
|
||||
int oneSecondWidth = oneSecondWidthSlider.getValue();
|
||||
int msPerLineX =
|
||||
oneSecondWidth <= 2000 ? 100 :
|
||||
oneSecondWidth <= 4000 ? 50 :
|
||||
oneSecondWidth <= 8000 ? 25 :
|
||||
10;
|
||||
|
||||
lineChart.setOneSecondWidth( oneSecondWidth );
|
||||
lineChart.setMsPerLineX( msPerLineX );
|
||||
lineChart.revalidate();
|
||||
lineChart.repaint();
|
||||
|
||||
if( xLabelText == null )
|
||||
xLabelText = xLabel.getText();
|
||||
xLabel.setText( MessageFormat.format( xLabelText, msPerLineX ) );
|
||||
}
|
||||
private String xLabelText;
|
||||
|
||||
private void updateChartDelayedChanged() {
|
||||
lineChart.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() );
|
||||
}
|
||||
|
||||
private void clearChart() {
|
||||
lineChart.clear();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
|
||||
lineChartScrollPane = new JScrollPane();
|
||||
lineChart = new LineChartPanel.LineChart();
|
||||
JPanel legendPanel = new JPanel();
|
||||
xLabel = new JLabel();
|
||||
legend1Label = new JLabel();
|
||||
JLabel yLabel = new JLabel();
|
||||
yValueLabel = new JLabel();
|
||||
JLabel yLabel2 = new JLabel();
|
||||
JPanel hSpacer1 = new JPanel(null);
|
||||
legend2Label = new JLabel();
|
||||
JLabel oneSecondWidthLabel = new JLabel();
|
||||
oneSecondWidthSlider = new JSlider();
|
||||
updateChartDelayedCheckBox = new JCheckBox();
|
||||
clearChartButton = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new MigLayout(
|
||||
"hidemode 3",
|
||||
// columns
|
||||
"[grow,fill]",
|
||||
// rows
|
||||
"[100:300,grow,fill]" +
|
||||
"[]"));
|
||||
|
||||
//======== lineChartScrollPane ========
|
||||
{
|
||||
lineChartScrollPane.setViewportView(lineChart);
|
||||
}
|
||||
add(lineChartScrollPane, "cell 0 0");
|
||||
|
||||
//======== legendPanel ========
|
||||
{
|
||||
legendPanel.setLayout(new MigLayout(
|
||||
"insets 0,hidemode 3,gapy 0",
|
||||
// columns
|
||||
"[fill]para" +
|
||||
"[fill]",
|
||||
// rows
|
||||
"[]" +
|
||||
"[]"));
|
||||
|
||||
//---- xLabel ----
|
||||
xLabel.setText("X: time ({0}ms per line)");
|
||||
legendPanel.add(xLabel, "cell 0 0");
|
||||
legendPanel.add(legend1Label, "cell 1 0");
|
||||
|
||||
//---- yLabel ----
|
||||
yLabel.setText("Y: ");
|
||||
legendPanel.add(yLabel, "cell 0 1,gapx 0 0");
|
||||
|
||||
//---- yValueLabel ----
|
||||
yValueLabel.setText("value");
|
||||
legendPanel.add(yValueLabel, "cell 0 1,gapx 0 0");
|
||||
|
||||
//---- yLabel2 ----
|
||||
yLabel2.setText(" (10% per line)");
|
||||
legendPanel.add(yLabel2, "cell 0 1,gapx 0 0");
|
||||
legendPanel.add(hSpacer1, "cell 0 1,growx");
|
||||
legendPanel.add(legend2Label, "cell 1 1");
|
||||
}
|
||||
add(legendPanel, "cell 0 1");
|
||||
|
||||
//---- oneSecondWidthLabel ----
|
||||
oneSecondWidthLabel.setText("Scale X:");
|
||||
oneSecondWidthLabel.setDisplayedMnemonic('A');
|
||||
oneSecondWidthLabel.setLabelFor(oneSecondWidthSlider);
|
||||
add(oneSecondWidthLabel, "cell 0 1,alignx right,growx 0");
|
||||
|
||||
//---- oneSecondWidthSlider ----
|
||||
oneSecondWidthSlider.setMinimum(1000);
|
||||
oneSecondWidthSlider.setMaximum(10000);
|
||||
oneSecondWidthSlider.addChangeListener(e -> oneSecondWidthChanged());
|
||||
add(oneSecondWidthSlider, "cell 0 1,alignx right,growx 0,wmax 100");
|
||||
|
||||
//---- updateChartDelayedCheckBox ----
|
||||
updateChartDelayedCheckBox.setText("Update chart delayed");
|
||||
updateChartDelayedCheckBox.setMnemonic('P');
|
||||
updateChartDelayedCheckBox.setSelected(true);
|
||||
updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged());
|
||||
add(updateChartDelayedCheckBox, "cell 0 1,alignx right,growx 0");
|
||||
|
||||
//---- clearChartButton ----
|
||||
clearChartButton.setText("Clear Chart");
|
||||
clearChartButton.setMnemonic('C');
|
||||
clearChartButton.addActionListener(e -> clearChart());
|
||||
add(clearChartButton, "cell 0 1,alignx right,growx 0");
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
|
||||
private JScrollPane lineChartScrollPane;
|
||||
private LineChartPanel.LineChart lineChart;
|
||||
private JLabel xLabel;
|
||||
private JLabel legend1Label;
|
||||
private JLabel yValueLabel;
|
||||
private JLabel legend2Label;
|
||||
private JSlider oneSecondWidthSlider;
|
||||
private JCheckBox updateChartDelayedCheckBox;
|
||||
private JButton clearChartButton;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
|
||||
|
||||
|
||||
//---- class LineChart ----------------------------------------------------
|
||||
|
||||
private static class LineChart
|
||||
extends JComponent
|
||||
implements Scrollable
|
||||
{
|
||||
private static final int UPDATE_DELAY_MS = 20;
|
||||
|
||||
private static final int NEW_SEQUENCE_TIME_LAG = 500;
|
||||
private static final int NEW_SEQUENCE_GAP = 100;
|
||||
private static final int HIT_OFFSET = 4;
|
||||
|
||||
private int oneSecondWidth = 1000;
|
||||
private int msPerLineX = 200;
|
||||
private final HashMap<String, String> methodHighlightMap = new HashMap<>();
|
||||
|
||||
private static class Data {
|
||||
final double value;
|
||||
final int ivalue;
|
||||
final Color dotColor;
|
||||
final boolean dotOnly;
|
||||
final long time; // in milliseconds
|
||||
final String name;
|
||||
final Exception stack;
|
||||
|
||||
Data( double value, int ivalue, Color dotColor, boolean dotOnly, long time, String name, Exception stack ) {
|
||||
this.value = value;
|
||||
this.ivalue = ivalue;
|
||||
this.dotColor = dotColor;
|
||||
this.dotOnly = dotOnly;
|
||||
this.time = time;
|
||||
this.name = name;
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// for debugging
|
||||
return "value=" + value + ", ivalue=" + ivalue + ", dotColor=" + dotColor
|
||||
+ ", dotOnly=" + dotOnly + ", time=" + time + ", name=" + name;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Color, List<Data>> color2dataMap = new HashMap<>();
|
||||
private final Timer repaintTime;
|
||||
private Color lastUsedChartColor;
|
||||
private boolean updateDelayed;
|
||||
|
||||
private final List<Point> lastPoints = new ArrayList<>();
|
||||
private final List<Data> lastDatas = new ArrayList<>();
|
||||
private double lastSystemScaleFactor = 1;
|
||||
private String lastToolTipPrinted;
|
||||
|
||||
LineChart() {
|
||||
repaintTime = new Timer( UPDATE_DELAY_MS, e -> repaintAndRevalidate() );
|
||||
repaintTime.setRepeats( false );
|
||||
|
||||
ToolTipManager.sharedInstance().registerComponent( this );
|
||||
}
|
||||
|
||||
void addValue( Color chartColor, double value, int ivalue, Color dotColor, boolean dotOnly, String name ) {
|
||||
List<Data> chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() );
|
||||
chartData.add( new Data( value, ivalue, dotColor, dotOnly, System.nanoTime() / 1000000, name, new Exception() ) );
|
||||
|
||||
lastUsedChartColor = chartColor;
|
||||
|
||||
if( updateDelayed ) {
|
||||
repaintTime.stop();
|
||||
repaintTime.start();
|
||||
} else
|
||||
repaintAndRevalidate();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
color2dataMap.clear();
|
||||
lastUsedChartColor = null;
|
||||
|
||||
repaint();
|
||||
revalidate();
|
||||
}
|
||||
|
||||
void setUpdateDelayed( boolean updateDelayed ) {
|
||||
this.updateDelayed = updateDelayed;
|
||||
}
|
||||
|
||||
void setOneSecondWidth( int oneSecondWidth ) {
|
||||
this.oneSecondWidth = oneSecondWidth;
|
||||
}
|
||||
|
||||
void setMsPerLineX( int msPerLineX ) {
|
||||
this.msPerLineX = msPerLineX;
|
||||
}
|
||||
|
||||
private void repaintAndRevalidate() {
|
||||
repaint();
|
||||
revalidate();
|
||||
|
||||
// scroll horizontally
|
||||
if( lastUsedChartColor != null ) {
|
||||
// compute chart width of last used color and start of last sequence
|
||||
int[] lastSeqX = new int[1];
|
||||
int cw = chartWidth( color2dataMap.get( lastUsedChartColor ), lastSeqX );
|
||||
|
||||
// scroll to end of last sequence (of last used color)
|
||||
int lastSeqWidth = cw - lastSeqX[0];
|
||||
int width = Math.min( lastSeqWidth, getParent().getWidth() );
|
||||
int x = cw - width;
|
||||
scrollRectToVisible( new Rectangle( x, 0, width, getHeight() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent( Graphics g ) {
|
||||
Graphics g2 = g.create();
|
||||
try {
|
||||
HiDPIUtils.paintAtScale1x( (Graphics2D) g2, this, this::paintImpl );
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
|
||||
FlatUIUtils.setRenderingHints( g );
|
||||
|
||||
int oneSecondWidth = (int) (UIScale.scale( this.oneSecondWidth ) * scaleFactor);
|
||||
int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor);
|
||||
int hitOffset = (int) Math.round( UIScale.scale( HIT_OFFSET ) * scaleFactor );
|
||||
|
||||
Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray );
|
||||
Color lineColor2 = FlatLaf.isLafDark()
|
||||
? new HSLColor( lineColor ).adjustTone( 30 )
|
||||
: new HSLColor( lineColor ).adjustShade( 30 );
|
||||
|
||||
g.translate( x, y );
|
||||
|
||||
// fill background
|
||||
g.setColor( UIManager.getColor( "Table.background" ) );
|
||||
g.fillRect( x, y, width, height );
|
||||
|
||||
// paint horizontal lines
|
||||
for( int i = 1; i < 10; i++ ) {
|
||||
int hy = (height * i) / 10;
|
||||
g.setColor( (i != 5) ? lineColor : lineColor2 );
|
||||
g.drawLine( 0, hy, width, hy );
|
||||
}
|
||||
|
||||
// paint vertical lines
|
||||
int perLineXWidth = Math.round( (oneSecondWidth / 1000f) * msPerLineX );
|
||||
for( int i = 1, xv = perLineXWidth; xv < width; xv += perLineXWidth, i++ ) {
|
||||
g.setColor( (i % 5 != 0) ? lineColor : lineColor2 );
|
||||
g.drawLine( xv, 0, xv, height );
|
||||
}
|
||||
|
||||
lastPoints.clear();
|
||||
lastDatas.clear();
|
||||
lastSystemScaleFactor = scaleFactor;
|
||||
|
||||
// paint lines
|
||||
for( Map.Entry<Color, List<Data>> e : color2dataMap.entrySet() ) {
|
||||
List<Data> chartData = e.getValue();
|
||||
Color chartColor = e.getKey();
|
||||
if( FlatLaf.isLafDark() )
|
||||
chartColor = new HSLColor( chartColor ).adjustTone( 50 );
|
||||
Color temporaryValueColor = ColorFunctions.fade( chartColor, FlatLaf.isLafDark() ? 0.7f : 0.3f );
|
||||
Color dataPointColor = ColorFunctions.fade( chartColor, FlatLaf.isLafDark() ? 0.6f : 0.2f );
|
||||
|
||||
// sequence start time and x coordinate
|
||||
long seqTime = 0;
|
||||
int seqX = 0;
|
||||
|
||||
// "previous" data point time, x/y coordinates and count
|
||||
long ptime = 0;
|
||||
int px = 0;
|
||||
int py = 0;
|
||||
int pcount = 0;
|
||||
|
||||
boolean first = true;
|
||||
boolean isTemporaryValue = false;
|
||||
int lastTemporaryValueIndex = -1;
|
||||
|
||||
int size = chartData.size();
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
Data data = chartData.get( i );
|
||||
|
||||
boolean newSeq = (data.time > ptime + NEW_SEQUENCE_TIME_LAG);
|
||||
ptime = data.time;
|
||||
|
||||
if( newSeq ) {
|
||||
// paint short horizontal line for previous sequence that has only one data point
|
||||
if( !first && pcount == 0 ) {
|
||||
g.setColor( chartColor );
|
||||
g.drawLine( px, py, px + (int) Math.round( UIScale.scale( 8 ) * scaleFactor ), py );
|
||||
}
|
||||
|
||||
// start new sequence
|
||||
seqTime = data.time;
|
||||
seqX = !first ? px + seqGapWidth : 0;
|
||||
px = seqX;
|
||||
pcount = 0;
|
||||
first = false;
|
||||
isTemporaryValue = false;
|
||||
}
|
||||
|
||||
// x/y coordinates of current data point
|
||||
int dy = (int) ((height - 1) * data.value);
|
||||
int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * oneSecondWidth));
|
||||
|
||||
// paint rectangle to indicate data point
|
||||
g.setColor( dataPointColor );
|
||||
g.drawRect( dx - hitOffset, dy - hitOffset, hitOffset * 2, hitOffset * 2 );
|
||||
|
||||
// remember data point for tooltip
|
||||
lastPoints.add( new Point( dx, dy ) );
|
||||
lastDatas.add( data );
|
||||
|
||||
if( data.dotColor != null ) {
|
||||
int s1 = (int) Math.round( UIScale.scale( 1 ) * scaleFactor );
|
||||
int s3 = (int) Math.round( UIScale.scale( 3 ) * scaleFactor );
|
||||
g.setColor( data.dotColor );
|
||||
g.fillRect( dx - s1, dy - s1, s3, s3 );
|
||||
if( data.dotOnly )
|
||||
continue;
|
||||
}
|
||||
|
||||
if( !newSeq ) {
|
||||
if( isTemporaryValue && i > lastTemporaryValueIndex )
|
||||
isTemporaryValue = false;
|
||||
|
||||
g.setColor( isTemporaryValue ? temporaryValueColor : chartColor );
|
||||
|
||||
// line in sequence
|
||||
g.drawLine( px, py, dx, dy );
|
||||
|
||||
px = dx;
|
||||
pcount++;
|
||||
|
||||
// check next data points for "temporary" value(s)
|
||||
if( !isTemporaryValue ) {
|
||||
// one or two values between two equal values are considered "temporary",
|
||||
// which means that they are the target value for the following scroll animation
|
||||
int stage = 0;
|
||||
for( int j = i + 1; j < size && stage <= 2 && !isTemporaryValue; j++ ) {
|
||||
Data nextData = chartData.get( j );
|
||||
if( nextData.dotOnly )
|
||||
continue; // ignore dots
|
||||
|
||||
// check whether next data point is within 10 milliseconds
|
||||
if( nextData.time > data.time + 10 )
|
||||
break;
|
||||
|
||||
if( stage >= 1 && stage <= 2 && nextData.value == data.value ) {
|
||||
isTemporaryValue = true;
|
||||
lastTemporaryValueIndex = j;
|
||||
}
|
||||
stage++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
py = dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int chartWidth() {
|
||||
int width = 0;
|
||||
for( List<Data> chartData : color2dataMap.values() )
|
||||
width = Math.max( width, chartWidth( chartData, null ) );
|
||||
return width;
|
||||
}
|
||||
|
||||
private int chartWidth( List<Data> chartData, int[] lastSeqX ) {
|
||||
long seqTime = 0;
|
||||
int seqX = 0;
|
||||
long ptime = 0;
|
||||
int px = 0;
|
||||
|
||||
int size = chartData.size();
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
Data data = chartData.get( i );
|
||||
|
||||
if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) {
|
||||
// start new sequence
|
||||
seqTime = data.time;
|
||||
seqX = (i > 0) ? px + NEW_SEQUENCE_GAP : 0;
|
||||
px = seqX;
|
||||
} else {
|
||||
// line in sequence
|
||||
int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * UIScale.scale( oneSecondWidth )));
|
||||
px = dx;
|
||||
}
|
||||
|
||||
ptime = data.time;
|
||||
}
|
||||
|
||||
if( lastSeqX != null )
|
||||
lastSeqX[0] = seqX;
|
||||
|
||||
return px;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension( chartWidth(), 200 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredScrollableViewportSize() {
|
||||
return new Dimension( chartWidth(), 200 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
|
||||
return UIScale.scale( oneSecondWidth );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) {
|
||||
JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this );
|
||||
return (viewport != null) ? viewport.getWidth() : 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportWidth() {
|
||||
JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this );
|
||||
return (viewport != null) ? viewport.getWidth() > chartWidth() : true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportHeight() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText( MouseEvent e ) {
|
||||
int x = (int) Math.round( e.getX() * lastSystemScaleFactor );
|
||||
int y = (int) Math.round( e.getY() * lastSystemScaleFactor );
|
||||
int hitOffset = (int) Math.round( UIScale.scale( HIT_OFFSET ) * lastSystemScaleFactor );
|
||||
StringBuilder buf = null;
|
||||
|
||||
int pointsCount = lastPoints.size();
|
||||
for( int i = 0; i < pointsCount; i++ ) {
|
||||
Point pt = lastPoints.get( i );
|
||||
|
||||
// check X/Y coordinates
|
||||
if( x < pt.x - hitOffset || x > pt.x + hitOffset ||
|
||||
y < pt.y - hitOffset || y > pt.y + hitOffset )
|
||||
continue;
|
||||
|
||||
if( buf == null ) {
|
||||
buf = new StringBuilder( 5000 );
|
||||
buf.append( "<html>" );
|
||||
}
|
||||
|
||||
Data data = lastDatas.get( i );
|
||||
buf.append( "<h2>" );
|
||||
if( data.dotOnly )
|
||||
buf.append( "DOT: " );
|
||||
buf.append( data.name ).append( ' ' ).append( data.ivalue ).append( "</h2>" );
|
||||
|
||||
StackTraceElement[] stackTrace = data.stack.getStackTrace();
|
||||
for( int j = 0; j < stackTrace.length; j++ ) {
|
||||
StackTraceElement stackElement = stackTrace[j];
|
||||
String className = stackElement.getClassName();
|
||||
String methodName = stackElement.getMethodName();
|
||||
String classAndMethod = className + '.' + methodName;
|
||||
|
||||
// ignore methods from this class
|
||||
if( className.startsWith( LineChartPanel.class.getName() ) )
|
||||
continue;
|
||||
|
||||
int repeatCount = 0;
|
||||
for( int k = j + 1; k < stackTrace.length; k++ ) {
|
||||
if( !stackElement.equals( stackTrace[k] ) )
|
||||
break;
|
||||
repeatCount++;
|
||||
}
|
||||
j += repeatCount;
|
||||
|
||||
String highlight = methodHighlightMap.get( classAndMethod );
|
||||
if( highlight == null )
|
||||
highlight = methodHighlightMap.get( className );
|
||||
if( highlight == null )
|
||||
highlight = methodHighlightMap.get( methodName );
|
||||
if( highlight != null )
|
||||
buf.append( "<span color=\"" ).append( highlight ).append( "\">" );
|
||||
|
||||
// append method
|
||||
buf.append( className )
|
||||
.append( ".<b>" )
|
||||
.append( methodName )
|
||||
.append( "</b>" );
|
||||
if( highlight != null )
|
||||
buf.append( "</span>" );
|
||||
|
||||
// append source
|
||||
buf.append( " <span color=\"#888888\">" );
|
||||
if( stackElement.getFileName() != null ) {
|
||||
buf.append( '(' );
|
||||
buf.append( stackElement.getFileName() );
|
||||
if( stackElement.getLineNumber() >= 0 )
|
||||
buf.append( ':' ).append( stackElement.getLineNumber() );
|
||||
buf.append( ')' );
|
||||
} else
|
||||
buf.append( "(Unknown Source)" );
|
||||
buf.append( "</span>" );
|
||||
|
||||
// append repeat count
|
||||
if( repeatCount > 0 )
|
||||
buf.append( " <b>" ).append( repeatCount + 1 ).append( "x</b>" );
|
||||
buf.append( "<br>" );
|
||||
|
||||
// break at some methods to make stack smaller
|
||||
if( classAndMethod.equals( "java.awt.event.InvocationEvent.dispatch" ) ||
|
||||
classAndMethod.equals( "java.awt.Component.processMouseEvent" ) ||
|
||||
classAndMethod.equals( "java.awt.Component.processMouseWheelEvent" ) ||
|
||||
classAndMethod.equals( "java.awt.Component.processMouseMotionEvent" ) ||
|
||||
classAndMethod.equals( "javax.swing.JComponent.processKeyBinding" ) ||
|
||||
classAndMethod.equals( "com.formdev.flatlaf.util.Animator.timingEvent" ) )
|
||||
break;
|
||||
}
|
||||
buf.append( "..." );
|
||||
}
|
||||
|
||||
if( buf == null )
|
||||
return null;
|
||||
|
||||
buf.append( "<html>" );
|
||||
String toolTip = buf.toString();
|
||||
|
||||
// print to console
|
||||
if( !Objects.equals( toolTip, lastToolTipPrinted ) ) {
|
||||
lastToolTipPrinted = toolTip;
|
||||
|
||||
System.out.println( toolTip
|
||||
.replace( "<br>", "\n" )
|
||||
.replace( "<h2>", "\n---- " )
|
||||
.replace( "</h2>", " ----\n" )
|
||||
.replaceAll( "<[^>]+>", "" ) );
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
root: new FormRoot {
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "hidemode 3"
|
||||
"$columnConstraints": "[grow,fill]"
|
||||
"$rowConstraints": "[100:300,grow,fill][]"
|
||||
} ) {
|
||||
name: "this"
|
||||
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||
name: "lineChartScrollPane"
|
||||
add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel$LineChart" ) {
|
||||
name: "lineChart"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "insets 0,hidemode 3,gapy 0"
|
||||
"$columnConstraints": "[fill]para[fill]"
|
||||
"$rowConstraints": "[][]"
|
||||
} ) {
|
||||
name: "legendPanel"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "xLabel"
|
||||
"text": "X: time ({0}ms per line)"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "legend1Label"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "yLabel"
|
||||
"text": "Y: "
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,gapx 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "yValueLabel"
|
||||
"text": "value"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,gapx 0 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "yLabel2"
|
||||
"text": " (10% per line)"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,gapx 0 0"
|
||||
} )
|
||||
add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
|
||||
name: "hSpacer1"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,growx"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "legend2Label"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "oneSecondWidthLabel"
|
||||
"text": "Scale X:"
|
||||
"displayedMnemonic": 65
|
||||
"labelFor": new FormReference( "oneSecondWidthSlider" )
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,alignx right,growx 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JSlider" ) {
|
||||
name: "oneSecondWidthSlider"
|
||||
"minimum": 1000
|
||||
"maximum": 10000
|
||||
addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "oneSecondWidthChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,alignx right,growx 0,wmax 100"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "updateChartDelayedCheckBox"
|
||||
"text": "Update chart delayed"
|
||||
"mnemonic": 80
|
||||
"selected": true
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,alignx right,growx 0"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "clearChartButton"
|
||||
"text": "Clear Chart"
|
||||
"mnemonic": 67
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "clearChart", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1,alignx right,growx 0"
|
||||
} )
|
||||
}, new FormLayoutConstraints( null ) {
|
||||
"location": new java.awt.Point( 0, 0 )
|
||||
"size": new java.awt.Dimension( 880, 300 )
|
||||
} )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package com.formdev.flatlaf.testing.contrib;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ItemEvent;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLayeredPane;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.Scrollable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeModel;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
|
||||
/**
|
||||
* from https://github.com/JFormDesigner/FlatLaf/pull/683#issuecomment-1585667066
|
||||
*
|
||||
* @author Chrriis
|
||||
*/
|
||||
public class SmoothScrollingTest {
|
||||
|
||||
private static class CustomTree extends JTree {
|
||||
|
||||
public CustomTree() {
|
||||
super(getDefaultTreeModel());
|
||||
}
|
||||
|
||||
protected static TreeModel getDefaultTreeModel() {
|
||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode("JTree");
|
||||
for(int i=0; i<1000; i++) {
|
||||
DefaultMutableTreeNode parent;
|
||||
parent = new DefaultMutableTreeNode("colors-" + i);
|
||||
root.add(parent);
|
||||
parent.add(new DefaultMutableTreeNode("blue"));
|
||||
parent.add(new DefaultMutableTreeNode("violet"));
|
||||
parent.add(new DefaultMutableTreeNode("red"));
|
||||
parent.add(new DefaultMutableTreeNode("yellow"));
|
||||
parent = new DefaultMutableTreeNode("sports-" + i);
|
||||
root.add(parent);
|
||||
parent.add(new DefaultMutableTreeNode("basketball"));
|
||||
parent.add(new DefaultMutableTreeNode("soccer"));
|
||||
parent.add(new DefaultMutableTreeNode("football"));
|
||||
parent.add(new DefaultMutableTreeNode("hockey"));
|
||||
parent = new DefaultMutableTreeNode("food-" + i);
|
||||
root.add(parent);
|
||||
parent.add(new DefaultMutableTreeNode("hot dogs"));
|
||||
parent.add(new DefaultMutableTreeNode("pizza"));
|
||||
parent.add(new DefaultMutableTreeNode("ravioli"));
|
||||
parent.add(new DefaultMutableTreeNode("bananas"));
|
||||
}
|
||||
return new DefaultTreeModel(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomTable extends JTable {
|
||||
public CustomTable() {
|
||||
super(createDefaultTableModel());
|
||||
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
setCellSelectionEnabled(true);
|
||||
}
|
||||
private static TableModel createDefaultTableModel() {
|
||||
int columnChunkCount = 90;
|
||||
Object[][] data = new Object[1000][3 * columnChunkCount];
|
||||
String[] prefixes = {"", " ", " ", " "};
|
||||
String[] titles = new String[columnChunkCount * 3];
|
||||
for(int j=0; j<columnChunkCount; j++) {
|
||||
titles[j * 3] = "Column" + j * 3 + 1;
|
||||
titles[j * 3 + 1] = "Column" + j * 3 + 2;
|
||||
titles[j * 3 + 2] = "Column" + j * 3 + 3;
|
||||
for(int i=0; i<data.length; i++) {
|
||||
data[i][j * 3] = prefixes[i % prefixes.length] + "Cell " + (i + 1) + "/" + (j + 1);
|
||||
data[i][j * 3 + 1] = "Cell " + (i + 1) + "/" + (j + 2);
|
||||
data[i][j * 3 + 2] = Boolean.valueOf(i%5 == 0);
|
||||
}
|
||||
}
|
||||
AbstractTableModel tableModel = new AbstractTableModel() {
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
return data[rowIndex][columnIndex];
|
||||
}
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return data.length;
|
||||
}
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return titles.length;
|
||||
}
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
return titles[column];
|
||||
}
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex) {
|
||||
return columnIndex % 3 == 2 ? Boolean.class : String.class;
|
||||
}
|
||||
};
|
||||
return tableModel;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScrollableCustomPane extends JPanel implements Scrollable {
|
||||
public ScrollableCustomPane() {
|
||||
super(new GridLayout(100, 0));
|
||||
for(int i=0; i<10000; i++) {
|
||||
add(new JButton("Button " + (i + 1)));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Dimension getPreferredScrollableViewportSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportWidth() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportHeight() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
|
||||
Dimension referenceSize = getComponent(0).getSize();
|
||||
switch(orientation) {
|
||||
case SwingConstants.VERTICAL: return referenceSize.height;
|
||||
case SwingConstants.HORIZONTAL: return referenceSize.width;
|
||||
}
|
||||
return 20;
|
||||
}
|
||||
@Override
|
||||
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
|
||||
Dimension referenceSize = getComponent(0).getSize();
|
||||
switch(orientation) {
|
||||
case SwingConstants.VERTICAL: return referenceSize.height * 10;
|
||||
case SwingConstants.HORIZONTAL: return referenceSize.width * 5;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
UIManager.setLookAndFeel(new FlatLightLaf());
|
||||
// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
UIManager.getDefaults().put("ScrollBar.showButtons", true);
|
||||
ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
|
||||
JFrame frame = new JFrame();
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
JPanel contentPane = new JPanel(new BorderLayout());
|
||||
contentPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
JCheckBox smoothCheckBox = new JCheckBox("Smooth", true);
|
||||
JComboBox<Integer> scrollModeComboBox = new JComboBox<>(new Integer[] {JViewport.BLIT_SCROLL_MODE, JViewport.BACKINGSTORE_SCROLL_MODE, JViewport.SIMPLE_SCROLL_MODE});
|
||||
JCheckBox blitBlockCheckBox = new JCheckBox("Prevent Blit");
|
||||
@SuppressWarnings( "rawtypes" )
|
||||
ListCellRenderer defaultComboRenderer = scrollModeComboBox.getRenderer();
|
||||
scrollModeComboBox.setRenderer(new ListCellRenderer<Integer>() {
|
||||
@SuppressWarnings( "unchecked" )
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends Integer> list, Integer value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
String sValue = null;
|
||||
switch(value) {
|
||||
case JViewport.BLIT_SCROLL_MODE: sValue = "Blit"; break;
|
||||
case JViewport.BACKINGSTORE_SCROLL_MODE: sValue = "Backing Store"; break;
|
||||
case JViewport.SIMPLE_SCROLL_MODE: sValue = "Simple"; break;
|
||||
}
|
||||
return defaultComboRenderer.getListCellRendererComponent(list, sValue, index, isSelected, cellHasFocus);
|
||||
}
|
||||
});
|
||||
JPanel northBar = new JPanel();
|
||||
JButton lightPopupButton = new JButton("Light Popup");
|
||||
lightPopupButton.addActionListener(e -> {
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.setLightWeightPopupEnabled(true);
|
||||
popupMenu.putClientProperty(FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, 0);
|
||||
JTable table = new CustomTable();
|
||||
table.setColumnSelectionInterval(1, 1);
|
||||
table.setRowSelectionInterval(28, 28);
|
||||
JScrollPane tableScrollPane = new JScrollPane(table);
|
||||
tableScrollPane.setPreferredSize(new Dimension(400, 600));
|
||||
tableScrollPane.putClientProperty(FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, !smoothCheckBox.isSelected()? Boolean.FALSE: null);
|
||||
tableScrollPane.getViewport().setScrollMode((Integer)scrollModeComboBox.getSelectedItem());
|
||||
JRootPane popupRootPane = new JRootPane();
|
||||
popupRootPane.getContentPane().add(tableScrollPane);
|
||||
popupMenu.add(popupRootPane);
|
||||
popupMenu.show(lightPopupButton, 0, lightPopupButton.getHeight());
|
||||
});
|
||||
northBar.add(lightPopupButton);
|
||||
JButton heavyPopupButton = new JButton("Heavy Popup");
|
||||
heavyPopupButton.addActionListener(e -> {
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.setLightWeightPopupEnabled(false);
|
||||
JTable table = new CustomTable();
|
||||
table.setColumnSelectionInterval(1, 1);
|
||||
table.setRowSelectionInterval(28, 28);
|
||||
JScrollPane tableScrollPane = new JScrollPane(table);
|
||||
tableScrollPane.setPreferredSize(new Dimension(400, 600));
|
||||
tableScrollPane.putClientProperty(FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, !smoothCheckBox.isSelected()? Boolean.FALSE: null);
|
||||
tableScrollPane.getViewport().setScrollMode((Integer)scrollModeComboBox.getSelectedItem());
|
||||
JRootPane popupRootPane = new JRootPane();
|
||||
popupRootPane.getContentPane().add(tableScrollPane);
|
||||
popupMenu.add(popupRootPane);
|
||||
popupMenu.show(heavyPopupButton, 0, heavyPopupButton.getHeight());
|
||||
});
|
||||
northBar.add(heavyPopupButton);
|
||||
contentPane.add(northBar, BorderLayout.NORTH);
|
||||
JPanel centerPane = new JPanel(new GridLayout(1, 0));
|
||||
JTree tree = new CustomTree();
|
||||
JScrollPane treeScrollPane = new JScrollPane(tree);
|
||||
centerPane.add(treeScrollPane);
|
||||
for(int i=tree.getRowCount()-1; i>=0; i--) {
|
||||
tree.expandRow(i);
|
||||
}
|
||||
tree.setSelectionRow(28);
|
||||
JTable table = new CustomTable();
|
||||
table.setColumnSelectionInterval(1, 1);
|
||||
table.setRowSelectionInterval(28, 28);
|
||||
JScrollPane tableScrollPane = new JScrollPane(table);
|
||||
centerPane.add(tableScrollPane);
|
||||
ScrollableCustomPane scrollableCustomPane = new ScrollableCustomPane();
|
||||
JScrollPane customPaneScrollPane = new JScrollPane(scrollableCustomPane);
|
||||
centerPane.add(customPaneScrollPane);
|
||||
contentPane.add(centerPane, BorderLayout.CENTER);
|
||||
JPanel southBar = new JPanel(new FlowLayout());
|
||||
smoothCheckBox.addItemListener(e -> {
|
||||
boolean isSmooth = e.getStateChange() == ItemEvent.SELECTED;
|
||||
treeScrollPane.putClientProperty(FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, !isSmooth? Boolean.FALSE: null);
|
||||
tableScrollPane.putClientProperty(FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, !isSmooth? Boolean.FALSE: null);
|
||||
customPaneScrollPane.putClientProperty(FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, !isSmooth? Boolean.FALSE: null);
|
||||
});
|
||||
southBar.add(smoothCheckBox);
|
||||
southBar.add(Box.createHorizontalStrut(30));
|
||||
JButton scrollButton = new JButton("Scroll rect");
|
||||
scrollButton.addActionListener(e -> {
|
||||
treeScrollPane.getViewport().setViewPosition(new Point(9, Integer.MAX_VALUE / 2));
|
||||
tableScrollPane.getViewport().setViewPosition(new Point(9, Integer.MAX_VALUE / 2));
|
||||
customPaneScrollPane.getViewport().setViewPosition(new Point(9, Integer.MAX_VALUE / 2));
|
||||
});
|
||||
southBar.add(scrollButton);
|
||||
southBar.add(Box.createHorizontalStrut(30));
|
||||
scrollModeComboBox.addItemListener(e -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
int scrollMode = (Integer)scrollModeComboBox.getSelectedItem();
|
||||
treeScrollPane.getViewport().setScrollMode(scrollMode);
|
||||
tableScrollPane.getViewport().setScrollMode(scrollMode);
|
||||
customPaneScrollPane.getViewport().setScrollMode(scrollMode);
|
||||
});
|
||||
});
|
||||
southBar.add(scrollModeComboBox);
|
||||
southBar.add(Box.createHorizontalStrut(30));
|
||||
JButton xButton1 = new JButton("Blit Blocker");
|
||||
xButton1.setBounds(20, 400, xButton1.getPreferredSize().width, xButton1.getPreferredSize().height);
|
||||
JButton xButton2 = new JButton("Blit Blocker");
|
||||
xButton2.setBounds(600, 400, xButton2.getPreferredSize().width, xButton2.getPreferredSize().height);
|
||||
JButton xButton3 = new JButton("Blit Blocker");
|
||||
xButton3.setBounds(800, 400, xButton3.getPreferredSize().width, xButton3.getPreferredSize().height);
|
||||
blitBlockCheckBox.addItemListener(e -> {
|
||||
boolean isBlockingBlit = e.getStateChange() == ItemEvent.SELECTED;
|
||||
JLayeredPane layeredPane = frame.getLayeredPane();
|
||||
if(isBlockingBlit) {
|
||||
layeredPane.add(xButton1);
|
||||
layeredPane.add(xButton2);
|
||||
layeredPane.add(xButton3);
|
||||
} else {
|
||||
layeredPane.remove(xButton1);
|
||||
layeredPane.remove(xButton2);
|
||||
layeredPane.remove(xButton3);
|
||||
}
|
||||
layeredPane.revalidate();
|
||||
layeredPane.repaint();
|
||||
});
|
||||
southBar.add(blitBlockCheckBox);
|
||||
contentPane.add(southBar, BorderLayout.SOUTH);
|
||||
frame.getContentPane().add(contentPane);
|
||||
frame.setSize(1200, 800);
|
||||
frame.setLocationByPlatform(true);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=ISO-8859-1
|
||||
Binary file not shown.
6
images/sponsor.svg
Normal file
6
images/sponsor.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="51" height="12" viewBox="0 0 51 12">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#DA6301" fill-rule="nonzero" stroke="#DA6301" stroke-linejoin="round" d="M43 .5L48.5 6 43 11.5 2.516 11.531C1.5 11.533.5 10.5.5 9.5V2.5C.5 1.428 1.497.5 2.5.5H43z"/>
|
||||
<path fill="#FFF" fill-rule="nonzero" d="M4.878 9.627C5.344 9.627 5.742 9.539 6.072 9.363 6.402 9.188 6.664 8.931 6.856 8.594 7.048 8.257 7.144 7.884 7.144 7.474 7.144 7.06 7.053 6.704 6.87 6.407A1.965 1.965 0 0 0 6.06 5.687C5.826 5.569 5.419 5.43 4.84 5.27 4.246 5.11 3.865 4.948 3.696 4.788 3.527 4.628 3.443 4.404 3.443 4.114 3.443 3.802 3.558 3.547 3.789 3.35 4.021 3.153 4.356 3.055 4.795 3.055 5.228 3.055 5.565 3.167 5.804 3.392 6.043 3.616 6.182 3.962 6.22 4.427L6.968 4.358C6.955 3.922 6.858 3.543 6.678 3.221A1.84 1.84 0 0 0 5.916 2.479C5.589 2.306 5.207 2.219 4.77 2.219 4.136 2.22 3.63 2.405 3.257 2.772 2.883 3.14 2.696 3.613 2.696 4.192 2.696 4.515 2.76 4.804 2.89 5.06 3.021 5.315 3.204 5.525 3.438 5.691 3.672 5.857 4.066 6.021 4.62 6.185 5.24 6.367 5.646 6.508 5.833 6.609 6.02 6.71 6.16 6.84 6.253 6.998 6.346 7.155 6.392 7.34 6.392 7.552 6.392 7.913 6.264 8.207 6.009 8.433 5.753 8.66 5.394 8.773 4.932 8.773A1.884 1.884 0 0 1 4.062 8.572 1.495 1.495 0 0 1 3.47 8.042C3.33 7.822 3.243 7.515 3.204 7.122L2.47 7.2C2.487 7.946 2.711 8.536 3.142 8.973 3.574 9.409 4.152 9.627 4.878 9.627zM8.98 9.5V6.59H10.484C11.29 6.59 11.859 6.386 12.188 5.98 12.517 5.572 12.68 5.046 12.68 4.401 12.681 3.914 12.581 3.502 12.378 3.167A1.555 1.555 0 0 0 11.568 2.479C11.32 2.387 10.936 2.342 10.415 2.342H8.204V9.5H8.98zM10.498 5.745H8.98V3.187H10.484C10.887 3.187 11.167 3.224 11.32 3.299 11.476 3.374 11.608 3.512 11.717 3.714 11.826 3.916 11.88 4.158 11.88 4.44 11.88 4.878 11.77 5.204 11.548 5.421 11.327 5.636 10.977 5.744 10.498 5.744zM16.26 9.622C17.12 9.622 17.815 9.254 18.345 8.52 18.827 7.85 19.068 6.987 19.068 5.926 19.068 5.154 18.943 4.489 18.694 3.93S18.107 2.947 17.681 2.654A2.446 2.446 0 0 0 16.265 2.214C15.465 2.215 14.795 2.544 14.258 3.2 13.721 3.855 13.453 4.793 13.453 6.014 13.453 6.98 13.692 7.806 14.17 8.489 14.698 9.244 15.394 9.622 16.26 9.622zM16.255 8.812C15.7 8.812 15.225 8.568 14.837 8.082 14.447 7.595 14.253 6.91 14.253 6.028 14.253 5.006 14.443 4.251 14.823 3.763 15.2 3.274 15.683 3.03 16.27 3.03 16.635 3.03 16.97 3.142 17.278 3.365 17.586 3.588 17.816 3.885 17.97 4.255 18.168 4.742 18.267 5.292 18.267 5.907 18.267 6.86 18.074 7.583 17.688 8.074 17.303 8.566 16.825 8.812 16.255 8.812zM20.777 9.5V3.875L23.857 9.5H24.655V2.342H23.912V7.962L20.826 2.342H20.03V9.5H20.777zM28.116 9.627C28.58 9.627 28.979 9.539 29.309 9.363 29.64 9.188 29.901 8.931 30.093 8.594 30.285 8.257 30.381 7.884 30.381 7.474 30.381 7.06 30.291 6.704 30.108 6.407A1.965 1.965 0 0 0 29.298 5.687C29.062 5.569 28.655 5.43 28.076 5.27 27.484 5.11 27.103 4.948 26.934 4.788 26.764 4.628 26.68 4.404 26.68 4.114 26.68 3.802 26.796 3.547 27.027 3.35 27.257 3.153 27.593 3.055 28.033 3.055 28.466 3.055 28.802 3.167 29.04 3.392 29.28 3.616 29.419 3.962 29.458 4.427L30.205 4.358C30.192 3.922 30.095 3.543 29.915 3.221A1.84 1.84 0 0 0 29.153 2.479C28.826 2.306 28.444 2.219 28.008 2.219 27.373 2.22 26.868 2.405 26.495 2.772 26.12 3.14 25.933 3.613 25.933 4.192 25.933 4.515 25.998 4.804 26.128 5.06 26.258 5.315 26.441 5.525 26.675 5.691 26.91 5.857 27.303 6.021 27.857 6.185 28.479 6.367 28.883 6.508 29.07 6.609 29.257 6.71 29.397 6.84 29.49 6.998 29.583 7.155 29.63 7.34 29.63 7.552 29.63 7.913 29.501 8.207 29.245 8.433 28.99 8.66 28.632 8.773 28.169 8.773A1.877 1.877 0 0 1 27.3 8.572 1.495 1.495 0 0 1 26.707 8.042C26.569 7.822 26.48 7.515 26.44 7.122L25.708 7.2C25.725 7.946 25.948 8.536 26.38 8.973 26.81 9.409 27.39 9.627 28.116 9.627zM34.029 9.622C34.889 9.622 35.583 9.254 36.114 8.52 36.595 7.85 36.836 6.987 36.836 5.926 36.836 5.154 36.712 4.489 36.463 3.93 36.213 3.373 35.876 2.947 35.45 2.654A2.446 2.446 0 0 0 34.034 2.214C33.233 2.215 32.564 2.544 32.027 3.2 31.49 3.855 31.22 4.793 31.22 6.014 31.221 6.98 31.461 7.806 31.94 8.489 32.466 9.244 33.163 9.622 34.029 9.622zM34.024 8.812C33.467 8.812 32.994 8.568 32.605 8.082 32.216 7.595 32.022 6.91 32.022 6.028 32.022 5.006 32.212 4.251 32.59 3.763 32.97 3.274 33.453 3.03 34.038 3.03 34.403 3.03 34.74 3.142 35.047 3.365 35.354 3.588 35.585 3.885 35.737 4.255 35.938 4.742 36.037 5.292 36.037 5.907 36.036 6.86 35.843 7.583 35.456 8.074 35.072 8.566 34.594 8.812 34.025 8.812zM38.594 9.5V6.321H39.493C39.753 6.321 39.956 6.356 40.1 6.426 40.245 6.496 40.395 6.632 40.55 6.834 40.704 7.036 40.933 7.428 41.236 8.011L42.017 9.5H42.994L41.968 7.552A5.017 5.017 0 0 0 41.358 6.639 1.998 1.998 0 0 0 40.86 6.243C41.397 6.152 41.806 5.94 42.088 5.603 42.369 5.269 42.51 4.83 42.51 4.29 42.51 3.88 42.427 3.52 42.261 3.208 42.095 2.898 41.883 2.675 41.624 2.542S40.964 2.342 40.42 2.342H37.818V9.5H38.594zM40.264 5.501H38.594V3.133H40.45C40.866 3.133 41.18 3.236 41.392 3.443 41.604 3.65 41.709 3.933 41.709 4.295 41.71 4.545 41.654 4.765 41.543 4.955 41.433 5.142 41.283 5.28 41.094 5.368 40.905 5.457 40.63 5.501 40.264 5.501z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
Reference in New Issue
Block a user