FlatSmoothScrollingTest:

- better list/tree/etc items for easier recognizing jittery scrolling
- sliders to modify animation duration and resolution
- slider to invoke `scrollRectToVisible()`
- option to show row header for table
- use viewport.viewPosition for chart (instead of scrollbar.value)
- highlight methods in stack of tooltip (e.g. JViewport.setViewPosition())
This commit is contained in:
Karl Tauber
2023-09-01 17:12:53 +02:00
parent b32b8db97a
commit 44a04cca2c
4 changed files with 367 additions and 91 deletions

View File

@@ -180,7 +180,7 @@ public class FlatAnimatorTest
value = startValue + Math.round( (targetValue - startValue) * fraction );
valueLabel.setText( String.valueOf( value ) );
lineChartPanel.addValue( value / (double) MAX_VALUE, value, false, Color.red, null );
lineChartPanel.addValue( Color.red, value / (double) MAX_VALUE, value, null );
}, () -> {
targetValue = -1;
} );
@@ -198,7 +198,7 @@ public class FlatAnimatorTest
// 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.addValue( 0.5 + (preciseWheelRotation / 20.), (int) (preciseWheelRotation * 1000), true, Color.red, null );
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));
@@ -215,7 +215,7 @@ public class FlatAnimatorTest
value = newValue;
valueLabel.setText( String.valueOf( value ) );
lineChartPanel.addValue( value / (double) MAX_VALUE, value, false, Color.red, null );
lineChartPanel.addValue( Color.red, value / (double) MAX_VALUE, value, null );
return;
}

View File

