/* Part of the G4P library for Processing http://www.lagers.org.uk/g4p/index.html http://sourceforge.net/projects/g4p/files/?source=navbar Copyright (c) 2012 Peter Lager 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 */ package automenta.vivisect.gui; import processing.core.PApplet; /** * Base class for all slider and knob type controls. * * This class enables the creation of tick marks and constraining values to * the tick mark values. <br> * * It also controls how the values are to be displayed INTEGER, DECIMAL or EXPONENT * * @author Peter Lager * */ public abstract class GValueControl extends GControl { protected StyledString ssStartLimit, ssEndLimit, ssValue; protected float startLimit = 0, endLimit = 1; protected boolean showLimits = false; protected int valueType = DECIMAL; protected int precision = 2; protected String unit = ""; protected boolean showValue = false; protected float parametricPos = 0.5f, parametricTarget = 0.5f; protected float easing = 1.0f; // must be >= 1.0 protected int nbrTicks = 2; protected boolean stickToTicks = false; protected boolean showTicks = false; protected boolean limitsInvalid = true; // Offset to between mouse and thumb centre protected float offset; public GValueControl(PApplet theApplet, float p0, float p1, float p2, float p3) { super(theApplet, p0, p1, p2, p3); } public void pre(){ if(Math.abs(parametricTarget - parametricPos) > epsilon){ parametricPos += (parametricTarget - parametricPos) / easing; updateDueToValueChanging(); bufferInvalid = true; if(Math.abs(parametricTarget - parametricPos) > epsilon){ fireEvent(this, GEvent.VALUE_CHANGING); } else { parametricPos = parametricTarget; fireEvent(this, GEvent.VALUE_STEADY); } } } /** * This should be overridden in child classes so they can perform any class specific * actions when the value changes. * Override this in GSlider to change the hotshot poaition. */ protected void updateDueToValueChanging(){ } /** * Used to format the number into a string for display. * @param number * @return a string representing the number */ protected String getNumericDisplayString(float number){ String s = ""; switch(valueType){ case INTEGER: s = String.format("%d %s", Math.round(number), unit); break; case DECIMAL: s = String.format("%." + precision + "f %s", number, unit); break; case EXPONENT: s = String.format("%." + precision + "e %s", number, unit); break; } return s.trim(); } /** * Sets the range of values to be returned. This method will * assume that you want to set the valueType to INTEGER * * @param start the start value of the range * @param end the end value of the range */ public void setLimits(int start, int end){ startLimit = start; endLimit = end; setEpsilon(); valueType = INTEGER; limitsInvalid = true; bufferInvalid = true; } /** * Sets the initial value and the range of values to be returned. This * method will assume that you want to set the valueType to INTEGER. * * @param initValue the initial value * @param start the start value of the range * @param end the end value of the range */ public void setLimits(int initValue, int start, int end){ startLimit = start; endLimit = end; valueType = INTEGER; setEpsilon(); limitsInvalid = true; bufferInvalid = true; setValue(initValue); parametricPos = parametricTarget; updateDueToValueChanging(); } /** * Sets the range of values to be returned. This method will * assume that you want to set the valueType to DECIMAL * * @param start * @param end */ public void setLimits(float start, float end){ startLimit = start; endLimit = end; if(valueType == INTEGER){ valueType = DECIMAL; setPrecision(1); } setEpsilon(); limitsInvalid = true; bufferInvalid = true; } /** * Sets the initial value and the range of values to be returned. This * method will assume that you want to set the valueType to DECIMAL. * * @param initValue the initial value * @param start the start value of the range * @param end the end value of the range */ public void setLimits(float initValue, float start, float end){ startLimit = start; endLimit = end; initValue = PApplet.constrain(initValue, start, end); if(valueType == INTEGER){ valueType = DECIMAL; setPrecision(1); } setEpsilon(); limitsInvalid = true; bufferInvalid = true; setValue(initValue); parametricPos = parametricTarget; updateDueToValueChanging(); } /** * Set the value for the slider. <br> * The user must ensure that the value is valid for the slider range. * @param v */ public void setValue(float v){ if(valueType == INTEGER) v = Math.round(v); float t = (v - startLimit) / (endLimit - startLimit); t = PApplet.constrain(t, 0.0f, 1.0f); if(stickToTicks) t = findNearestTickValueTo(t); parametricTarget = t; } /** * For DECIMAL values this sets the number of decimal places to * be displayed. * @param nd must be >= 1 otherwise will use 1 */ public void setPrecision(int nd){ nd = PApplet.constrain(nd, 1, 5); if(nd < 1) nd = 1; if(nd != precision){ precision = nd; setEpsilon(); limitsInvalid = true; bufferInvalid = true; } } /** * Make epsilon to match the value of 1 pixel or the precision which ever is the smaller */ protected void setEpsilon(){ epsilon = (float) Math.min(0.001, Math.pow(10, -precision)); } /** * The units to be displayed with the current and limit values e.g. * kg, m, ($), fps etc. <br> * Do not use long labels such as 'miles per hour' as these take a * lot of space and can look messy. * * @param units for example kg, m, ($), fps */ public void setUnits(String units){ if(units == null) units = ""; if(!unit.equals(units)){ unit = units; limitsInvalid = true; bufferInvalid = true; } } /** * Set the numberFormat, precision and units in one go. <br> * Valid number formats are INTEGER, DECIMAL, EXPONENT <br> * Precision must be >= 1 and is ignored for INTEGER. * * @param numberFormat INTEGER, DECIMAL or EXPONENT * @param precision must be >= 1 * @param unit for example kg, m, ($), fps */ public void setNumberFormat(int numberFormat, int precision, String unit){ this.unit = (unit == null) ? "" : unit; setNumberFormat(numberFormat, precision); } /** * Set the numberFormat and precision in one go. <br> * Valid number formats are INTEGER, DECIMAL, EXPONENT <br> * Precision must be >= 1 and is ignored for INTEGER. * * @param numberFormat G4P.INTEGER, G4P.DECIMAL orG4P. EXPONENT * @param precision must be >= 1 */ public void setNumberFormat(int numberFormat, int precision){ switch(numberFormat){ case INTEGER: case DECIMAL: case EXPONENT: this.valueType = numberFormat; break; default: valueType = DECIMAL; } setPrecision(precision); bufferInvalid = true; } /** * Set the numberFormat and precision in one go. <br> * Valid number formats are INTEGER, DECIMAL, EXPONENT <br> * Precision must be >= 1 and is ignored for INTEGER. * * @param numberFormat G4P.INTEGER, G4P.DECIMAL or G4P.EXPONENT */ public void setNumberFormat(int numberFormat){ switch(numberFormat){ case INTEGER: case DECIMAL: case EXPONENT: this.valueType = numberFormat; break; default: valueType = DECIMAL; } bufferInvalid = true; } /** * Get the current value as a float */ public float getValueF(){ return startLimit + (endLimit - startLimit) * parametricPos; } /** * Get the current value as an integer. <br> * DECIMAL and EXPONENT value types will be rounded to the nearest integer. */ public int getValueI(){ return Math.round(startLimit + (endLimit - startLimit) * parametricPos); } /** * If we are using labels then this will get the label text * associated with the current value. <br> * If labels have not been set then return null */ public String getValueS(){ return getNumericDisplayString(getValueF()); } /** * Get the current value used for easing. * @return the easing */ public float getEasing() { return easing; } /** * Set the amount of easing to be used when a value is changing. The default value * is 1 (no easing) values > 1 will cause the value to rush from its starting value * and decelerate towards its final values. In other words it smoothes the movement * of the slider thumb or knob rotation. * * @param easeBy the easing to set */ public void setEasing(float easeBy) { easing = (easeBy < 1) ? 1 : easeBy; } /** * Get the number of tick marks. * @return the nbrTicks */ public int getNbrTicks() { return nbrTicks; } /** * The number of ticks must be >= 2 since 2 are required for the slider limits. * * @param noOfTicks the nbrTicks to set */ public void setNbrTicks(int noOfTicks) { if(noOfTicks < 2) noOfTicks = 2; if(nbrTicks != noOfTicks){ nbrTicks = noOfTicks; bufferInvalid = true; if(stickToTicks) parametricTarget = findNearestTickValueTo(parametricPos); } } /** * Is the value constrained to the tick marks? * @return the stickToTicks true if values constrained else false */ public boolean isStickToTicks() { return stickToTicks; } /** * Specify whether the values are to be constrained to the tick marks or not. * It will automatically display tick marks if set true. * @param stickToTicks true if you want to constrain the values else false */ public void setStickToTicks(boolean stickToTicks) { this.stickToTicks = stickToTicks; if(stickToTicks){ setShowTicks(true); parametricTarget = findNearestTickValueTo(parametricPos); bufferInvalid = true; } } /** * These are normalised values i.e. between 0.0 and 1.0 inclusive * @param p * @return the parametric value of the nearest tick */ protected float findNearestTickValueTo(float p){ float tickSpace = 1.0f / (nbrTicks - 1); int tn = (int) (p / tickSpace + 0.5f); return tickSpace * tn; } /** * Are the tick marks visible? * @return the showTicks */ public boolean isShowTicks() { return showTicks; } /** * Set whether the tick marks are to be displayed or not. * @param showTicks the showTicks to set */ public void setShowTicks(boolean showTicks) { if(this.showTicks != showTicks){ this.showTicks = showTicks; bufferInvalid = true; } } /** * Are the limit values visible? * @return the showLimits */ public boolean isShowLimits() { return showLimits; } /** * Set whether the limits are to be displayed or not. * @param showLimits the showLimits to set */ public void setShowLimits(boolean showLimits) { this.showLimits = showLimits; bufferInvalid = true; } /** * Is the current value to be displayed? * @return the showValue */ public boolean isShowValue() { return showValue; } /** * Set whether the current value is to be displayed or not. * @param showValue the showValue to set */ public void setShowValue(boolean showValue) { this.showValue = showValue; bufferInvalid = true; } /** * Convenience method to set what is to be drawn to the screen. * @param opaque show background * @param ticks show tick marks * @param value show current value * @param limits show min and max values (limits) */ public void setShowDecor(boolean opaque, boolean ticks, boolean value, boolean limits){ setShowValue(value); bufferInvalid = true; setOpaque(opaque); setShowTicks(ticks); setShowLimits(limits); } /** * @return the startLimit */ public float getStartLimit() { return startLimit; } /** * @return the endLimit */ public float getEndLimit() { return endLimit; } /** * * @return the valueType */ public int getValueType() { return valueType; } /** * Precision used with floating point numbers * @return the precision */ public int getPrecision() { return precision; } /** * @return the unit */ public String getUnit() { return unit; } }