diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java index b4619561..20d26bcd 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java @@ -48,7 +48,7 @@ public class FlatAnimatorTest updateChartDelayedChanged(); - lineChartPanel.setSecondWidth( 500 ); + lineChartPanel.setOneSecondWidth( 500 ); mouseWheelTestPanel.lineChartPanel = lineChartPanel; } @@ -217,7 +217,7 @@ public class FlatAnimatorTest value = startValue + Math.round( (targetValue - startValue) * fraction ); valueLabel.setText( String.valueOf( value ) ); - lineChartPanel.addValue( value / (double) MAX_VALUE, Color.red ); + lineChartPanel.addValue( value / (double) MAX_VALUE, false, Color.red, null ); }, () -> { targetValue = -1; } ); @@ -235,7 +235,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.), true, Color.red ); + lineChartPanel.addValue( 0.5 + (preciseWheelRotation / 20.), true, Color.red, null ); // increase/decrease target value if animation is in progress int newValue = (int) ((targetValue < 0 ? value : targetValue) + (STEP * preciseWheelRotation)); @@ -252,7 +252,7 @@ public class FlatAnimatorTest value = newValue; valueLabel.setText( String.valueOf( value ) ); - lineChartPanel.addValue( value / (double) MAX_VALUE, Color.red ); + lineChartPanel.addValue( value / (double) MAX_VALUE, false, Color.red, null ); return; } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java index f76e3c7e..77686668 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java @@ -24,11 +24,15 @@ 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.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -37,6 +41,7 @@ import javax.swing.table.AbstractTableModel; import javax.swing.tree.*; 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; @@ -59,8 +64,12 @@ public class FlatSmoothScrollingTest FlatSmoothScrollingTest() { initComponents(); + oneSecondWidthChanged(); updateChartDelayedChanged(); + ToolTipManager.sharedInstance().setInitialDelay( 0 ); + ToolTipManager.sharedInstance().setDismissDelay( Integer.MAX_VALUE ); + // allow enabling/disabling smooth scrolling with Alt+S without moving focus to checkbox registerKeyboardAction( e -> { @@ -107,8 +116,17 @@ public class FlatSmoothScrollingTest 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() ) ); + // clear chart on startup + addHierarchyListener( e -> { + if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && isShowing() ) + EventQueue.invokeLater( this::clearChart ); + }); + ArrayList 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 ); items.add( new String( chars ) ); @@ -167,20 +185,47 @@ public class FlatSmoothScrollingTest } // text components - String text = items.stream().collect( Collectors.joining( "\n" ) ); - textArea.setText( text ); + String longText = items.stream().collect( Collectors.joining( " " ) ) + ' ' + + items.stream().limit( 20 ).collect( Collectors.joining( " " ) ); + String text = items.stream().collect( Collectors.joining( "\n" ) ) + '\n'; + textArea.setText( longText + '\n' + text ); textPane.setText( text ); editorPane.setText( text ); textArea.select( 0, 0 ); textPane.select( 0, 0 ); editorPane.select( 0, 0 ); + + EventQueue.invokeLater( () -> { + EventQueue.invokeLater( () -> { + list.requestFocusInWindow(); + } ); + } ); } private void smoothScrollingChanged() { UIManager.put( "ScrollPane.smoothScrolling", smoothScrollingCheckBox.isSelected() ); } + private void oneSecondWidthChanged() { + int oneSecondWidth = oneSecondWidthSlider.getValue(); + int msPerLineX = + oneSecondWidth <= 2000 ? 100 : + oneSecondWidth <= 4000 ? 50 : + oneSecondWidth <= 8000 ? 25 : + 10; + + lineChartPanel.setOneSecondWidth( oneSecondWidth ); + lineChartPanel.setMsPerLineX( msPerLineX ); + lineChartPanel.revalidate(); + lineChartPanel.repaint(); + + if( xLabelText == null ) + xLabelText = xLabel.getText(); + xLabel.setText( MessageFormat.format( xLabelText, msPerLineX ) ); + } + private String xLabelText; + private void clearChart() { lineChartPanel.clear(); } @@ -243,7 +288,13 @@ public class FlatSmoothScrollingTest panel3 = new JPanel(); chartScrollPane = new JScrollPane(); lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel(); - label1 = new JLabel(); + panel4 = new JPanel(); + xLabel = new JLabel(); + JLabel rectsLabel = new JLabel(); + JLabel yLabel = new JLabel(); + JLabel dotsLabel = new JLabel(); + JLabel oneSecondWidthLabel = new JLabel(); + oneSecondWidthSlider = new JSlider(); updateChartDelayedCheckBox = new JCheckBox(); clearChartButton = new JButton(); @@ -407,7 +458,7 @@ public class FlatSmoothScrollingTest // columns "[grow,fill]", // rows - "[300,grow,fill]")); + "[100:300,grow,fill]")); //======== chartScrollPane ======== { @@ -420,13 +471,50 @@ public class FlatSmoothScrollingTest } add(splitPane1, "cell 0 1"); - //---- label1 ---- - label1.setText("X: time (200ms per line) / Y: scroll bar value (10% per line)"); - add(label1, "cell 0 2"); + //======== panel4 ======== + { + panel4.setLayout(new MigLayout( + "insets 0,hidemode 3,gapy 0", + // columns + "[fill]para" + + "[fill]", + // rows + "[]" + + "[]")); + + //---- xLabel ---- + xLabel.setText("X: time ({0}ms per line)"); + panel4.add(xLabel, "cell 0 0"); + + //---- rectsLabel ---- + rectsLabel.setText("Rectangles: scrollbar values (mouse hover shows stack)"); + panel4.add(rectsLabel, "cell 1 0"); + + //---- yLabel ---- + yLabel.setText("Y: scroll bar value (10% per line)"); + panel4.add(yLabel, "cell 0 1"); + + //---- dotsLabel ---- + dotsLabel.setText("Dots: disabled blitting mode in JViewport"); + panel4.add(dotsLabel, "cell 1 1"); + } + add(panel4, "cell 0 2"); + + //---- oneSecondWidthLabel ---- + oneSecondWidthLabel.setText("Scale X:"); + oneSecondWidthLabel.setDisplayedMnemonic('A'); + oneSecondWidthLabel.setLabelFor(oneSecondWidthSlider); + add(oneSecondWidthLabel, "cell 0 2,alignx right,growx 0"); + + //---- oneSecondWidthSlider ---- + oneSecondWidthSlider.setMinimum(1000); + oneSecondWidthSlider.setMaximum(10000); + oneSecondWidthSlider.addChangeListener(e -> oneSecondWidthChanged()); + add(oneSecondWidthSlider, "cell 0 2,alignx right,growx 0,wmax 100"); //---- updateChartDelayedCheckBox ---- updateChartDelayedCheckBox.setText("Update chart delayed"); - updateChartDelayedCheckBox.setMnemonic('U'); + updateChartDelayedCheckBox.setMnemonic('P'); updateChartDelayedCheckBox.setSelected(true); updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged()); add(updateChartDelayedCheckBox, "cell 0 2,alignx right,growx 0"); @@ -471,7 +559,9 @@ public class FlatSmoothScrollingTest private JPanel panel3; private JScrollPane chartScrollPane; private FlatSmoothScrollingTest.LineChartPanel lineChartPanel; - private JLabel label1; + private JPanel panel4; + private JLabel xLabel; + private JSlider oneSecondWidthSlider; private JCheckBox updateChartDelayedCheckBox; private JButton clearChartButton; // JFormDesigner - End of variables declaration //GEN-END:variables @@ -490,7 +580,7 @@ public class FlatSmoothScrollingTest ScrollBarChangeHandler( DebugScrollPane scrollPane, boolean vertical, String name, Color chartColor ) { this.name = name; this.chartColor = chartColor; - this.chartColor2 = chartColor.brighter(); + this.chartColor2 = ColorFunctions.lighten( chartColor, 0.1f ); // add change listener to viewport that is invoked from JViewport.setViewPosition() scrollPane.getViewport().addChangeListener( e -> { @@ -498,8 +588,15 @@ public class FlatSmoothScrollingTest if( vertical == scrollPane.lastScrollingWasVertical && scrollPane.getViewport().getScrollMode() != JViewport.BLIT_SCROLL_MODE ) { - JScrollBar sb = vertical ? scrollPane.getVerticalScrollBar() : scrollPane.getHorizontalScrollBar(); - lineChartPanel.addValue( getChartValue( sb.getModel() ), true, chartColor ); + // calculate value from view position because scrollbar value is not yet up-to-date + JViewport viewport = scrollPane.getViewport(); + Point viewPosition = viewport.getViewPosition(); + Dimension viewSize = viewport.getViewSize(); + double value = vertical + ? ((double) viewPosition.y) / (viewSize.height - viewport.getHeight()) + : ((double) viewPosition.x) / (viewSize.width - viewport.getWidth()); + + lineChartPanel.addValue( value, true, chartColor, name ); } } ); } @@ -509,7 +606,7 @@ public class FlatSmoothScrollingTest DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource(); boolean smoothScrolling = smoothScrollingCheckBox.isSelected(); - lineChartPanel.addValue( getChartValue( m ), smoothScrolling ? chartColor : chartColor2 ); + lineChartPanel.addValue( getChartValue( m ), false, smoothScrolling ? chartColor : chartColor2, name ); long t = System.nanoTime() / 1000000; @@ -561,6 +658,20 @@ public class FlatSmoothScrollingTest 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( "---------------------------------------------" ); + } + } }; } } @@ -572,25 +683,31 @@ public class FlatSmoothScrollingTest implements Scrollable { private static final int NEW_SEQUENCE_TIME_LAG = 500; - private static final int NEW_SEQUENCE_GAP = 50; + private static final int NEW_SEQUENCE_GAP = 100; + private static final int HIT_OFFSET = 4; - private int secondWidth = 1000; + private int oneSecondWidth = 1000; + private int msPerLineX = 200; private static class Data { final double value; final boolean dot; final long time; // in milliseconds + final String name; + final Exception stack; - Data( double value, boolean dot, long time ) { + Data( double value, boolean dot, long time, String name, Exception stack ) { this.value = value; this.dot = dot; this.time = time; + this.name = name; + this.stack = stack; } @Override public String toString() { // for debugging - return String.valueOf( value ); + return "value=" + value + ", dot=" + dot + ", time=" + time + ", name=" + name; } } @@ -599,20 +716,23 @@ public class FlatSmoothScrollingTest private Color lastUsedChartColor; private boolean updateDelayed; + private final List lastPoints = new ArrayList<>(); + private final List lastDatas = new ArrayList<>(); + private double lastSystemScaleFactor = 1; + private String lastToolTipPrinted; + LineChartPanel() { int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 ); repaintTime = new Timer( resolution * 2, e -> repaintAndRevalidate() ); repaintTime.setRepeats( false ); + + ToolTipManager.sharedInstance().registerComponent( this ); } - void addValue( double value, Color chartColor ) { - addValue( value, false, chartColor ); - } - - void addValue( double value, boolean dot, Color chartColor ) { + void addValue( double value, boolean dot, Color chartColor, String name ) { List chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); - chartData.add( new Data( value, dot, System.nanoTime() / 1000000) ); + chartData.add( new Data( value, dot, System.nanoTime() / 1000000, name, new Exception() ) ); lastUsedChartColor = chartColor; @@ -635,8 +755,12 @@ public class FlatSmoothScrollingTest this.updateDelayed = updateDelayed; } - void setSecondWidth( int secondWidth ) { - this.secondWidth = secondWidth; + void setOneSecondWidth( int oneSecondWidth ) { + this.oneSecondWidth = oneSecondWidth; + } + + void setMsPerLineX( int msPerLineX ) { + this.msPerLineX = msPerLineX; } private void repaintAndRevalidate() { @@ -670,8 +794,9 @@ public class FlatSmoothScrollingTest private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { FlatUIUtils.setRenderingHints( g ); - int secondWidth = (int) (this.secondWidth * scaleFactor); + int oneSecondWidth = (int) (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() @@ -692,51 +817,51 @@ public class FlatSmoothScrollingTest } // paint vertical lines - int twoHundredMillisWidth = secondWidth / 5; - for( int i = twoHundredMillisWidth; i < width; i += twoHundredMillisWidth ) { - g.setColor( (i % secondWidth != 0) ? lineColor : lineColor2 ); - g.drawLine( i, 0, i, height ); + 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> e : color2dataMap.entrySet() ) { List chartData = e.getValue(); Color chartColor = e.getKey(); if( FlatLaf.isLafDark() ) chartColor = new HSLColor( chartColor ).adjustTone( 50 ); - Color temporaryValueColor = new Color( (chartColor.getRGB() & 0xffffff) | 0x40000000, true ); + 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; - int s1 = UIScale.scale( 1 ); - int s3 = UIScale.scale( 3 ); - - g.setColor( chartColor ); 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 ); - int dy = (int) ((height - 1) * data.value); - if( data.dot ) { - int dotx = px; - if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG ) - dotx += seqGapWidth; - g.fillRect( dotx - s1, dy - s1, s3, s3 ); - continue; - } + boolean newSeq = (data.time > ptime + NEW_SEQUENCE_TIME_LAG); + ptime = data.time; - if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { + if( newSeq ) { + // paint short horizontal line for previous sequence that has only one data point if( !first && pcount == 0 ) { - g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); - - // small vertical line to indicate data point - g.drawLine( px, py - s1, px, py + s1 ); + g.setColor( chartColor ); + g.drawLine( px, py, px + (int) Math.round( UIScale.scale( 8 ) * scaleFactor ), py ); } // start new sequence @@ -745,54 +870,69 @@ public class FlatSmoothScrollingTest px = seqX; pcount = 0; first = false; - } else { - boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); - if( isTemporaryValue ) - g.setColor( temporaryValueColor ); + 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.dot ) { + int s1 = (int) Math.round( UIScale.scale( 1 ) * scaleFactor ); + int s3 = (int) Math.round( UIScale.scale( 3 ) * scaleFactor ); + g.setColor( chartColor ); + g.fillRect( dx - s1, dy - s1, s3, s3 ); + continue; + } + + if( !newSeq ) { + if( isTemporaryValue && i > lastTemporaryValueIndex ) + isTemporaryValue = false; + + g.setColor( isTemporaryValue ? temporaryValueColor : chartColor ); // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); g.drawLine( px, py, dx, dy ); - if( isTemporaryValue ) - g.setColor( chartColor ); - - // small vertical lines to indicate data point - g.drawLine( px, py - s1, px, py + s1 ); - g.drawLine( dx, dy - s1, dx, dy + s1 ); - 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.dot ) + 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; - ptime = data.time; } } } - /** - * One or two values between two equal values are considered "temporary", - * which means that they are the target value for the following scroll animation. - */ - private boolean isTemporaryValue( List chartData, int i ) { - if( i == 0 || i == chartData.size() - 1 ) - return false; - - Data dataBefore = chartData.get( i - 1 ); - Data dataAfter = chartData.get( i + 1 ); - - if( dataBefore.dot || dataAfter.dot ) - return false; - - double valueBefore = dataBefore.value; - double valueAfter = dataAfter.value; - - return valueBefore == valueAfter || - (i < chartData.size() - 2 && valueBefore == chartData.get( i + 2 ).value) || - (i > 1 && chartData.get( i - 2 ).value == valueAfter); - } - private int chartWidth() { int width = 0; for( List chartData : color2dataMap.values() ) @@ -817,7 +957,7 @@ public class FlatSmoothScrollingTest px = seqX; } else { // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * oneSecondWidth)); px = dx; } @@ -842,7 +982,7 @@ public class FlatSmoothScrollingTest @Override public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { - return secondWidth; + return oneSecondWidth; } @Override @@ -861,6 +1001,98 @@ public class FlatSmoothScrollingTest 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( "" ); + } + + Data data = lastDatas.get( i ); + buf.append( "

