/** * 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.fuelneedle; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.TelemetryData; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BackgroundProperty; import net.ctdp.rfdynhud.properties.FactoredIntProperty; import net.ctdp.rfdynhud.properties.ImageProperty; import net.ctdp.rfdynhud.properties.IntProperty; import net.ctdp.rfdynhud.properties.PosSizeProperty; import net.ctdp.rfdynhud.properties.Position; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.Property; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.properties.Size; import net.ctdp.rfdynhud.render.DrawnStringFactory; import net.ctdp.rfdynhud.render.ImageTemplate; import net.ctdp.rfdynhud.render.TextureImage2D; 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.valuemanagers.IntervalManager; import net.ctdp.rfdynhud.values.AbstractSize; import net.ctdp.rfdynhud.values.RelativePositioning; import net.ctdp.rfdynhud.widgets.base.needlemeter.NeedleMeterWidget; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link FuelNeedleWidget} displays the current fuel load with a needle. * * @author Marvin Froehlich (CTDP) */ public class FuelNeedleWidget extends NeedleMeterWidget { private final ImageProperty lowFuelWarningImageNameOff = new ImageProperty( "lowFuelWarningImageOff", "imageOff", "standard/shiftlight_off.png", false, true ) { @Override protected void onValueChanged( String oldValue, String newValue ) { lowFuelWarningImageOff = null; } }; private TransformableTexture lowFuelWarningImageOff = null; private final ImageProperty lowFuelWarningImageNameOn = new ImageProperty( "lowFuelWarningImageOn", "imageOn", "standard/shiftlight_on_red.png", false, true ) { @Override protected void onValueChanged( String oldValue, String newValue ) { lowFuelWarningImageOn = null; } }; private TransformableTexture lowFuelWarningImageOn = null; private final AbstractSize lowFuelWarnImgSize = new AbstractSize() { @Override public int getEffectiveWidth() { loadLowFuelWarningImages( null ); if ( lowFuelWarningImageOn == null ) return ( 0 ); return ( lowFuelWarningImageOn.getWidth() ); } @Override public int getEffectiveHeight() { loadLowFuelWarningImages( null ); if ( lowFuelWarningImageOn == null ) return ( 0 ); return ( lowFuelWarningImageOn.getHeight() ); } }; private final Position lowFuelWarningImagePosition = Position.newLocalPosition( this, RelativePositioning.CENTER_CENTER, 29.0f, false, 4.0f, false, lowFuelWarnImgSize ); private final Property lowFuelWarningImagePositionPositioningProperty = lowFuelWarningImagePosition.getPositioningProperty( "lowFuelWarningImagePositioning", "imagePositioning" ); private final PosSizeProperty lowFuelWarningImagePositionXProperty = lowFuelWarningImagePosition.getXProperty( "lowFuelWarningImagePositionX", "imagePosX" ); private final PosSizeProperty lowFuelWarningImagePositionYProperty = lowFuelWarningImagePosition.getYProperty( "lowFuelWarningImagePositionY", "imagePosY" ); private final Size lowFuelWarningImageSize = Size.newLocalSize( this, 10.0f, true, 10.0f, true ); //private final PosSizeProperty lowFuelWarningImageWidthProperty = lowFuelWarningImageSize.getWidthProperty( "lowFuelWarningImageWidth", "imageWidth" ); private final PosSizeProperty lowFuelWarningImageHeightProperty = lowFuelWarningImageSize.getHeightProperty( "lowFuelWarningImageHeight", "imageHeight" ); private final IntProperty lowFuelWarningLaps = new IntProperty( "lowFuelWarningLaps", "laps", 1, 1, 10, false ); private final FactoredIntProperty lowFuelBlinkTime = new FactoredIntProperty( "lowFuelBlinkTime", "blinkTime", 1000000, 0, 500, 0, 5000 ); private final IntervalManager lowFuelBlinkManager = new IntervalManager( lowFuelBlinkTime ); public FuelNeedleWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 16.3125f, 21.75f ); getBorderProperty().setBorder( "" ); lastMarkerBig.setBooleanValue( true ); displayValue.setBooleanValue( false ); displayMarkers.setBooleanValue( false ); displayMarkerNumbers.setBooleanValue( false ); needleMountY.setIntValue( 520 ); needleRotationForMinValue.setFloatValue( -44 ); needleRotationForMaxValue.setFloatValue( +42 ); } @Override protected boolean hasText() { return ( false ); } @Override protected void initParentProperties() { super.initParentProperties(); markersBigStep.setIntValue( 100 ); markersSmallStep.setIntValue( 10 ); } @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( lowFuelWarningImageNameOff, "Image name for the off-state of the low fuel warning." ); writer.writeProperty( lowFuelWarningImageNameOn, "Image name for the on-state of the low fuel warning." ); writer.writeProperty( lowFuelWarningImagePositionPositioningProperty, "Positioning type for the low-fuel-warning image." ); writer.writeProperty( lowFuelWarningImagePositionXProperty, "X-position for the low-fuel-warning image." ); writer.writeProperty( lowFuelWarningImagePositionYProperty, "Y-position for the low-fuel-warning image." ); //writer.writeProperty( lowFuelWarningImageWidthProperty, "Width for the low-fuel-warning image." ); writer.writeProperty( lowFuelWarningImageHeightProperty, "Height for the low-fuel-warning image." ); writer.writeProperty( lowFuelWarningLaps, "Number of laps to start warning before out of fuel." ); writer.writeProperty( lowFuelBlinkTime, "Blink time in milli seconds for low fuel warning (0 to disable)." ); } @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( lowFuelWarningImageNameOff ) ); else if ( loader.loadProperty( lowFuelWarningImageNameOn ) ); else if ( loader.loadProperty( lowFuelWarningImagePositionPositioningProperty ) ); else if ( loader.loadProperty( lowFuelWarningImagePositionXProperty ) ); else if ( loader.loadProperty( lowFuelWarningImagePositionYProperty ) ); //else if ( loader.loadProperty( lowFuelWarningImageWidthProperty ) ); else if ( loader.loadProperty( lowFuelWarningImageHeightProperty ) ); else if ( loader.loadProperty( lowFuelWarningLaps ) ); else if ( loader.loadProperty( lowFuelBlinkTime ) ); } /** * {@inheritDoc} */ @Override protected void addMaxValuePropertyToContainer( PropertiesContainer propsCont, boolean forceAll ) { // We don't need this here! } @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Low Fuel Warning" ); propsCont.addProperty( lowFuelWarningImageNameOff ); propsCont.addProperty( lowFuelWarningImageNameOn ); propsCont.addProperty( lowFuelWarningImagePositionPositioningProperty ); propsCont.addProperty( lowFuelWarningImagePositionXProperty ); propsCont.addProperty( lowFuelWarningImagePositionYProperty ); //propsCont.addProperty( lowFuelWarningImageWidthProperty ); propsCont.addProperty( lowFuelWarningImageHeightProperty ); propsCont.addProperty( lowFuelWarningLaps ); propsCont.addProperty( lowFuelBlinkTime ); } @Override public void onPropertyChanged( Property property, Object oldValue, Object newValue ) { super.onPropertyChanged( property, oldValue, newValue ); if ( property == lowFuelWarningImagePositionPositioningProperty ) forceReinitialization(); else if ( property == lowFuelWarningImagePositionXProperty ) forceReinitialization(); else if ( property == lowFuelWarningImagePositionYProperty ) forceReinitialization(); //else if ( property == lowFuelWarningImageWidthProperty ) // forceReinitialization(); else if ( property == lowFuelWarningImageHeightProperty ) forceReinitialization(); } @Override protected String getInitialBackground() { return ( BackgroundProperty.IMAGE_INDICATOR + "standard/fuel_needle_bg.png" ); } @Override protected String getInitialNeedleImage() { return ( "standard/fuel_needle.png" ); } private final boolean isLowFuelWaningUsed() { return ( lowFuelBlinkManager.isUsed() && !lowFuelWarningImageNameOn.isNoImage() ); } private void resetBlink( boolean isEditorMode ) { lowFuelBlinkManager.reset(); if ( lowFuelWarningImageOff != null ) { lowFuelWarningImageOff.setTranslation( lowFuelWarningImagePosition.getEffectiveX(), lowFuelWarningImagePosition.getEffectiveY() ); lowFuelWarningImageOff.setVisible( false ); } if ( lowFuelWarningImageOn != null ) { lowFuelWarningImageOn.setTranslation( lowFuelWarningImagePosition.getEffectiveX(), lowFuelWarningImagePosition.getEffectiveY() ); lowFuelWarningImageOn.setVisible( isEditorMode ); } } private void loadLowFuelWarningImages( Boolean isEditorMode ) { if ( !isLowFuelWaningUsed() ) { lowFuelWarningImageOff = null; lowFuelWarningImageOn = null; return; } boolean offVisible = ( lowFuelWarningImageOff == null ) ? false : lowFuelWarningImageOff.isVisible(); boolean onVisible = ( lowFuelWarningImageOn == null ) ? false : lowFuelWarningImageOn.isVisible(); boolean offReloaded = false; if ( !lowFuelWarningImageNameOff.isNoImage() ) { ImageTemplate it = lowFuelWarningImageNameOff.getImage(); int h = lowFuelWarningImageSize.getEffectiveHeight(); int w = Math.round( h * it.getBaseAspect() ); boolean isEditorMode2; if ( isEditorMode == null ) { if ( lowFuelWarningImageOff == null ) isEditorMode2 = false; else isEditorMode2 = ( lowFuelWarningImageOff.getTexture().getWidth() != lowFuelWarningImageOff.getTexture().getMaxWidth() ) || ( lowFuelWarningImageOff.getTexture().getHeight() != lowFuelWarningImageOff.getTexture().getMaxHeight() ); } else { isEditorMode2 = isEditorMode.booleanValue(); } if ( ( lowFuelWarningImageOff == null ) || ( lowFuelWarningImageOff.getWidth() != w ) || ( lowFuelWarningImageOff.getHeight() != h ) ) { lowFuelWarningImageOff = it.getScaledTransformableTexture( w, h, lowFuelWarningImageOff, isEditorMode2 ); offReloaded = true; } } boolean onReloaded = false; if ( !lowFuelWarningImageNameOn.isNoImage() ) { ImageTemplate it = lowFuelWarningImageNameOn.getImage(); int h = lowFuelWarningImageSize.getEffectiveHeight(); int w = Math.round( h * it.getBaseAspect() ); boolean isEditorMode2; if ( isEditorMode == null ) { if ( lowFuelWarningImageOn == null ) isEditorMode2 = false; else isEditorMode2 = ( lowFuelWarningImageOn.getTexture().getWidth() != lowFuelWarningImageOn.getTexture().getMaxWidth() ) || ( lowFuelWarningImageOn.getTexture().getHeight() != lowFuelWarningImageOn.getTexture().getMaxHeight() ); } else { isEditorMode2 = isEditorMode.booleanValue(); } if ( ( lowFuelWarningImageOn == null ) || ( lowFuelWarningImageOn.getWidth() != w ) || ( lowFuelWarningImageOn.getHeight() != h ) ) { lowFuelWarningImageOn = it.getScaledTransformableTexture( w, h, lowFuelWarningImageOn, isEditorMode2 ); onReloaded = true; } } if ( offReloaded && ( lowFuelWarningImageOff != null ) ) { lowFuelWarningImageOff.setTranslation( lowFuelWarningImagePosition.getEffectiveX(), lowFuelWarningImagePosition.getEffectiveY() ); lowFuelWarningImageOff.setVisible( offVisible ); } if ( onReloaded && ( lowFuelWarningImageOn != null ) ) { lowFuelWarningImageOn.setTranslation( lowFuelWarningImagePosition.getEffectiveX(), lowFuelWarningImagePosition.getEffectiveY() ); lowFuelWarningImageOn.setVisible( onVisible ); } } /** * {@inheritDoc} */ @Override public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode ) { super.onCockpitEntered( gameData, isEditorMode ); loadLowFuelWarningImages( isEditorMode ); resetBlink( isEditorMode ); } /** * {@inheritDoc} */ @Override protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode ); return ( viewedVSI.isPlayer() ); } /** * {@inheritDoc} */ @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { super.initSubTextures( gameData, isEditorMode, widgetInnerWidth, widgetInnerHeight, collector ); if ( !isLowFuelWaningUsed() ) return; loadLowFuelWarningImages( isEditorMode ); if ( lowFuelWarningImageOff != null ) collector.add( lowFuelWarningImageOff ); if ( lowFuelWarningImageOn != null ) collector.add( lowFuelWarningImageOn ); } @Override protected int getMarkersBigStepLowerLimit() { return ( 20 ); } @Override protected int getMarkersSmallStepLowerLimit() { return ( 5 ); } /* @Override protected FontProperty getValueFont() { return ( getFontProperty() ); } @Override protected ColorProperty getValueFontColor() { return ( getFontColorProperty() ); } */ @Override protected float getMinDataValue( LiveGameData gameData, boolean isEditorMode ) { return ( 0 ); } @Override protected float getMaxDataValue( LiveGameData gameData, boolean isEditorMode ) { return ( gameData.getPhysics().getFuelRange().getMaxValue() ); } @Override protected float getValue( LiveGameData gameData, boolean isEditorMode ) { VehicleScoringInfo vsi = gameData.getScoringInfo().getViewedVehicleScoringInfo(); if ( vsi.isPlayer() ) return ( gameData.getTelemetryData().getFuel() ); return ( 0 ); } @Override protected String getMarkerLabelForValue( LiveGameData gameData, boolean isEditorMode, float value ) { return ( String.valueOf( (int)value ) ); } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { super.initialize( gameData, isEditorMode, dsf, texture, width, height ); resetBlink( isEditorMode ); } @Override protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { super.drawWidget( clock, needsCompleteRedraw, gameData, isEditorMode, texture, offsetX, offsetY, width, height ); ScoringInfo scoringInfo = gameData.getScoringInfo(); TelemetryData telemData = gameData.getTelemetryData(); VehicleScoringInfo vsi = scoringInfo.getPlayersVehicleScoringInfo(); final float fuel = telemData.getFuel(); final float avgFuelUsage = telemData.getFuelUsageAverageL(); final float stintLength = !isEditorMode ? vsi.getStintLength() : 5.2f; if ( isEditorMode ) { if ( lowFuelWarningImageOn != null ) { lowFuelWarningImageOn.setVisible( true ); if ( lowFuelWarningImageOff != null ) { lowFuelWarningImageOff.setVisible( false ); } } else if ( lowFuelWarningImageOff != null ) { lowFuelWarningImageOff.setVisible( false ); } } else if ( lowFuelBlinkManager.isUsed() && ( lowFuelWarningImageOn != null ) ) { boolean warn = false; if ( avgFuelUsage > 0f ) { float halfLiter = gameData.getProfileInfo().getMeasurementUnits().getFuelAmountFromLiters( 0.5f ); float lapsForFuel = ( ( fuel - halfLiter ) / avgFuelUsage ) + ( stintLength - (int)stintLength ); int maxLaps = scoringInfo.getEstimatedMaxLaps( vsi ); if ( maxLaps < 0 ) maxLaps = 999999; int lapsRemaining = maxLaps - vsi.getLapsCompleted(); warn = ( lapsForFuel < 1.05f + lowFuelWarningLaps.getIntValue() ) && ( lapsForFuel < lapsRemaining ); } if ( warn ) { lowFuelBlinkManager.update( scoringInfo.getSessionNanos() ); } else { lowFuelBlinkManager.reset(); } if ( lowFuelWarningImageOff != null ) lowFuelWarningImageOff.setVisible( !lowFuelBlinkManager.getState() ); lowFuelWarningImageOn.setVisible( lowFuelBlinkManager.getState() ); } } }