/*
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 java.awt.Graphics2D;
import java.awt.font.TextLayout;
import processing.core.PApplet;
import processing.event.MouseEvent;
/**
* Base class for linear sliders.
*
* This class provides the ability to control the orientation
* the text should be displayed. It also enables the use of labels
* for each tick mark.
*
* @author Peter Lager
*
*/
public abstract class GLinearTrackControl extends GValueControl {
static protected float TINSET = 2;
static protected int THUMB_SPOT = 1;
static protected int TRACK_SPOT = 2;
protected float trackWidth, trackLength, trackDisplayLength;
protected float trackOffset;
protected int textOrientation = ORIENT_TRACK;
protected int downHotSpot = -1;
// Mouse over status
protected int status = OFF_CONTROL;
// For labels
protected StyledString[] labels;
protected boolean labelsInvalid = true;
public GLinearTrackControl(PApplet theApplet, float p0, float p1, float p2, float p3) {
super(theApplet, p0, p1, p2, p3);
}
/**
* Set the text orientation for the display of the limits and value if appropriate. <br>
* Acceptable values are G4P.ORIENT_LEFT, G4P.ORIENT_RIGHT or G4P.ORIENT_TRACK <br>
* If an invalid value is passed the ORIENT_TRACK is used.
*
* @param orient the orientation of the number labels
*/
public void setTextOrientation(int orient){
switch(orient){
case ORIENT_LEFT:
case ORIENT_RIGHT:
case ORIENT_TRACK:
textOrientation = orient;
break;
default:
textOrientation = ORIENT_TRACK;
}
bufferInvalid = true;
}
/**
* This method is used to set the text to appear alongside the tick marks. <br>
* The array passed must have a minimum of 2 elements and each label (element)
* must have at least 1 character. If these two conditions are not met then
* the call to this method will be ignored and no changes are made.
*
* @param tickLabels an array of strings for the labels
*/
public void setTickLabels(String[] tickLabels){
if(tickLabels == null || tickLabels.length < 2)
return;
for(String s : tickLabels)
if(s == null || s.length() == 0)
return;
labels = new StyledString[tickLabels.length];
for(int i = 0; i < tickLabels.length; i++)
labels[i] = new StyledString(tickLabels[i]);
stickToTicks = true;
nbrTicks = labels.length;
startLimit = 0;
endLimit = nbrTicks - 1;
valueType = INTEGER;
showLimits = false;
showValue = false;
bufferInvalid = true;
}
/**
* Set whether the tick marks are to be displayed or not. Then
* recalculate the track offset for the value and limit text.
* @param showTicks the showTicks to set
*/
public void setShowTicks(boolean showTicks) {
super.setShowTicks(showTicks);
float newTrackOffset = calcTrackOffset();
if(newTrackOffset != trackOffset){
trackOffset = newTrackOffset;
bufferInvalid = true;
}
bufferInvalid = true;
}
/**
* Calculates the amount of offset for the labels
*/
protected float calcTrackOffset(){
return (showTicks) ? trackWidth + 2 : trackWidth/2 + 2;
}
/**
* The offset is the distance the value/labels are drawn from the
* centre of the track. <br>
* You may wish to tweak this value for visual effect.
* @param offset
*/
public void setTrackOffset(float offset){
trackOffset = offset;
}
/**
* Get the visual offset for the value/label text.
*/
public float getTrackOffset(){
return trackOffset;
}
/**
* 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(){
// Use the valueTarget rather than the valuePos since intermediate values
// have no meaning in this case.
int idx = Math.round(startLimit + (endLimit - startLimit) * parametricTarget);
return (labels == null) ? getNumericDisplayString(getValueF()) : labels[idx].getPlainText();
}
public void mouseEvent(MouseEvent event){
if(!visible || !enabled || !available) return;
calcTransformedOrigin(winApp.getCursorX(), winApp.getCursorY());
currSpot = whichHotSpot(ox, oy);
// Normalise ox and oy to the centre of the slider
ox -= width/2;
ox /= trackLength;
if(currSpot >= 0 || focusIsWith == this)
cursorIsOver = this;
else if(cursorIsOver == this)
cursorIsOver = null;
switch(event.getAction()){
case MouseEvent.PRESS:
if(focusIsWith != this && currSpot > -1 && z >= focusObjectZ()){
downHotSpot = currSpot;
status = (downHotSpot == THUMB_SPOT) ? PRESS_CONTROL : OFF_CONTROL;
offset = ox + 0.5f - parametricPos; // normalised
takeFocus();
bufferInvalid = true;
}
break;
case MouseEvent.CLICK:
if(focusIsWith == this ){
parametricTarget = ox + 0.5f;
if(stickToTicks)
parametricTarget = findNearestTickValueTo(parametricTarget);
dragging = false;
status = OFF_CONTROL;
loseFocus(null);
bufferInvalid = true;
}
break;
case MouseEvent.RELEASE:
if(focusIsWith == this && dragging){
if(downHotSpot == THUMB_SPOT){
parametricTarget = (ox - offset) + 0.5f;
if(parametricTarget < 0){
parametricTarget = 0;
offset = 0;
}
else if(parametricTarget > 1){
parametricTarget = 1;
offset = 0;
}
if(stickToTicks)
parametricTarget = findNearestTickValueTo(parametricTarget);
}
status = OFF_CONTROL;
bufferInvalid = true;
loseFocus(null);
}
dragging = false;
break;
case MouseEvent.DRAG:
if(focusIsWith == this){
status = DRAG_CONTROL;
dragging = true;
if(downHotSpot == THUMB_SPOT){
parametricTarget = (ox - offset) + 0.5f;
if(parametricTarget < 0){
parametricTarget = 0;
offset = 0;
}
else if(parametricTarget > 1){
parametricTarget = 1;
offset = 0;
}
bufferInvalid = true;
}
}
break;
case MouseEvent.MOVE:
int currStatus = status;
// If dragged state will stay as PRESSED
if(currSpot == THUMB_SPOT)
status = OVER_CONTROL;
else
status = OFF_CONTROL;
if(currStatus != status)
bufferInvalid = true;
break;
}
}
public void draw(){
if(!visible) return;
// Update buffer if invalid
updateBuffer();
winApp.pushStyle();
winApp.pushMatrix();
// Perform the rotation
applyTransform();
winApp.pushMatrix();
// Move matrix to line up with top-left corner
winApp.translate(-halfWidth, -halfHeight);
// Draw buffer
winApp.imageMode(PApplet.CORNER);
if(alphaLevel < 255)
winApp.tint(TINT_FOR_ALPHA, alphaLevel);
winApp.image(buffer, 0, 0);
winApp.popMatrix();
winApp.popMatrix();
winApp.popStyle();
}
protected void drawValue(){
Graphics2D g2d = buffer.g2;
float px, py;
TextLayout line;
ssValue = new StyledString(getNumericDisplayString(getValueF()));
line = ssValue.getLines(g2d).getFirst().layout;
float advance = line.getVisibleAdvance();
switch(textOrientation){
case ORIENT_LEFT:
px = (parametricPos - 0.5f) * trackLength + line.getDescent();
py = -trackOffset;
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(-PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
break;
case ORIENT_RIGHT:
px = (parametricPos - 0.5f) * trackLength - line.getDescent();
py = - trackOffset - advance;
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
break;
case ORIENT_TRACK:
px = (parametricPos - 0.5f) * trackLength - advance /2;
if(px < -trackDisplayLength/2)
px = -trackDisplayLength/2;
else if(px + advance > trackDisplayLength /2)
px = trackDisplayLength/2 - advance;
py = -trackOffset - line.getDescent();
line.draw(g2d, px, py );
line = ssEndLimit.getLines(g2d).getFirst().layout;
break;
}
}
protected void drawLimits(){
Graphics2D g2d = buffer.g2;
float px, py;
TextLayout line;
if(limitsInvalid){
ssStartLimit = new StyledString(getNumericDisplayString(startLimit));
ssEndLimit = new StyledString(getNumericDisplayString(endLimit));
limitsInvalid = false;
}
switch(textOrientation){
case ORIENT_LEFT:
line = ssStartLimit.getLines(g2d).getFirst().layout;
px = -trackLength/2 + line.getDescent();
py = trackOffset + line.getVisibleAdvance();
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(-PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
line = ssEndLimit.getLines(g2d).getFirst().layout;
px = trackLength/2 + line.getDescent();
py = trackOffset + line.getVisibleAdvance();
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(-PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
break;
case ORIENT_RIGHT:
line = ssStartLimit.getLines(g2d).getFirst().layout;
px = -trackLength/2 - line.getDescent();
py = trackOffset;
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
line = ssEndLimit.getLines(g2d).getFirst().layout;
px = trackLength/2 - line.getDescent();
py = trackOffset;
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
break;
case ORIENT_TRACK:
line = ssStartLimit.getLines(g2d).getFirst().layout;
px = -(trackLength + trackWidth)/2;
py = trackOffset + line.getAscent();
line.draw(g2d, px, py );
line = ssEndLimit.getLines(g2d).getFirst().layout;
px = (trackLength + trackWidth)/2 - line.getVisibleAdvance();
py = trackOffset + line.getAscent();
line.draw(g2d, px, py );
break;
}
}
protected void drawLabels(){
Graphics2D g2d = buffer.g2;
float px, py;
TextLayout line;
if(labelsInvalid){
ssStartLimit = new StyledString(getNumericDisplayString(startLimit));
ssEndLimit = new StyledString(getNumericDisplayString(endLimit));
limitsInvalid = false;
}
float deltaX = 1.0f / (nbrTicks - 1);
switch(textOrientation){
case ORIENT_LEFT:
for(int i = 0; i < labels.length; i++){
line = labels[i].getLines(g2d).getFirst().layout;
px = (i * deltaX - 0.5f)*trackLength + line.getDescent();
py = trackOffset + line.getVisibleAdvance();
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(-PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
}
break;
case ORIENT_RIGHT:
for(int i = 0; i < labels.length; i++){
line = labels[i].getLines(g2d).getFirst().layout;
px = (i * deltaX - 0.5f)*trackLength - line.getDescent();
py = trackOffset;
buffer.pushMatrix();
buffer.translate(px, py);
buffer.rotate(PI/2);
line.draw(g2d, 0, 0 );
buffer.popMatrix();
}
break;
case ORIENT_TRACK:
for(int i = 0; i < labels.length; i++){
line = labels[i].getLines(g2d).getFirst().layout;
px = (i * deltaX - 0.5f)*trackLength - 0.5f * line.getVisibleAdvance();
py = trackOffset + line.getAscent();
line.draw(g2d, px, py );
}
break;
}
}
}