/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.widgets.standard.controls;
import java.awt.Font;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import net.ctdp.rfdynhud.gamedata.LiveGameData;
import net.ctdp.rfdynhud.gamedata.TelemetryData;
import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo;
import net.ctdp.rfdynhud.properties.BooleanProperty;
import net.ctdp.rfdynhud.properties.ColorProperty;
import net.ctdp.rfdynhud.properties.ImageProperty;
import net.ctdp.rfdynhud.properties.IntProperty;
import net.ctdp.rfdynhud.properties.PropertiesContainer;
import net.ctdp.rfdynhud.properties.PropertyLoader;
import net.ctdp.rfdynhud.render.DrawnStringFactory;
import net.ctdp.rfdynhud.render.ImageTemplate;
import net.ctdp.rfdynhud.render.TextureImage2D;
import net.ctdp.rfdynhud.render.TextureImage2D.TextDirection;
import net.ctdp.rfdynhud.render.TransformableTexture;
import net.ctdp.rfdynhud.util.PropertyWriter;
import net.ctdp.rfdynhud.util.SubTextureCollector;
import net.ctdp.rfdynhud.valuemanagers.Clock;
import net.ctdp.rfdynhud.widgets.base.widget.Widget;
import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet;
/**
* The {@link ControlsWidget} displays clutch, brake and throttle.
*
* @author Marvin Froehlich (CTDP)
*/
public class ControlsWidget extends Widget
{
private final BooleanProperty horizontalBars = new BooleanProperty( "horizontalBars", false )
{
@Override
protected void onValueChanged( Boolean oldValue, boolean newValue )
{
clutchDirty = true;
brakeDirty = true;
throttleDirty = true;
}
};
private final BooleanProperty swapThrottleAndBrake = new BooleanProperty( "swapThrottleAndBrake", "swapThrottle/Brake", false );
private boolean oldSwapTB = swapThrottleAndBrake.getBooleanValue();
private boolean clutchDirty = true;
private final BooleanProperty displayClutch = new BooleanProperty( "displayClutch", true )
{
@Override
protected void onValueChanged( Boolean oldValue, boolean newValue )
{
clutchDirty = true;
}
};
private final ImageProperty clutchImage = new ImageProperty( "clutchImage", null, "", false, true )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
clutchDirty = true;
}
};
private final ColorProperty clutchColor = new ColorProperty( "clutchColor", "#0000FF" )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
clutchDirty = true;
}
};
private TextureImage2D scaledClutchTexture = null;
private TransformableTexture texClutch = null;
private boolean brakeDirty = true;
private final BooleanProperty displayBrake = new BooleanProperty( "displayBrake", true )
{
@Override
protected void onValueChanged( Boolean oldValue, boolean newValue )
{
brakeDirty = true;
}
};
private final ImageProperty brakeImage = new ImageProperty( "brakeImage", null, "", false, true )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
brakeDirty = true;
}
};
private final ColorProperty brakeColor = new ColorProperty( "brakeColor", "#FF0000" )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
brakeDirty = true;
}
};
private TextureImage2D scaledBrakeTexture = null;
private TransformableTexture texBrake = null;
private boolean throttleDirty = true;
private final BooleanProperty displayThrottle = new BooleanProperty( "displayThrottle", true )
{
@Override
protected void onValueChanged( Boolean oldValue, boolean newValue )
{
throttleDirty = true;
}
};
private final ImageProperty throttleImage = new ImageProperty( "throttleImage", null, "", false, true )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
throttleDirty = true;
}
};
private final ColorProperty throttleColor = new ColorProperty( "throttleColor", "#00FF00" )
{
@Override
protected void onValueChanged( String oldValue, String newValue )
{
throttleDirty = true;
}
};
private TextureImage2D scaledThrottleTexture = null;
private TransformableTexture texThrottle = null;
private final IntProperty gap = new IntProperty( "gap", 5 );
private final IntProperty labelOffset = new IntProperty( "labelOffset", 20 )
{
@Override
protected void onValueChanged( Integer oldValue, int newValue )
{
clutchDirty = true;
brakeDirty = true;
throttleDirty = true;
}
};
public ControlsWidget()
{
super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 9.9f, 16.5f );
setPadding( 3, 3, 3, 3 );
getFontProperty().setFont( "DokChampa", Font.BOLD, 22, true, true );
getFontColorProperty().setColor( "#FFFFFF" );
}
/**
* {@inheritDoc}
*/
@Override
public void prepareForMenuItem()
{
super.prepareForMenuItem();
getFontProperty().setFont( "Dialog", Font.PLAIN, 5, false, true );
labelOffset.setIntValue( 3 );
}
/**
* {@inheritDoc}
*/
@Override
public void saveProperties( PropertyWriter writer ) throws IOException
{
super.saveProperties( writer );
writer.writeProperty( horizontalBars, "Extend the bars horizontally instead of vertically?" );
writer.writeProperty( swapThrottleAndBrake, "Swap throttle and brake order?" );
writer.writeProperty( displayClutch, "Display the clutch bar?" );
writer.writeProperty( clutchImage, "The image for the clutch bar. (overrules the color)" );
writer.writeProperty( clutchColor, "The color used for the clutch bar in the format #RRGGBB (hex)." );
writer.writeProperty( displayBrake, "Display the brake bar?" );
writer.writeProperty( brakeImage, "The image for the brake bar. (overrules the color)" );
writer.writeProperty( brakeColor, "The color used for the brake bar in the format #RRGGBB (hex)." );
writer.writeProperty( displayThrottle, "Display the throttle bar?" );
writer.writeProperty( throttleImage, "The image for the throttle bar. (overrules the color)" );
writer.writeProperty( throttleColor, "The color used for the throttle bar in the format #RRGGBB (hex)." );
writer.writeProperty( gap, "Gap between the bars" );
writer.writeProperty( labelOffset, "The offset for bar text from the left or bottom boundary of the bar." );
}
/**
* {@inheritDoc}
*/
@Override
public void loadProperty( PropertyLoader loader )
{
super.loadProperty( loader );
if ( loader.loadProperty( horizontalBars ) );
else if ( loader.loadProperty( swapThrottleAndBrake ) );
else if ( loader.loadProperty( displayClutch ) );
else if ( loader.loadProperty( clutchImage ) );
else if ( loader.loadProperty( clutchColor ) );
else if ( loader.loadProperty( displayBrake ) );
else if ( loader.loadProperty( brakeImage ) );
else if ( loader.loadProperty( brakeColor ) );
else if ( loader.loadProperty( displayThrottle ) );
else if ( loader.loadProperty( throttleImage ) );
else if ( loader.loadProperty( throttleColor ) );
else if ( loader.loadProperty( gap ) );
else if ( loader.loadProperty( labelOffset ) );
}
/**
* {@inheritDoc}
*/
@Override
public void getProperties( PropertiesContainer propsCont, boolean forceAll )
{
super.getProperties( propsCont, forceAll );
propsCont.addGroup( "Misc" );
propsCont.addProperty( horizontalBars );
propsCont.addProperty( swapThrottleAndBrake );
propsCont.addProperty( displayClutch );
propsCont.addProperty( clutchImage );
propsCont.addProperty( clutchColor );
propsCont.addProperty( displayBrake );
propsCont.addProperty( brakeImage );
propsCont.addProperty( brakeColor );
propsCont.addProperty( displayThrottle );
propsCont.addProperty( throttleImage );
propsCont.addProperty( throttleColor );
propsCont.addProperty( gap );
propsCont.addProperty( labelOffset );
}
/**
* {@inheritDoc}
*/
@Override
protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode )
{
super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode );
return ( viewedVSI.isPlayer() );
}
private void drawBarLabel( String label, TextureImage2D texture, int offsetX, int offsetY, int width, int height )
{
Rectangle2D bounds = TextureImage2D.getStringBounds( label, getFontProperty() );
if ( labelOffset.getIntValue() > -bounds.getWidth() )
{
if ( horizontalBars.getBooleanValue() )
{
if ( labelOffset.getIntValue() < width )
texture.drawString( label, offsetX + labelOffset.getIntValue(), offsetY + ( height - (int)bounds.getHeight() ) / 2 - (int)bounds.getY(), bounds, getFont(), isFontAntiAliased(), getFontColor(), true, null );
}
else
{
if ( labelOffset.getIntValue() < height )
texture.drawString( label, offsetX + width / 2 - (int)bounds.getHeight() / 2 - (int)bounds.getY(), offsetY + height - labelOffset.getIntValue(), bounds, getFont(), isFontAntiAliased(), getFontColor(), TextDirection.UP, true, null );
//texture.drawString( label, offsetX + width / 2 + (int)bounds.getY() + (int)bounds.getHeight() / 2, offsetY + labelOffset.getIntValue(), bounds, getFont(), isFontAntiAliased(), getFontColor(), TextDirection.DOWN, true, null );
}
}
}
private int initClutchTexture( boolean isEditorMode, int offset, int w, int h, int gap )
{
if ( displayClutch.getBooleanValue() )
{
if ( ( texClutch == null ) || ( texClutch.getWidth() != w ) || ( texClutch.getHeight() != h ) || clutchDirty )
{
texClutch = TransformableTexture.getOrCreate( w, h, TransformableTexture.DEFAULT_PIXEL_PERFECT_POSITIONING, texClutch, isEditorMode );
ImageTemplate it = clutchImage.getImage();
if ( it == null )
{
scaledClutchTexture = null;
texClutch.getTexture().clear( clutchColor.getColor(), true, null );
}
else
{
if ( horizontalBars.getBooleanValue() )
{
scaledClutchTexture = it.getScaledTextureImage( w, h * 2, scaledClutchTexture, isEditorMode );
texClutch.getTexture().clear( scaledClutchTexture, 0, h, w, h, 0, 0, w, h, true, null );
}
else
{
scaledClutchTexture = it.getScaledTextureImage( w * 2, h, scaledClutchTexture, isEditorMode );
texClutch.getTexture().clear( scaledClutchTexture, w, 0, w, h, 0, 0, w, h, true, null );
}
}
drawBarLabel( Loc.clutch_label, texClutch.getTexture(), 0, 0, texClutch.getWidth(), texClutch.getHeight() );
if ( horizontalBars.getBooleanValue() )
texClutch.setTranslation( 0, offset );
else
texClutch.setTranslation( offset, 0 );
clutchDirty = false;
}
if ( horizontalBars.getBooleanValue() )
offset += h + gap;
else
offset += w + gap;
}
return ( offset );
}
private int initBrakeTexture( boolean isEditorMode, int offset, int w, int h, int gap )
{
if ( displayBrake.getBooleanValue() )
{
if ( ( texBrake == null ) || ( texBrake.getWidth() != w ) || ( texBrake.getHeight() != h ) || brakeDirty || ( swapThrottleAndBrake.getBooleanValue() != oldSwapTB ) )
{
texBrake = TransformableTexture.getOrCreate( w, h, TransformableTexture.DEFAULT_PIXEL_PERFECT_POSITIONING, texBrake, isEditorMode );
ImageTemplate it = brakeImage.getImage();
if ( it == null )
{
scaledBrakeTexture = null;
texBrake.getTexture().clear( brakeColor.getColor(), true, null );
}
else
{
if ( horizontalBars.getBooleanValue() )
{
scaledBrakeTexture = it.getScaledTextureImage( w, h * 2, scaledBrakeTexture, isEditorMode );
texBrake.getTexture().clear( scaledBrakeTexture, 0, h, w, h, 0, 0, w, h, true, null );
}
else
{
scaledBrakeTexture = it.getScaledTextureImage( w * 2, h, scaledBrakeTexture, isEditorMode );
texBrake.getTexture().clear( scaledBrakeTexture, w, 0, w, h, 0, 0, w, h, true, null );
}
}
drawBarLabel( Loc.brake_label, texBrake.getTexture(), 0, 0, texBrake.getWidth(), texBrake.getHeight() );
if ( horizontalBars.getBooleanValue() )
texBrake.setTranslation( 0, offset );
else
texBrake.setTranslation( offset, 0 );
brakeDirty = false;
}
if ( horizontalBars.getBooleanValue() )
offset += h + gap;
else
offset += w + gap;
}
return ( offset );
}
private int initThrottleTexture( boolean isEditorMode, int offset, int w, int h, int gap )
{
if ( displayThrottle.getBooleanValue() )
{
if ( ( texThrottle == null ) || ( texThrottle.getWidth() != w ) || ( texThrottle.getHeight() != h ) || throttleDirty || ( swapThrottleAndBrake.getBooleanValue() != oldSwapTB ) )
{
texThrottle = TransformableTexture.getOrCreate( w, h, TransformableTexture.DEFAULT_PIXEL_PERFECT_POSITIONING, texThrottle, isEditorMode );
ImageTemplate it = throttleImage.getImage();
if ( it == null )
{
scaledThrottleTexture = null;
texThrottle.getTexture().clear( throttleColor.getColor(), true, null );
}
else
{
if ( horizontalBars.getBooleanValue() )
{
scaledThrottleTexture = it.getScaledTextureImage( w, h * 2, scaledThrottleTexture, isEditorMode );
texThrottle.getTexture().clear( scaledThrottleTexture, 0, h, w, h, 0, 0, w, h, true, null );
}
else
{
scaledThrottleTexture = it.getScaledTextureImage( w * 2, h, scaledThrottleTexture, isEditorMode );
texThrottle.getTexture().clear( scaledThrottleTexture, w, 0, w, h, 0, 0, w, h, true, null );
}
}
drawBarLabel( Loc.throttle_label, texThrottle.getTexture(), 0, 0, texThrottle.getWidth(), texThrottle.getHeight() );
if ( horizontalBars.getBooleanValue() )
texThrottle.setTranslation( 0, offset );
else
texThrottle.setTranslation( offset, 0 );
throttleDirty = false;
}
if ( horizontalBars.getBooleanValue() )
offset += h + gap;
else
offset += w + gap;
}
return ( offset );
}
private int initSubTextures( boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight )
{
int numBars = 0;
if ( displayClutch.getBooleanValue() )
numBars++;
if ( displayBrake.getBooleanValue() )
numBars++;
if ( displayThrottle.getBooleanValue() )
numBars++;
if ( numBars == 0 )
{
texClutch = null;
texBrake = null;
texThrottle = null;
return ( 0 );
}
final int gap = this.gap.getIntValue();
final int w = horizontalBars.getBooleanValue() ? widgetInnerWidth : ( widgetInnerWidth + gap ) / numBars - gap;
final int h = horizontalBars.getBooleanValue() ? ( widgetInnerHeight + gap ) / numBars - gap : widgetInnerHeight;
int offset = 0;
offset = initClutchTexture( isEditorMode, offset, w, h, gap );
if ( swapThrottleAndBrake.getBooleanValue() )
{
offset = initThrottleTexture( isEditorMode, offset, w, h, gap );
offset = initBrakeTexture( isEditorMode, offset, w, h, gap );
}
else
{
offset = initBrakeTexture( isEditorMode, offset, w, h, gap );
offset = initThrottleTexture( isEditorMode, offset, w, h, gap );
}
oldSwapTB = swapThrottleAndBrake.getBooleanValue();
return ( numBars );
}
@Override
protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector )
{
int numBars = initSubTextures( isEditorMode, widgetInnerWidth, widgetInnerHeight );
if ( numBars == 0 )
return;
if ( displayClutch.getBooleanValue() )
collector.add( texClutch );
if ( swapThrottleAndBrake.getBooleanValue() )
{
if ( displayThrottle.getBooleanValue() )
collector.add( texThrottle );
if ( displayBrake.getBooleanValue() )
collector.add( texBrake );
}
else
{
if ( displayBrake.getBooleanValue() )
collector.add( texBrake );
if ( displayThrottle.getBooleanValue() )
collector.add( texThrottle );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height )
{
}
@Override
protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height )
{
final TelemetryData telemData = gameData.getTelemetryData();
float uClutch = isEditorMode ? 1.0f : telemData.getUnfilteredClutch();
float uBrake = isEditorMode ? 0.2f : telemData.getUnfilteredBrake();
float uThrottle = isEditorMode ? 0.4f : telemData.getUnfilteredThrottle();
if ( needsCompleteRedraw )
{
if ( displayClutch.getBooleanValue() && ( scaledClutchTexture != null ) )
texture.drawImage( scaledClutchTexture, 0, 0, texClutch.getWidth(), texClutch.getHeight(), offsetX + (int)texClutch.getTransX(), offsetY + (int)texClutch.getTransY(), texClutch.getWidth(), texClutch.getHeight(), true, null );
if ( displayClutch.getBooleanValue() )
drawBarLabel( Loc.clutch_label, texture, offsetX + (int)texClutch.getTransX(), offsetY + (int)texClutch.getTransY(), texClutch.getWidth(), texClutch.getHeight() );
if ( displayBrake.getBooleanValue() && ( scaledBrakeTexture != null ) )
texture.drawImage( scaledBrakeTexture, 0, 0, texBrake.getWidth(), texBrake.getHeight(), offsetX + (int)texBrake.getTransX(), offsetY + (int)texBrake.getTransY(), texBrake.getWidth(), texBrake.getHeight(), true, null );
if ( displayBrake.getBooleanValue() )
drawBarLabel( Loc.brake_label, texture, offsetX + (int)texBrake.getTransX(), offsetY + (int)texBrake.getTransY(), texBrake.getWidth(), texBrake.getHeight() );
if ( displayThrottle.getBooleanValue() && ( scaledThrottleTexture != null ) )
texture.drawImage( scaledThrottleTexture, 0, 0, texThrottle.getWidth(), texThrottle.getHeight(), offsetX + (int)texThrottle.getTransX(), offsetY + (int)texThrottle.getTransY(), texThrottle.getWidth(), texThrottle.getHeight(), true, null );
if ( displayThrottle.getBooleanValue() )
drawBarLabel( Loc.throttle_label, texture, offsetX + (int)texThrottle.getTransX(), offsetY + (int)texThrottle.getTransY(), texThrottle.getWidth(), texThrottle.getHeight() );
}
if ( horizontalBars.getBooleanValue() )
{
final int w = displayThrottle.getBooleanValue() ? texThrottle.getWidth() : ( displayBrake.getBooleanValue() ? texBrake.getWidth() : ( displayClutch.getBooleanValue() ? texClutch.getWidth() : 0 ) );
int clutch = (int)( w * uClutch );
int brake = (int)( w * uBrake );
int throttle = (int)( w * uThrottle );
if ( displayClutch.getBooleanValue() )
texClutch.setClipRect( 0, 0, clutch, texClutch.getHeight(), true );
if ( displayBrake.getBooleanValue() )
texBrake.setClipRect( 0, 0, brake, texBrake.getHeight(), true );
if ( displayThrottle.getBooleanValue() )
texThrottle.setClipRect( 0, 0, throttle, texThrottle.getHeight(), true );
}
else
{
final int h = displayThrottle.getBooleanValue() ? texThrottle.getHeight() : ( displayBrake.getBooleanValue() ? texBrake.getHeight() : ( displayClutch.getBooleanValue() ? texClutch.getHeight() : 0 ) );
int clutch = (int)( h * uClutch );
int brake = (int)( h * uBrake );
int throttle = (int)( h * uThrottle );
if ( displayClutch.getBooleanValue() )
texClutch.setClipRect( 0, h - clutch, texClutch.getWidth(), clutch, true );
if ( displayBrake.getBooleanValue() )
texBrake.setClipRect( 0, h - brake, texBrake.getWidth(), brake, true );
if ( displayThrottle.getBooleanValue() )
texThrottle.setClipRect( 0, h - throttle, texThrottle.getWidth(), throttle, true );
}
}
}