package controlP5; /** * controlP5 is a processing gui library. * * 2006-2015 by Andreas Schlegel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * * @author Andreas Schlegel (http://www.sojamo.de) * @modified ##date## * @version ##version## * */ import processing.core.PApplet; import processing.core.PGraphics; /** * A knob is a circular slider which can be used with a limited and unlimited range. Knobs come in 3 * designs LINE, ARC and ELLIPSE and can be controller with both the mouse and the mouse wheel. * * @example controllers/ControlP5knob */ public class Knob extends Controller< Knob > { protected float _myDiameter; protected float _myRadius; protected float myAngle; protected float startAngle; protected float angleRange; protected float resolution = 200.0f; // sensitivity. protected int _myTickMarksNum = 8; protected boolean isShowTickMarks; protected boolean isSnapToTickMarks; protected int myTickMarkLength = 2; protected float myTickMarkWeight = 1; protected boolean isShowAngleRange = true; protected float currentValue; protected float previousValue; protected float modifiedValue; protected boolean isConstrained; protected int _myDragDirection = HORIZONTAL; protected int viewStyle = LINE; public static int autoWidth = 39; public static int autoHeight = 39; protected float[] autoSpacing = new float[] { 10 , 20 }; private float scrollSensitivity = 1.0f / resolution; /** * Convenience constructor to extend Knob. * * @example use/ControlP5extendController * @param theControlP5 * @param theName */ public Knob( ControlP5 theControlP5 , String theName ) { this( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 100 , 0 , 0 , 0 , autoWidth ); theControlP5.register( theControlP5.papplet , theName , this ); } /** * @exclude */ public Knob( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theMin , float theMax , float theDefaultValue , int theX , int theY , int theWidth ) { super( theControlP5 , theParent , theName , theX , theY , theWidth , theWidth ); _myValue = theDefaultValue; setMin( theMin ); setMax( theMax ); _myDiameter = theWidth; _myRadius = _myDiameter / 2; _myUnit = ( _myMax - _myMin ) / ControlP5Constants.TWO_PI; startAngle = HALF_PI + PI * 0.25f; angleRange = PI + HALF_PI; myAngle = startAngle; isConstrained = true; getCaptionLabel( ).align( CENTER , BOTTOM_OUTSIDE ); setViewStyle( ARC ); } @Override public Knob setSize( int theWidth , int theHeight ) { return setRadius( theWidth / 2 ); } public Knob setRadius( float theValue ) { _myRadius = theValue; _myDiameter = _myRadius * 2; setWidth( ( int ) _myDiameter ); setHeight( ( int ) _myDiameter ); return this; } public float getRadius( ) { return _myRadius; } /** * The start angle is a value between 0 and TWO_PI. By default the start angle is set to HALF_PI * + PI * 0.25f */ public Knob setStartAngle( float theAngle ) { startAngle = theAngle; setInternalValue( modifiedValue ); return this; } /** * get the start angle, 0 is at 3 o'clock. */ public float getStartAngle( ) { return startAngle; } /** * set the range in between which the know operates. By default the range is PI + HALF_PI */ public Knob setAngleRange( float theRange ) { angleRange = theRange; setInternalValue( modifiedValue ); return this; } public float getAngleRange( ) { return angleRange; } public float getAngle( ) { return myAngle; } public boolean isShowAngleRange( ) { return isShowAngleRange; } public Knob setShowAngleRange( boolean theValue ) { isShowAngleRange = theValue; return this; } /** * Sets the drag direction, when controlling a knob, parameter is either Controller.HORIZONTAL * or Controller.VERTICAL. * * @param theValue * must be Controller.HORIZONTAL or Controller.VERTICAL * @return Knob */ public Knob setDragDirection( int theValue ) { if ( theValue == HORIZONTAL ) { _myDragDirection = HORIZONTAL; } else { _myDragDirection = VERTICAL; } return this; } /** * Gets the drag direction which is either Controller.HORIZONTAL or Controller.VERTICAL. * * @return int returns Controller.HORIZONTAL or Controller.VERTICAL */ public int getDragDirection( ) { return _myDragDirection; } /** * resolution is a sensitivity value when dragging a knob. the higher the value, the more * sensitive the dragging. */ public Knob setResolution( float theValue ) { resolution = theValue; return this; } public float getResolution( ) { return resolution; } public Knob setNumberOfTickMarks( int theNumber ) { _myTickMarksNum = theNumber; showTickMarks( ); return this; } public int getNumberOfTickMarks( ) { return _myTickMarksNum; } public Knob showTickMarks( ) { isShowTickMarks = true; return this; } public Knob hideTickMarks( ) { isShowTickMarks = false; return this; } public boolean isShowTickMarks( ) { return isShowTickMarks; } public Knob snapToTickMarks( boolean theFlag ) { isSnapToTickMarks = theFlag; update( ); return this; } public Knob setTickMarkLength( int theLength ) { myTickMarkLength = theLength; return this; } public int getTickMarkLength( ) { return myTickMarkLength; } public Knob setTickMarkWeight( float theWeight ) { myTickMarkWeight = theWeight; return this; } public float getTickMarkWeight( ) { return myTickMarkWeight; } public Knob setConstrained( boolean theValue ) { isConstrained = theValue; if ( !isConstrained ) { setShowAngleRange( false ); } else { setShowAngleRange( true ); } return this; } public boolean isConstrained( ) { return isConstrained; } /** * @exclude */ @Override @ControlP5.Invisible public Knob updateInternalEvents( PApplet theApplet ) { if ( isMousePressed && !cp5.isAltDown( ) ) { if ( isActive ) { float c = ( _myDragDirection == HORIZONTAL ) ? _myControlWindow.mouseX - _myControlWindow.pmouseX : _myControlWindow.mouseY - _myControlWindow.pmouseY; currentValue += ( c ) / resolution; if ( isConstrained ) { currentValue = PApplet.constrain( currentValue , 0 , 1 ); } setInternalValue( currentValue ); } } return this; } protected void onEnter( ) { isActive = true; } protected void onLeave( ) { isActive = false; } /** * @exclude {@inheritDoc} */ @Override @ControlP5.Invisible public void mousePressed( ) { float x = x(_myParent.getAbsolutePosition( )) + x(position) + _myRadius; float y = y(_myParent.getAbsolutePosition( )) + y(position) + _myRadius; if ( PApplet.dist( x , y , _myControlWindow.mouseX , _myControlWindow.mouseY ) < _myRadius ) { isActive = true; if ( PApplet.dist( x , y , _myControlWindow.mouseX , _myControlWindow.mouseY ) > ( _myRadius * 0.6 ) ) { myAngle = ( PApplet.atan2( _myControlWindow.mouseY - y , _myControlWindow.mouseX - x ) - startAngle ); if ( myAngle < 0 ) { myAngle = TWO_PI + myAngle; } if ( isConstrained ) { myAngle %= TWO_PI; } currentValue = PApplet.map( myAngle , 0 , angleRange , 0 , 1 ); setInternalValue( currentValue ); } } } /** * @exclude {@inheritDoc} */ @Override @ControlP5.Invisible public void mouseReleasedOutside( ) { isActive = false; } @Override public Knob setMin( float theValue ) { _myMin = theValue; return this; } @Override public Knob setMax( float theValue ) { _myMax = theValue; return this; } public Knob setRange( float theMin , float theMax ) { setMin( theMin ); setMax( theMax ); update( ); return this; } protected void setInternalValue( float theValue ) { modifiedValue = ( isSnapToTickMarks ) ? PApplet.round( ( theValue * _myTickMarksNum ) ) / ( ( float ) _myTickMarksNum ) : theValue; currentValue = theValue; myAngle = PApplet.map( isSnapToTickMarks == true ? modifiedValue : currentValue , 0 , 1 , startAngle , startAngle + angleRange ); if ( isSnapToTickMarks ) { if ( previousValue != modifiedValue && isSnapToTickMarks ) { broadcast( FLOAT ); _myValueLabel.set( adjustValue( getValue( ) ) ); previousValue = modifiedValue; return; } } if ( previousValue != currentValue ) { broadcast( FLOAT ); _myValueLabel.set( adjustValue( getValue( ) ) ); previousValue = modifiedValue; } } @Override public Knob setValue( float theValue ) { theValue = PApplet.map( theValue , _myMin , _myMax , 0 , 1 ); if ( isConstrained ) { theValue = PApplet.constrain( theValue , 0 , 1 ); } _myValueLabel.set( adjustValue( getValue( ) ) ); setInternalValue( theValue ); return this; } @Override public float getValue( ) { _myValue = PApplet.map( _myTickMarksNum > 0 ? modifiedValue : currentValue , 0 , 1 , _myMin , _myMax ); return _myValue; } /** * Assigns a random value to the controller. */ public Knob shuffle( ) { float r = ( float ) Math.random( ); setValue( PApplet.map( r , 0 , 1 , getMin( ) , getMax( ) ) ); return this; } /** * Sets the sensitivity for the scroll behavior when using the mouse wheel or the scroll * function of a multi-touch track pad. The smaller the value (closer to 0) the higher the * sensitivity. * * @param theValue * @return Knob */ public Knob setScrollSensitivity( float theValue ) { scrollSensitivity = theValue; return this; } /** * Changes the value of the knob when hovering and using the mouse wheel or the scroll function * of a multi-touch track pad. */ @ControlP5.Invisible public Knob scrolled( int theRotationValue ) { float f = getValue( ); float steps = isSnapToTickMarks ? ( 1.0f / getNumberOfTickMarks( ) ) : scrollSensitivity; f += ( getMax( ) - getMin( ) ) * ( -theRotationValue * steps ); setValue( f ); return this; } /** * @exclude */ @Override @ControlP5.Invisible public Knob update( ) { setValue( _myValue ); return this; } /** * set the display style of a knob. takes parameters Knob.LINE, Knob.ELLIPSE or Knob.ARC. * default style is Knob.LINE * * @param theStyle * use Knob.LINE, Knob.ELLIPSE or Knob.ARC * @return Knob */ public Knob setViewStyle( int theStyle ) { viewStyle = theStyle; return this; } public int getViewStyle( ) { return viewStyle; } /** * @exclude {@inheritDoc} */ @Override @ControlP5.Invisible public Knob updateDisplayMode( int theMode ) { _myDisplayMode = theMode; switch ( theMode ) { case ( DEFAULT ): _myControllerView = new KnobView( ); break; case ( SPRITE ): case ( IMAGE ): _myControllerView = new KnobView( ); break; case ( CUSTOM ): default: break; } return this; } class KnobView implements ControllerView< Knob > { public void display( PGraphics theGraphics , Knob theController ) { theGraphics.translate( ( int ) getRadius( ) , ( int ) getRadius( ) ); theGraphics.pushMatrix( ); theGraphics.ellipseMode( PApplet.CENTER ); theGraphics.noStroke( ); theGraphics.fill( getColor( ).getBackground( ) ); theGraphics.ellipse( 0 , 0 , getRadius( ) * 2 , getRadius( ) * 2 ); theGraphics.popMatrix( ); int c = isActive( ) ? getColor( ).getActive( ) : getColor( ).getForeground( ); theGraphics.pushMatrix( ); if ( getViewStyle( ) == Controller.LINE ) { theGraphics.rotate( getAngle( ) ); theGraphics.stroke( c ); theGraphics.strokeWeight( getTickMarkWeight( ) ); theGraphics.line( 0 , 0 , getRadius( ) , 0 ); } else if ( getViewStyle( ) == Controller.ELLIPSE ) { theGraphics.rotate( getAngle( ) ); theGraphics.fill( c ); theGraphics.ellipse( getRadius( ) * 0.75f , 0 , getRadius( ) * 0.2f , getRadius( ) * 0.2f ); } else if ( getViewStyle( ) == Controller.ARC ) { theGraphics.fill( c ); theGraphics.arc( 0 , 0 , getRadius( ) * 1.8f , getRadius( ) * 1.8f , getStartAngle( ) , getAngle( ) + ( ( getStartAngle( ) == getAngle( ) ) ? 0.06f : 0f ) ); theGraphics.fill( theGraphics.red( getColor( ).getBackground( ) ) , theGraphics.green( getColor( ).getBackground( ) ) , theGraphics.blue( getColor( ).getBackground( ) ) , 255 ); theGraphics.ellipse( 0 , 0 , getRadius( ) * 1.2f , getRadius( ) * 1.2f ); } theGraphics.popMatrix( ); theGraphics.pushMatrix( ); theGraphics.rotate( getStartAngle( ) ); if ( isShowTickMarks( ) ) { float step = getAngleRange( ) / getNumberOfTickMarks( ); theGraphics.stroke( getColor( ).getForeground( ) ); theGraphics.strokeWeight( getTickMarkWeight( ) ); for ( int i = 0 ; i <= getNumberOfTickMarks( ) ; i++ ) { theGraphics.line( getRadius( ) + 2 , 0 , getRadius( ) + getTickMarkLength( ) + 2 , 0 ); theGraphics.rotate( step ); } } else { if ( isShowAngleRange( ) ) { theGraphics.stroke( getColor( ).getForeground( ) ); theGraphics.strokeWeight( getTickMarkWeight( ) ); theGraphics.line( getRadius( ) + 2 , 0 , getRadius( ) + getTickMarkLength( ) + 2 , 0 ); theGraphics.rotate( getAngleRange( ) ); theGraphics.line( getRadius( ) + 2 , 0 , getRadius( ) + getTickMarkLength( ) + 2 , 0 ); } } theGraphics.noStroke( ); theGraphics.popMatrix( ); theGraphics.pushMatrix( ); theGraphics.translate( -getWidth( ) / 2 , -getHeight( ) / 2 ); if ( isLabelVisible ) { _myCaptionLabel.draw( theGraphics , 0 , 0 , theController ); _myValueLabel.align( ControlP5.CENTER , ControlP5.CENTER ); _myValueLabel.draw( theGraphics , 0 , 0 , theController ); } theGraphics.popMatrix( ); } } /** * @exclude * @deprecated */ @Deprecated public Knob setOffsetAngle( float theValue ) { return setStartAngle( theValue ); } /** * @exclude * @deprecated */ @Deprecated public float value( ) { return getValue( ); } /** * @exclude * @deprecated */ @Deprecated public Knob setDisplayStyle( int theStyle ) { viewStyle = theStyle; return this; } /** * @exclude * @deprecated */ @Deprecated public int getDisplayStyle( ) { return viewStyle; } /** * @exclude * @deprecated */ @Deprecated @ControlP5.Invisible public Knob setSensitivity( float theValue ) { scrollSensitivity = theValue; return this; } /** * @exclude * @deprecated */ @Deprecated public Knob showTickMarks( boolean theFlag ) { isShowTickMarks = theFlag; return this; } } /* settings for: * * TODO tickmarks: distance from edge * * TODO only start-end marks if isLimited and tickmarks are off. * * TODO arc: add setter for distance to center + distance to edge currently percental. * * TODO enable/disable drag and click control (for endless, click should be disabled). * * TODO dragging: add another option to control the knob. currently only linear dragging is * implemented, add circular dragging (as before) as well */ /* (non-Javadoc) * * @see controlP5.Controller#updateInternalEvents(processing.core.PApplet) */