controlp5/src/controlP5/Knob.java

573 lines
15 KiB
Java
Raw Normal View History

package controlP5;
/**
* controlP5 is a processing gui library.
*
2016-04-14 12:39:16 +02:00
* 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) */