" ); + if( data.dot ) + buf.append( "DOT: " ); + buf.append( data.name ).append( ' ' ).append( data.value ).append( "

" ); + + 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(); + + // ignore methods from this class + if( className.startsWith( FlatSmoothScrollingTest.class.getName() ) ) + continue; + + int repeatCount = 0; + for( int k = j + 1; k < stackTrace.length; k++ ) { + if( !stackElement.equals( stackTrace[k] ) ) + break; + repeatCount++; + } + j += repeatCount; + + // append method + buf.append( className ) + .append( "." ) + .append( methodName ) + .append( " " ); + 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( "" ); + if( repeatCount > 0 ) + buf.append( " " ).append( repeatCount + 1 ).append( "x" ); + buf.append( "
" ); + + // 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" )) ) + break; + } + buf.append( "..." ); + } + + if( buf == null ) + return null; + + buf.append( "" ); + String toolTip = buf.toString(); + + // print to console + if( !Objects.equals( toolTip, lastToolTipPrinted ) ) { + lastToolTipPrinted = toolTip; + + System.out.println( toolTip + .replace( "
", "\n" ) + .replace( "

", "\n---- " ) + .replace( "

", " ----\n" ) + .replaceAll( "<[^>]+>", "" ) ); + } + + return buf.toString(); + } } //---- class ColorIcon ---------------------------------------------------- @@ -892,6 +1124,5 @@ public class FlatSmoothScrollingTest public int getIconHeight() { return UIScale.scale( 12 ); } - } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd index f6f3c553..f316a84d 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd @@ -178,7 +178,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 3,hidemode 3" "$columnConstraints": "[grow,fill]" - "$rowConstraints": "[300,grow,fill]" + "$rowConstraints": "[100:300,grow,fill]" } ) { name: "panel3" add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { @@ -196,16 +196,71 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1" } ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "label1" - "text": "X: time (200ms per line) / Y: scroll bar value (10% per line)" + 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: "panel4" + 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: "rectsLabel" + "text": "Rectangles: scrollbar values (mouse hover shows stack)" + auxiliary() { + "JavaCodeGenerator.variableLocal": true + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "yLabel" + "text": "Y: scroll bar value (10% per line)" + auxiliary() { + "JavaCodeGenerator.variableLocal": true + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "dotsLabel" + "text": "Dots: disabled blitting mode in JViewport" + auxiliary() { + "JavaCodeGenerator.variableLocal": true + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + 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 2,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 2,alignx right,growx 0,wmax 100" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "updateChartDelayedCheckBox" "text": "Update chart delayed" - "mnemonic": 85 + "mnemonic": 80 "selected": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {