/* * CurvePanel.java * Eisenkraut * * Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.eisenkraut.gui; import javax.swing.*; import javax.swing.event.MouseInputAdapter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.util.prefs.Preferences; /** * This class describes a generic GUI tool * that can be aquired and dismissed by * a <code>Component</code>. */ @SuppressWarnings("serial") public class CurvePanel extends JComponent { private static final String KEY_CTRLX1 = "ctrlx1"; private static final String KEY_CTRLY1 = "ctrly1"; private static final String KEY_CTRLX2 = "ctrlx2"; private static final String KEY_CTRLY2 = "ctrly2"; // private static final String[] KEYS = { KEY_CTRLX1, KEY_CTRLY1, KEY_CTRLX2, KEY_CTRLY2 }; private final CubicCurve2D[] shpFades; private final AffineTransform at = new AffineTransform(); protected static final Insets insets = new Insets( 1, 1, 1, 1 ); private static final Shape shpCtrlIn = new Ellipse2D.Double( -2, -2, 5, 5 ); private static final Area shpCtrlOut; private static final Paint pntCtrlInLight = new Color(0x00, 0x00, 0x00, 0x7F); private static final Paint pntCtrlOutLight = new Color(0x00, 0x00, 0x00, 0x3F); private static final Paint pntCtrlOutSLight = new Color(0x00, 0x00, 0xFF, 0x7F); private static final Paint pntCtrlInDark = new Color(0xFF, 0xFF, 0xFF, 0x7F); private static final Paint pntCtrlOutDark = new Color(0xFF, 0xFF, 0xFF, 0x3F); private static final Paint pntCtrlOutSDark = new Color(0x7F, 0x7F, 0xFF, 0x7F); private final Paint pntCtrlIn, pntCtrlOut, pntCtrlOutS; private final Shape[] tShpFades; protected final Point2D[] ctrlPt = new Point2D[]{ new Point2D.Double(0.5, 0.5), new Point2D.Double(0.5, 0.5)}; protected int recentWidth = -1; protected int recentHeight = -1; protected boolean recalculate = true; protected Point2D dragPt = null; private ActionListener actionListener = null; static { shpCtrlOut = new Area(new Ellipse2D.Double(-7, -7, 15, 15)); shpCtrlOut.subtract(new Area(new Ellipse2D.Double(-4, -4, 9, 9))); } public CurvePanel(CubicCurve2D[] basicCurves, final Preferences prefs) { super(); final Dimension d = new Dimension(64, 64); final boolean isDark = GraphicsUtil.isDarkSkin(); pntCtrlIn = isDark ? pntCtrlInDark : pntCtrlInLight; pntCtrlOut = isDark ? pntCtrlOutDark : pntCtrlOutLight; pntCtrlOutS = isDark ? pntCtrlOutSDark : pntCtrlOutSLight; shpFades = basicCurves; tShpFades = new Shape[shpFades.length]; if (prefs != null) { ctrlPt[0].setLocation(prefs.getDouble(KEY_CTRLX1, 0.5), prefs.getDouble(KEY_CTRLY1, 0.5)); ctrlPt[1].setLocation(prefs.getDouble(KEY_CTRLX2, 0.5), prefs.getDouble(KEY_CTRLY2, 0.5)); } setMinimumSize(d); setPreferredSize(d); setBorder(BorderFactory.createEmptyBorder(insets.top, insets.left, insets.bottom, insets.right)); MouseInputAdapter mia = new MouseInputAdapter() { private boolean didThemDragga = false; public void mousePressed( MouseEvent e ) { Point2D mousePt = getVirtualMousePos( e ); if( mousePt.distanceSq( ctrlPt[ 0 ]) <= mousePt.distanceSq( ctrlPt[ 1 ])) { dragPt = ctrlPt[ 0 ]; } else { dragPt = ctrlPt[ 1 ]; } processDrag( mousePt, !e.isControlDown() ); } public void mouseReleased( MouseEvent e ) { dragPt = null; repaint(); if( didThemDragga ) { dispatchAction(); didThemDragga = false; } } private Point2D getVirtualMousePos( MouseEvent e ) { return new Point2D.Double( Math.max( 0.0, Math.min( 1.0, (double) (e.getX() - insets.left) / recentWidth )), 1.0 - Math.max( 0.0, Math.min( 1.0, (double) (e.getY() - insets.top) / recentHeight ))); } public void mouseMoved( MouseEvent e ) { mouseDragged( e ); // mouseDragged not called with popup dialog! } public void mouseDragged( MouseEvent e ) { if( dragPt != null ) processDrag( getVirtualMousePos( e ), !e.isControlDown() ); } private void processDrag( Point2D mousePt, boolean snap ) { didThemDragga = true; if( snap ) { if( Math.abs( mousePt.getX() - 0.5 ) < 0.1 ) mousePt.setLocation( 0.5, mousePt.getY() ); if( Math.abs( mousePt.getY() - 0.5 ) < 0.1 ) mousePt.setLocation( mousePt.getX(), 0.5 ); } dragPt.setLocation( mousePt ); if( prefs != null ) { if( dragPt == ctrlPt[0] ) { prefs.putDouble( KEY_CTRLX1, dragPt.getX() ); prefs.putDouble( KEY_CTRLY1, dragPt.getY() ); } else { prefs.putDouble( KEY_CTRLX2, dragPt.getX() ); prefs.putDouble( KEY_CTRLY2, dragPt.getY() ); } } recalculate = true; repaint(); } }; addMouseListener( mia ); addMouseMotionListener( mia ); } public void addActionListener( ActionListener l ) { synchronized( this ) { actionListener = AWTEventMulticaster.add( actionListener, l ); } } public void removeActionListener( ActionListener l ) { synchronized( this ) { actionListener = AWTEventMulticaster.remove( actionListener, l ); } } protected void dispatchAction() { final ActionListener listener = actionListener; if( listener != null ) { listener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null )); } } public Point2D[] getControlPoints() { return new Point2D[] { new Point2D.Double( ctrlPt[ 0 ].getX(), ctrlPt[ 0 ].getY() ), new Point2D.Double( ctrlPt[ 1 ].getX(), ctrlPt[ 1 ].getY() )}; } public void setControlPoints( Point2D ctrlPt1, Point2D ctrlPt2 ) { ctrlPt[0].setLocation( ctrlPt1.getX(), ctrlPt1.getY() ); ctrlPt[1].setLocation( ctrlPt2.getX(), ctrlPt2.getY() ); recalculate = true; repaint(); } // this is static to allow calculations // without actually creating GUI elements public static Point2D[] getControlPoints( Preferences prefs ) { return new Point2D[] { new Point2D.Double( prefs.getDouble( KEY_CTRLX1, 0.5 ), prefs.getDouble( KEY_CTRLY1, 0.5 )), new Point2D.Double( prefs.getDouble( KEY_CTRLX2, 0.5 ), prefs.getDouble( KEY_CTRLY2, 0.5 )) }; } public static void toPrefs( Point2D[] ctrlPt, Preferences prefs ) { prefs.putDouble( KEY_CTRLX1, ctrlPt[0].getX() ); prefs.putDouble( KEY_CTRLY1, ctrlPt[0].getY() ); prefs.putDouble( KEY_CTRLX2, ctrlPt[1].getX() ); prefs.putDouble( KEY_CTRLY2, ctrlPt[1].getY() ); } public void toPrefs( Preferences prefs ) { toPrefs( ctrlPt, prefs ); } public void paintComponent( Graphics g ) { super.paintComponent( g ); final Graphics2D g2 = (Graphics2D) g; final int currentWidth = getWidth() - insets.left - insets.right; final int currentHeight = getHeight() - insets.top - insets.bottom; final AffineTransform atOrig = g2.getTransform(); double trnsX, trnsY; if( (currentWidth != recentWidth) || (currentHeight != recentHeight) || recalculate) { recentWidth = currentWidth; recentHeight = currentHeight; at.setToScale( currentWidth, -currentHeight ); at.translate( 0, -1.0 ); recalculateTransforms(); } g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); g2.translate( insets.left, insets.top ); for (Shape tShpFade : tShpFades) { g2.draw(tShpFade); } for (Point2D aCtrlPt : ctrlPt) { trnsX = aCtrlPt.getX() * currentWidth; trnsY = (1.0 - aCtrlPt.getY()) * currentHeight; g2.translate(trnsX, trnsY); g2.setPaint(pntCtrlIn); g2.fill(shpCtrlIn); if (aCtrlPt == dragPt) { g2.setPaint(pntCtrlOutS); } else { g2.setPaint(pntCtrlOut); } g2.fill(shpCtrlOut); g2.translate(-trnsX, -trnsY); } g2.setTransform(atOrig); } private void recalculateTransforms() { // if( prefs != null ) { // ctrlPt[0].setLocation( prefs.getDouble( KEY_CTRLX1, 0.5 ), prefs.getDouble( KEY_CTRLY1, 0.5 )); // ctrlPt[1].setLocation( prefs.getDouble( KEY_CTRLX2, 0.5 ), prefs.getDouble( KEY_CTRLY2, 0.5 )); // } for( int i = 0; i < shpFades.length; i++ ) { // shpFades[ i ].setCurve( shpFades[ i ].getP1(), ctrlPt[ i % 2 ], ctrlPt[ (i+1) % 2 ], shpFades[ i ].getP2() ); shpFades[ i ].setCurve( shpFades[ i ].getX1(), shpFades[ i ].getY1(), ctrlPt[ 0 ].getX(), ctrlPt[ i % 2 ].getY(), ctrlPt[ 1 ].getX(), ctrlPt[ (i+1) % 2 ].getY(), shpFades[ i ].getX2(), shpFades[ i ].getY2() ); tShpFades[ i ] = at.createTransformedShape( shpFades[ i ]); } recalculate = false; } public static class Icon implements javax.swing.Icon { private final int width; private final int height; private final AffineTransform at; private final CubicCurve2D[] shpFades; private final Shape[] tShpFades; private Stroke strk = null; private final Point2D[] ctrlPt = new Point2D[]{ new Point2D.Double(0.5, 0.5), new Point2D.Double(0.5, 0.5)}; public Icon(CubicCurve2D[] basicCurves) { this(basicCurves, 16, 16); } public Icon(CubicCurve2D[] basicCurves, int width, int height) { this.width = width; this.height = height; at = AffineTransform.getScaleInstance(width, -height); at.translate(0, -1.0); shpFades = basicCurves; tShpFades = new Shape[shpFades.length]; } public int getIconWidth() { return width; } public int getIconHeight() { return height; } public void update(Point2D ctrl1, Point2D ctrl2) { ctrlPt[0].setLocation(ctrl1.getX(), ctrl1.getY()); ctrlPt[1].setLocation(ctrl2.getX(), ctrl2.getY()); for (int i = 0; i < shpFades.length; i++) { shpFades[i].setCurve(shpFades[i].getX1(), shpFades[i].getY1(), ctrlPt[0].getX(), ctrlPt[i % 2].getY(), ctrlPt[1].getX(), ctrlPt[(i + 1) % 2].getY(), shpFades[i].getX2(), shpFades[i].getY2()); tShpFades[i] = at.createTransformedShape(shpFades[i]); } } public void setStroke(Stroke stroke) { this.strk = stroke; } public Stroke getStroke(Stroke stroke) { return strk; } public void paintIcon(Component c, Graphics g, int x, int y) { final Graphics2D g2 = (Graphics2D) g; final AffineTransform atOrig = g2.getTransform(); final Stroke strkOrig = g2.getStroke(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.translate(x, y); g2.setColor(c.getForeground()); if (strk != null) g2.setStroke(strk); for (Shape tShpFade : tShpFades) { g2.draw(tShpFade); } g2.setTransform(atOrig); g2.setStroke(strkOrig); } } }