/** * 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.trackposition; import java.awt.Color; import java.awt.Font; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ModInfo; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.ColorProperty; import net.ctdp.rfdynhud.properties.EnumProperty; import net.ctdp.rfdynhud.properties.FontProperty; import net.ctdp.rfdynhud.properties.IntProperty; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.Property; import net.ctdp.rfdynhud.properties.PropertyLoader; 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.util.MapTools; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.widgets.WidgetsConfiguration; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.standard._util.LabelPositioning; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link TrackPositionWidget} displays all driving vehicles on a line. * * @author Marvin Froehlich (CTDP) */ public class TrackPositionWidget extends Widget { private static final int LINE_THICKNESS = 1; private static final int BASE_LINE_PADDING = 30; private final IntProperty baseItemRadius = new IntProperty( "itemRadius", "radius", 9, 1, 100, false ) { @Override protected void onValueChanged( Integer oldValue, int newValue ) { super.onValueChanged( oldValue, newValue ); if ( getConfiguration() != null ) updateItemRadius(); forceAndSetDirty( false ); } }; private int itemRadius = baseItemRadius.getIntValue(); private void updateItemRadius() { itemRadius = Math.round( baseItemRadius.getIntValue() * getConfiguration().getGameResolution().getViewportHeight() / 960f ); } private int itemBlackBorderWidth = 2; private final ColorProperty lineColor = new ColorProperty( "lineColor", "#FFFFFF" ); private final ColorProperty markColorNormal = new ColorProperty( "markColorNormal", StandardWidgetSet.POSITION_ITEM_COLOR_NORMAL.getKey() ); private final ColorProperty markColorLeader = new ColorProperty( "markColorLeader", StandardWidgetSet.POSITION_ITEM_COLOR_LEADER.getKey() ); private final ColorProperty markColorMe = new ColorProperty( "markColorMe", StandardWidgetSet.POSITION_ITEM_COLOR_ME.getKey() ); private final BooleanProperty useMyColorForMe1st = new BooleanProperty( "useMyColorForMe1st", false ); private final ColorProperty markColorNextInFront = new ColorProperty( "markColorNextInFront", StandardWidgetSet.POSITION_ITEM_COLOR_NEXT_IN_FRONT.getKey() ); private final ColorProperty markColorNextBehind = new ColorProperty( "markColorNextBehind", StandardWidgetSet.POSITION_ITEM_COLOR_NEXT_BEHIND.getKey() ); private final BooleanProperty displayPositionNumbers = new BooleanProperty( "displayPosNumbers", true ); private final BooleanProperty displayNameLabels = new BooleanProperty( "displayNameLabels", false ); private final EnumProperty<LabelPositioning> nameLabelPos = new EnumProperty<LabelPositioning>( "nameLabelPos", LabelPositioning.BELOW ); private final FontProperty nameLabelFont = new FontProperty( "nameLabelFont", StandardWidgetSet.POSITION_ITEM_FONT.getKey() ); private final ColorProperty nameLabelFontColor = new ColorProperty( "nameLabelFontColor", StandardWidgetSet.POSITION_ITEM_COLOR_NORMAL.getKey() ); private int maxDisplayedVehicles = -1; private static final int ANTI_ALIAS_RADIUS_OFFSET = 1; private TransformableTexture[] itemTextures = null; private int middleOffsetY = 0; private int[] itemLabelOffsetsY = null; private VehicleScoringInfo[] vsis = null; private int[] itemStates = null; private int numVehicles = -1; private int lineLength = 0; public TrackPositionWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 35.0f, 5.859375f ); getFontProperty().setFont( StandardWidgetSet.POSITION_ITEM_FONT.getKey() ); getFontColorProperty().setColor( StandardWidgetSet.POSITION_ITEM_FONT_COLOR.getKey() ); } @Override public void prepareForMenuItem() { super.prepareForMenuItem(); //baseItemRadius.setIntValue( 20 ); itemRadius = 3; itemBlackBorderWidth = 0; } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( lineColor, "Color for the base line." ); writer.writeProperty( baseItemRadius, "The abstract radius for any displayed driver item." ); writer.writeProperty( markColorNormal, "The color used for all, but special cars in #RRGGBBAA (hex)." ); writer.writeProperty( markColorLeader, "The color used for the leader's car in #RRGGBBAA (hex)." ); writer.writeProperty( markColorMe, "The color used for your own car in #RRGGBBAA (hex)." ); writer.writeProperty( useMyColorForMe1st, "Use 'markColorMe' for my item when I am at 1st place?" ); writer.writeProperty( markColorNextInFront, "The color used for the car in front of you in #RRGGBBAA (hex)." ); writer.writeProperty( markColorNextBehind, "The color used for the car behind you in #RRGGBBAA (hex)." ); writer.writeProperty( displayPositionNumbers, "Display numbers on the position markers?" ); writer.writeProperty( displayNameLabels, "Display name label near the position markers?" ); writer.writeProperty( nameLabelPos, "Positioning of the name labels." ); writer.writeProperty( nameLabelFont, "Font for the name labels." ); writer.writeProperty( nameLabelFontColor, "Font color for the name labels." ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( lineColor ) ); else if ( loader.loadProperty( baseItemRadius ) ); else if ( loader.loadProperty( markColorNormal ) ); else if ( loader.loadProperty( markColorLeader ) ); else if ( loader.loadProperty( markColorMe ) ); else if ( loader.loadProperty( useMyColorForMe1st ) ); else if ( loader.loadProperty( markColorNextInFront ) ); else if ( loader.loadProperty( markColorNextBehind ) ); else if ( loader.loadProperty( displayPositionNumbers ) ); else if ( loader.loadProperty( displayNameLabels ) ); else if ( loader.loadProperty( nameLabelPos ) ); else if ( loader.loadProperty( nameLabelFont ) ); else if ( loader.loadProperty( nameLabelFontColor ) ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Misc" ); propsCont.addProperty( lineColor ); propsCont.addProperty( baseItemRadius ); propsCont.addProperty( markColorNormal ); propsCont.addProperty( markColorLeader ); propsCont.addProperty( markColorMe ); propsCont.addProperty( useMyColorForMe1st ); propsCont.addProperty( markColorNextInFront ); propsCont.addProperty( markColorNextBehind ); propsCont.addProperty( displayPositionNumbers ); propsCont.addProperty( displayNameLabels ); //if ( displayNameLabels.getBooleanValue() || forceAll ) { propsCont.addProperty( nameLabelPos ); propsCont.addProperty( nameLabelFont ); propsCont.addProperty( nameLabelFontColor ); } } /** * {@inheritDoc} */ @Override public void onPropertyChanged( Property property, Object oldValue, Object newValue ) { super.onPropertyChanged( property, oldValue, newValue ); if ( itemStates != null ) { for ( int i = 0; i < itemStates.length; i++ ) itemStates[i] = 0; } } /** * {@inheritDoc} */ @Override public void afterConfigurationLoaded( WidgetsConfiguration widgetsConfig, LiveGameData gameData, boolean isEditorMode ) { super.afterConfigurationLoaded( widgetsConfig, gameData, isEditorMode ); updateItemRadius(); } private final boolean getUseClassScoring() { return ( getConfiguration().getUseClassScoring() ); } private void initMaxDisplayedVehicles( boolean isEditorMode, ModInfo modInfo ) { if ( isEditorMode ) this.maxDisplayedVehicles = 22 + 1; else this.maxDisplayedVehicles = modInfo.getMaxOpponents() + 1; this.maxDisplayedVehicles = Math.max( 4, Math.min( maxDisplayedVehicles, 32 ) ); } private void updateVSIs( LiveGameData gameData, boolean isEditorMode ) { initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); if ( ( vsis == null ) || ( vsis.length < maxDisplayedVehicles ) ) { vsis = new VehicleScoringInfo[ maxDisplayedVehicles ]; if ( itemStates == null ) { itemStates = new int[ maxDisplayedVehicles ]; } else { int[] tmpItemStates = new int[ maxDisplayedVehicles ]; System.arraycopy( itemStates, 0, tmpItemStates, 0, itemStates.length ); itemStates = tmpItemStates; } for ( int i = 0; i < itemStates.length; i++ ) itemStates[i] = 0; } numVehicles = MapTools.getDisplayedVSIsForMap( gameData.getScoringInfo(), gameData.getScoringInfo().getViewedVehicleScoringInfo(), getUseClassScoring(), true, vsis ); } /** * {@inheritDoc} */ @Override public void onScoringInfoUpdated( LiveGameData gameData, boolean isEditorMode ) { updateVSIs( gameData, isEditorMode ); } @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { updateItemRadius(); initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); if ( ( itemTextures == null ) || ( itemTextures.length != maxDisplayedVehicles ) ) { itemTextures = new TransformableTexture[ maxDisplayedVehicles ]; } if ( itemTextures[0] == null ) itemTextures[0] = new TransformableTexture( 1, 1, isEditorMode, false ); java.awt.Dimension size = StandardWidgetSet.getPositionItemSize( itemRadius, null, null, false ); int itemHeightWithoutLabel = size.height; size = StandardWidgetSet.getPositionItemSize( itemRadius, displayNameLabels.getBooleanValue() ? nameLabelPos.getEnumValue() : null, nameLabelFont.getFont(), nameLabelFont.isAntiAliased() ); int w = size.width; int h = size.height; if ( nameLabelPos.getEnumValue() == LabelPositioning.ABOVE ) middleOffsetY = ( h - itemHeightWithoutLabel ) / 2; else middleOffsetY = ( itemHeightWithoutLabel - h ) / 2; if ( ( itemTextures[maxDisplayedVehicles - 1] == null ) || ( itemTextures[maxDisplayedVehicles - 1].getWidth() != w ) || ( itemTextures[maxDisplayedVehicles - 1].getHeight() != h ) ) { for ( int i = 0; i < maxDisplayedVehicles; i++ ) { itemTextures[i] = TransformableTexture.getOrCreate( w, h, isEditorMode, itemTextures[i], isEditorMode ); itemTextures[i].setVisible( false ); } } for ( int i = 0; i < itemTextures.length; i++ ) collector.add( itemTextures[i] ); } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); if ( isEditorMode ) updateVSIs( gameData, isEditorMode ); } @Override protected void drawBackground( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height, boolean isRoot ) { super.drawBackground( gameData, isEditorMode, texture, offsetX, offsetY, width, height, isRoot ); Texture2DCanvas texCanvas = texture.getTextureCanvas(); texCanvas.setAntialiazingEnabled( true ); int lineHeight = LINE_THICKNESS; int linePadding = (int)( BASE_LINE_PADDING * getConfiguration().getGameResolution().getViewportHeight() / (float)1200 ); lineLength = width - 2 * linePadding; texCanvas.setColor( lineColor.getColor() ); texCanvas.fillRect( offsetX + linePadding, offsetY + ( height - lineHeight ) / 2 + middleOffsetY, lineLength, lineHeight ); } @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 VehicleScoringInfo viewedVSI = scoringInfo.getViewedVehicleScoringInfo(); final boolean useClassScoring = getUseClassScoring(); int linePadding = (int)( BASE_LINE_PADDING * getConfiguration().getGameResolution().getViewportWidth() / 1920f ); int off2 = ( isFontAntiAliased() ? ANTI_ALIAS_RADIUS_OFFSET : 0 ); short ownPlace = scoringInfo.getOwnPlace( useClassScoring ); final Font font = getFont(); final boolean posNumberFontAntiAliased = isFontAntiAliased(); int n = Math.min( scoringInfo.getNumVehicles(), maxDisplayedVehicles ); if ( ( itemLabelOffsetsY == null ) || ( itemLabelOffsetsY.length < itemTextures.length ) ) { itemLabelOffsetsY = new int[ itemTextures.length ]; } for ( int i = 0; i < numVehicles; i++ ) { VehicleScoringInfo vsi = vsis[i]; if ( vsi != null ) { short place = vsi.getPlace( useClassScoring ); TransformableTexture tt = itemTextures[i]; itemTextures[i].setVisible( true ); int itemState = ( place << 0 ) | ( vsi.getDriverId() << 9 ); Color color = null; if ( ( place == 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 26; if ( vsi.isPlayer() && useMyColorForMe1st.getBooleanValue() ) color = markColorMe.getColor(); else color = markColorLeader.getColor(); } else if ( vsi.isPlayer() ) { itemState |= 1 << 27; color = markColorMe.getColor(); } else if ( ( place == ownPlace - 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 28; color = markColorNextInFront.getColor(); } else if ( ( place == ownPlace + 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 29; color = markColorNextBehind.getColor(); } else { itemState |= 1 << 30; color = markColorNormal.getColor(); } if ( itemStates[i] != itemState ) { itemStates[i] = itemState; itemLabelOffsetsY[i] = StandardWidgetSet.drawPositionItem( tt.getTexture(), 0, 0, itemRadius, place, color, itemBlackBorderWidth, displayPositionNumbers.getBooleanValue() ? font : null, posNumberFontAntiAliased, getFontColor(), displayNameLabels.getBooleanValue() ? nameLabelPos.getEnumValue() : null, vsi.getDriverNameTLC(), nameLabelFont.getFont(), nameLabelFont.isAntiAliased(), nameLabelFontColor.getColor() ); } int yOff3 = vsi.isInPits() ? -3 : 0; tt.setTranslation( linePadding + off2 + vsi.getNormalizedLapDistance() * lineLength - itemRadius, off2 + height / 2 - itemRadius - itemLabelOffsetsY[i] + yOff3 + middleOffsetY ); } else { itemTextures[i].setVisible( false ); } } for ( int i = n; i < maxDisplayedVehicles; i++ ) itemTextures[i].setVisible( false ); } }