/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.ui.hud.widgets;
import java.util.ArrayList;
import org.jagatoo.input.devices.components.MouseButton;
import org.openmali.types.twodee.Dim2f;
import org.openmali.types.twodee.Dim2i;
import org.openmali.vecmath2.Colorf;
import org.xith3d.scenegraph.Texture2D;
import org.xith3d.scenegraph.Texture2DCanvas;
import org.xith3d.ui.hud.HUD;
import org.xith3d.ui.hud.base.Widget;
import org.xith3d.ui.hud.listeners.SliderListener;
import org.xith3d.ui.hud.utils.DrawUtils;
import org.xith3d.ui.hud.utils.HUDTextureUtils;
import org.xith3d.ui.hud.utils.TileMode;
/**
* A simple Slider implementation used to select a certain value from a range.
*
* @author Amos Wenger (aka BlueSky)
* @author Marvin Froehlich (aka Qudus)
*/
public class Slider extends Widget
{
/**
* This class is used to describe a (set of) Slider Widget(s). You can
* pass it to the Slider constructor. Modifications on the used instance
* after creating the Slider Widget won't have any effect.
*
* @author Marvin Froehlich (aka Qudus)
*/
public static class Description extends Widget.DescriptionBase
{
private Texture2D leftTexture;
private Texture2D bodyTexture;
private Texture2D valueMarkTexture;
private Texture2D rightTexture;
private int height;
private Texture2D handleTexture;
private int handleYOffset;
private boolean smoothSliding;
/**
* Sets the height.
*
* @param height the new height
*/
public void setHeight( int height )
{
this.height = height;
}
/**
* @return the height
*/
public final int getHeight()
{
return ( height );
}
/**
* Sets the texture to use for the left end.
*
* @param texture the texture
*/
public void setLeftTexture( Texture2D texture )
{
this.leftTexture = texture;
}
/**
* Sets the texture to use for the left end.
*
* @param texture the texture
*/
public void setLeftTexture( String texture )
{
setLeftTexture( HUDTextureUtils.getTexture( texture, true ) );
}
/**
* @return the texture to use for the left end.
*/
public final Texture2D getLeftTexture()
{
return ( leftTexture );
}
/**
* Sets the texture to use for the right end.
*
* @param texture the texture
*/
public void setRightTexture( Texture2D texture )
{
this.rightTexture = texture;
}
/**
* Sets the texture to use for the right end.
*
* @param texture the texture
*/
public void setRightTexture( String texture )
{
setRightTexture( HUDTextureUtils.getTexture( texture, true ) );
}
/**
* @return the texture to use for the left end.
*/
public final Texture2D getRightTexture()
{
return ( rightTexture );
}
/**
* Sets the texture to use for the body.
*
* @param texture the texture
*/
public void setBodyTexture( Texture2D texture )
{
this.bodyTexture = texture;
}
/**
* Sets the texture to use for the body.
*
* @param texture the texture
*/
public void setBodyTexture( String texture )
{
setBodyTexture( HUDTextureUtils.getTexture( texture, true ) );
}
/**
* @return the texture to use for the body.
*/
public final Texture2D getBodyTexture()
{
return ( bodyTexture );
}
/**
* Sets the texture to use for the value mark.
*
* @param texture the texture
*/
public void setValueMarkTexture( Texture2D texture )
{
this.valueMarkTexture = texture;
}
/**
* Sets the texture to use for the value mark.
*
* @param texture the texture
*/
public void setValueMarkTexture( String texture )
{
setValueMarkTexture( HUDTextureUtils.getTexture( texture, true ) );
}
/**
* @return the texture to use for the value mark.
*/
public final Texture2D getValueMarkTexture()
{
return ( valueMarkTexture );
}
/**
* Sets the texture to use for the handle.
*
* @param texture the texture
*/
public void setHandleTexture( Texture2D texture )
{
this.handleTexture = texture;
}
/**
* Sets the texture to use for the handle.
*
* @param texture the texture
*/
public void setHandleTexture( String texture )
{
setHandleTexture( HUDTextureUtils.getTexture( texture, true ) );
}
/**
* @return the texture to use for the handle.
*/
public final Texture2D getHandleTexture()
{
return ( handleTexture );
}
/**
* Sets the handle button's y-offset.
*
* @param yOffset the y-offset of the handle button
*/
public void setHandleButtonYOffset( int yOffset )
{
this.handleYOffset = yOffset;
}
/**
* @return the handle button's y-offset
*/
public final int getHandleButtonYOffset()
{
return ( handleYOffset );
}
/**
* If true, the handle doesn't snap to discrete positiones depending on
* the available scroll values.
*
* @param b enable/disable
*/
public void setSmoothSliding( boolean b )
{
this.smoothSliding = b;
}
/**
* @return true, if the handle doesn't snap to discrete positiones depending on
* the available scroll values.
*/
public final boolean getSmoothSliding()
{
return ( smoothSliding );
}
/**
* Sets all values of this Description to the values of the given
* Description.
*
* @param desc the original to be duplicated
*/
public void set( Description desc )
{
this.height = desc.height;
this.leftTexture = desc.leftTexture;
this.bodyTexture = desc.bodyTexture;
this.valueMarkTexture = desc.valueMarkTexture;
this.rightTexture = desc.rightTexture;
this.handleTexture = desc.handleTexture;
this.handleYOffset = desc.handleYOffset;
this.smoothSliding = desc.smoothSliding;
}
/**
* @return a Clone of this Scrollbar.Description.
*/
@Override
public Description clone()
{
return ( new Description( this ) );
}
/**
* Clone-Constructor.
*
* @param desc the original to be duplicated
*/
private Description( Description desc )
{
this.set( desc );
}
/**
* Creates a new Slider.Description.
*
* @param height the height
* @param leftTexture the texture to use for the left end
* @param rightTexture the texture to use for the right end
* @param bodyTexture the texture to use for the body (tiled)
* @param valueMarkTexture the texture to use for the value mark (repeated)
* @param handleTexture the handle texture to use
* @param handleYOffset the y-offset for the handle button
* @param smoothSliding use smooth sliding?
*/
public Description( int height, Texture2D leftTexture, Texture2D rightTexture, Texture2D bodyTexture, Texture2D valueMarkTexture, Texture2D handleTexture, int handleYOffset, boolean smoothSliding )
{
this.height = height;
this.leftTexture = leftTexture;
this.bodyTexture = bodyTexture;
this.rightTexture = rightTexture;
this.valueMarkTexture = valueMarkTexture;
this.handleTexture = handleTexture;
this.handleYOffset = handleYOffset;
this.smoothSliding = smoothSliding;
}
/**
* Creates a new Slider.Description.
*
* @param height the height
* @param leftTexture the texture to use for the left end
* @param rightTexture the texture to use for the right end
* @param bodyTexture the texture to use for the body (tiled)
* @param valueMarkTexture the texture to use for the value mark (repeated)
* @param handleTexture the handle texture to use
* @param handleYOffset the y-offset for the handle button
* @param smoothSliding use smooth sliding?
*/
public Description( int height, String leftTexture, String rightTexture, String bodyTexture, String valueMarkTexture, String handleTexture, int handleYOffset, boolean smoothSliding )
{
this( height, HUDTextureUtils.getTexture( leftTexture, true ), HUDTextureUtils.getTexture( rightTexture, true ), HUDTextureUtils.getTexture( bodyTexture, true ), HUDTextureUtils.getTexture( valueMarkTexture, true ), HUDTextureUtils.getTexture( handleTexture, true ), handleYOffset, smoothSliding );
}
}
private int heightInPx;
private Texture2D leftTex;
private Texture2D rightTex;
private Texture2D bodyTex;
private Texture2D valueMarkTex;
private Texture2D handleTex;
private int handleYOffset;
private int currentHandlePosPx = -1;
private int forcedHandlePosPx = -1;
private boolean isSliding = false;
private float slideStartMousePos;
private int slideStartHandlePos;
private int minValue;
private int maxValue;
private int value;
private boolean smoothSliding;
private final ArrayList<SliderListener> sliderListeners = new ArrayList<SliderListener>();
public void setHandleYOffset( int yOffset )
{
this.handleYOffset = yOffset;
setTextureDirty();
}
public final int getHandleYOffset()
{
return ( handleYOffset );
}
/**
* Checks, if the mouse is over the handle.
*
* @param currentHandlePosPx
* @param mouseX widget local mouse-x
* @param mouseY widget local mouse-y
*
* @return -1, if the mouse is on the smaller side of the handle, +1, if it is on the greater side, 0 if it is over the handle.
*/
protected int getMousePosition( int currentHandlePosPx, int mouseX, int mouseY )
{
int halfHandleSize = HUDTextureUtils.getTextureWidth( handleTex ) / 2;
if ( mouseX < currentHandlePosPx - halfHandleSize )
return ( -1 );
if ( mouseX > currentHandlePosPx + halfHandleSize )
return ( +1 );
return ( 0 );
}
/**
* {@inheritDoc}
*/
@Override
protected void onMouseButtonPressed( MouseButton button, float x, float y, long when, long lastWhen, boolean isTopMost, boolean hasFocus )
{
super.onMouseButtonPressed( button, x, y, when, lastWhen, isTopMost, hasFocus );
if ( isTopMost && ( getMaxValue() > getMinValue() ) )
{
Dim2i buffer = Dim2i.fromPool();
getSizeHUD2Pixels_( x, y, buffer );
int localX = buffer.getWidth();
int localY = buffer.getHeight();
Dim2i.toPool( buffer );
if ( getMousePosition( currentHandlePosPx, localX, localY ) == 0 )
{
bindToGlobalMouseMovement();
slideStartHandlePos = currentHandlePosPx;
slideStartMousePos = x;
this.isSliding = true;
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onMouseButtonReleased( MouseButton button, float x, float y, long when, long lastWhen, boolean isTopMost, boolean hasFocus )
{
super.onMouseButtonReleased( button, x, y, when, lastWhen, isTopMost, hasFocus );
this.isSliding = false;
if ( getSmoothSliding() )
{
setTextureDirty();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onMouseMoved( float x, float y, int buttonsState, long when, boolean isTopMost, boolean hasFocus )
{
super.onMouseMoved( x, y, buttonsState, when, isTopMost, hasFocus );
if ( isSliding && ( getMaxValue() > getMinValue() ) )
{
int oldForcedHandlePosPx = forcedHandlePosPx;
int newValue;
float dx = x - slideStartMousePos;
int leftWidth = HUDTextureUtils.getTextureWidth( leftTex );
int rightWidth = HUDTextureUtils.getTextureWidth( rightTex );
Dim2i buffer = Dim2i.fromPool();
getSizeHUD2Pixels_( this.getWidth(), 0, buffer );
int widgetWidthPx = buffer.getWidth();
getSizeHUD2Pixels_( slideStartHandlePos + dx, 0, buffer );
int slideHandlePos = buffer.getWidth();
Dim2i.toPool( buffer );
int barRestWidth = widgetWidthPx - leftWidth - rightWidth;
forcedHandlePosPx = Math.max( leftWidth, Math.min( slideHandlePos, barRestWidth + leftWidth ) );
newValue = Math.round( ( ( forcedHandlePosPx - leftWidth ) * ( getMaxValue() - getMinValue() ) ) / (float)barRestWidth ) + getMinValue();
if ( !getSmoothSliding() )
{
forcedHandlePosPx = leftWidth + barRestWidth * ( newValue - getMinValue() ) / ( getMaxValue() - getMinValue() );
}
if ( newValue != getValue() )
{
setValue( newValue );
}
else if ( forcedHandlePosPx != oldForcedHandlePosPx )
{
setTextureDirty();
}
}
}
/**
* Sets the lower bound of scroll values.
*/
public void setMinValue( int minValue )
{
if ( minValue > maxValue )
throw new IllegalArgumentException( "minValue must never be greater than maxValue." );
if ( this.minValue == minValue )
return;
this.minValue = minValue;
setTextureDirty();
}
/**
* @return the lower bound of scroll values
*/
public final int getMinValue()
{
return ( minValue );
}
/**
* Sets the upper bound of scroll values.
*/
public void setMaxValue( int maxValue )
{
if ( maxValue < minValue )
throw new IllegalArgumentException( "maxValue must never be less than minValue." );
if ( this.maxValue == maxValue )
return;
this.maxValue = maxValue;
setTextureDirty();
}
/**
* @return the upper bound of scroll values
*/
public final int getMaxValue()
{
return ( maxValue );
}
/**
* Sets the lower bound of scroll values.
*
* @param minValue
* @param maxValue
*/
public void setMinAndMax( int minValue, int maxValue )
{
if ( minValue > maxValue )
throw new IllegalArgumentException( "minValue must be <= maxValue." );
if ( ( minValue == this.minValue ) && ( maxValue == this.maxValue ) )
return;
this.minValue = minValue;
this.maxValue = maxValue;
setTextureDirty();
}
/**
* Sets the lower bound of scroll values.
*
* @param minValue
* @param maxValue
* @param value
*/
public void setMinMaxAndValue( int minValue, int maxValue, int value )
{
if ( minValue > maxValue )
throw new IllegalArgumentException( "minValue must be <= maxValue." );
if ( ( value < minValue ) || ( value > maxValue ) )
throw new IllegalArgumentException( "value must be >= minValue and <= maxValue." );
if ( ( minValue == this.minValue ) && ( maxValue == this.maxValue ) )
return;
this.minValue = minValue;
this.maxValue = maxValue;
this.value = value;
setTextureDirty();
}
/**
* @param oldValue
* @param newValue
*/
protected void onSliderValueChanged( int oldValue, int newValue )
{
for ( int i = 0; i < sliderListeners.size(); i++ )
{
sliderListeners.get( i ).onSliderValueChanged( this, newValue );
}
}
/**
* Sets the current slide value
*
* @param value
*/
public boolean setValue( int value )
{
value = Math.max( getMinValue(), Math.min( value, getMaxValue() ) );
if ( value == this.value )
return ( false );
int oldValue = this.value;
this.value = value;
setTextureDirty();
onSliderValueChanged( oldValue, value );
return ( true );
}
/**
* @return the current slide value
*/
public final int getValue()
{
return ( value );
}
/**
* Adds a SliderListener to the List to be notified, when the value has
* changed.
*
* @param l the new SliderListener
*/
public void addSliderListener( SliderListener l )
{
sliderListeners.add( l );
}
/**
* Removes a SliderListener from the List.
*
* @param l the SliderListener to be removed
*/
public boolean removeSliderListener( SliderListener l )
{
return ( sliderListeners.remove( l ) );
}
/**
* If true, the handle doesn't snap to discrete positiones depending on the
* available slide values.
*
* @param b enable/disable
*/
public void setSmoothSliding( boolean b )
{
this.smoothSliding = b;
}
/**
* @return true, if the handle doesn't snap to discrete positiones depending on the
* available slide values.
*/
public final boolean getSmoothSliding()
{
return ( smoothSliding );
}
protected int drawHandle( Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height, Texture2D handleTex, int forcedHandlePos )
{
int handlePos;
int handleSize = HUDTextureUtils.getTextureWidth( handleTex );
if ( forcedHandlePos >= 0 )
handlePos = forcedHandlePos;
else
handlePos = width * ( getValue() - getMinValue() ) / ( getMaxValue() - getMinValue() );
DrawUtils.drawImage( null, handleTex, null, texCanvas, offsetX + handlePos - ( handleSize / 2 ), offsetY, handleSize, height );
return ( handlePos );
}
@Override
protected void drawWidget( Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height, boolean drawsSelf )
{
if ( drawsSelf )
texCanvas.getImage().clear( Colorf.BLACK_TRANSPARENT, offsetX, offsetY, width, height );
int leftWidth = HUDTextureUtils.getTextureWidth( leftTex );
int rightWidth = HUDTextureUtils.getTextureWidth( rightTex );
int bodyWidth = width - leftWidth - rightWidth;
DrawUtils.drawImage( null, leftTex, null, texCanvas, offsetX, offsetY, width, height );
DrawUtils.drawImage( null, bodyTex, TileMode.TILE_X, texCanvas, offsetX + leftWidth, offsetY, bodyWidth, height );
DrawUtils.drawImage( null, rightTex, null, texCanvas, offsetX + width - rightWidth, offsetY, rightWidth, height );
if ( valueMarkTex != null )
{
int valueMarkWidth = HUDTextureUtils.getTextureWidth( valueMarkTex );
int valueMarkHeight = HUDTextureUtils.getTextureHeight( valueMarkTex );
int n = getMaxValue() - getMinValue();
for ( int i = 0; i <= n; i++ )
{
int pos = bodyWidth * i / n;
DrawUtils.drawImage( null, valueMarkTex, null, texCanvas, offsetX + leftWidth + pos - ( valueMarkWidth / 2 ) - 1, offsetY + height - valueMarkHeight, valueMarkWidth, valueMarkHeight );
}
}
if ( getMaxValue() > getMinValue() )
{
this.currentHandlePosPx = leftWidth + drawHandle( texCanvas, offsetX + leftWidth, offsetY, width - leftWidth - rightWidth, height, handleTex, isSliding ? forcedHandlePosPx - leftWidth : -1 );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void init()
{
if ( heightInPx > 0 )
{
final Dim2f buffer = Dim2f.fromPool();
getSizePixels2HUD_( 0, heightInPx, buffer );
setSize( this.getWidth(), buffer.getHeight(), true );
Dim2f.toPool( buffer );
setTextureDirty();
}
}
/**
* Creates a new Slider.
*
* @param width the width of the Scrollbar
* @param height the height of the Scrollbar
* @param desc a Slider.Description instance holding information about
* this new Slider
*/
public Slider( float width, float height, Description desc )
{
super( false, false, width, height );
if ( height < 0f )
this.heightInPx = desc.getHeight();
else
this.heightInPx = -1;
this.leftTex = desc.getLeftTexture();
this.rightTex = desc.getRightTexture();
this.bodyTex = desc.getBodyTexture();
this.valueMarkTex = desc.getValueMarkTexture();
this.handleTex = desc.getHandleTexture();
this.handleYOffset = desc.getHandleButtonYOffset();
this.minValue = 0;
this.maxValue = 100;
this.value = 0;
this.smoothSliding = desc.getSmoothSliding();
}
/**
* Creates a new Slider.
*
* @param width the width of the Slider
* @param desc a Slider.Description instance holding information about
* this new Slider
*/
public Slider( float width, Description desc )
{
this( width, -1f, desc );
}
/**
* Creates a new Slider.
*
* @param width the width of the Slider
* @param height the height of the Slider
*/
public Slider( float width, float height )
{
this( width, height, HUD.getTheme().getSliderDescription() );
}
/**
* Creates a new Slider.
*
* @param width the width of the slider
*/
public Slider( float width )
{
this( width, HUD.getTheme().getSliderDescription() );
}
}