From 5294ca6d1eb58499b8fec6ca08df616f9c319906 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 9 Oct 2020 16:01:45 +0200 Subject: [PATCH] FlatAnimatorTest: added test for wheel scrolling (including chart) --- .../flatlaf/testing/FlatAnimatorTest.java | 121 ++++++++++++++++++ .../flatlaf/testing/FlatAnimatorTest.jfd | 58 ++++++++- .../testing/FlatSmoothScrollingTest.java | 51 ++++++-- 3 files changed, 216 insertions(+), 14 deletions(-) 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 831a132d7..a8456db4c 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 @@ -17,7 +17,12 @@ 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.testing.FlatSmoothScrollingTest.LineChartPanel; +import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; import net.miginfocom.swing.*; @@ -40,6 +45,9 @@ public static void main( String[] args ) { FlatAnimatorTest() { initComponents(); + + lineChartPanel.setSecondWidth( 500 ); + mouseWheelTestPanel.lineChartPanel = lineChartPanel; } private void start() { @@ -72,6 +80,14 @@ private void startEaseInOut() { } } + private void updateChartDelayedChanged() { + lineChartPanel.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() ); + } + + private void clearChart() { + lineChartPanel.clear(); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JLabel label1 = new JLabel(); @@ -79,6 +95,13 @@ private void initComponents() { JLabel label2 = new JLabel(); easeInOutScrollBar = new JScrollBar(); startButton = new JButton(); + JLabel label3 = new JLabel(); + mouseWheelTestPanel = new FlatAnimatorTest.MouseWheelTestPanel(); + JScrollPane scrollPane1 = new JScrollPane(); + lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel(); + JLabel label4 = new JLabel(); + updateChartDelayedCheckBox = new JCheckBox(); + JButton clearChartButton = new JButton(); //======== this ======== setLayout(new MigLayout( @@ -89,6 +112,10 @@ private void initComponents() { // rows "[]" + "[]" + + "[]" + + "[]" + + "[top]" + + "[400,grow,fill]" + "[]")); //---- label1 ---- @@ -113,6 +140,37 @@ private void initComponents() { startButton.setText("Start"); startButton.addActionListener(e -> start()); add(startButton, "cell 0 2"); + + //---- label3 ---- + label3.setText("Mouse wheel test:"); + add(label3, "cell 0 4"); + + //---- mouseWheelTestPanel ---- + mouseWheelTestPanel.setBorder(new LineBorder(Color.red)); + add(mouseWheelTestPanel, "cell 1 4,height 100"); + + //======== scrollPane1 ======== + { + scrollPane1.setViewportView(lineChartPanel); + } + add(scrollPane1, "cell 0 5 2 1"); + + //---- label4 ---- + label4.setText("X: time (500ms per line) / Y: value (10% per line)"); + add(label4, "cell 0 6 2 1"); + + //---- updateChartDelayedCheckBox ---- + updateChartDelayedCheckBox.setText("Update chart delayed"); + updateChartDelayedCheckBox.setMnemonic('U'); + updateChartDelayedCheckBox.setSelected(true); + updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged()); + add(updateChartDelayedCheckBox, "cell 0 6 2 1,alignx right,growx 0"); + + //---- clearChartButton ---- + clearChartButton.setText("Clear Chart"); + clearChartButton.setMnemonic('C'); + clearChartButton.addActionListener(e -> clearChart()); + add(clearChartButton, "cell 0 6 2 1,alignx right,growx 0"); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -120,5 +178,68 @@ private void initComponents() { private JScrollBar linearScrollBar; private JScrollBar easeInOutScrollBar; private JButton startButton; + private FlatAnimatorTest.MouseWheelTestPanel mouseWheelTestPanel; + private FlatSmoothScrollingTest.LineChartPanel lineChartPanel; + private JCheckBox updateChartDelayedCheckBox; // 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( value / (double) MAX_VALUE, Color.red ); + }, () -> { + targetValue = -1; + } ); + animator.setResolution( resolution ); + animator.setInterpolator( new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) ); + + addMouseWheelListener( this ); + } + + @Override + public void mouseWheelMoved( MouseWheelEvent e ) { + lineChartPanel.addValue( 0.5 + (e.getWheelRotation() / 10.), true, Color.red ); + + // start next animation at the current value + startValue = value; + + // increase/decrease target value if animation is in progress + targetValue = (targetValue < 0 ? value : targetValue) + (STEP * e.getWheelRotation()); + targetValue = Math.min( Math.max( targetValue, 0 ), MAX_VALUE ); + + // restart animator + animator.cancel(); + animator.start(); + } + } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd index bb92a1299..fdb1849f3 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.2.0.298" Java: "14.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.2.0.298" Java: "15" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -9,7 +9,7 @@ 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": "[][][][][top][400,grow,fill][]" } ) { name: "this" add( new FormComponent( "javax.swing.JLabel" ) { @@ -54,9 +54,61 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label3" + "text": "Mouse wheel test:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + 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 4,height 100" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "scrollPane1" + add( new FormComponent( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$LineChartPanel" ) { + name: "lineChartPanel" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5 2 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label4" + "text": "X: time (500ms per line) / Y: value (10% per line)" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "updateChartDelayedCheckBox" + "text": "Update chart delayed" + "mnemonic": 85 + "selected": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6 2 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 6 2 1,alignx right,growx 0" + } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 415, 350 ) + "size": new java.awt.Dimension( 625, 625 ) } ) } } 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 1aff8f4cc..632f8d27d 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 @@ -460,20 +460,23 @@ public Point getViewPosition() { //---- class LineChartPanel ----------------------------------------------- - private static class LineChartPanel + static class LineChartPanel extends JComponent implements Scrollable { - private static final int SECOND_WIDTH = 200; private static final int NEW_SEQUENCE_TIME_LAG = 500; private static final int NEW_SEQUENCE_GAP = 20; + private int secondWidth = 1000; + private static class Data { final double value; + final boolean dot; final long time; // in milliseconds - Data( double value, long time ) { + Data( double value, boolean dot, long time ) { this.value = value; + this.dot = dot; this.time = time; } @@ -497,8 +500,12 @@ public String toString() { } void addValue( double value, Color chartColor ) { + addValue( value, false, chartColor ); + } + + void addValue( double value, boolean dot, Color chartColor ) { List chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); - chartData.add( new Data( value, System.nanoTime() / 1000000) ); + chartData.add( new Data( value, dot, System.nanoTime() / 1000000) ); lastUsedChartColor = chartColor; @@ -521,6 +528,10 @@ void setUpdateDelayed( boolean updateDelayed ) { this.updateDelayed = updateDelayed; } + void setSecondWidth( int secondWidth ) { + this.secondWidth = secondWidth; + } + private void repaintAndRevalidate() { repaint(); revalidate(); @@ -546,7 +557,7 @@ protected void paintComponent( Graphics g ) { private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { FlatUIUtils.setRenderingHints( g ); - int secondWidth = (int) (SECOND_WIDTH * scaleFactor); + int secondWidth = (int) (this.secondWidth * scaleFactor); int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray ); @@ -591,20 +602,32 @@ private void paintImpl( Graphics2D g, int x, int y, int width, int height, doubl g.setColor( chartColor ); + boolean first = true; 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; + int o = UIScale.scale( 1 ); + int s = UIScale.scale( 3 ); + g.fillRect( dotx - o, dy - o, s, s ); + continue; + } + if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { - if( i > 0 && pcount == 0 ) + if( !first && pcount == 0 ) g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); // start new sequence seqTime = data.time; - seqX = (i > 0) ? px + seqGapWidth : 0; + seqX = !first ? px + seqGapWidth : 0; px = seqX; pcount = 0; + first = false; } else { boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); if( isTemporaryValue ) @@ -634,8 +657,14 @@ private boolean isTemporaryValue( List chartData, int i ) { if( i == 0 || i == chartData.size() - 1 ) return false; - double valueBefore = chartData.get( i - 1 ).value; - double valueAfter = chartData.get( i + 1 ).value; + 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) || @@ -666,7 +695,7 @@ private int chartWidth( List chartData, int[] lastSeqX ) { px = seqX; } else { // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * SECOND_WIDTH)); + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); px = dx; } @@ -691,7 +720,7 @@ public Dimension getPreferredScrollableViewportSize() { @Override public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { - return SECOND_WIDTH; + return secondWidth; } @Override