/** * 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.base.widget; import java.awt.Color; import java.io.IOException; import java.net.URL; import java.util.Iterator; import java.util.List; import net.ctdp.rfdynhud.editor.__EDPrivilegedAccess; 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.gamedata.VehicleSetup; import net.ctdp.rfdynhud.input.InputAction; import net.ctdp.rfdynhud.properties.AbstractPropertiesKeeper; import net.ctdp.rfdynhud.properties.BackgroundProperty; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.BorderProperty; import net.ctdp.rfdynhud.properties.ColorProperty; import net.ctdp.rfdynhud.properties.FlatPropertiesContainer; import net.ctdp.rfdynhud.properties.FontProperty; import net.ctdp.rfdynhud.properties.GenericPropertiesIterator; 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.PropertiesKeeper; import net.ctdp.rfdynhud.properties.Property; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.properties.Size; import net.ctdp.rfdynhud.properties.StringProperty; import net.ctdp.rfdynhud.render.BorderWrapper; 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.__RenderPrivilegedAccess; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.util.__UtilPrivilegedAccess; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.values.GenericPositionsIterator; import net.ctdp.rfdynhud.values.GenericSizesIterator; import net.ctdp.rfdynhud.values.InnerSize; import net.ctdp.rfdynhud.values.RelativePositioning; import net.ctdp.rfdynhud.widgets.WidgetsConfiguration; import net.ctdp.rfdynhud.widgets.__WCPrivilegedAccess; import org.jagatoo.logging.Log; import org.jagatoo.logging.LogChannel; import org.jagatoo.logging.LogLevel; import org.jagatoo.util.classes.ClassUtil; import org.jagatoo.util.strings.StringUtils; import org.openmali.types.twodee.Rect2i; /** * This is the base for all Widgets to be drawn on the HUD.<br> * Any concrete extension must have a parameterless constructor. * * @author Marvin Froehlich (CTDP) */ public abstract class Widget implements Cloneable, PropertiesKeeper { public static final LogChannel LOG_CHANNEL = new LogChannel( "rfDynHUD-Widget" ); public static final int NEEDED_DATA_TELEMETRY = 1; public static final int NEEDED_DATA_SCORING = 2; //public static final int NEEDED_DATA_SETUP = 4; public static final int NEEDED_DATA_ALL = NEEDED_DATA_TELEMETRY | NEEDED_DATA_SCORING/* | NEEDED_DATA_SETUP*/; private final WidgetSet widgetSet; private final WidgetPackage widgetPackage; private WidgetsConfiguration config = null; private boolean dirtyFlag = true; private final StringProperty type = new StringProperty( "type", this.getClass().getSimpleName(), true ); private final StringProperty name = new StringProperty( "name", this.getClass().getSimpleName() + "1" ); private WidgetController controller = null; private final BooleanProperty inputVisible = new BooleanProperty( "initialVisibility", true ); private boolean autoVisible = true; private boolean updatedVisible = true; private boolean visibilityChangedSinceLastDraw = true; private boolean needsCompleteRedraw = true; private boolean needsCompleteClear = false; private boolean initialized = false; private final Position position; private final Property positioningProperty; private final PosSizeProperty xProperty; private final PosSizeProperty yProperty; private final Size size; private final PosSizeProperty widthProperty; private final PosSizeProperty heightProperty; private final InnerSize innerSize; private final IntProperty zIndex = new IntProperty( "zIndex", 0 ); private final IntProperty paddingTop = new IntProperty( "paddingTop", "top", 0, 0, 1000, false ); private final IntProperty paddingLeft = new IntProperty( "paddingLeft", "left", 0, 0, 1000, false ); private final IntProperty paddingRight = new IntProperty( "paddingRight", "right", 0, 0, 1000, false ); private final IntProperty paddingBottom = new IntProperty( "paddingBottom", "bottom", 0, 0, 1000, false ); private final BorderProperty border = new BorderProperty( "border", BorderProperty.DEFAULT_BORDER.getKey(), paddingTop, paddingLeft, paddingRight, paddingBottom ); /** * Gets the initial value for the background property. * * @return the initial value for the background property. */ protected String getInitialBackground() { return ( BackgroundProperty.COLOR_INDICATOR + ColorProperty.STANDARD_BACKGROUND_COLOR.getKey() ); } final boolean overridesDrawBackground = ClassUtil.overridesMethod( Widget.class, this.getClass(), "drawBackground", LiveGameData.class, boolean.class, TextureImage2D.class, int.class, int.class, int.class, int.class, boolean.class ); /** * This method is invoked when the background has changed. * * @param imageChanged whether the image has changed * @param deltaScaleX the x-scale factor in as a difference between the old background image and the new one or -1 of no background image was selected * @param deltaScaleY the y-scale factor in as a difference between the old background image and the new one or -1 of no background image was selected */ protected void onBackgroundChanged( boolean imageChanged, float deltaScaleX, float deltaScaleY ) {} private final BackgroundProperty backgroundProperty = canHaveBackground() || overridesDrawBackground ? new BackgroundProperty( "background", getInitialBackground() ) { @Override protected void onValueChanged( BackgroundType oldBGType, BackgroundType newBGType, String oldValue, String newValue ) { if ( ( background != null ) && ( getConfiguration() != null ) ) background.onPropertyValueChanged( Widget.this, oldBGType, newBGType, oldValue, newValue ); } } : null; private final WidgetBackground background = canHaveBackground() || overridesDrawBackground ? new WidgetBackground( this, backgroundProperty ) : null; private final FontProperty font = new FontProperty( "font", FontProperty.STANDARD_FONT.getKey() ); private final ColorProperty fontColor = new ColorProperty( "fontColor", ColorProperty.STANDARD_FONT_COLOR.getKey() ); private TransformableTexture[] subTextures = null; private final DrawnStringFactory drawnStringFactory = new DrawnStringFactory( this ); private AbstractAssembledWidget masterWidget = null; /** * Logs data to the plugin's log file. * * @param logLevel the log level * @param data the data to log */ protected final void log( LogLevel logLevel, Object... data ) { Log.println( LOG_CHANNEL, logLevel, data ); } /** * Logs data to the plugin's log file. * * @param logLevel the log level * @param data the data to log (comma separated) */ protected final void logCS( LogLevel logLevel, Object... data ) { Log.printlnCS( LOG_CHANNEL, logLevel, data ); } /** * Logs data to the plugin's log file. * * @param data the data to log */ protected final void log( Object... data ) { Log.println( LOG_CHANNEL, data ); } /** * Logs data to the plugin's log file. * * @param data the data to log (comma separated) */ protected final void logCS( Object... data ) { Log.printlnCS( LOG_CHANNEL, data ); } /** * Logs data to the plugin's log file. * * @param data the data to log */ protected final void debug( Object... data ) { Log.debug( LOG_CHANNEL, data ); } /** * Logs data to the plugin's log file. * * @param data the data to log (comma separated) */ protected final void debugCS( Object... data ) { Log.debugCS( LOG_CHANNEL, data ); } public void setWidgetController( WidgetController controller ) { this.controller = controller; } public final WidgetController getWidgetController() { return ( controller ); } protected void onVisibilityChanged( boolean visible ) { if ( visible ) this.needsCompleteRedraw = true; else this.needsCompleteClear = true; this.visibilityChangedSinceLastDraw = true; } /** * {@inheritDoc} */ @Override public void onPropertyChanged( Property property, Object oldValue, Object newValue ) { forceAndSetDirty( true ); if ( __EDPrivilegedAccess.editorClassLoader != null ) { if ( ( property == zIndex ) && ( getConfiguration() != null ) ) { __WCPrivilegedAccess.sortWidgets( getConfiguration() ); } } } /** * * @param oldPositioning the old value for the positioning * @param oldX the old x * @param oldY the old y * @param newPositioning the new value for the positioning * @param newX the new x * @param newY the new y */ protected void onPositionChanged( RelativePositioning oldPositioning, int oldX, int oldY, RelativePositioning newPositioning, int newX, int newY ) { if ( __EDPrivilegedAccess.editorClassLoader != null ) { if ( getMasterWidget() == null ) { WidgetsConfiguration wc = getConfiguration(); if ( wc != null ) { __WCPrivilegedAccess.sortWidgets( wc ); } } else { getMasterWidget().sortParts(); if ( getMasterWidget().getBackground() != null ) getMasterWidget().getBackground().setMergedBGDirty(); } } } /** * * @param oldWidth the old width * @param oldHeight the old height * @param newWidth the new width * @param newHeight the new height */ protected void onSizeChanged( int oldWidth, int oldHeight, int newWidth, int newHeight ) { if ( __EDPrivilegedAccess.editorClassLoader != null ) { if ( getMasterWidget() == null ) { WidgetsConfiguration wc = getConfiguration(); if ( wc != null ) { __WCPrivilegedAccess.sortWidgets( wc ); } } else { getMasterWidget().sortParts(); if ( getMasterWidget().getBackground() != null ) getMasterWidget().getBackground().setMergedBGDirty(); } } if ( getBackground() != null ) { getBackground().onWidgetSizeChanged(); } } protected void onCanvasSizeChanged() { forceAndSetDirty( true ); if ( getBackground() != null ) { getBackground().onWidgetSizeChanged(); } FlatPropertiesContainer propsCont = new FlatPropertiesContainer(); getProperties( propsCont, true ); for ( int i = 0; i < propsCont.getList().size(); i++ ) { if ( propsCont.getList().get( i ) instanceof FontProperty ) ( (FontProperty)propsCont.getList().get( i ) ).refresh(); } } /** * Gets the {@link WidgetSet} this {@link Widget} belongs to. * * @return the {@link WidgetSet} this {@link Widget} belongs to. */ public final WidgetSet getWidgetSet() { return ( widgetSet ); } /** * Gets the package to group the Widget in the editor. * This can be an <code>null</code> to be displayed in the root or a slash separated path. * * @return the package to group the Widget in the editor. */ public final WidgetPackage getWidgetPackage() { return ( widgetPackage ); } /** * Gets the default value for the given border alias/name. * * @param name the border name to query * * @return the default value for the given border alias/name. */ public String getDefaultBorderValue( String name ) { String result = BorderProperty.getDefaultBorderValue( name ); if ( ( result == null ) && ( getWidgetSet() != null ) ) { result = getWidgetSet().getDefaultBorderValue( name ); } return ( result ); } /** * Gets the default value for the given named color. * * @param name the color name to query * * @return the default value for the given named color. */ public String getDefaultNamedColorValue( String name ) { String result = ColorProperty.getDefaultNamedColorValue( name ); if ( ( result == null ) && ( getWidgetSet() != null ) ) { result = getWidgetSet().getDefaultNamedColorValue( name ); } return ( result ); } /** * Gets the default value for the given named font. * * @param name the font name to query * * @return the default value for the given named font. */ public String getDefaultNamedFontValue( String name ) { String result = FontProperty.getDefaultNamedFontValue( name ); if ( ( result == null ) && ( getWidgetSet() != null ) ) { result = getWidgetSet().getDefaultNamedFontValue( name ); } return ( result ); } /** * This event is called after this Widget was added to its {@link WidgetsConfiguration}. * * @param config * @param gameData */ protected void onWidgetAttached( WidgetsConfiguration config, LiveGameData gameData ) { } /** * This event is called after this Widget was removed from its {@link WidgetsConfiguration}. * * @param config * @param gameData */ protected void onWidgetDetached( WidgetsConfiguration config, LiveGameData gameData ) { } void setConfiguration( WidgetsConfiguration config, LiveGameData gameData ) { if ( config == this.config ) return; WidgetsConfiguration oldConfig = this.config; this.config = config; if ( this.config != null ) { FlatPropertiesContainer pc = new FlatPropertiesContainer(); getProperties( pc, true ); List<Property> list = pc.getList(); for ( int i = 0; i < list.size(); i++ ) { if ( list.get( i ) != null ) AbstractPropertiesKeeper.setKeeper( list.get( i ), this ); } } if ( config == null ) onWidgetDetached( oldConfig, gameData ); else onWidgetAttached( oldConfig, gameData ); } /** * Gets the {@link WidgetsConfiguration}, this {@link Widget} is a member of. * * @return the {@link WidgetsConfiguration}, this {@link Widget} is a member of. */ public final WidgetsConfiguration getConfiguration() { return ( config ); } void setMasterWidget( AbstractAssembledWidget masterWidget ) { this.masterWidget = masterWidget; } /** * If this {@link Widget} is part of an {@link AbstractAssembledWidget}, this master {@link Widget} is returned. * * @return the master {@link AbstractAssembledWidget} or <code>null</code>. */ public final AbstractAssembledWidget getMasterWidget() { return ( masterWidget ); } /** * Gets the InputActions, that can be bound with a Widget of this type. * "Override" this method to return your own custom actions. * * @return the InputActions, that can be bound with a Widget of this type. */ public InputAction[] getInputActions() { return ( null ); } /** * Gets the {@link TransformableTexture}s, that this {@link Widget} keeps. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * @param widgetInnerWidth the total widget width excluding borders * @param widgetInnerHeight the total widget height excluding borders * @param collector the collector to collect all the sub textures */ protected abstract void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ); /** * Gets the {@link TransformableTexture}s, that this {@link Widget} keeps. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * @param widgetInnerWidth the total widget width excluding borders * @param widgetInnerHeight the total widget height excluding borders * * @return the {@link TransformableTexture}s, that this {@link Widget} keeps or null for no textures. */ public final TransformableTexture[] getSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight ) { if ( !initialized ) { SubTextureCollector collector = new SubTextureCollector(); initSubTextures( gameData, isEditorMode, widgetInnerWidth, widgetInnerHeight, collector ); subTextures = __UtilPrivilegedAccess.getSubTextureArray( collector, true ); if ( subTextures != null ) { for ( int i = 0; i < subTextures.length; i++ ) { if ( subTextures[i].getOwnerWidget() == null ) __RenderPrivilegedAccess.setOwnerWidget( this, subTextures[i] ); } } } return ( subTextures ); } protected void onDirtyFlagSet() { } void setDirtyFlag( boolean forwardCall ) { boolean changed = !this.dirtyFlag; this.dirtyFlag = true; if ( forwardCall && ( masterWidget != null ) ) masterWidget.setDirtyFlag( false ); if ( changed ) onDirtyFlagSet(); } public void setDirtyFlag() { this.dirtyFlag = true; if ( masterWidget != null ) masterWidget.setDirtyFlag(); } public final boolean getDirtyFlag( boolean reset ) { boolean result = dirtyFlag; if ( reset ) this.dirtyFlag = false; return ( result ); } protected void onReinitializationForced() { } final boolean isInitialized() { return ( initialized ); } void forceReinitialization( boolean forwardCall ) { boolean changed = this.initialized; this.initialized = false; if ( forwardCall && ( masterWidget != null ) ) masterWidget.forceReinitialization( false ); setDirtyFlag(); if ( changed ) onReinitializationForced(); } public final void forceReinitialization() { forceReinitialization( true ); } /** * Sets this {@link Widget}'s name. * * @param name the new name for this {@link Widget} */ public void setName( String name ) { String oldName = this.name.getStringValue(); this.name.setStringValue( name ); setDirtyFlag(); if ( getConfiguration() != null ) { __WCPrivilegedAccess.updateNameMapping( this, oldName, getConfiguration() ); } } /** * Gets this {@link Widget}'s name. * * @return this {@link Widget}'s name. */ public final String getName() { return ( name.getStringValue() ); } /** * Gets the {@link Widget}'s position. * * @return the {@link Widget}'s position. */ public final Position getPosition() { return ( position ); } /** * Gets the x-offset relative to the master Widget. * * @return the x-offset relative to the master Widget. */ public final int getOffsetXToRootMasterWidget() { if ( getMasterWidget() == null ) return ( 0 ); return ( getBorder().getInnerLeftWidth() + position.getEffectiveX() + getMasterWidget().getOffsetXToRootMasterWidget() ); } /** * Gets the y-offset relative to the master Widget. * * @return the y-offset relative to the master Widget. */ public final int getOffsetYToRootMasterWidget() { if ( getMasterWidget() == null ) return ( 0 ); return ( getBorder().getInnerTopHeight() + position.getEffectiveY() + getMasterWidget().getOffsetYToRootMasterWidget() ); } /** * Gets the absolute x-position relative to the configuration origin. * * @return the absolute x-position relative to the configuration origin. */ public final int getAbsoluteOffsetX() { int x = position.getEffectiveX(); if ( getMasterWidget() != null ) x += getMasterWidget().getBorder().getInnerLeftWidth() + getMasterWidget().getAbsoluteOffsetX(); return ( x ); } /** * Gets the absolute y-position relative to the configuration origin. * * @return the absolute y-position relative to the configuration origin. */ public final int getAbsoluteOffsetY() { int y = position.getEffectiveY(); if ( getMasterWidget() != null ) y += getMasterWidget().getBorder().getInnerTopHeight() + getMasterWidget().getAbsoluteOffsetY(); return ( y ); } /** * Sets the {@link Widget}'s z-index relative to other {@link Widget}s in the same configuration. * * @param zIndex the new z-index */ public void setZIndex( int zIndex ) { this.zIndex.setIntValue( zIndex ); } /** * Gets the {@link Widget}'s z-index relative to other {@link Widget}s in the same configuration. * * @return the {@link Widget}'s z-index relative to other {@link Widget}s in the same configuration. */ public final int getZIndex() { return ( zIndex.getIntValue() ); } /** * Gets, whether this {@link Widget} has a fixed (unmodifiable) size. * * @return whether this {@link Widget} has a fixed (unmodifiable) size. */ public boolean hasFixedSize() { return ( false ); } /** * Gets this {@link Widget}'s size. * * @return this {@link Widget}'s width. */ public final Size getSize() { return ( size ); } /** * Gets the inner size of the {@link Widget}. * * @return the inner size of the {@link Widget}. */ public final InnerSize getInnerSize() { return ( innerSize ); } /** * Gets the result of getSize().getEffectiveWidth(). * * @return the result of getSize().getEffectiveWidth(). */ public final int getEffectiveWidth() { return ( size.getEffectiveWidth() ); } /** * Gets the result of getSize().getEffectiveHeight(). * * @return the result of getSize().getEffectiveHeight(). */ public final int getEffectiveHeight() { return ( size.getEffectiveHeight() ); } /** * Gets the minimum width for this {@link Widget} in pixels. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * * @return the minimum width for this {@link Widget} in pixels. */ public int getMinWidth( LiveGameData gameData, boolean isEditorMode ) { return ( 25 ); } /** * Gets the minimum height for this {@link Widget} in pixels. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * * @return the minimum height for this {@link Widget} in pixels. */ public int getMinHeight( LiveGameData gameData, boolean isEditorMode ) { return ( 25 ); } /** * Gets the maximum width covered by this {@link Widget}. * By default this method returns the result of getEffectiveWidth(gameResX). * Override this method, if it will change its size during game play. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * * @return the maximum width covered by this {@link Widget}. */ public int getMaxWidth( LiveGameData gameData, boolean isEditorMode ) { return ( size.getEffectiveWidth() ); } /** * Gets the maximum height covered by this {@link Widget}. * By default this method returns the result of getEffectiveHeight(gameResX). * Override this method, if it will change its size during game play. * * @param gameData the live game data * @param isEditorMode rendering in the editor? * * @return the maximum height covered by this {@link Widget}. */ public int getMaxHeight( LiveGameData gameData, boolean isEditorMode ) { return ( size.getEffectiveHeight() ); } /** * Bakes effective position and size to variables, so that they don't need to be recalculated * during runtime on each access. * * @param convertToPixels if true, all coordinates are converted to absolute pixels and positioned to TOP_LEFT. */ public void bake( boolean convertToPixels ) { Iterator<Position> it1 = new GenericPositionsIterator( this ); while ( it1.hasNext() ) { Position pos = it1.next(); /* if ( convertToPixels ) { pos.setXToPixels(); pos.setYToPixels(); pos.setEffectivePosition( RelativePositioning.TOP_LEFT, pos.getEffectiveX(), pos.getEffectiveY() ); } */ pos.bake(); } Iterator<Size> it2 = new GenericSizesIterator( this ); while ( it2.hasNext() ) { Size size = it2.next(); /* if ( convertToPixels ) { size.setWidthToPixels(); size.setHeightToPixels(); } */ size.bake(); } } public void setAllPosAndSizeToPercents() { Iterator<Position> it1 = new GenericPositionsIterator( this ); while ( it1.hasNext() ) { Position pos = it1.next(); pos.setXToPercents(); pos.setYToPercents(); } Iterator<Size> it2 = new GenericSizesIterator( this ); while ( it2.hasNext() ) { Size siz = it2.next(); siz.setWidthToPercents(); siz.setHeightToPercents(); } } public void setAllPosAndSizeToPixels() { Iterator<Position> it1 = new GenericPositionsIterator( this ); while ( it1.hasNext() ) { Position pos = it1.next(); pos.setXToPixels(); pos.setYToPixels(); } Iterator<Size> it2 = new GenericSizesIterator( this ); while ( it2.hasNext() ) { Size siz = it2.next(); siz.setWidthToPixels(); siz.setHeightToPixels(); } } public final BackgroundProperty getBackgroundProperty() { return ( backgroundProperty ); } /** * Gets the {@link Widget}'s background. * * @return the {@link Widget}'s background. */ public final WidgetBackground getBackground() { return ( background ); } public final FontProperty getFontProperty() { return ( font ); } protected final java.awt.Font getFont() { return ( font.getFont() ); } protected final boolean isFontAntiAliased() { return ( font.isAntiAliased() ); } public final ColorProperty getFontColorProperty() { return ( fontColor ); } /** * Gets the {@link Widget}'s font color. * * @return the {@link Widget}'s font color. */ protected final Color getFontColor() { return ( fontColor.getColor() ); } protected final IntProperty getPaddingPropertyTop() { return ( paddingTop ); } protected final int getPaddingTop() { if ( masterWidget != null ) return ( 0 ); return ( paddingTop.getIntValue() ); } protected final IntProperty getPaddingPropertyLeft() { return ( paddingLeft ); } protected final int getPaddingLeft() { if ( masterWidget != null ) return ( 0 ); return ( paddingLeft.getIntValue() ); } protected final IntProperty getPaddingPropertyRight() { return ( paddingRight ); } protected final int getPaddingRight() { if ( masterWidget != null ) return ( 0 ); return ( paddingRight.getIntValue() ); } protected final IntProperty getPaddingPropertyBottom() { return ( paddingBottom ); } protected final int getPaddingBottom() { if ( masterWidget != null ) return ( 0 ); return ( paddingBottom.getIntValue() ); } protected final BorderProperty getBorderProperty() { return ( border ); } /** * Sets padding for this Widget. * * @param top top padding value * @param left left padding value * @param right right padding value * @param bottom bottom padding value */ protected final void setPadding( int top, int left, int right, int bottom ) { paddingTop.setIntValue( top ); paddingLeft.setIntValue( left ); paddingRight.setIntValue( right ); paddingBottom.setIntValue( bottom ); } /** * Sets padding for this Widget. * * @param padding top, left, right and bottom padding value */ protected final void setPadding( int padding ) { setPadding( padding, padding, padding, padding ); } /** * Returns a {@link BorderWrapper}, that encapsulates the actual used border with convenience wrappers for the size getters. * The {@link BorderWrapper} instance is never null while the border can be null. * * @return a {@link BorderWrapper} for the used Border (never null). */ public final BorderWrapper getBorder() { return ( border.getBorder() ); } /** * This method is called when a complete redraw has been forced. */ protected void onCompleteRedrawForced() { } void forceCompleteRedraw_( boolean mergedBackgroundToo, boolean forwardCall ) { boolean changed = !this.needsCompleteRedraw; this.needsCompleteRedraw = true; if ( ( background != null ) && mergedBackgroundToo ) background.setMergedBGDirty(); if ( forwardCall && ( masterWidget != null ) ) if ( masterWidget != null ) masterWidget.forceCompleteRedraw_( mergedBackgroundToo, false ); setDirtyFlag(); if ( changed ) onCompleteRedrawForced(); } /** * Forces a complete redraw on the next render. * * @param mergedBackgroundToo if <code>true</code>, the clear-background will be redrawn and the {@link #drawBackground(LiveGameData, boolean, TextureImage2D, int, int, int, int, boolean)} methods will be called again. */ public final void forceCompleteRedraw( boolean mergedBackgroundToo ) { forceCompleteRedraw_( mergedBackgroundToo, true ); } /** * This simply calls {@link #forceCompleteRedraw(boolean)}, {@link #forceReinitialization()} and {@link #setDirtyFlag()}. * This method must be called after a value has been changed, that requires a reinitialization of all positioned strings, etc. * * @param mergedBackgroundToo whether to set merged background dirty, too */ public final void forceAndSetDirty( boolean mergedBackgroundToo ) { forceCompleteRedraw( mergedBackgroundToo ); forceReinitialization(); setDirtyFlag(); } /** * Sets this Widget's visibility usually controlled by the ToggleWidgetVisibility InputAction.<br /> * This flag is also restored when a different configurations is loaded unlike the others. * * @param visible visible? */ void setInputVisible( boolean visible ) { boolean wasVisible = isVisible(); this.inputVisible.setBooleanValue( visible ); if ( isVisible() != wasVisible ) onVisibilityChanged( visible ); } /** * Gets this Widget's visibility usually controlled by the ToggleWidgetVisibility InputAction.<br /> * This flag is also restored when a different configurations is loaded unlike the others. * * @return this Widget's visibility flag. */ public final boolean getInputVisibility() { return ( inputVisible.getBooleanValue() ); } private void setAutoVisiblility( boolean visible ) { boolean wasVisible = isVisible(); this.autoVisible = visible; if ( isVisible() != wasVisible ) onVisibilityChanged( visible ); } /** * Gets the automatically toggled visibility flag. * * @return this Widget's visibility flag. */ public final boolean getAutoVisibility() { return ( autoVisible ); } private void setUpdatedVisibility( boolean visible ) { boolean wasVisible = isVisible(); this.updatedVisible = visible; if ( isVisible() != wasVisible ) onVisibilityChanged( visible ); } /** * Gets this Widget's user visibility flag 2. This is the one, you should toggle in your widget code. * * @return this Widget's visibility flag. */ public final boolean getUpdatedVisibility() { return ( updatedVisible ); } /** * Gets this Widget's total visibility flag ({@link #getInputVisibility()} && {@link #getAutoVisibility()} && {@link #getUpdatedVisibility()}). * * @return this Widget's visibility flag. */ public final boolean isVisible() { return ( inputVisible.getBooleanValue() && autoVisible && updatedVisible ); } public final boolean visibilityChangedSinceLastDraw() { return ( visibilityChangedSinceLastDraw ); } /** * This method is called first by the rendering system each to check for visibility changes. * This doesn't affect the visiblity toggled by the {@link #onBoundInputStateChanged(InputAction, boolean, int, long, LiveGameData, boolean)} method. * Automatic visiblity can also override the result. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @return <code>true</code> to set visible, <code>false</code> to set invisible, <code>null</code> for no change in visibility. * * @see #getUpdatedVisibility() */ protected Boolean updateVisibility( LiveGameData gameData, boolean isEditorMode ) { return ( null ); } final void _updateVisibility( LiveGameData gameData, boolean isEditorMode ) { Boolean result; if ( controller != null ) { result = controller.isWidgetVisible(); if ( result == null ) result = updateVisibility( gameData, isEditorMode ); else updateVisibility( gameData, isEditorMode ); } else { result = updateVisibility( gameData, isEditorMode ); } if ( result != null ) setUpdatedVisibility( result.booleanValue() ); } private final boolean needsCompleteRedraw() { boolean result = needsCompleteRedraw; needsCompleteRedraw = false; return ( result ); } /** * Gets, whether this Widget has just been set invisible and its area hence needs to be cleared. * The flag is forced to false after this method has been called. * * @return whether this Widget has just been set invisible and its area hence needs to be cleared. */ final boolean needsCompleteClear() { boolean result = needsCompleteClear; needsCompleteClear = false; return ( result ); } /** * Gets the data indicators for the data needed for this {@link Widget} to be drawn (bitmask). * * @see #NEEDED_DATA_TELEMETRY * @see #NEEDED_DATA_SCORING * @see #NEEDED_DATA_SCORING * * @return the data indicators for the data needed for this {@link Widget} to be drawn. */ public int getNeededData() { return ( 0 ); } /** * Gets the {@link Widget}'s {@link DrawnStringFactory}. * * @return the {@link Widget}'s {@link DrawnStringFactory}. */ protected final DrawnStringFactory getDrawnStringFactory() { return ( drawnStringFactory ); } /** * This event is fired right after the {@link WidgetsConfiguration} has been (re-)loaded. * * @param widgetsConfig the widgets configuration * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void afterConfigurationLoaded( WidgetsConfiguration widgetsConfig, LiveGameData gameData, boolean isEditorMode ) { } /** * This event is fired right before the {@link WidgetsConfiguration} is cleared. * * @param widgetsConfig the widgets configuration * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void beforeConfigurationCleared( WidgetsConfiguration widgetsConfig, LiveGameData gameData, boolean isEditorMode ) { } /** * This method is executed when a new track was loaded. * * @param trackname the current track's name * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onTrackChanged( String trackname, LiveGameData gameData, boolean isEditorMode ) { } /** * This method is executed when a new session was started. * * @param sessionType the current session type * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onSessionStarted( SessionType sessionType, LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the user entered realtime mode. If your {@link Widget} needs some data * to be drawn correctly, consider using {@link #onNeededDataComplete(LiveGameData, boolean)}. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @deprecated replaced by {@link #onCockpitEntered(LiveGameData, boolean)} */ @Deprecated public void onRealtimeEntered( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the user entered the cockpit. If your {@link Widget} needs some data * to be drawn correctly, consider using {@link #onNeededDataComplete(LiveGameData, boolean)}. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode ) { onRealtimeEntered( gameData, isEditorMode ); } /** * This method is called when {@link ScoringInfo} have been updated (done at 2Hz). * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onScoringInfoUpdated( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when {@link VehicleSetup} has been updated. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onVehicleSetupUpdated( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when the needed data is available in realtime mode. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onNeededDataComplete( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the car entered the pits. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onPitsEntered( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the car entered the garage. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onGarageEntered( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the car exited the garage. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onGarageExited( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the car exited the pits. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onPitsExited( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the user exited realtime mode. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @deprecated replaced by {@link #onCockpitExited(LiveGameData, boolean)} */ @Deprecated public void onRealtimeExited( LiveGameData gameData, boolean isEditorMode ) { } /** * This method is called when a the user exited the cockpit. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onCockpitExited( LiveGameData gameData, boolean isEditorMode ) { onRealtimeExited( gameData, isEditorMode ); } /** * This method is called when either the player's vehicle control has changed or another vehicle is being viewed. * * @param viewedVSI the currently viewed vehicle * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @return <code>true</code> to set visible, <code>false</code> to set invisible, <code>null</code> for no change in visibility. * * This doesn't affect the visiblity toggled by the {@link #updateVisibility(LiveGameData, boolean)} method or {@link #onBoundInputStateChanged(InputAction, boolean, int, long, LiveGameData, boolean)} method. * * @see #getAutoVisibility() */ protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { return ( null ); } final void _onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { Boolean result = onVehicleControlChanged( viewedVSI, gameData, isEditorMode ); if ( result != null ) setAutoVisiblility( result.booleanValue() ); } /** * This method is called when a lap has been finished and a new one was started. * * @param vsi the driver, who started the lap. If this is the leader and the session type is RACE, the whole race has moved on to the next lap. * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor */ public void onLapStarted( VehicleScoringInfo vsi, LiveGameData gameData, boolean isEditorMode ) { } /** * This event is fired, when a bound input component has changed its state. * * @param action the triggered action * @param state the state of the input device component * @param modifierMask see {@link InputAction} * @param when the timestamp in nano seconds * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @return <code>true</code> to set visible, <code>false</code> to set invisible, <code>null</code> for no change in visibility. * * This doesn't affect the visiblity toggled by the {@link #updateVisibility(LiveGameData, boolean)} method. * Automatic visiblity can also override the result. * * @see #getInputVisibility() */ protected Boolean onBoundInputStateChanged( InputAction action, boolean state, int modifierMask, long when, LiveGameData gameData, boolean isEditorMode ) { return ( null ); } final void _onBoundInputStateChanged( InputAction action, boolean state, int modifierMask, long when, LiveGameData gameData, boolean isEditorMode ) { Boolean result = onBoundInputStateChanged( action, state, modifierMask, when, gameData, isEditorMode ); if ( result != null ) setInputVisible( result.booleanValue() ); } /** * Returns <code>true</code>, if this {@link Widget} draws on the main texture, <code>false</code> otherwise.<br /> * Default is <code>true</code>. * * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * * @return <code>true</code>, if this {@link Widget} draws on the main texture, <code>false</code> otherwise. */ public boolean hasMasterCanvas( boolean isEditorMode ) { return ( true ); } protected void clearRegion( TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { if ( texture == null ) return; texture.getTextureCanvas().pushClip( offsetX, offsetY, width, height ); texture.clear( offsetX, offsetY, width, height, true, null ); texture.getTextureCanvas().popClip(); this.visibilityChangedSinceLastDraw = false; } /** * Clears the region on the texture, that is covered by this {@link Widget}. * * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param offsetX the x offset of the {@link Widget} on the drawing texture * @param offsetY the y offset of the {@link Widget} on the drawing texture */ public final void clearRegion( TextureImage2D texture, int offsetX, int offsetY ) { int width = size.getEffectiveWidth(); int height = size.getEffectiveHeight(); clearRegion( texture, offsetX, offsetY, width, height ); } /** * Clears the given part of the {@link Widget} area with the current background. * * @param texture the target texture * @param offsetX the x offset of the {@link Widget} on the drawing texture * @param offsetY the y offset of the {@link Widget} on the drawing texture * @param localX the x coordinate of the upper left corner of the area to be cleared relative to the Widget's location * @param localY the y coordinate of the upper left corner of the area to be cleared relative to the Widget's location * @param width the width of the area to be cleared * @param height the height of the area to be cleared * @param markDirty if true, the pixel is marked dirty * @param dirtyRect if non null, the dirty rect is written to this instance * * @return <code>true</code>, if this Widgets defines a background to clear with, <code>false</code> otherwise. */ public boolean clearBackgroundRegion( TextureImage2D texture, int offsetX, int offsetY, int localX, int localY, int width, int height, boolean markDirty, Rect2i dirtyRect ) { if ( getMasterWidget() != null ) { int effX = getPosition().getEffectiveX(); int effY = getPosition().getEffectiveY(); return ( getMasterWidget().clearBackgroundRegion( texture, offsetX - effX, offsetY - effY, localX + effX, localY + effY, width, height, markDirty, dirtyRect ) ); } final WidgetBackground background = getBackground(); if ( background == null ) { if ( dirtyRect != null ) dirtyRect.set( -1, -1, 0, 0 ); return ( false ); } TextureImage2D mergedBG = background.getMergedTexture(); if ( mergedBG != null ) { texture.clear( mergedBG, localX, localY, width, height, offsetX + localX, offsetY + localY, markDirty, dirtyRect ); return ( true ); } if ( background.getType().isColor() ) { texture.clear( background.getColor(), offsetX + localX, offsetY + localY, width, height, markDirty, dirtyRect ); return ( true ); } if ( background.getType().isImage() ) { texture.clear( background.getTexture(), localX, localY, width, height, offsetX + localX, offsetY + localY, markDirty, dirtyRect ); return ( true ); } return ( false ); } /** * This method is called once to initialized {@link DrawnString}s used on this Widget. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param drawnStringFactory a factory to get {@link DrawnString} instances from * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param width the width on the texture * @param height the height on the texture */ protected abstract void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory drawnStringFactory, TextureImage2D texture, int width, int height ); /** * Checks, if the Widget needs any changes before it is drawn. If true, {@link #drawBorder(boolean, BorderWrapper, TextureImage2D, int, int, int, int)} * and possibly {@link #drawBackground(LiveGameData, boolean, TextureImage2D, int, int, int, int, boolean)} are (re-)invoked.<br /> * The original method is just an empty stub returning false. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param width the width on the texture * @param height the height on the texture * * @return true, if size has changed. */ protected boolean checkForChanges( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int width, int height ) { return ( false ); } /** * * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param border the border * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param offsetX the x-offset on the drawing texture * @param offsetY the y offset on the drawing texture * @param width the width of the area on the drawing texture * @param height the height of the area on the drawing texture */ protected void drawBorder( boolean isEditorMode, BorderWrapper border, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { if ( hasBorder() && ( texture != null ) ) { border.drawBorder( ( ( background == null ) || !background.getType().isColor() ) ? null : background.getColor(), texture, offsetX, offsetY, width, height ); } } /** * You can use this method to directly draw static content onto your Widget's background. * Overriding this method makes the Widget use a background texture no matter, if the background is defined with a color only or an image. * * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param offsetX the x-offset on the drawing texture * @param offsetY the y offset on the drawing texture * @param width the width of the area on the drawing texture * @param height the height of the area on the drawing texture * @param isRoot if this is true, you can possibly clear your stuff onto the texture instead of drawing it. */ protected void drawBackground( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height, boolean isRoot ) { if ( canHaveBackground() ) { if ( background.getType().isColor() ) { if ( isRoot ) texture.clear( background.getColor(), offsetX, offsetY, width, height, false, null ); else if ( background.getColor().getAlpha() > 0 ) texture.fillRectangle( background.getColor(), offsetX, offsetY, width, height, false, null ); } else if ( background.getType().isImage() ) { if ( isRoot ) texture.clear( background.getTexture(), 0, 0, width, height, offsetX, offsetY, width, height, false, null ); else texture.drawImage( background.getTexture(), 0, 0, width, height, offsetX, offsetY, width, height, false, null ); } } } void _drawBackground( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height, boolean isRoot ) { texture.getTextureCanvas().pushClip( offsetX, offsetY, width, height, true ); try { drawBackground( gameData, isEditorMode, texture, offsetX, offsetY, width, height, isRoot ); } finally { texture.getTextureCanvas().popClip(); } } /** * @param background never <code>null</code>! * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param offsetX the x-offset on the drawing texture * @param offsetY the y offset on the drawing texture * @param width the width of the area on the drawing texture * @param height the height of the area on the drawing texture */ private final void clearBackground( WidgetBackground background, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { if ( texture != null ) { TextureImage2D mergedBG = background.getMergedTexture(); if ( mergedBG == null ) { if ( background.getColor() != null ) texture.clear( background.getColor(), offsetX, offsetY, width, height, true, null ); else if ( background.getTexture() != null ) texture.clear( background.getTexture(), offsetX, offsetY, width, height, true, null ); else texture.clear( offsetX, offsetY, width, height, true, null ); } else { texture.clear( mergedBG, offsetX, offsetY, width, height, true, null ); } } } /** * This method must contain the actual drawing code for this Widget. * * @param clock this is a clock for very dynamic content, that needs smooth display. If 'needsCompleteRedraw' is true, clock1 is also true. * @param needsCompleteRedraw whether this widget needs to be completely redrawn (true) or just the changed parts (false) * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param offsetX the x-offset on the texture * @param offsetY the y-offset on the texture * @param width the width on the texture * @param height the height on the texture */ protected abstract void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ); /** * This method invokes the parts of the actual drawing code for this Widget. * * @param clock this is a clock for very dynamic content, that needs smooth display. If 'needsCompleteRedraw' is true, clock1 is also true. * @param completeRedrawForced whether this widget needs to be completely redrawn (true) or just the changed parts (false) * @param gameData the live game data * @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor * @param texture the texture image to draw on. Use {@link TextureImage2D#getTextureCanvas()} to retrieve the {@link Texture2DCanvas} for Graphics2D drawing. * @param drawAtZero draw at position 0,0? */ public final void drawWidget( Clock clock, boolean completeRedrawForced, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, boolean drawAtZero ) { int offsetX = drawAtZero ? 0 : position.getEffectiveX(); int offsetY = drawAtZero ? 0 : position.getEffectiveY(); int width = size.getEffectiveWidth(); int height = size.getEffectiveHeight(); int borderOLW = getBorder().getOpaqueLeftWidth(); int borderOTH = getBorder().getOpaqueTopHeight(); int borderORW = getBorder().getOpaqueRightWidth(); int borderOBH = getBorder().getOpaqueBottomHeight(); int borderLW = getBorder().getInnerLeftWidth(); int borderTH = getBorder().getInnerTopHeight(); int borderRW = getBorder().getInnerRightWidth(); int borderBH = getBorder().getInnerBottomHeight(); int offsetX2 = offsetX + borderLW; int offsetY2 = offsetY + borderTH; int width2 = width - borderLW - borderRW; int height2 = height - borderTH - borderBH; if ( !isEditorMode && !hasMasterCanvas( isEditorMode ) ) texture = null; final Texture2DCanvas texCanvas = ( texture == null ) ? null : texture.getTextureCanvas(); if ( !initialized ) { initialize( gameData, isEditorMode, drawnStringFactory, texture, width2, height2 ); initialized = true; } if ( checkForChanges( gameData, isEditorMode, texture, width2, height2 ) ) { clearRegion( texture, offsetX, offsetY, width, height ); forceCompleteRedraw( true ); completeRedrawForced = true; //offsetX = position.getEffectiveX(); //offsetY = position.getEffectiveY(); width = size.getEffectiveWidth(); height = size.getEffectiveHeight(); offsetX2 = offsetX + borderLW; offsetY2 = offsetY + borderTH; width2 = width - borderLW - borderRW; height2 = height - borderTH - borderBH; } if ( texCanvas != null ) texCanvas.setClip( offsetX, offsetY, width, height ); completeRedrawForced = needsCompleteRedraw() || completeRedrawForced; if ( completeRedrawForced ) { __RenderPrivilegedAccess.onWidgetCleared( drawnStringFactory ); drawBorder( isEditorMode, getBorder(), texture, offsetX, offsetY, width, height ); if ( texture != null ) texture.markDirty( offsetX, offsetY, width, height, null ); if ( ( getMasterWidget() == null ) && ( background != null ) ) { background.updateMergedBackground( gameData, isEditorMode ); clearBackground( background, texture, offsetX2 - getBorder().getPaddingLeft(), offsetY2 - getBorder().getPaddingTop(), width2 + getBorder().getPaddingLeft() + getBorder().getPaddingRight(), height2 + getBorder().getPaddingTop() + getBorder().getPaddingBottom() ); } } if ( texCanvas != null ) texCanvas.setClip( offsetX + borderOLW, offsetY + borderOTH, width - borderOLW - borderORW, height - borderOTH - borderOBH ); drawWidget( clock, completeRedrawForced, gameData, isEditorMode, texture, offsetX2, offsetY2, width2, height2 ); this.visibilityChangedSinceLastDraw = false; } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { writer.writeProperty( positioningProperty, "The way, position coordinates are interpreted (relative to). Valid values: TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT." ); writer.writeProperty( xProperty, "The x-coordinate for the position." ); writer.writeProperty( yProperty, "The y-coordinate for the position." ); writer.writeProperty( zIndex, "A z-index, to sort Widgets by." ); writer.writeProperty( widthProperty, "The width. Use negative values to make the Widget be sized relative to screen size." ); writer.writeProperty( heightProperty, "The height. Use negative values to make the Widget be sized relative to screen size." ); if ( masterWidget == null ) { writer.writeProperty( border, "The widget's border." ); writer.writeProperty( paddingTop, "top padding" ); writer.writeProperty( paddingLeft, "left padding" ); writer.writeProperty( paddingRight, "right padding" ); writer.writeProperty( paddingBottom, "bottom padding" ); writer.writeProperty( inputVisible, "The initial visibility." ); } if ( canHaveBackground() ) { writer.writeProperty( backgroundProperty, "The Widget's background (color or image)." ); } if ( hasText() ) { writer.writeProperty( font, "The used font." ); writer.writeProperty( fontColor, "The Widget's font color in the format #RRGGBB (hex)." ); } } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { if ( loader.getSourceVersion().getBuild() < 78 ) { if ( loader.getCurrentKey().equals( "backgroundColor" ) && !this.getClass().getSimpleName().startsWith( "ETV" ) ) backgroundProperty.loadValue( loader, "color:" + loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "backgroundImageName" ) ) backgroundProperty.loadValue( loader, "image:" + loader.getCurrentValue() ); } if ( loader.loadProperty( name ) ); else if ( loader.loadProperty( positioningProperty ) ); else if ( loader.loadProperty( xProperty ) ); else if ( loader.loadProperty( yProperty ) ); else if ( loader.loadProperty( zIndex ) ); else if ( loader.loadProperty( widthProperty ) ); else if ( loader.loadProperty( heightProperty ) ); else if ( ( masterWidget == null ) && loader.loadProperty( border ) ); else if ( loader.loadProperty( paddingTop ) ); else if ( loader.loadProperty( paddingLeft ) ); else if ( loader.loadProperty( paddingRight ) ); else if ( loader.loadProperty( paddingBottom ) ); else if ( loader.loadProperty( inputVisible ) ); else if ( canHaveBackground() && loader.loadProperty( backgroundProperty ) ); else if ( loader.loadProperty( font ) ); else if ( loader.loadProperty( fontColor ) ); } /** * This method is called instead of {@link #loadProperty(PropertyLoader)} to load the property for compatiblity reasons. * It simply forwards the call to {@link #loadProperty(PropertyLoader)}. So override this method, if you need game data. * * @param loader * @param gameData */ public void loadProperty( PropertyLoader loader, LiveGameData gameData ) { loadProperty( loader ); } /** * Gets an {@link Iterator} to iterate all properties, defined in this class. * * @return an {@link Iterator} to iterate all properties, defined in this class. */ public Iterator<Property> getPropertiesIterator() { return ( new GenericPropertiesIterator( this ) ); } /** * Adds the type and name properties to the {@link PropertiesContainer}. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addTypeAndNamePropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addProperty( type ); propsCont.addProperty( name ); } /** * Adds the visibility properties to the {@link PropertiesContainer}. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addVisibilityPropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { if ( masterWidget == null ) { propsCont.addProperty( inputVisible ); } } /** * Adds the position and size properties to the {@link PropertiesContainer}. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addPositionAndSizePropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addProperty( positioningProperty ); propsCont.addProperty( xProperty ); propsCont.addProperty( yProperty ); propsCont.addProperty( zIndex ); propsCont.addProperty( widthProperty ); propsCont.addProperty( heightProperty ); } /** * Adds the border property to the container. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addBorderPropertyToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addProperty( border ); } /** * Adds the padding properties to the container. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addPaddingPropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.pushGroup( "Padding", false ); propsCont.addProperty( paddingTop ); propsCont.addProperty( paddingLeft ); propsCont.addProperty( paddingRight ); propsCont.addProperty( paddingBottom ); propsCont.popGroup(); } /** * Adds the background property to the container. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addBackgroundPropertyToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addProperty( backgroundProperty ); } /** * Adds the font and font color properties to the container. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void addFontPropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addProperty( font ); propsCont.addProperty( fontColor ); } /** * Puts all editable properties to the editor. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void getPropertiesForParentGroup( PropertiesContainer propsCont, boolean forceAll ) { } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addGroup( "General" ); addTypeAndNamePropertiesToContainer( propsCont, forceAll ); addVisibilityPropertiesToContainer( propsCont, forceAll ); addPositionAndSizePropertiesToContainer( propsCont, forceAll ); if ( masterWidget == null ) { addPaddingPropertiesToContainer( propsCont, forceAll ); } if ( ( masterWidget == null ) && canHaveBorder() ) { addBorderPropertyToContainer( propsCont, forceAll ); } if ( canHaveBackground() ) { addBackgroundPropertyToContainer( propsCont, forceAll ); } if ( hasText() ) { addFontPropertiesToContainer( propsCont, forceAll ); } getPropertiesForParentGroup( propsCont, forceAll ); //propsCont.dump(); } protected Widget getNewInstanceForClone() { @SuppressWarnings( "unchecked" ) Class<Widget> clazz = (Class<Widget>)getClass(); return ( WidgetFactory.createWidget( clazz, "CloneOf" + getName() ) ); } /** * Clones the value of the src property to the trg property. * This method is invoked on the target Widget. * * @param src * @param trg * * @return <code>true</code>, if the properties list needs to be refreshed, <code>false</code> otherwise. */ protected boolean cloneProperty( Property src, Property trg ) { trg.setValue( src.getValue() ); return ( false ); } @Override public Widget clone() { Widget newWidget = getNewInstanceForClone(); if ( newWidget == null ) return ( null ); FlatPropertiesContainer pcTemplate = new FlatPropertiesContainer(); FlatPropertiesContainer pcTarget = new FlatPropertiesContainer(); this.getProperties( pcTemplate, true ); newWidget.getProperties( pcTarget, true ); List<Property> lstTemplate = pcTemplate.getList(); List<Property> lstTarget = pcTarget.getList(); // We assume, that the order will be the same in both lists. for ( int i = 0; i < lstTemplate.size(); i++ ) { Property p0 = lstTemplate.get( i ); Property p1 = lstTarget.get( i ); //if ( ( includePosition || ( !p0.getName().equals( "x" ) && !p0.getName().equals( "y" ) && !p0.getName().equals( "positioning" ) ) ) ) if ( newWidget.cloneProperty( p0, p1 ) ) { pcTarget.clear(); newWidget.getProperties( pcTarget, true ); } } return ( newWidget ); } private String getDocumentationSource( Class<?> clazz ) { URL docURL = this.getClass().getClassLoader().getResource( clazz.getPackage().getName().replace( '.', '/' ) + "/doc/widget.html" ); if ( docURL == null ) { if ( ( clazz.getSuperclass() != null ) && ( clazz.getSuperclass() != Object.class ) ) return ( getDocumentationSource( clazz.getSuperclass() ) ); return ( "" ); } return ( StringUtils.loadString( docURL ) ); } public final String getDocumentationSource() { return ( getDocumentationSource( this.getClass() ) ); } /** * Defines, if this Widget type can have a border. * * @return if this Widget type can have a border. */ protected boolean canHaveBorder() { return ( true ); } /** * Defines, if this Widget type can have a background. * * @return if this Widget type can have a background. */ protected boolean canHaveBackground() { return ( true ); } /** * Gets whether this {@link Widget} has a border or not. * * @return whether this {@link Widget} has a border or not. */ protected final boolean hasBorder() { if ( !canHaveBorder() ) return ( false ); if ( !getBorder().hasBorder() ) return ( false ); /* if ( background == null ) return ( false ); return ( background.getType().isColor() ); */ return ( true ); } /** * Defines, if a Widget type (potentially) contains any text. * If <code>false</code>, the editor won't provide font or font-color selection. * Should return a contant value. * * @return if this Widget can contain any text. */ protected boolean hasText() { return ( true ); } /** * This method is called by the editor before it draws the Widget to a menu item. */ public void prepareForMenuItem() { border.setBorder( null ); setPadding( 0, 0, 0, 0 ); } /** * Creates a new {@link Widget}. * * @param widgetSet the {@link WidgetSet} this {@link Widget} belongs to * @param widgetPackage the package in the editor * @param width negative numbers for (screen_width - width) * @param widthPercent width parameter treated as percents * @param height negative numbers for (screen_height - height) * @param heightPercent height parameter treated as percents */ protected Widget( WidgetSet widgetSet, WidgetPackage widgetPackage, float width, boolean widthPercent, float height, boolean heightPercent ) { this.widgetSet = widgetSet; this.widgetPackage = widgetPackage; this.size = Size.newGlobalSize( this, width, widthPercent, height, heightPercent ); this.innerSize = new InnerSize( size, border ); this.position = Position.newGlobalPosition( this, RelativePositioning.TOP_LEFT, 0f, true, 0f, true, size ); this.positioningProperty = position.getPositioningProperty( "positioning" ); this.xProperty = position.getXProperty( "x" ); this.yProperty = position.getYProperty( "y" ); this.widthProperty = size.getWidthProperty( "width" ); this.heightProperty = size.getHeightProperty( "height" ); if ( !canHaveBorder() ) border.setBorder( null ); } /** * Creates a new {@link Widget}. * * @param widgetSet the {@link WidgetSet} this {@link Widget} belongs to * @param widgetPackage the package in the editor * @param width negative numbers for (screen_width - width) * @param height negative numbers for (screen_height - height) */ protected Widget( WidgetSet widgetSet, WidgetPackage widgetPackage, float width, float height ) { this( widgetSet, widgetPackage, width, true, height, true ); } }