@@ -22,6 +22,7 @@ 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;
@@ -30,6 +31,8 @@ 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.*;
@@ -51,8 +54,7 @@ public class FlatSmoothScrollingTest
FlatSmoothScrollingTest() {
initComponents();
ToolTipManager.sharedInstance().setInitialDelay( 0 );
ToolTipManager.sharedInstance().setDismissDelay( Integer.MAX_VALUE );
initializeDurationAndResolution();
// allow enabling/disabling smooth scrolling with Alt+S without moving focus to checkbox
registerKeyboardAction(
@@ -93,14 +95,16 @@ public class FlatSmoothScrollingTest
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, false, "custom horz", Color.pink.darker() ) );
ArrayList<String> items = new ArrayList<>();
for( char ch = '0'; ch < 'z'; ch++ ) {
if( (ch > '9' && ch < 'A') || (ch > 'Z' && ch < 'a') )
continue;
char[] chars = new char[ch - '0' + 1];
Arrays.fill( chars, ch );
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>() {
@@ -137,12 +141,10 @@ public class FlatSmoothScrollingTest
}
@Override
public int getColumnCount() {
return 4;
return (table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF) ? 100 : 2;
}
@Override
public Object getValueAt( int rowIndex, int columnIndex ) {
if( columnIndex > 0 )
rowIndex = (items.size() + rowIndex - ((items.size() / 4) * columnIndex)) % items.size();
return items.get( rowIndex );
}
} );
@@ -155,17 +157,49 @@ public class FlatSmoothScrollingTest
}
// text components
String longText = items.stream().collect( Collectors.joining( " " ) ) + ' '
+ items.stream().limit( 20 ).collect( Collectors.joining( " " ) );
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( "&nbsp;" );
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();
@@ -177,6 +211,82 @@ public class FlatSmoothScrollingTest
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 );
@@ -187,6 +297,7 @@ public class FlatSmoothScrollingTest
private void autoResizeModeChanged() {
table.setAutoResizeMode( autoResizeModeCheckBox.isSelected() ? JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS : JTable.AUTO_RESIZE_OFF );
((AbstractTableModel)table.getModel()).fireTableStructureChanged();
}
@Override
@@ -201,12 +312,20 @@ public class FlatSmoothScrollingTest
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();
@@ -215,6 +334,7 @@ public class FlatSmoothScrollingTest
tree = new JTree();
tableScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
table = new JTable();
scrollToSlider = new JSlider();
panel2 = new JPanel();
textAreaLabel = new JLabel();
textPaneLabel = new JLabel();
@@ -227,7 +347,7 @@ public class FlatSmoothScrollingTest
editorPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
editorPane = new JEditorPane();
customScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
button1 = new JButton();
customButton = new JButton();
lineChartPanel = new LineChartPanel();
//======== this ========
@@ -245,6 +365,41 @@ public class FlatSmoothScrollingTest
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 ========
{
@@ -264,7 +419,8 @@ public class FlatSmoothScrollingTest
"[200,grow,fill]" +
"[200,grow,fill]" +
"[200,grow,fill]" +
"[200,grow,fill]",
"[200,grow,fill]" +
"[fill]",
// rows
"[]0" +
"[200,grow,fill]"));
@@ -284,6 +440,11 @@ public class FlatSmoothScrollingTest
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');
@@ -313,6 +474,13 @@ public class FlatSmoothScrollingTest
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);
@@ -369,12 +537,11 @@ public class FlatSmoothScrollingTest
//======== customScrollPane ========
{
//---- button1 ----
button1.setText("I'm a large button, but do not implement Scrollable interface");
button1.setPreferredSize(new Dimension(800, 800));
button1.setHorizontalAlignment(SwingConstants.LEADING);
button1.setVerticalAlignment(SwingConstants.TOP);
customScrollPane.setViewportView(button1);
//---- 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");
}
@@ -394,12 +561,17 @@ public class FlatSmoothScrollingTest
// 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;
@@ -408,6 +580,7 @@ public class FlatSmoothScrollingTest
private JTree tree;
private FlatSmoothScrollingTest.DebugScrollPane tableScrollPane;
private JTable table;
private JSlider scrollToSlider;
private JPanel panel2;
private JLabel textAreaLabel;
private JLabel textPaneLabel;
@@ -420,7 +593,7 @@ public class FlatSmoothScrollingTest
private FlatSmoothScrollingTest.DebugScrollPane editorPaneScrollPane;
private JEditorPane editorPane;
private FlatSmoothScrollingTest.DebugScrollPane customScrollPane;
private JButton button1;
private JButton customButton;
private LineChartPanel lineChartPanel;
// JFormDesigner - End of variables declaration //GEN-END:variables
@@ -433,6 +606,7 @@ public class FlatSmoothScrollingTest
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 ) {
@@ -442,20 +616,27 @@ public class FlatSmoothScrollingTest
// add change listener to viewport that is invoked from JViewport.setViewPosition()
scrollPane.getViewport().addChangeListener( e -> {
// add dot to chart if blit scroll mode is disabled
if( vertical == scrollPane.lastScrollingWasVertical &&
scrollPane.getViewport().getScrollMode() != JViewport.BLIT_SCROLL_MODE )
{
// calculate value from view position because scrollbar value is not yet up-to-date
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;
lineChartPanel.addValue( value, ivalue, true, chartColor, name );
// 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 );
}
} );
}
@@ -463,26 +644,25 @@ public class FlatSmoothScrollingTest
@Override
public void stateChanged( ChangeEvent e ) {
DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource();
boolean smoothScrolling = smoothScrollingCheckBox.isSelected();
lineChartPanel.addValue( getChartValue( m ), m.getValue(), false,
smoothScrolling ? chartColor : chartColor2, name );
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 %3d ms %b%n",
System.out.printf( "%s (%d): %4d --> %4d %3d ms %-5b %s%n",
name, ++count,
m.getValue(),
lastValue,
value,
t - lastTime,
m.getValueIsAdjusting() );
m.getValueIsAdjusting(),
value > lastValue ? "down" : value < lastValue ? "up" : "" );
lastValue = value;
lastTime = t;
}
private double getChartValue( BoundedRangeModel m ) {
int value = m.getValue();
return (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
}
}
//---- class DebugViewport ------------------------------------------------
@@ -490,7 +670,7 @@ public class FlatSmoothScrollingTest
private static class DebugScrollPane
extends JScrollPane
{
boolean lastScrollingWasVertical;
Point previousViewPosition = new Point();
@Override
protected JViewport createViewport() {
@@ -504,34 +684,11 @@ public class FlatSmoothScrollingTest
@Override
public void setViewPosition( Point p ) {
// remember whether scrolling vertically or horizontally
Component view = getView();
if( view != null ) {
int oldY = (view instanceof JComponent)
? ((JComponent) view).getY()
: view.getBounds().y;
int newY = -p.y;
lastScrollingWasVertical = (oldY != newY);
} else
lastScrollingWasVertical = true;
// remember previous view position
previousViewPosition = getViewPosition();
super.setViewPosition( p );
}
@Override
public void paint( Graphics g ) {
super.paint( g );
if( backingStoreImage != null ) {
System.out.println( "---------------------------------------------" );
System.out.println( "WARNING: backingStoreImage was used for painting" );
System.out.println( "View: " + getView() );
System.out.println( "Clip: " + g.getClipBounds() );
new Exception().printStackTrace( System.out );
System.out.println( "---------------------------------------------" );
}
}
};
}
}

