/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.components.visibleComponents.widgets; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import org.mt4j.components.TransformSpace; import org.mt4j.components.visibleComponents.StyleInfo; import org.mt4j.components.visibleComponents.shapes.AbstractShape; import org.mt4j.components.visibleComponents.shapes.MTEllipse; import org.mt4j.components.visibleComponents.shapes.MTRectangle; import org.mt4j.components.visibleComponents.shapes.MTRoundRectangle; import org.mt4j.input.inputProcessors.IGestureEventListener; import org.mt4j.input.inputProcessors.MTGestureEvent; import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor; import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapEvent; import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor; import org.mt4j.util.MTColor; import org.mt4j.util.math.ToolsMath; import org.mt4j.util.math.Tools3D; import org.mt4j.util.math.Vector3D; import processing.core.PApplet; import processing.core.PImage; /** * A slider widget. Allows to select a value between the specified minnimum and maximum values. * To recieve notice of a value change we can add a propertyChangeListener object to it. * <br>NOTE: The silder has to be created horizontally but can afterwards be rotated freely. * To get a vertical slider, just rotate the slider locally around 90 degrees. * * @author Christopher Ruff */ public class MTSlider extends MTRectangle { /** The horizontal. */ private boolean horizontal; /** The outer shape. */ private AbstractShape outerShape; /** The slider. */ private AbstractShape knob; /** The max value. */ private float maxValue; /** The min value. */ private float minValue; /** The value range. */ private float valueRange; /** The x. */ private float x; /** The y. */ private float y; /** The inner padding. */ private float innerPadding; /** The property change support. */ private PropertyChangeSupport propertyChangeSupport; private PApplet app; //TODO display a MTLine for the knob to slide on? /** * Instantiates a new mT slider. * * @param _x the _x * @param _y the _y * @param width the width * @param height the height * @param minValue the min value * @param maxValue the max value * @param applet the applet */ public MTSlider(float _x, float _y, float width, float height, float minValue, float maxValue, PApplet applet) { super(_x, _y, width, height, applet); this.app = applet; this.propertyChangeSupport = new PropertyChangeSupport(this); if (minValue> maxValue){ System.err.println("Minimum value is bigger than the maximum value in " + this); } this.x = _x; this.y = _y; this.minValue = minValue; this.maxValue = maxValue; this.valueRange = maxValue-minValue; this.setNoFill(true); this.setNoStroke(true); this.setPickable(false); float knobWidth; float knobHeight; horizontal = true; //FIXME only horizontal is implemented at the moment if (horizontal){ innerPadding = 2; knobHeight = height - 2*innerPadding; //Check if we can make the knob as broad as the slider height so we get a circle knob //But if the height is too big the knob width would exceed the sliders width if (height >= width){ //We check if slider height > slider width => not good! knobWidth = (width - 2*innerPadding)*0.4f; // if (knobWidth >= 2*innerPadding) //FIXME REMOVE?? why is that here.. // knobWidth = 1.5f; // while (knobWidth >= 2*innerPadding){ // knobWidth *= 0.9f; // } }else{ knobWidth = knobHeight; } }else{ innerPadding = 5; knobWidth = width - 2*innerPadding; knobHeight = knobWidth; } // outerShape = new MTRectangle(x,y, width, height, applet); outerShape = new MTRoundRectangle(x,y,0, width, height, knobWidth/2f + innerPadding, knobHeight/2f + innerPadding, applet); outerShape.unregisterAllInputProcessors(); //When we click on the outershape move the knob in that direction a certain step outerShape.registerInputProcessor(new TapProcessor(applet, 35)); outerShape.addGestureListener(TapProcessor.class, new IGestureEventListener() { public boolean processGestureEvent(MTGestureEvent ge) { TapEvent te = (TapEvent)ge; switch (te.getTapID()) { case TapEvent.BUTTON_CLICKED: Vector3D screenPos = te.getLocationOnScreen(); Vector3D intersection = outerShape.getIntersectionGlobal(Tools3D.getCameraPickRay(app, outerShape, screenPos.x, screenPos.y)); if (intersection != null){ //Get the intersection point into knob local relative space Vector3D localClickPos = knob.globalToLocal(intersection); Vector3D knobCenterLocal = knob.getCenterPointLocal(); float range = getValueRange(); float step = range/5f; //Arbitrary step value float oldValue = getValue(); if (localClickPos.x < knobCenterLocal.x){ setValue(oldValue - step); //Move knob Left }else if (localClickPos.x > knobCenterLocal.x){ setValue(oldValue + step); //Move knob Right } } break; } return false; } }); // knob = new MTRectangle(x+innerOffset, y+innerOffset, knobWidth, knobHeight, applet); knob = new MTEllipse(applet, new Vector3D(0,0,0), knobWidth*0.5f, knobHeight*0.5f); knob.setFillColor(new MTColor(140, 140, 140, 255)); AbstractComponentProcessor[] inputPs = knob.getInputProcessors(); for (int i = 0; i < inputPs.length; i++) { AbstractComponentProcessor p = inputPs[i]; if (!(p instanceof DragProcessor)){ knob.unregisterInputProcessor(p); } } knob.removeAllGestureEventListeners(DragProcessor.class); outerShape.addChild(knob); this.addChild(outerShape); //TODO these have to be updated if knob or outershape are changed // final float knobWidthRelParent = knob.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); // final float knobHeightRelParent = knob.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); // final float outerWidthLocal = outerShape.getWidthXY(TransformSpace.LOCAL); knob.addGestureListener(DragProcessor.class, new IGestureEventListener() { //@Override public boolean processGestureEvent(MTGestureEvent ge) { DragEvent de = (DragEvent)ge; Vector3D dir = new Vector3D(de.getTranslationVect()); //Transform the global direction vector into knob local coordiante space dir.transformDirectionVector(knob.getGlobalInverseMatrix()); float oldValue = getValue(); if (horizontal){ knob.translate(new Vector3D(dir.x,0,0), TransformSpace.LOCAL); float knobWidthRelParent = knob.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); float knobHeightRelParent = knob.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); float outerWidthLocal = outerShape.getWidthXY(TransformSpace.LOCAL); Vector3D knobCenterRelToParent = knob.getCenterPointRelativeToParent(); //Cap the movement at both ends of the slider if( (knobCenterRelToParent.x + knobWidthRelParent*0.5f) > (x + outerWidthLocal - innerPadding) ){ //FIXME we could insetead just set the new value with setValue() // System.out.println("OUT OF BOUNDS RIGHT ->"); Vector3D pos = new Vector3D( x + outerWidthLocal - innerPadding -knobWidthRelParent*0.5f, y + knobHeightRelParent*0.5f + innerPadding, 0); // pos.transform(outerShape.getGlobalMatrix()); // knob.setPositionGlobal(pos); pos.transform(outerShape.getLocalMatrix()); knob.setPositionRelativeToParent(pos); }else if( (knobCenterRelToParent.x - knobWidthRelParent*0.5f ) < (x + innerPadding) ){ // System.out.println("OUT OF BOUNDS LEFT <-"); Vector3D pos = new Vector3D( x + knobWidthRelParent*0.5f + innerPadding, y + knobHeightRelParent*0.5f + innerPadding, 0); // pos.transform(outerShape.getGlobalMatrix()); // knob.setPositionGlobal(pos); pos.transform(outerShape.getLocalMatrix()); knob.setPositionRelativeToParent(pos); } // System.out.println("Slider value: " + getValue()); //Fire property change event if (propertyChangeSupport.hasListeners("value")){ propertyChangeSupport.firePropertyChange("value", oldValue, getValue()); } }else{ knob.translate(new Vector3D(0,dir.y,0), TransformSpace.LOCAL); } return false; } }); //Default - Sets the current value to be the middle between min and max value (needed or knob may not appear correctly) this.setValue((minValue+maxValue)/2f); } /** * Gets the value. * * @return the value */ public float getValue(){ float outerShapeWidthLocal = outerShape.getWidthXY(TransformSpace.LOCAL); float knobWidthRelParent = knob.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); float leftMostPossibleKnobPosX = x + innerPadding + knobWidthRelParent * 0.5f; float rightMostPossibleKnobPosX = x + outerShapeWidthLocal - innerPadding - knobWidthRelParent * 0.5f; //in outershape local coords float slideableArea = rightMostPossibleKnobPosX-leftMostPossibleKnobPosX; float knobPosX = knob.getCenterPointRelativeToParent().x; float sliderAreaToValueAreaRatio = valueRange/slideableArea; float knobCurr = knobPosX-leftMostPossibleKnobPosX; float valueCurr = minValue + knobCurr * sliderAreaToValueAreaRatio; valueCurr = ToolsMath.clamp(valueCurr, minValue, maxValue); /* System.out.println("sliderCurr: " + sliderCurr); System.out.println("ValueCurr: " + valueCurrr); */ /*//Show begin and end point of slider works only unrotated MTEllipse e1 = new MTEllipse(this.getRenderer(), new Vector3D(leftMostPossibleSliderPosX, y + slider.getHeightXY(TransformSpace.RELATIVE_TO_PARENT)*0.5f),5,5); this.getRoot().addChild(e1); MTEllipse e2 = new MTEllipse(this.getRenderer(), new Vector3D(rightMostPossibleSliderPosX, y + slider.getHeightXY(TransformSpace.RELATIVE_TO_PARENT)*0.5f),5,5); this.getRoot().addChild(e2); */ return valueCurr; } /** * Sets the value. * * @param value the new value */ public void setValue(float value){ if (value > maxValue){ value = maxValue; }else if (value < minValue){ value = minValue; } float oldValue = this.getValue(); float outerShapeWidthLocal = outerShape.getWidthXY(TransformSpace.LOCAL); float knobWidthRelParent = knob.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); float leftMostPossibleknobPosX = x + innerPadding + knobWidthRelParent * 0.5f; float rightMostPossibleknobPosX = x + outerShapeWidthLocal - innerPadding - knobWidthRelParent * 0.5f; //in outershape local coords float slideableRange = rightMostPossibleknobPosX-leftMostPossibleknobPosX; float valueRangeToSliderValueRange = slideableRange/valueRange; float valueOffsetFromMinValue = Math.abs(value - minValue); // float knobValue = minValue + value*valueAreaToSliderValueArea; float knobAdvanceFromLeftValue = valueOffsetFromMinValue * valueRangeToSliderValueRange; Vector3D pos = new Vector3D( x + innerPadding + knobWidthRelParent*0.5f + knobAdvanceFromLeftValue , y + knob.getHeightXY(TransformSpace.RELATIVE_TO_PARENT)*0.5f + innerPadding, 0); // pos.transform(outerShape.getGlobalMatrix()); // knob.setPositionGlobal(pos); pos.transform(outerShape.getLocalMatrix()); knob.setPositionRelativeToParent(pos); // System.out.println("slidervalue: " + sliderAdvanceFromLeftValue); //Fire property change event if (propertyChangeSupport.hasListeners("value")){ this.propertyChangeSupport.firePropertyChange("value", oldValue, this.getValue()); } } /** * Sets the value range. * * @param min the min * @param max the max */ public void setValueRange(float min, float max){ if (minValue > maxValue){ System.err.println("Minimum value is bigger than the maximum value in " + this); } float oldValue = this.getValue(); float oldMin = this.minValue; float oldValueRange = valueRange; this.minValue = min; this.maxValue = max; this.valueRange = maxValue-minValue; //Keeping the relative slider andvancement float newValue = minValue + (valueRange * ((oldValue-oldMin)/oldValueRange)); this.setValue(newValue); // this.setCurrentValue(minValue); //Fire property change event if (propertyChangeSupport.hasListeners("valueRange")){ this.propertyChangeSupport.firePropertyChange("valueRange", oldValueRange, valueRange); } } /** * Gets the value range. * * @return the value range */ public float getValueRange(){ return this.valueRange; } public float getMaxValue() { return this.maxValue; } public float getMinValue() { return this.minValue; } /** * Gets the outer shape. * * @return the outer shape */ public AbstractShape getOuterShape() { return outerShape; } /** * Gets the knob. * * @return the slider */ public AbstractShape getKnob() { return knob; } /** * Adds the property change listener. * The slider supports listening to the following properties changes. * <li>"value" * <li>"valueRange" * * @param propertyName the property name * @param listener the listener */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } /** * Gets the property change listeners. * * @param propertyName the property name * * @return the property change listeners */ public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { return propertyChangeSupport.getPropertyChangeListeners(propertyName); } /** * Removes the property change listener. * * @param propertyName the property name * @param listener the listener */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } // DELEGATE APPEARANCE TO OUTERSHAPE SINCE THE SLIDER ITSELF ISNT DISPLAYED! @Override public void setFillColor(MTColor color) { super.setFillColor(color); this.getOuterShape().setFillColor(color); } @Override public void setStrokeColor(MTColor strokeColor) { super.setStrokeColor(strokeColor); this.getOuterShape().setStrokeColor(strokeColor); } @Override public void setStyleInfo(StyleInfo styleInfo) { super.setStyleInfo(styleInfo); this.getOuterShape().setStyleInfo(styleInfo); } @Override public void setTexture(PImage newTexImage) { // super.setTexture(newTexImage); this.getOuterShape().setTexture(newTexImage); } }