mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-06 14:00:55 +03:00
Compare commits
30 Commits
3.4
...
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 |
@@ -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 ) {}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user