View File

@@ -18,6 +18,66 @@ new FormModel {
}, 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
@@ -27,7 +87,7 @@ new FormModel {
"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]"
"$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"
} ) {
@@ -53,6 +113,13 @@ new FormModel {
}, 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"
@@ -97,6 +164,15 @@ new FormModel {
}, 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"
} )
@@ -160,9 +236,8 @@ new FormModel {
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "customScrollPane"
add( new FormComponent( "javax.swing.JButton" ) {
name: "button1"
name: "customButton"
"text": "I'm a large button, but do not implement Scrollable interface"
"preferredSize": new java.awt.Dimension( 800, 800 )
"horizontalAlignment": 10
"verticalAlignment": 1
} )

View File

@@ -61,6 +61,10 @@ class LineChartPanel
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
@@ -106,8 +110,24 @@ class LineChartPanel
updateChartDelayedCheckBox.setSelected( updateChartDelayed );
}
void addValue( double value, int ivalue, boolean dot, Color chartColor, String name ) {
lineChart.addValue( value, ivalue, dot, chartColor, name );
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() {
@@ -255,19 +275,22 @@ class LineChartPanel
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 boolean dot;
final Color dotColor;
final boolean dotOnly;
final long time; // in milliseconds
final String name;
final Exception stack;
Data( double value, int ivalue, boolean dot, long time, String name, Exception stack ) {
Data( double value, int ivalue, Color dotColor, boolean dotOnly, long time, String name, Exception stack ) {
this.value = value;
this.ivalue = ivalue;
this.dot = dot;
this.dotColor = dotColor;
this.dotOnly = dotOnly;
this.time = time;
this.name = name;
this.stack = stack;
@@ -276,7 +299,8 @@ class LineChartPanel
@Override
public String toString() {
// for debugging
return "value=" + value + ", ivalue=" + ivalue + ", dot=" + dot + ", time=" + time + ", name=" + name;
return "value=" + value + ", ivalue=" + ivalue + ", dotColor=" + dotColor
+ ", dotOnly=" + dotOnly + ", time=" + time + ", name=" + name;
}
}
@@ -297,9 +321,9 @@ class LineChartPanel
ToolTipManager.sharedInstance().registerComponent( this );
}
void addValue( double value, int ivalue, boolean dot, Color chartColor, String name ) {
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, dot, System.nanoTime() / 1000000, name, new Exception() ) );
chartData.add( new Data( value, ivalue, dotColor, dotOnly, System.nanoTime() / 1000000, name, new Exception() ) );
lastUsedChartColor = chartColor;
@@ -452,11 +476,12 @@ class LineChartPanel
lastPoints.add( new Point( dx, dy ) );
lastDatas.add( data );
if( data.dot ) {
if( data.dotColor != null ) {
int s1 = (int) Math.round( UIScale.scale( 1 ) * scaleFactor );
int s3 = (int) Math.round( UIScale.scale( 3 ) * scaleFactor );
g.setColor( chartColor );
g.setColor( data.dotColor );
g.fillRect( dx - s1, dy - s1, s3, s3 );
if( data.dotOnly )
continue;
}
@@ -479,7 +504,7 @@ class LineChartPanel
int stage = 0;
for( int j = i + 1; j < size && stage <= 2 && !isTemporaryValue; j++ ) {
Data nextData = chartData.get( j );
if( nextData.dot )
if( nextData.dotOnly )
continue; // ignore dots
// check whether next data point is within 10 milliseconds
@@ -592,7 +617,7 @@ class LineChartPanel
Data data = lastDatas.get( i );
buf.append( "<h2>" );
if( data.dot )
if( data.dotOnly )
buf.append( "DOT: " );
buf.append( data.name ).append( ' ' ).append( data.ivalue ).append( "</h2>" );
@@ -601,6 +626,7 @@ class LineChartPanel
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() ) )
@@ -614,11 +640,24 @@ class LineChartPanel
}
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> <span color=\"#888888\">" );
.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() );
@@ -628,14 +667,19 @@ class LineChartPanel
} 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( (className.startsWith( "java.awt.event.InvocationEvent" ) && methodName.equals( "dispatch" )) ||
(className.startsWith( "java.awt.Component" ) && methodName.equals( "processMouseWheelEvent" )) ||
(className.startsWith( "javax.swing.JComponent" ) && methodName.equals( "processKeyBinding" )) )
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( "..." );