/** * 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.properties; import net.ctdp.rfdynhud.values.AbstractSize; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.base.widget.__WPrivilegedAccess; public class Size extends AbstractSize { private static final float PIXEL_OFFSET = 10f; private static final float PIXEL_OFFSET_CHECK_POSITIVE = +PIXEL_OFFSET - 0.001f; private static final float PIXEL_OFFSET_CHECK_NEGATIVE = -PIXEL_OFFSET + 0.001f; private float width; private float height; private int bakedWidth = -1; private int bakedHeight = -1; private final Widget widget; private final boolean isGlobalSize; /* public final Widget getWidget() { return ( widget ); } */ public final boolean isGlobalSize() { return ( isGlobalSize ); } /** * Gets this Widget's width. If it is a negative number, the actual width is (screen_width - width). * * @return this Widget's width. */ private final float getWidth() { return ( width ); } /** * Gets this Widget's height. If it is a negative number, the actual height is (screen_height - height). * * @return this Widget's height. */ private final float getHeight() { return ( height ); } public final boolean isNegativeWidth() { return ( width <= 0f ); } public final boolean isNegativeHeight() { return ( height <= 0f ); } private static final boolean isNegPixelValue( float v ) { return ( v < PIXEL_OFFSET_CHECK_NEGATIVE ); } private static final boolean isPosPixelValue( float v ) { return ( v > PIXEL_OFFSET_CHECK_POSITIVE ); } private static final boolean isPixelValue( float v ) { return ( ( v < PIXEL_OFFSET_CHECK_NEGATIVE ) || ( v > PIXEL_OFFSET_CHECK_POSITIVE ) ); } private final float getMinWidth() { if ( isGlobalSize ) return ( widget.getMinWidth( null, false ) ); return ( 10f ); } private final float getMinHeight() { if ( isGlobalSize ) return ( widget.getMinHeight( null, false ) ); return ( 10f ); } private final float getScaleWidth() { if ( isGlobalSize ) { if ( widget.getMasterWidget() == null ) return ( widget.getConfiguration().getGameResolution().getViewportWidth() ); return ( widget.getMasterWidget().getInnerSize().getEffectiveWidth() ); } return ( widget.getInnerSize().getEffectiveWidth() ); } private final float getScaleHeight() { if ( isGlobalSize ) { if ( widget.getMasterWidget() == null ) return ( widget.getConfiguration().getGameResolution().getViewportHeight() ); return ( widget.getMasterWidget().getInnerSize().getEffectiveHeight() ); } return ( widget.getInnerSize().getEffectiveHeight() ); } private final float getHundretPercentWidth() { if ( isGlobalSize ) { if ( widget.getMasterWidget() == null ) return ( widget.getConfiguration().getGameResolution().getViewportHeight() * 4 / 3 ); return ( widget.getMasterWidget().getInnerSize().getEffectiveWidth() ); } return ( widget.getInnerSize().getEffectiveWidth() ); } private void applyLimits() { unbake(); if ( widget.getConfiguration() == null ) return; if ( isPosPixelValue( width ) ) width = +PIXEL_OFFSET + Math.min( width - PIXEL_OFFSET, getScaleWidth() ); else if ( isNegPixelValue( width ) ) width = -PIXEL_OFFSET + Math.max( width + PIXEL_OFFSET, -getScaleWidth() ); else if ( width > 0f ) width = Math.max( 0f, Math.min( width, +1.0f ) ); else if ( width <= 0f ) width = Math.min( 0f, Math.max( width, -1.0f ) ); if ( isPosPixelValue( height ) ) height = +PIXEL_OFFSET + Math.min( height - PIXEL_OFFSET, getScaleHeight() ); else if ( isNegPixelValue( height ) ) height = -PIXEL_OFFSET + Math.max( height + PIXEL_OFFSET, -getScaleHeight() ); else if ( height > 0f ) height = Math.max( 0f, Math.min( height, +1.0f ) ); else if ( height <= 0f ) height = Math.min( 0f, Math.max( height, -1.0f ) ); } /** * Sets this {@link Widget}'s size. (only works for non-fixed-sized {@link Widget}s) * * @param width * @param height */ private boolean set( float width, float height ) { /* if ( widget.getConfiguration() != null ) { if ( isPosPixelValue( width ) ) width = Math.max( width, getMinWidth() ); else if ( isNegPixelValue( width ) ) width = -Math.max( -width, -getMinWidth() ); else if ( width > 0f ) width = +PERCENT_OFFSET + Math.max( width - PERCENT_OFFSET, getMinWidth() / getHundretPercentWidth() ); else if ( width <= 0f ) width = -PERCENT_OFFSET + Math.max( width + PERCENT_OFFSET, ( getMinWidth() / getScaleWidth() ) - 1.0f ); if ( isPosPixelValue( height ) ) height = Math.max( height, getMinHeight() ); else if ( isNegPixelValue( height ) ) height = -Math.max( -height, -getMinHeight() ); else if ( height > 0f ) height = +PERCENT_OFFSET + Math.max( height - PERCENT_OFFSET, getMinHeight() / getScaleHeight() ); else if ( height <= 0f ) height = -PERCENT_OFFSET + Math.min( height + PERCENT_OFFSET, -( getMinHeight() / getScaleHeight() ) ); } */ unbake(); boolean changed = false; if ( ( width != this.width ) || ( height != this.height ) ) { boolean b = ( widget.getConfiguration() != null ); int oldW = b ? getEffectiveWidth() : 0; int oldH = b ? getEffectiveHeight() : 0; this.width = width; this.height = height; applyLimits(); widget.forceAndSetDirty( true ); if ( b ) { int newW = getEffectiveWidth(); int newH = getEffectiveHeight(); if ( oldW != newW || oldH != newH ) __WPrivilegedAccess.onSizeChanged( oldW, oldH, newW, newH, widget ); } changed = true; } //widget.setDirtyFlag(); return ( changed ); } /** * Sets this {@link Widget}'s width. (only works for non-fixed-sized {@link Widget}s) * * @param width */ private boolean setWidth( float width ) { return ( set( width, getHeight() ) ); } /** * Sets this {@link Widget}'s height. (only works for non-fixed-sized {@link Widget}s) * * @param height */ private boolean setHeight( float height ) { return ( set( getWidth(), height ) ); } /** * Sets the {@link Size} to the values of the given {@link Size}. * * @param size */ public void setTo( Size size) { this.set( size.width, size.height ); } /** * Sets this {@link Widget}'s size in absolute pixel coordinates. (only works for non-fixed-sized {@link Widget}s) * * @param width the new absolute pixel width * @param height the new absolute pixel height * * @return changed? */ public final boolean setEffectiveSize( int width, int height ) { float scaleW = getScaleWidth(); float scaleH = getScaleHeight(); width = Math.max( width, (int)getMinWidth() ); height = Math.max( height, (int)getMinHeight() ); if ( !isPixelValue( this.width ) && !isNegativeWidth() ) { width = Math.min( width, (int)getHundretPercentWidth() ); } if ( isNegativeWidth() ) width -= (int)scaleW; if ( isNegativeHeight() ) height -= (int)scaleH; float newW, newH; if ( isPixelValue( this.width ) ) { if ( isPixelValue( this.height ) ) { newW = ( width <= 0 ? -PIXEL_OFFSET : +PIXEL_OFFSET ) + width; newH = ( height <= 0 ? -PIXEL_OFFSET : +PIXEL_OFFSET ) + height; } else { newW = ( width <= 0 ? -PIXEL_OFFSET : +PIXEL_OFFSET ) + width; newH = height / scaleH; } } else { float hundretPercentW = isNegativeWidth() ? scaleW : getHundretPercentWidth(); if ( isPixelValue( this.height ) ) { newW = width / hundretPercentW; newH = ( height <= 0 ? -PIXEL_OFFSET : +PIXEL_OFFSET ) + height; } else { newW = width / hundretPercentW; newH = height / scaleH; } } boolean changed = set( newW, newH ); applyLimits(); return ( changed ); } /** * Gets the effective Widget's width. If {@link #getWidth()} returns a * negative number, the effective width is (screen_width - width). * * @return the effective Widget's width. */ @Override public final int getEffectiveWidth() { if ( bakedWidth >= 0 ) return ( bakedWidth ); float scaleW = getScaleWidth(); if ( isPosPixelValue( width ) ) return ( (int)Math.max( getMinWidth(), width - PIXEL_OFFSET ) ); if ( isNegPixelValue( width ) ) return ( (int)Math.max( getMinWidth(), scaleW + width + PIXEL_OFFSET ) ); if ( width > 0f ) return ( Math.round( Math.max( getMinWidth(), width * getHundretPercentWidth() ) ) ); return ( Math.round( Math.max( getMinWidth(), scaleW + ( width * scaleW ) ) ) ); } /** * Gets the effective Widget's height. If {@link #getHeight()} returns a * negative number, the effective height is (screen_height - height). * * @return the effective Widget's height. */ @Override public final int getEffectiveHeight() { if ( bakedHeight >= 0 ) return ( bakedHeight ); float scaleH = getScaleHeight(); if ( isPosPixelValue( height ) ) return ( (int)Math.max( getMinHeight(), height - PIXEL_OFFSET ) ); if ( isNegPixelValue( height ) ) return ( (int)Math.max( getMinHeight(), scaleH + height + PIXEL_OFFSET ) ); if ( height > 0f ) return ( Math.round( Math.max( getMinHeight(), height * scaleH ) ) ); return ( Math.round( Math.max( getMinHeight(), scaleH + ( height * scaleH ) ) ) ); } public final boolean equalsEffective( int width, int height ) { return ( ( getEffectiveWidth() == width ) && ( getEffectiveHeight() == height ) ); } public void unbake() { bakedWidth = -1; bakedHeight = -1; } public void bake() { unbake(); bakedWidth = getEffectiveWidth(); bakedHeight = getEffectiveHeight(); } public boolean isBaked() { return ( bakedWidth >= 0 ); } public Size setWidthToPercents() { if ( isPixelValue( width ) ) { int effW = getEffectiveWidth(); int effH = getEffectiveHeight(); if ( width > 0f ) this.width = +PIXEL_OFFSET * 0.9f; else this.width = -PIXEL_OFFSET * 0.9f; setEffectiveSize( effW, effH ); } return ( this ); } public Size setWidthToPixels() { if ( !isPixelValue( width ) ) { int effW = getEffectiveWidth(); int effH = getEffectiveHeight(); if ( width > 0f ) this.width = +PIXEL_OFFSET + 10000f; else this.width = -PIXEL_OFFSET - 10000f; setEffectiveSize( effW, effH ); } return ( this ); } public Size flipWidthPercentagePx() { if ( isPixelValue( width ) ) setWidthToPercents(); else setWidthToPixels(); return ( this ); } public Size setHeightToPercents() { if ( isPixelValue( height ) ) { int effW = getEffectiveWidth(); int effH = getEffectiveHeight(); if ( height > 0f ) this.height = +PIXEL_OFFSET * 0.9f; else this.height = -PIXEL_OFFSET * 0.9f; setEffectiveSize( effW, effH ); } return ( this ); } public Size setHeightToPixels() { if ( !isPixelValue( height ) ) { int effW = getEffectiveWidth(); int effH = getEffectiveHeight(); if ( height > 0f ) this.height = +PIXEL_OFFSET + 10000f; else this.height = -PIXEL_OFFSET - 10000f; setEffectiveSize( effW, effH ); } return ( this ); } public Size flipHeightPercentagePx() { if ( isPixelValue( height ) ) setHeightToPercents(); else setHeightToPixels(); return ( this ); } public Size flipWidthSign() { int gameResX = widget.getConfiguration().getGameResolution().getViewportWidth(); if ( isNegPixelValue( width ) ) width = +PIXEL_OFFSET + gameResX + ( width + PIXEL_OFFSET ); else if ( isPosPixelValue( width ) ) width = -PIXEL_OFFSET + ( width - PIXEL_OFFSET ) - gameResX; else if ( width < 0f ) width = + ( 1.0f + width ) * ( getScaleWidth() / getHundretPercentWidth() ); else if ( width > 0f ) width = - 1.0f + ( width / ( getScaleWidth() / getHundretPercentWidth() ) ); applyLimits(); widget.forceAndSetDirty( true ); return ( this ); } public Size flipHeightSign() { int gameResY = widget.getConfiguration().getGameResolution().getViewportHeight(); if ( isNegPixelValue( height ) ) height = +PIXEL_OFFSET + gameResY + ( height + PIXEL_OFFSET ); else if ( isPosPixelValue( height ) ) height = -PIXEL_OFFSET + ( height - PIXEL_OFFSET ) - gameResY; else if ( height < 0f ) height = 1.0f + height; else if ( height > 0f ) height = height - 1.0f; applyLimits(); widget.forceAndSetDirty( true ); return ( this ); } public static float parseValue( String value, boolean defaultPerc ) { boolean isPerc = value.endsWith( "%" ); boolean isPx = value.endsWith( "px" ); if ( !isPerc && !isPx ) { if ( defaultPerc ) { value += "%"; isPerc = true; } else { value += "px"; isPx = true; } } if ( isPerc ) { float f = Float.parseFloat( value.substring( 0, value.length() - 1 ) ); return ( f / 100f ); } if ( isPx ) { float f = Float.parseFloat( value.substring( 0, value.length() - 2 ) ); if ( f <= 0f ) return ( -PIXEL_OFFSET + f ); return ( +PIXEL_OFFSET + f ); } // Unreachable! return ( Float.parseFloat( value ) ); } /* private float parseWidth( String value ) { setWidth( parseValue( value ) ); return ( getWidth() ); } private float parseHeight( String value ) { setHeight( parseValue( value ) ); return ( getHeight() ); } */ public static String unparseValue( float value ) { if ( isPosPixelValue( value ) ) { int px = (int)( value - PIXEL_OFFSET ); if ( px == 0 ) return ( "-" + String.valueOf( px ) + "px" ); return ( String.valueOf( px ) + "px" ); } if ( isNegPixelValue( value ) ) { int px = (int)( value + PIXEL_OFFSET ); if ( px == 0 ) return ( "-" + String.valueOf( px ) + "px" ); return ( String.valueOf( px ) + "px" ); } String s; if ( value == 0.0f ) s = "-" + String.valueOf( value * 100f ) + "%"; else s = String.valueOf( value * 100f ) + "%"; if ( s.startsWith( "--" ) ) return ( s.substring( 1 ) ); return ( s ); } /* private String unparseWidth() { return ( unparseValue( getWidth() ) ); } private String unparseHeight() { return ( unparseValue( getHeight() ) ); } */ private static final boolean propExistsWithName( Property prop, String name, String nameForDisplay ) { if ( prop == null ) return ( false ); if ( !prop.getName().equals( name ) ) return ( false ); if ( ( nameForDisplay == null ) && !prop.getName().equals( prop.getNameForDisplay() ) ) return ( false ); return ( true ); } /** * * @param width the new width */ protected void onWidthPropertySet( float width ) { } private PosSizeProperty widthProp = null; public PosSizeProperty getWidthProperty( String name, String nameForDisplay ) { if ( !propExistsWithName( widthProp, name, nameForDisplay ) ) { boolean ro = isGlobalSize ? widget.hasFixedSize() : false; widthProp = new PosSizeProperty( name, nameForDisplay, ro, true ) { @Override public boolean isPercentage() { return ( !isPixelValue( width ) ); } @Override public void setValue( Object value ) { float oldValue = Size.this.width; float width = ( (Number)value ).floatValue(); //if ( width != oldValue ) { set( width, getHeight() ); if ( width != oldValue ) triggerKeepersOnPropertyChanged( oldValue, width ); onWidthPropertySet( width ); } } @Override public Object getValue() { return ( getWidth() ); } /** * {@inheritDoc} */ @Override public Object getDefaultValue() { return ( null ); } /** * {@inheritDoc} */ @Override public boolean hasDefaultValue() { return ( false ); } @Override public void onButtonClicked( Object button ) { flipWidthSign(); } @Override public void onButton2Clicked( Object button ) { flipWidthPercentagePx(); } @Override public Boolean quoteValueInConfigurationFile() { return ( false ); } @Override public Object getValueForConfigurationFile() { return ( unparseValue( getWidth() ) ); } @Override public void loadValue( PropertyLoader loader, String value ) { if ( !value.endsWith( "%" ) && !value.endsWith( "px" ) ) value += "px"; setWidth( parseValue( value, !isPixelValue( width ) ) ); } }; } return ( widthProp ); } public PosSizeProperty getWidthProperty( String name ) { return ( getWidthProperty( name, name ) ); } /** * * @param height the new height */ protected void onHeightPropertySet( float height ) { } private PosSizeProperty heightProp = null; public PosSizeProperty getHeightProperty( String name, String nameForDisplay ) { if ( !propExistsWithName( heightProp, name, nameForDisplay ) ) { boolean ro = isGlobalSize ? widget.hasFixedSize() : false; heightProp = new PosSizeProperty( name, nameForDisplay, ro, true ) { @Override public boolean isPercentage() { return ( !isPixelValue( height ) ); } @Override public void setValue( Object value ) { float oldValue = Size.this.height; float height = ( (Number)value ).floatValue(); //if ( height != oldValue ) { set( getWidth(), height ); if ( height != oldValue ) triggerKeepersOnPropertyChanged( oldValue, height ); onHeightPropertySet( height ); } } @Override public Object getValue() { return ( getHeight() ); } /** * {@inheritDoc} */ @Override public Object getDefaultValue() { return ( null ); } /** * {@inheritDoc} */ @Override public boolean hasDefaultValue() { return ( false ); } @Override public void onButtonClicked( Object button ) { flipHeightSign(); } @Override public void onButton2Clicked( Object button ) { flipHeightPercentagePx(); } @Override public Boolean quoteValueInConfigurationFile() { return ( false ); } @Override public Object getValueForConfigurationFile() { return ( unparseValue( getHeight() ) ); } @Override public void loadValue( PropertyLoader loader, String value ) { if ( !value.endsWith( "%" ) && !value.endsWith( "px" ) ) value += "px"; setHeight( parseValue( value, !isPixelValue( height ) ) ); } }; } return ( heightProp ); } public PosSizeProperty getHeightProperty( String name ) { return ( getHeightProperty( name, name ) ); } protected Size( Widget widget, boolean isGlobalSize, float width, boolean widthPercent, float height, boolean heightPercent ) { this.widget = widget; this.isGlobalSize = isGlobalSize; this.width = widthPercent ? width * 0.01f : PIXEL_OFFSET + width; this.height = heightPercent ? height * 0.01f : PIXEL_OFFSET + height; } /** * Create a new size property for sizes local to a Widget's area. * * @param widget the owning {@link Widget}. * @param width the new width value * @param widthPercent interpret 'width' as percents? * @param height the new height value * @param heightPercent interpret 'height' as percents? * * @return the new Size. */ public static final Size newLocalSize( Widget widget, float width, boolean widthPercent, float height, boolean heightPercent ) { return ( new Size( widget, false, width, widthPercent, height, heightPercent ) ); } /** * Create a new size property for global positions on the whole screen area. * * @param widget the owning {@link Widget}. * @param width the new width value * @param widthPercent interpret 'width' as percents? * @param height the new height value * @param heightPercent interpret 'height' as percents? * * @return the new Size. */ public static final Size newGlobalSize( Widget widget, float width, boolean widthPercent, float height, boolean heightPercent ) { return ( new Size( widget, true, width, widthPercent, height, heightPercent ) ); } }