/** * 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.etv2010.standings; import java.awt.FontMetrics; import java.awt.geom.Rectangle2D; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.Laptime; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.SessionType; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.ColorProperty; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.Size; import net.ctdp.rfdynhud.render.DrawnString; import net.ctdp.rfdynhud.render.DrawnStringFactory; import net.ctdp.rfdynhud.render.Texture2DCanvas; import net.ctdp.rfdynhud.render.TextureImage2D; import net.ctdp.rfdynhud.render.TransformableTexture; import net.ctdp.rfdynhud.render.DrawnString.Alignment; import net.ctdp.rfdynhud.util.StandingsTools; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.util.TimingUtil; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.values.FloatValue; import net.ctdp.rfdynhud.values.IntValue; import net.ctdp.rfdynhud.values.StandingsView; import net.ctdp.rfdynhud.values.StringValue; import net.ctdp.rfdynhud.widgets.etv2010._base.ETVWidgetBase; import net.ctdp.rfdynhud.widgets.etv2010._util.ETVUtils; import net.ctdp.rfdynhud.widgets.etv2010._util.ETVWidgetSet; import net.ctdp.rfdynhud.widgets.etv2010._util.ETVImages.BGType; /** * The {@link ETVStandingsWidget} displays the list of drivers and gaps. * * @author Marvin Froehlich (CTDP) */ public class ETVStandingsWidget extends ETVWidgetBase { private final ColorProperty captionBackgroundColor1st = new ColorProperty( "captionBgColor1st", ETVWidgetSet.ETV_CAPTION_BACKGROUND_COLOR_1ST.getKey() ); private final ColorProperty dataBackgroundColor1st = new ColorProperty( "dataBgColor1st", ETVWidgetSet.ETV_DATA_BACKGROUND_COLOR_1ST.getKey() ); private final BooleanProperty forceLeaderDisplayed = new BooleanProperty( "forceLeaderDisplayed", "forceLeaderDispl", true ); private final BooleanProperty showFastestLapsInRace = new BooleanProperty( "showFastestLapsInRace", "showFastLapsRace", true ); private DrawnString[] captionStrings = null; private DrawnString[] nameStrings = null; private DrawnString[] gapStrings = null; private IntValue[] positions = null; private StringValue[] driverNames = null; private FloatValue[] gaps = null; private int maxNumItems = 0; private int oldNumItems = 0; private Boolean[] itemsVisible = null; private final Size itemHeight = Size.newGlobalSize( this, 0f, true, 2.5f, true ); private TextureImage2D itemClearImage = null; private static final int NUM_FLAG_TEXTURES = 3; private TransformableTexture[] flagTextures = null; private final FloatValue[] laptimes = new FloatValue[ NUM_FLAG_TEXTURES ]; private final DrawnString[] laptimeStrings = new DrawnString[ NUM_FLAG_TEXTURES ]; private VehicleScoringInfo[] vehicleScoringInfos = null; private int oldNumVehicles = -1; private IntValue[] lap = null; private float displayTime; private int lastVisibleIndex = -1; public ETVStandingsWidget() { super( ETVWidgetSet.INSTANCE, ETVWidgetSet.WIDGET_PACKAGE, 14.0f, 10f * 2.5f ); } /** * {@inheritDoc} */ @Override public void prepareForMenuItem() { super.prepareForMenuItem(); itemHeight.setEffectiveSize( itemHeight.getEffectiveWidth(), 5 ); } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( captionBackgroundColor1st, "The background color for the \"Position\" caption for first place." ); writer.writeProperty( dataBackgroundColor1st, "The background color for the data area, for first place." ); writer.writeProperty( itemHeight.getHeightProperty( "itemHeight" ), "The height of one item." ); writer.writeProperty( forceLeaderDisplayed, "Display leader regardless of maximum displayed drivers setting?" ); writer.writeProperty( showFastestLapsInRace, "Display fastest lap flags in race session?" ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( captionBackgroundColor1st ) ); else if ( loader.loadProperty( dataBackgroundColor1st ) ); else if ( loader.loadProperty( itemHeight.getHeightProperty( "itemHeight" ) ) ); else if ( loader.loadProperty( forceLeaderDisplayed ) ); else if ( loader.loadProperty( showFastestLapsInRace ) ); } /** * {@inheritDoc} */ @Override public void getPropertiesCaptionBG( PropertiesContainer propsCont, boolean forceAll ) { super.getPropertiesCaptionBG( propsCont, forceAll ); if ( forceAll || !useImages.getBooleanValue() ) { propsCont.addProperty( captionBackgroundColor1st ); } } /** * {@inheritDoc} */ @Override public void getPropertiesDataBG( PropertiesContainer propsCont, boolean forceAll ) { super.getPropertiesDataBG( propsCont, forceAll ); if ( forceAll || !useImages.getBooleanValue() ) { propsCont.addProperty( dataBackgroundColor1st ); } } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Misc" ); propsCont.addProperty( itemHeight.getHeightProperty( "itemHeight" ) ); propsCont.addProperty( forceLeaderDisplayed ); propsCont.addProperty( showFastestLapsInRace ); } /** * {@inheritDoc} */ @Override public int getMinHeight( LiveGameData gameData, boolean isEditorMode ) { return ( 5 ); } private final boolean getUseClassScoring() { return ( getConfiguration().getUseClassScoring() ); } @Override public void onSessionStarted( SessionType sessionType, LiveGameData gameData, boolean isEditorMode ) { super.onSessionStarted( sessionType, gameData, isEditorMode ); if ( driverNames != null ) { for ( int i = 0; i < driverNames.length; i++ ) { positions[i].reset(); driverNames[i].reset(); gaps[i].reset(); lap[i].reset(); } } lastVisibleIndex = -1; forceReinitialization(); } @Override public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode ) { super.onCockpitEntered( gameData, isEditorMode ); if ( laptimes != null ) { for ( int i = 0; i < laptimes.length; i++ ) { if ( laptimes[i] != null ) laptimes[i].reset(); } } oldNumVehicles = -1; } /** * {@inheritDoc} */ @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { if ( !isEditorMode && gameData.getScoringInfo().getSessionType().isRace() && !showFastestLapsInRace.getBooleanValue() ) { flagTextures = null; return; } int itemHeight = this.itemHeight.getEffectiveHeight(); if ( ( flagTextures == null ) || ( flagTextures[0].getWidth() != widgetInnerWidth ) || ( flagTextures[0].getHeight() != itemHeight ) ) { flagTextures = new TransformableTexture[ NUM_FLAG_TEXTURES ]; for ( int i = 0; i < NUM_FLAG_TEXTURES; i++ ) { flagTextures[i] = new TransformableTexture( widgetInnerWidth, itemHeight ); } } for ( int i = 0; i < flagTextures.length; i++ ) collector.add( flagTextures[i] ); } /** * {@inheritDoc} */ @Override protected boolean checkForChanges( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int width, int height ) { int numVehicles = getUseClassScoring() ? gameData.getScoringInfo().getNumVehiclesInSameClass( gameData.getScoringInfo().getViewedVehicleScoringInfo() ) : gameData.getScoringInfo().getNumVehicles(); boolean result = ( numVehicles != oldNumVehicles ); oldNumVehicles = numVehicles; return ( result ); } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { int itemHeight = this.itemHeight.getEffectiveHeight(); maxNumItems = ( height + itemGap.getIntValue() ) / ( itemHeight + itemGap.getIntValue() ); vehicleScoringInfos = new VehicleScoringInfo[ maxNumItems ]; itemClearImage = TextureImage2D.getOrCreateDrawTexture( width, itemHeight * 2, true, itemClearImage, isEditorMode ); boolean useImages = this.useImages.getBooleanValue(); if ( useImages ) { ETVUtils.drawLabeledDataBackgroundI( 0, 0, width, itemHeight, "00", getFontProperty(), getImages(), BGType.POSITION_FIRST, itemClearImage, true ); ETVUtils.drawLabeledDataBackgroundI( 0, itemHeight, width, itemHeight, "00", getFontProperty(), getImages(), BGType.NEUTRAL, itemClearImage, true ); } else { ETVUtils.drawLabeledDataBackground( 0, 0, width, itemHeight, "00", getFontProperty(), captionBackgroundColor1st.getColor(), dataBackgroundColor1st.getColor(), itemClearImage, true ); ETVUtils.drawLabeledDataBackground( 0, itemHeight, width, itemHeight, "00", getFontProperty(), captionBackgroundColor.getColor(), dataBackgroundColor.getColor(), itemClearImage, true ); } Texture2DCanvas texCanvas = texture.getTextureCanvas(); texCanvas.setFont( getFont() ); FontMetrics metrics = texCanvas.getFontMetrics(); Rectangle2D numBounds = metrics.getStringBounds( "00", texCanvas ); //int capWidth = (int)Math.ceil( numBounds.getWidth() ); int captionRight = useImages ? getImages().getLabeledDataCaptionRight( itemHeight, numBounds ) : ETVUtils.getLabeledDataCaptionRight( itemHeight, numBounds ); int dataAreaLeft = useImages ? getImages().getLabeledDataDataLeft( itemHeight, numBounds ) : ETVUtils.getLabeledDataDataLeft( itemHeight, numBounds ); int dataAreaRight = useImages ? getImages().getLabeledDataDataRight( width, itemHeight ) : ETVUtils.getLabeledDataDataRight( width, itemHeight ); int vMiddle = ETVUtils.getLabeledDataVMiddle( itemHeight, numBounds ); captionStrings = new DrawnString[ maxNumItems ]; nameStrings = new DrawnString[ maxNumItems ]; gapStrings = new DrawnString[ maxNumItems ]; positions = new IntValue[ maxNumItems ]; driverNames = new StringValue[ maxNumItems ]; gaps = new FloatValue[ maxNumItems ]; lap = new IntValue[ maxNumItems ]; itemsVisible = new Boolean[ maxNumItems ]; for ( int i = 0; i < maxNumItems; i++ ) { captionStrings[i] = dsf.newDrawnString( "captionStrings" + i, captionRight, vMiddle, Alignment.RIGHT, false, getFont(), isFontAntiAliased(), captionColor.getColor() ); nameStrings[i] = dsf.newDrawnString( "nameStrings" + i, dataAreaLeft, vMiddle, Alignment.LEFT, false, getFont(), isFontAntiAliased(), getFontColor() ); gapStrings[i] = dsf.newDrawnString( "gapStrings" + i, dataAreaRight, vMiddle, Alignment.RIGHT, false, getFont(), isFontAntiAliased(), getFontColor() ); positions[i] = new IntValue(); driverNames[i] = new StringValue(); gaps[i] = new FloatValue(); lap[i] = new IntValue(); itemsVisible[i] = null; } TransformableTexture[] flagTextures = getSubTextures( gameData, isEditorMode, width, height ); if ( flagTextures != null ) { for ( int i = 0; i < NUM_FLAG_TEXTURES; i++ ) { if ( useImages ) ETVUtils.drawDataBackgroundI( 0, 0, flagTextures[i].getWidth(), flagTextures[i].getHeight(), getImages(), BGType.NEUTRAL, flagTextures[i].getTexture(), true ); else ETVUtils.drawDataBackground( 0, 0, flagTextures[i].getWidth(), flagTextures[i].getHeight(), dataBackgroundColor.getColor(), flagTextures[i].getTexture(), true ); laptimes[i] = new FloatValue(); laptimeStrings[i] = dsf.newDrawnString( "laptimeStrings" + i, flagTextures[i].getWidth() / 2, vMiddle, Alignment.CENTER, false, getFont(), isFontAntiAliased(), getFontColor() ); } } } @Override public void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { final ScoringInfo scoringInfo = gameData.getScoringInfo(); final int itemHeight = this.itemHeight.getEffectiveHeight(); final int numDrivers = StandingsTools.getDisplayedVSIsForScoring( scoringInfo, scoringInfo.getViewedVehicleScoringInfo(), getUseClassScoring(), StandingsView.RELATIVE_TO_LEADER, forceLeaderDisplayed.getBooleanValue(), vehicleScoringInfos ); int numDisplayedLaptimes = 0; if ( flagTextures != null ) { for ( int i = 0; i < flagTextures.length; i++ ) { flagTextures[i].setVisible( false ); } } if ( scoringInfo.getSessionTime() > displayTime ) { lastVisibleIndex = -1; } int i2 = 0; if ( ( numDrivers > 1 ) && ( vehicleScoringInfos[1].getPlace( getUseClassScoring() ) - vehicleScoringInfos[0].getPlace( getUseClassScoring() ) > 1 ) ) { i2 = 1; } for ( int i = 0; i < numDrivers; i++ ) { VehicleScoringInfo vsi = vehicleScoringInfos[i]; lap[i].update( vsi.getCurrentLap() ); if ( lap[i].hasChanged() ) { if ( ( i == 0 ) || ( i == i2 ) ) { lastVisibleIndex = 0; displayTime = scoringInfo.getSessionTime() + 40f; } else if ( scoringInfo.getSessionTime() <= displayTime ) { lastVisibleIndex = Math.max( lastVisibleIndex, i ); displayTime = Math.max( displayTime, scoringInfo.getSessionTime() + 20f ); } } } for ( int i = 0; i < numDrivers; i++ ) { VehicleScoringInfo vsi = vehicleScoringInfos[i]; short place = vsi.getPlace( getUseClassScoring() ); Boolean visible; if ( isEditorMode ) { visible = true; } else { if ( scoringInfo.getSessionType().isRace() ) visible = ( i <= lastVisibleIndex ); else visible = ( vsi.getBestLapTime() > 0.0f ); } boolean drawBackground = needsCompleteRedraw; boolean visibilityChanged = false; if ( visible != itemsVisible[i] ) { itemsVisible[i] = visible; drawBackground = true; visibilityChanged = true; } int offsetY2 = i * ( itemHeight + itemGap.getIntValue() ); int srcOffsetY = ( place == 1 ) ? 0 : itemHeight; if ( drawBackground ) { if ( visible ) texture.clear( itemClearImage, 0, srcOffsetY, width, itemHeight, offsetX, offsetY + offsetY2, width, itemHeight, true, null ); else texture.clear( offsetX, offsetY + offsetY2, width, itemHeight, true, null ); } positions[i].update( place ); if ( ( needsCompleteRedraw || visibilityChanged || positions[i].hasChanged() ) && visible ) captionStrings[i].draw( offsetX, offsetY + offsetY2, positions[i].getValueAsString(), captionColor.getColor(), texture, itemClearImage, offsetX, offsetY + offsetY2 - srcOffsetY ); driverNames[i].update( vsi.getDriverNameTLC( getShowNamesInAllUppercase() ) ); if ( ( needsCompleteRedraw || visibilityChanged || driverNames[i].hasChanged() ) && visible ) nameStrings[i].draw( offsetX, offsetY + offsetY2, driverNames[i].getValue(), getFontColor(), texture, itemClearImage, offsetX, offsetY + offsetY2 - srcOffsetY ); if ( place > 1 ) { if ( scoringInfo.getSessionType().isRace() ) gaps[i].update( ( vsi.getLapsBehindLeader( getUseClassScoring() ) > 0 ) ? -vsi.getLapsBehindLeader( getUseClassScoring() ) - 10000 : Math.abs( vsi.getTimeBehindLeader( getUseClassScoring() ) ) ); else gaps[i].update( vsi.getBestLapTime() - scoringInfo.getLeadersVehicleScoringInfo().getBestLapTime() ); if ( ( needsCompleteRedraw || visibilityChanged || gaps[i].hasChanged() ) && visible ) { String s; if ( vsi.getBestLapTime() < 0.0f ) { s = ""; } else if ( gaps[i].getValue() < -10000f ) { int l = ( (int)-( gaps[i].getValue() + 10000.0f ) ); if ( l == 1 ) s = "+" + l + Loc.gap_lap; else s = "+" + l + Loc.gap_laps; } else { s = TimingUtil.getTimeAsGapString( gaps[i].getValue() ); } gapStrings[i].draw( offsetX, offsetY + offsetY2, s, getFontColor(), texture, itemClearImage, offsetX, offsetY + offsetY2 - srcOffsetY ); } } if ( ( flagTextures != null ) && visible && ( numDisplayedLaptimes < flagTextures.length - 1 ) ) { Laptime lt = vsi.getFastestLaptime(); boolean show = ( ( lt != null ) && ( lt.getLap() == vsi.getCurrentLap() - 1 ) && ( vsi.getStintStartLap() != vsi.getCurrentLap() ) && ( scoringInfo.getSessionTime() - vsi.getLapStartTime() < 20.0f ) ); if ( isEditorMode ) show = ( numDisplayedLaptimes < 2 ); if ( show ) { int tti = numDisplayedLaptimes++; TransformableTexture tt = flagTextures[tti]; laptimes[tti].update( lt.getLapTime() ); if ( laptimes[tti].hasChanged() ) { laptimeStrings[tti].draw( 0, 0, TimingUtil.getTimeAsLaptimeString( laptimes[tti].getValue() ), tt.getTexture(), dataBackgroundColor.getColor() ); } int off; if ( useImages.getBooleanValue() ) { float scale = getImages().getLabeledDataImageScale( itemHeight ); off = (int)( ( getImages().getLabeledDataVirtualProjectionBorderRight() + getImages().getDataVirtualProjectionBorderRight() ) * scale ); int b1 = getImages().getLabeledDataDataBorderRight() + 1; int b2 = getImages().getDataBorderLeft() + 1; int b = (int)Math.floor( ( Math.min( b1, b2 ) ) * scale ); off -= b; off += itemGap.getIntValue(); } else { off = -( ETVUtils.getTriangleWidth( itemHeight ) - itemGap.getIntValue() ); } boolean isOnLeftSide = ( getPosition().getEffectiveX() < getConfiguration().getGameResolution().getViewportWidth() - getPosition().getEffectiveX() - getSize().getEffectiveWidth() ); if ( isOnLeftSide ) tt.setTranslation( width + off, offsetY2 ); else tt.setTranslation( -width - off, offsetY2 ); tt.setVisible( true ); } } } for ( int i = numDrivers; i < oldNumItems; i++ ) { int offsetY2 = i * ( itemHeight + itemGap.getIntValue() ); texture.clear( offsetX, offsetY + offsetY2, width, itemHeight, true, null ); } oldNumItems = numDrivers; } }