/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.ui.hud.base;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jagatoo.input.devices.components.ControllerAxis;
import org.jagatoo.input.devices.components.ControllerButton;
import org.jagatoo.input.devices.components.DeviceComponent;
import org.jagatoo.input.devices.components.Key;
import org.jagatoo.input.devices.components.MouseButton;
import org.openmali.types.twodee.Dim2f;
import org.openmali.types.twodee.Dim2i;
import org.openmali.vecmath2.Colorf;
import org.openmali.vecmath2.Point2i;
import org.openmali.vecmath2.Tuple2f;
import org.openmali.vecmath2.Tuple2i;
import org.openmali.vecmath2.Vector2f;
import org.openmali.vecmath2.Vector2i;
import org.xith3d.render.ScissorRect;
import org.xith3d.scenegraph.Group;
import org.xith3d.scenegraph.GroupNode;
import org.xith3d.scenegraph.Node;
import org.xith3d.scenegraph.Texture2D;
import org.xith3d.scenegraph.Texture2DCanvas;
import org.xith3d.scenegraph.TransformGroup;
import org.xith3d.ui.hud.HUD;
import org.xith3d.ui.hud.__HUD_PrivilegedAccess;
import org.xith3d.ui.hud.HUD.FocusMoveDirection;
import org.xith3d.ui.hud.layout.LayoutManager;
import org.xith3d.ui.hud.utils.HUDPickResult;
import org.xith3d.ui.hud.utils.LocalZIndexComparator;
import org.xith3d.ui.hud.utils.TileMode;
import org.xith3d.ui.hud.utils.HUDPickResult.HUDPickReason;
/**
* A WidgetContainer is a Widget, that can hold arbitrary Widgets.
* It can have it's own coordinate system and the contained Widget's
* transformations are relative to it.
*
* @author Marvin Froehlich (aka Qudus)
*/
public abstract class WidgetContainer extends BackgroundSettableWidget implements PaddingSettable
{
private static final LocalZIndexComparator Z_INDEX_COMPARATOR = new LocalZIndexComparator();
private final GroupNode childrenGroup;
private Tuple2f resolution = null;
protected final Vector2f childrenOffset_HUD = new Vector2f( 0f, 0f );
protected final Vector2i childrenOffset_PX = new Vector2i( 0, 0 );
private Window parentWindow = null;
private final ArrayList<Widget> widgets = new ArrayList<Widget>();
private boolean widgetsSorted = true;
private Boolean hasOverlappingWidgets = null;
private Widget topMostWidget = null;
private Widget currentHoveredWidget = null;
private Widget currentFocusedWidget = null;
private final ArrayList<HUDPickResult> pickedWidgets = new ArrayList<HUDPickResult>();
private LayoutManager layoutManager = null;
private boolean layoutDirty = false;
private int paddingBottom = 0, paddingRight = 0, paddingTop = 0, paddingLeft = 0;
/**
* @return the scenegraph Group to add children to
*/
protected final GroupNode getSGGroup()
{
return ( childrenGroup );
}
void setContentPaneOf( Window window )
{
this.parentWindow = window;
}
/**
* Gets, if this WidgetContainer is a content pane of a Window.
*
* @return true, if this container is the contentpane of a Window.
*/
public final boolean isContentPane()
{
return ( parentWindow != null );
}
/**
* Gets, the Window, of which this is the content pane.
*
* @return the parent Window.
*/
@Override
public final Window getParentWindow()
{
if ( parentWindow != null )
return ( parentWindow );
if ( getContainer() == null )
return ( null );
return ( getContainer().getParentWindow() );
}
/**
* @return a height that's visually equal to the given width
*
* @param height the height to calculate a visually equal width
*/
protected final float getEqualHeight( float width )
{
float resAspect = getResAspect();
if ( resAspect == 0f )
return ( 0f );
return ( width * this.getContentAspect() / resAspect );
}
/**
* @return a width that's visually equal to the given height
*
* @param height the height to calculate a visually equal height
*/
protected final float getEqualWidth( float height )
{
float contentAspect = getContentAspect();
if ( contentAspect == 0f )
return ( 0f );
return ( height * getResAspect() / contentAspect );
}
/**
* Calculates HUD size from these pixel-values.
*
* @param w the canvas-x-value to transform
* @param h the canvas-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Dim2f_ extends Dim2f> Dim2f_ getSizePixels2HUD( int w, int h, Dim2f_ buffer )
{
if ( getContainer() != null )
getContainer().getSizePixels2HUD( w, h, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getSizePixels2HUD( w, h, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
buffer.scale( transformWidth_Pixels2HUD, transformHeight_Pixels2HUD );
return ( buffer );
}
/**
* Calculates HUD location from these pixel-values.
*
* @param x the canvas-x-value to transform
* @param y the canvas-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Tuple2f_ extends Tuple2f> Tuple2f_ getLocationPixels2HUD( int x, int y, Tuple2f_ buffer )
{
x -= getContentLeftPX();
y -= getContentTopPX();
if ( getContainer() != null )
{
getContainer().getLocationPixels2HUD( x, y, buffer );
}
else if ( getHUD() != null )
{
/*
if ( isContentPane() )
{
Window window = getParentWindow();
x -= window.getLeft();
y -= window.getTop() + window.getHeaderHeight();
if ( window.getBorder() != null )
{
x -= window.getBorder().getLeftWidth();
y -= window.getBorder().getTopHeight();
}
}
*/
getHUD().getCoordinatesConverter().getLocationPixels2HUD( x, y, buffer );
}
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
if ( getContentWidthPX() == 0 )
{
buffer.setX( 0f );
}
else
{
buffer.subX( getLeft() );
buffer.mulX( getResX() / getContentWidth() );
}
if ( getContentHeightPX() == 0 )
{
buffer.setY( 0f );
}
else
{
buffer.subY( getTop() );
buffer.mulY( ( getResY() / getContentHeight() ) );
}
buffer.add( -childrenOffset_HUD.getX(), childrenOffset_HUD.getY() );
return ( buffer );
}
/**
* Calculates pixel size from these HUD-values.
*
* @param w the HUD-x-value to transform
* @param h the HUD-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Dim2i_ extends Dim2i> Dim2i_ getSizeHUD2Pixels( float w, float h, Dim2i_ buffer )
{
if ( getHUD() == null )
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
if ( ( transformWidth_Pixels2HUD == 0f ) || ( transformHeight_Pixels2HUD == 0f ) )
{
w = 0f;
h = 0f;
}
else
{
w /= transformWidth_Pixels2HUD;
h /= transformHeight_Pixels2HUD;
}
if ( getContainer() != null )
getContainer().getSizeHUD2Pixels( w, h, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getSizeHUD2Pixels( w, h, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
return ( buffer );
}
/**
* Calculates container relative pixel location from these HUD-values.
*
* @param x the HUD-x-value to transform
* @param y the HUD-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Tuple2i_ extends Tuple2i> Tuple2i_ getRelLocationHUD2Pixels( float x, float y, Tuple2i_ buffer )
{
if ( getResX() == 0f )
x = 0f;
else
x *= ( getContentWidth() / getResX() );
if ( getResY() == 0f )
y = 0f;
else
y *= ( getContentHeight() / getResY() );
if ( getContainer() != null )
getContainer().getRelLocationHUD2Pixels( x, y, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getLocationHUD2Pixels( x, y, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
return ( buffer );
}
/**
* Calculates pixel location from these HUD-values.
*
* @param x the HUD-x-value to transform
* @param y the HUD-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Tuple2i_ extends Tuple2i> Tuple2i_ getLocationHUD2Pixels( float x, float y, Tuple2i_ buffer )
{
if ( getContainer() != null )
getContainer().getLocationHUD2Pixels( getLeft(), getTop(), buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getLocationHUD2Pixels( getLeft(), getTop(), buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
final float left = buffer.getX();
final float top = buffer.getY();
//x += getChildrenOffsetX();
//y += getChildrenOffsetY();
if ( getBorder() != null )
x += getBorder().getLeftWidth();
x += getPaddingLeft();
if ( getResX() == 0f )
x = 0f;
else
x *= ( getContentWidth() / getResX() );
if ( getBorder() != null )
y += getBorder().getTopHeight();
y += getPaddingTop();
if ( getResY() == 0f )
y = 0f;
else
y *= ( getContentHeight() / getResY() );
Dim2i dim = Dim2i.fromPool();
if ( getContainer() != null )
getContainer().getSizeHUD2Pixels( x, y, dim );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getSizeHUD2Pixels( x, y, dim );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
buffer.set( dim.getWidth(), dim.getHeight() );
Dim2i.toPool( dim );
buffer.addX( (int)left );
buffer.addY( (int)top );
return ( buffer );
}
/**
* Calculates scenegraph width and height from these HUD-values.
*
* @param w the HUD-x-value to transform
* @param h the HUD-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Dim2f_ extends Dim2f> Dim2f_ getSizeHUD2SG( float w, float h, Dim2f_ buffer )
{
if ( getHUD() == null )
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
if ( ( transformWidth_Pixels2HUD == 0f ) || ( transformHeight_Pixels2HUD == 0f ) )
{
w = 0f;
h = 0f;
}
else
{
w /= transformWidth_Pixels2HUD;
h /= transformHeight_Pixels2HUD;
}
if ( getContainer() != null )
getContainer().getSizeHUD2SG( w, h, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getSizeHUD2SG( w, h, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
return ( buffer );
}
/**
* Calculates scenegraph location from these HUD-values.
*
* @param x the HUD-x-value to transform
* @param y the HUD-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Tuple2f_ extends Tuple2f> Tuple2f_ getLocationHUD2SG( float x, float y, Tuple2f_ buffer )
{
if ( getResX() == 0f )
x = 0f;
else
x *= ( getContentWidth() / getResX() );
if ( getResY() == 0f )
y = 0f;
else
y *= ( getContentHeight() / getResY() );
x += getLeft();
y += getTop();
if ( getBorder() != null )
{
x += getBorder().getLeftWidth();
y += getBorder().getTopHeight();
}
x += getPaddingLeft();
y += getPaddingTop();
if ( getContainer() != null )
getContainer().getLocationHUD2SG( x, y, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getLocationHUD2SG( x, y, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
buffer.subX( ( (TransformGroup)getSGNode() ).getTransform().getMatrix4f().m03() );
buffer.subY( ( (TransformGroup)getSGNode() ).getTransform().getMatrix4f().m13() );
return ( buffer );
}
/**
* Calculates HUD size from these scenegraph-values.
*
* @param w the scenegraph-x-value to transform
* @param h the scenegraph-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Dim2f_ extends Dim2f> Dim2f_ getSizeSG2HUD( float w, float h, Dim2f_ buffer )
{
if ( getContainer() != null )
getContainer().getSizeSG2HUD( w, h, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getSizeSG2HUD( w, h, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
if ( getContentWidthPX() == 0 )
buffer.setWidth( 0f );
else
buffer.scale( getResX() / getContentWidth(), 1f );
if ( getContentHeightPX() == 0 )
buffer.setHeight( 0f );
else
buffer.scale( 1f, getResY() / getContentHeight() );
return ( buffer );
}
/**
* Calculates HUD location from these scenegraph-values.
*
* @param x the scenegraph-x-value to transform
* @param y the scenegraph-y-value to transform
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Tuple2f_ extends Tuple2f> Tuple2f_ getLocationSG2HUD( float x, float y, Tuple2f_ buffer )
{
x += ( (TransformGroup)getSGNode() ).getTransform().getMatrix4f().m03();
y += ( (TransformGroup)getSGNode() ).getTransform().getMatrix4f().m13();
if ( getContainer() != null )
getContainer().getLocationSG2HUD( x, y, buffer );
else if ( getHUD() != null )
getHUD().getCoordinatesConverter().getLocationSG2HUD( x, y, buffer );
else
throw new Error( "This method can't be executed on a widget, that is not attached to the HUD." );
buffer.subX( getLeft() );
buffer.subY( getTop() );
if ( getBorder() != null )
{
buffer.subX( getBorder().getLeftWidth() );
buffer.subY( getBorder().getTopHeight() );
}
buffer.subX( getPaddingLeft() );
buffer.subY( getPaddingTop() );
if ( getResX() == 0f )
buffer.setX( 0f );
else
buffer.divX( getContentWidth() / getResX() );
if ( getResY() == 0f )
buffer.setY( 0f );
else
buffer.divY( getContentHeight() / getResY() );
return ( buffer );
}
/**
* Retrieves the size these pixels have on this WidgetContainer.
*
* @param x the x-count of pixels
* @param y the y-count of pixels
* @param buffer the buffer to write the values to
*
* @return the buffer back again
*/
protected final <Dim2f_ extends Dim2f> Dim2f_ getSizeOfPixels( int x, int y, Dim2f_ buffer )
{
getSizeSG2HUD( x, y, buffer );
return ( buffer );
}
/**
* Sets the container's resolution.<br>
* Set one of the values to -1 to use not custom resolution.
*
* @param resX
* @param resY
*/
public void setResolution( float resX, float resY )
{
if ( ( resX < 0f ) || ( resY < 0f ) )
{
if ( this.resolution == null )
return;
this.resolution = null;
}
if ( this.resolution == null )
this.resolution = new Tuple2f( resX, resY );
else
this.resolution.set( resX, resY );
update();
}
/**
* @return the x-resolution of the WidgetContainer.
*/
public final float getResX()
{
if ( resolution == null )
{
if ( getHUD() == null )
{
// This is a fallback!
float resX = getWidth();
if ( getBorder() != null )
resX -= getBorder().getLeftWidth() - getBorder().getRightWidth();
resX -= getPaddingLeft() + getPaddingRight();
return ( resX );
}
return ( getContentWidth() );
}
return ( resolution.getX() );
}
/**
* @return the y-resolution of the WidgetContainer.
*/
public final float getResY()
{
if ( resolution == null )
{
if ( getHUD() == null )
{
// This is a fallback!
float resY = getHeight();
if ( getBorder() != null )
resY -= getBorder().getTopHeight() - getBorder().getBottomHeight();
resY -= getPaddingTop() + getPaddingBottom();
return ( resY );
}
return ( getContentHeight() );
}
return ( resolution.getY() );
}
/**
* @return this WidgetContainer's resolution.
* By default this is equal to width and height.
*/
public final Tuple2f getResolution()
{
return ( new Tuple2f( getResX(), getResY() ) );
}
/**
* @return the aspect ratio of the resolution of the WidgetContainer.
*/
public final float getResAspect()
{
if ( getResY() == 0f )
return ( 0f );
return ( getResX() / getResY() );
}
/**
* Returns whether a custom resolution is defined on the widget container.
*
* @return whether a custom resolution is defined on the widget container.
*/
public final boolean hasCustomResolution()
{
return ( resolution != null );
}
/**
* {@inheritDoc}
*/
@Override
protected void onSizeChanged( float oldWidth, float oldHeight, float newWidth, float newHeight )
{
super.onSizeChanged( oldWidth, oldHeight, newWidth, newHeight );
layoutDirty = true;
updateLight( true );
}
/**
* {@inheritDoc}
*/
public boolean setPadding( int paddingBottom, int paddingRight, int paddingTop, int paddingLeft )
{
if ( ( this.paddingBottom == paddingBottom ) &&
( this.paddingRight == paddingRight ) &&
( this.paddingTop == paddingTop ) &&
( this.paddingLeft == paddingLeft ) )
{
return ( false );
}
this.paddingBottom = paddingBottom;
this.paddingRight = paddingRight;
this.paddingTop = paddingTop;
this.paddingLeft = paddingLeft;
this.update();
return ( true );
}
/**
* {@inheritDoc}
*/
public final boolean setPadding( int padding )
{
return ( setPadding( padding, padding, padding, padding ) );
}
/**
* {@inheritDoc}
*/
public final int getPaddingBottom()
{
return ( paddingBottom );
}
/**
* {@inheritDoc}
*/
public final int getPaddingRight()
{
return ( paddingRight );
}
/**
* {@inheritDoc}
*/
public final int getPaddingTop()
{
return ( paddingTop );
}
/**
* {@inheritDoc}
*/
public final int getPaddingLeft()
{
return ( paddingLeft );
}
/**
* Sets which LayoutManager to use for the child Widgets of this Container.
*
* @param layout the new LayoutManager to use
*/
public void setLayout( LayoutManager layout )
{
if ( this.layoutManager == layout )
{
return;
}
if ( this.layoutManager != null )
{
this.layoutManager.clear();
}
this.layoutManager = layout;
if ( layoutManager != null )
{
layoutManager.clear();
for ( int i = 0; i < widgets.size(); i++ )
{
layoutManager.addWidget( widgets.get( i ), null );
}
final HUD hud = getHUD();
if ( ( layoutManager != null ) && layoutDirty && isVisible() && ( hud != null ) && hud.isVisible() && hud.isConnected() )
{
layoutManager.doLayout( this );
layoutDirty = false;
}
else
{
layoutDirty = ( widgets.size() > 0 );
}
}
else
{
layoutDirty = false;
}
}
/**
* @return the currently used LayoutManager
*/
public final LayoutManager getLayout()
{
return ( layoutManager );
}
/**
*
* @param child
*/
void onChildMovedOrResized( Widget child )
{
this.hasOverlappingWidgets = null;
}
final boolean hasOverlappingWidgets()
{
if ( hasOverlappingWidgets == null )
{
hasOverlappingWidgets = Boolean.FALSE;
for ( int i = 0; i < widgets.size(); i++ )
{
Widget w0 = widgets.get( i );
for ( int j = i + 1; j < widgets.size(); j++ )
{
Widget w1 = widgets.get( j );
if ( w1.getLeft() + w1.getWidth() < w0.getLeft() )
{
// no overlapping!
}
else if ( w1.getTop() + w1.getHeight() < w0.getTop() )
{
// no overlapping!
}
else if ( w1.getLeft() > w0.getLeft() + w0.getWidth() )
{
// no overlapping!
}
else if ( w1.getTop() > w0.getTop() + w0.getHeight() )
{
// no overlapping!
}
else
{
hasOverlappingWidgets = Boolean.TRUE;
return ( true );
}
}
}
}
return ( hasOverlappingWidgets.booleanValue() );
}
protected void setZIndexSortingDirty()
{
this.widgetsSorted = false;
}
private void ensureWidgetsSortedByZIndex()
{
if ( widgetsSorted )
return;
Collections.sort( widgets, Z_INDEX_COMPARATOR );
widgetsSorted = true;
}
/**
* Adds a Widget to this container at the given location.
*
* @param widget the Widget to add
* @param locX the x-location to add the Widget at
* @param locY the y-location to add the Widget at
* @param zIndex the new Widget's z-index
* @param constraints the contraints to use for this Widget in the LayoutManager
*/
public Widget addWidget( Widget widget, float locX, float locY, int zIndex, Object constraints )
{
if ( ( widget.getContainer() != null ) || ( widget.getHUD() != null ) )
{
throw new Error( "This Widget is already added to the HUD." );
}
widgets.add( widget );
widgetsSorted = false;
final HUD hud = getHUD();
widget.setLocation( locX, locY );
widget.setZIndex( zIndex );
widget.setContainer( this, null );
if ( !widget.isHeavyWeight() )
{
//widget.setHostWidget( getHostWidget() );
widget.setHostWidget( this );
this.setTextureDirty();
}
widget.setHUD( hud );
//if ( ( topMostWidget == null ) || ( topMostWidget.getSGZPosition() <= widget.getSGZPosition() ) )
if ( ( topMostWidget == null ) || ( topMostWidget.getZIndex() <= widget.getZIndex() ) )
{
topMostWidget = widget;
}
if ( ( constraints != LayoutManager.IGNORED_BY_LAYOUT ) && ( layoutManager != null ) )
{
layoutManager.addWidget( widget, constraints );
if ( isVisible() && ( hud != null ) && hud.isVisible() && hud.isConnected() )
{
layoutManager.doLayout( this );
layoutDirty = false;
}
else
{
layoutDirty = true;
}
}
this.hasOverlappingWidgets = null;
return ( widget );
}
/**
* Adds a Widget to this container at the given location.
*
* @param widget the Widget to add
* @param locX the x-location to add the widget at
* @param locY the y-location to add the widget at
* @param zIndex the new Widget's z-index
*/
public final Widget addWidget( Widget widget, float locX, float locY, int zIndex )
{
return ( addWidget( widget, locX, locY, zIndex, null ) );
}
/**
* Adds a Widget to this container at the given location.
*
* @param widget the Widget to add
* @param locX the x-location to add the Widget at
* @param locY the y-location to add the Widget at
* @param constraints the contraints to use for this Widget in the LayoutManager
*/
public final Widget addWidget( Widget widget, float locX, float locY, Object constraints )
{
return ( addWidget( widget, locX, locY, widget.getZIndex(), constraints ) );
}
/**
* Adds a Widget to this container at the given location.
*
* @param widget the Widget to add
* @param locX the x-location to add the widget at
* @param locY the y-location to add the widget at
*/
public final Widget addWidget( Widget widget, float locX, float locY )
{
return ( addWidget( widget, locX, locY, widget.getZIndex(), null ) );
}
/**
* Adds a Widget to this container at the Widget's location.
*
* @param widget the widget to add
* @param constraints the contraints to use for this Widget in the LayoutManager
*/
public final Widget addWidget( Widget widget, Object constraints )
{
return ( addWidget( widget, widget.getLeft(), widget.getTop(), widget.getZIndex(), constraints ) );
}
/**
* Adds a Widget to this container at the Widget's location.
*
* @param widget the widget to add
*/
public final Widget addWidget( Widget widget )
{
return ( addWidget( widget, widget.getLeft(), widget.getTop(), widget.getZIndex(), null ) );
}
/**
* Adds a Widget to this container at the center.
*
* @param widget the widget to add (centered)
* @param zIndex the new Widget's z-index
*/
public final Widget addWidgetCentered( Widget widget, int zIndex )
{
float posUpperLeftX;
float posUpperLeftY;
if ( ( getHUD() == null ) && !hasCustomResolution() )
{
posUpperLeftX = Math.round( ( this.getWidth() - widget.getWidth() ) / 2.0f );
posUpperLeftY = Math.round( ( this.getHeight() - widget.getHeight() ) / 2.0f );
}
else
{
posUpperLeftX = Math.round( ( this.getResX() - widget.getWidth() ) / 2.0f );
posUpperLeftY = Math.round( ( this.getResY() - widget.getHeight() ) / 2.0f );
}
return ( addWidget( widget, posUpperLeftX, posUpperLeftY, zIndex ) );
}
/**
* Adds a Widget to this container at the center.
*
* @param widget the widget to add (centered)
*/
public final Widget addWidgetCentered( Widget widget )
{
return ( addWidgetCentered( widget, widget.getZIndex() ) );
}
/**
* Removes a Widget from this container.
*
* @param widget the widget to remove
*/
public void removeWidget( Widget widget )
{
if ( widget.getContainer() != this )
throw new Error( "the given Widget is not held in this Container." );
HUD hud = getHUD();
widgets.remove( widget );
widget.setContainer( null, null );
widget.setHUD( null );
widget.setHostWidget( null );
if ( topMostWidget == widget )
topMostWidget = null;
if ( layoutManager != null )
{
layoutManager.removeWidget( widget );
if ( isVisible() && ( hud != null ) && hud.isVisible() && hud.isConnected() )
{
layoutManager.doLayout( this );
layoutDirty = false;
}
else
{
layoutDirty = true;
}
}
this.hasOverlappingWidgets = null;
/*
for ( int i = 0; i < containerListeners.size(); i++ )
{
containerListeners.get( i ).onWidgetDetachedFromContainer( this, widget );
}
*/
}
/**
* Removes all Widgets from this WidgetContainer.
*/
public void clear()
{
/*
Widget[] tmpWidgets = new Widget[ widgets.size() ];
int i = 0;
for ( Widget widget: widgets )
{
tmpWidgets[ i++ ] = widget;
}
for ( i = 0; i < tmpWidgets.length; i++ )
{
removeWidget( tmpWidgets[ i ] );
}
*/
for ( int i = widgets.size() - 1; i >= 0; i-- )
{
removeWidget( widgets.get( i ) );
}
}
/**
* Gets the number of {@link Widget}s on this container.
*
* @return the number of contained {@link Widget}s.
*/
public final int getWidgetsCount()
{
return ( widgets.size() );
}
/**
* Gets the index'th Widget on this Container.
* The order may change depending on the z-index.
*
* @param index
*
* @return te index'th Widget.
*/
public final Widget getWidget( int index )
{
ensureWidgetsSortedByZIndex();
return ( widgets.get( index ) );
}
/**
* @return a List of all Widgets contained by this WidgetContainer.
* The returned Set is unmodifiable.
*/
public final List< Widget > getWidgets()
{
return ( Collections.unmodifiableList( widgets ) );
}
/**
* {@inheritDoc}
*/
@Override
protected void onVisibilityChanged( boolean visible )
{
super.onVisibilityChanged( visible );
if ( visible )
{
if ( ( layoutManager != null ) && layoutDirty && ( getHUD() != null ) && getHUD().isVisible() && getHUD().isConnected() )
{
layoutManager.doLayout( this );
layoutDirty = false;
/*
for ( int i = 0; i < widgets.size(); i++ )
{
widgets.get( i ).setVisible( widgets.get( i ).isVisible() );
}
*/
}
for ( int i = 0; i < widgets.size(); i++ )
{
widgets.get( i ).update();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void setTransparency( float transparency, boolean childrenToo )
{
super.setTransparency( transparency, childrenToo );
if ( childrenToo )
{
final java.util.List< Widget > children = getWidgets();
for ( int i = 0; i < children.size(); i++ )
{
final Widget child = children.get( i );
if ( child instanceof WidgetContainer )
( (WidgetContainer)child ).setTransparency( transparency, childrenToo );
else
child.setTransparency( transparency );
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected HUDPickResult pick( int canvasX, int canvasY, HUDPickReason pickReason, MouseButton button, long when, long meta, int flags )
{
if ( !pickConditionsMatch( pickReason ) )
return ( null );
final Tuple2f locP = Tuple2f.fromPool();
getLocationPixels2HUD_( canvasX, canvasY, locP );
float pickXHUD = locP.getX();
float pickYHUD = locP.getY();
Tuple2f.toPool( locP );
final boolean isInternal = ( flags & HUDPickResult.HUD_PICK_FLAG_IS_INTERNAL ) != 0;
final boolean eventsSuppressed = ( flags & HUDPickResult.HUD_PICK_FLAG_EVENTS_SUPPRESSED ) != 0;
HUDPickResult thisPick = super.pick( canvasX, canvasY, pickReason, button, when, meta, flags );
// We don't need to further test the children, if this container hasn't been picked!
if ( thisPick == null )
return ( null );
HUDPickResult topMost = thisPick;
HUDPickResult tmpHPR = null;
ensureWidgetsSortedByZIndex();
pickedWidgets.clear();
for ( int i = widgets.size() - 1; i >= 0; i-- )
{
final Widget widget = widgets.get( i );
if ( widget.isVisible() && widget.isPickable() )
{
tmpHPR = widget.pick( canvasX, canvasY, pickReason, button, when, meta, flags );
if ( tmpHPR != null )
{
pickedWidgets.add( tmpHPR );
if ( ( topMost == null ) || ( topMost.compareTo( tmpHPR ) <= 0 ) )
{
topMost = tmpHPR;
}
if ( !hasOverlappingWidgets() )
{
break;
}
}
}
}
//if ( ( currentHoveredWidget != null ) && ( ( currentHoveredWidget != topMost.getWidget() ) || ( topMost == thisPick ) ) )
if ( ( currentHoveredWidget != null ) && ( currentHoveredWidget != topMost.getWidget() ) )
{
// How can this be??? But we will check it to avoid problems...
if ( currentHoveredWidget.getContainer() != null )
{
currentHoveredWidget.onMouseExited( true, false );
}
currentHoveredWidget = null;
}
for ( int i = 0; i < pickedWidgets.size(); i++ )
{
final HUDPickResult hpr = pickedWidgets.get( i );
final Widget pickedWidget = hpr.getWidget();
final boolean isTopMost = ( pickedWidget == topMost.getWidget() ); // && topMost.isLeafResult();
boolean hasFocus = ( pickedWidget == currentFocusedWidget );
float pickXHUD_ = pickXHUD - pickedWidget.getLeft();
float pickYHUD_ = pickYHUD - pickedWidget.getTop();
if ( isInternal && !eventsSuppressed )
{
switch ( pickReason )
{
case BUTTON_PRESSED:
if ( isTopMost )
{
if ( currentFocusedWidget != pickedWidget )
{
focus( pickedWidget );
hasFocus = true;
}
}
pickedWidget.onMouseButtonPressed( button, pickXHUD_, pickYHUD_, when, meta, isTopMost, hasFocus );
break;
case BUTTON_RELEASED:
pickedWidget.onMouseButtonReleased( button, pickXHUD_, pickYHUD_, when, meta, isTopMost, hasFocus );
break;
case MOUSE_MOVED:
pickedWidget.onMouseMoved( pickXHUD_, pickYHUD_, (int)meta, when, isTopMost, hasFocus );
if ( ( currentHoveredWidget == null ) && isTopMost )
{
currentHoveredWidget = pickedWidget;
currentHoveredWidget.onMouseEntered( isTopMost, hasFocus );
}
break;
case MOUSE_WHEEL_MOVED_UP:
pickedWidget.onMouseWheelMoved( +1, ( meta != 0L ), pickXHUD_, pickYHUD_, when, isTopMost );
break;
case MOUSE_WHEEL_MOVED_DOWN:
pickedWidget.onMouseWheelMoved( -1, ( meta != 0L ), pickXHUD_, pickYHUD_, when, isTopMost );
break;
}
}
if ( ( topMost != hpr ) && ( topMost != thisPick ) )
HUDPickResult.toPool( hpr );
}
pickedWidgets.clear();
if ( topMost != thisPick )
{
thisPick.setSubResult( topMost );
}
return ( thisPick );
}
/**
* Focusses the given Widget (non thread-safe).
*
* @param widget
*
* @return the previously focussed Widget
*/
public Widget focus( Widget widget )
{
final Widget pfw = currentFocusedWidget;
if ( getContainer() != null )
getContainer().focus( this );
else
__HUD_PrivilegedAccess.focus( getHUD(), widget );
if ( ( pfw == widget ) || !widget.isFocussable() )
return ( pfw );
if ( ( currentFocusedWidget != null ) && ( currentFocusedWidget.getContainer() != null ) )
currentFocusedWidget.onFocusLost();
currentFocusedWidget = widget;
currentFocusedWidget.onFocusGained();
return ( pfw );
}
/**
* @return the current focused Widget
*
* @param getLeaf recursively searches for the focused leaf (Widget), if true
*/
public final Widget getCurrentFocusedWidget( boolean getLeaf )
{
final Widget cfw = currentFocusedWidget;
if ( getLeaf )
{
if ( ( cfw != null ) && ( cfw instanceof WidgetContainer ) )
{
final Widget cfw2 = ( (WidgetContainer)cfw ).getCurrentFocusedWidget( getLeaf );
if ( cfw2 != null )
return ( cfw2 );
return ( cfw );
}
}
return ( cfw );
}
/**
* @return the current focused Widget
*/
public final Widget getCurrentFocusedWidget()
{
return ( currentFocusedWidget );
}
/**
* @return the current hovered Widget
*
* @param getLeaf recursively searches for the hovered leaf (Widget), if true
*/
public final Widget getCurrentHoveredWidget( boolean getLeaf )
{
final Widget chw = currentHoveredWidget;
if ( getLeaf )
{
if ( ( chw != null ) && ( chw instanceof WidgetContainer ) )
{
final Widget chw2 = ( (WidgetContainer)chw ).getCurrentHoveredWidget( getLeaf );
if ( chw2 != null )
return ( chw2 );
return ( chw );
}
}
return ( chw );
}
protected final Widget getCurrentHoveredWidget()
{
return ( currentHoveredWidget );
}
protected final void resetCurrentHoveredWidget()
{
currentHoveredWidget = null;
}
/**
* Moves the focus to the closest widget in the container
* in the given direction.
*
* @param direction
*
* @return the newly focussed Widget.
*/
Widget moveFocus( FocusMoveDirection direction )
{
if ( currentFocusedWidget == null )
return ( currentFocusedWidget );
if ( widgets.size() < 2 )
return ( currentFocusedWidget );
float currMidX = currentFocusedWidget.getLeft() + ( currentFocusedWidget.getWidth() / 2f );
float currMidY = currentFocusedWidget.getTop() + ( currentFocusedWidget.getHeight() / 2f );
float nextMidX = 0f;
float nextMidY = 0f;
Widget nextWidget = null;
for ( int i = 0; i < widgets.size(); i++ )
{
final Widget widget = widgets.get( i );
if ( !widget.isFocussable() )
continue;
if ( widget != currentFocusedWidget )
{
switch ( direction )
{
case UP:
{
final float midY = widget.getTop() + ( widget.getHeight() / 2f);
if ( midY < currMidY )
{
if ( ( nextWidget == null ) || ( midY > nextMidY ) )
{
nextWidget = widget;
nextMidY = midY;
}
}
break;
}
case LEFT:
{
final float midX = widget.getLeft() + ( widget.getWidth() / 2f);
if ( midX < currMidX )
{
if ( ( nextWidget == null ) || ( midX > nextMidX ) )
{
nextWidget = widget;
nextMidY = midX;
}
}
break;
}
case RIGHT:
{
final float midX = widget.getLeft() + ( widget.getWidth() / 2f);
if ( midX > currMidX )
{
if ( ( nextWidget == null ) || ( midX < nextMidX ) )
{
nextWidget = widget;
nextMidY = midX;
}
}
break;
}
case DOWN:
{
final float midY = widget.getTop() + ( widget.getHeight() / 2f);
if ( midY > currMidY )
{
if ( ( nextWidget == null ) || ( midY < nextMidY ) )
{
nextWidget = widget;
nextMidY = midY;
}
}
break;
}
case NEXT:
{
final float midX = widget.getLeft() + ( widget.getWidth() / 2f);
if ( midX > currMidX )
{
if ( ( nextWidget == null ) || ( midX < nextMidX ) )
{
nextWidget = widget;
nextMidY = midX;
}
}
if ( nextWidget == currentFocusedWidget )
{
currMidX = 0f;
final float midY = widget.getTop() + ( widget.getHeight() / 2f);
if ( midY > currMidY )
{
if ( ( nextWidget == null ) || ( midY < nextMidY ) )
{
nextWidget = widget;
nextMidY = midY;
}
}
}
break;
}
}
}
}
if ( nextWidget != null )
{
nextWidget.requestFocus();
}
return ( nextWidget );
}
/**
* {@inheritDoc}
*/
@Override
protected void onFocusGained()
{
super.onFocusGained();
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onFocusGained();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onFocusLost()
{
super.onFocusLost();
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onFocusLost();
}
}
private static void forwardOnMouseLeft( Widget widget, boolean isTopMost, boolean hasFocus )
{
if ( widget == null )
return;
if ( widget instanceof WidgetContainer )
{
forwardOnMouseLeft( ( (WidgetContainer)widget ).getCurrentHoveredWidget(), isTopMost, hasFocus );
}
widget.onMouseExited( isTopMost, false );
if ( widget instanceof WidgetContainer )
{
( (WidgetContainer)widget ).resetCurrentHoveredWidget();
}
}
@Override
protected void onMouseExited( boolean isTopMost, boolean hasFocus )
{
forwardOnMouseLeft( currentHoveredWidget, isTopMost, hasFocus );
resetCurrentHoveredWidget();
super.onMouseExited( isTopMost, hasFocus );
}
/**
* {@inheritDoc}
*/
@Override
protected void onKeyPressed( Key key, int modifierMask, long when )
{
super.onKeyPressed( key, modifierMask, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onKeyPressed( key, modifierMask, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onKeyReleased( Key key, int modifierMask, long when )
{
super.onKeyReleased( key, modifierMask, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onKeyReleased( key, modifierMask, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onKeyTyped( char ch, int modifierMask, long when )
{
super.onKeyTyped( ch, modifierMask, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onKeyTyped( ch, modifierMask, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onControllerButtonPressed( ControllerButton button, long when )
{
super.onControllerButtonPressed( button, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onControllerButtonPressed( button, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onControllerButtonReleased( ControllerButton button, long when )
{
super.onControllerButtonReleased( button, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onControllerButtonReleased( button, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onControllerAxisChanged( ControllerAxis axis, int axisDelta, long when )
{
super.onControllerAxisChanged( axis, axisDelta, when );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onControllerAxisChanged( axis, axisDelta, when );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onInputStateChanged( DeviceComponent comp, int delta, int state, long when, boolean isTopMost, boolean hasFocus )
{
super.onInputStateChanged( comp, delta, state, when, isTopMost, hasFocus );
if ( currentFocusedWidget != null )
{
currentFocusedWidget.onInputStateChanged( comp, delta, state, when, isTopMost, hasFocus );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void updateAbsZIndex()
{
super.updateAbsZIndex();
for ( int i = 0; i < widgets.size(); i++ )
{
widgets.get( i ).updateAbsZIndex();
}
}
private final void updateLight( boolean updateChildren )
{
updateSizeFactors();
final HUD hud = getHUD();
if ( hud != null )
{
final Tuple2i loc = Tuple2i.fromPool();
getLocationHUD2Pixels( 0f, 0f, loc );
// OpenGL wants the ScissorBox to be flipped upside down!
loc.setY( (int)hud.getHeight() - loc.getY() - getContentHeightPX() );
if ( childrenGroup.getScissorRect() != null )
childrenGroup.getScissorRect().init( loc.getX(), loc.getY(), getContentWidthPX(), getContentHeightPX() );
//System.out.println( childrenGroup.getScissorBox() );
//childrenGroup.setScissorRect( null );
Tuple2i.toPool( loc );
}
if ( updateChildren )
{
for ( int i = 0; i < widgets.size(); i++ )
{
widgets.get( i ).update();
}
}
}
/**
* Enables or disables clipping for this WidgetContainer.
*
* @param clippingEnabled
*/
public void setClippingEnabled( boolean clippingEnabled )
{
if ( clippingEnabled == isClippingEnbaled() )
return;
if ( clippingEnabled )
{
childrenGroup.setScissorRect( new ScissorRect( 0, 0, 0, 0 ) );
updateLight( false );
}
else
{
childrenGroup.setScissorRect( null );
}
}
/**
* @return whether clipping is enabled or disabled for this WidgetContainer.
*/
public final boolean isClippingEnbaled()
{
return ( childrenGroup.getScissorRect() != null );
}
/**
* {@inheritDoc}
*/
@Override
protected float calculateTransformWidth_Pixels2HUD( float contentWidth )
{
if ( hasCustomResolution() )
return ( getResX() / contentWidth );
return ( super.calculateTransformWidth_Pixels2HUD( contentWidth ) );
}
/**
* {@inheritDoc}
*/
@Override
protected float calculateTransformHeight_Pixels2HUD( float contentHeight )
{
if ( hasCustomResolution() )
return ( getResY() / contentHeight );
return ( super.calculateTransformHeight_Pixels2HUD( contentHeight ) );
}
/**
* {@inheritDoc}
*/
@Override
protected void updateTranslation()
{
super.updateTranslation();
updateLight( false );
}
/**
* {@inheritDoc}
*/
@Override
public void update()
{
super.update();
//updateLight( true );
if ( ( getHUD() != null ) && ( layoutManager != null ) )
{
layoutManager.doLayout( this );
layoutDirty = false;
}
// We don't need to explicitly update the children, since this is already done by the setSize() implementation.
}
/**
* {@inheritDoc}
*/
@Override
protected void onAttachedToHUD( HUD hud )
{
super.onAttachedToHUD( hud );
if ( hud.isVisible() )
{
update();
}
}
/**
* {@inheritDoc}
*/
@Override
void setHUD( HUD hud )
{
super.setHUD( hud );
for ( int i = 0; i < widgets.size(); i++ )
{
widgets.get( i ).setHUD( hud );
}
}
@Override
protected void setWidgetDirty()
{
super.setWidgetDirty();
setHostedWidgetDirty();
if ( widgets != null )
for ( int i = 0; i < widgets.size(); i++ )
{
if ( !widgets.get( i ).isHeavyWeight() )
widgets.get( i ).setWidgetDirty();
}
}
/**
* Draws the (parent-)background for a child Widget.
*
* @param forWidget
* @param texCanvas
* @param offsetX
* @param offsetY
* @param width
* @param height
*/
void drawParentBackground( Widget forWidget, Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height )
{
//int ox = forWidget.getContentLeftPX();
//int oy = forWidget.getContentTopPX();
int ox = 0;
int oy = 0;
Dim2i tmp = Dim2i.fromPool();
getSizeHUD2Pixels( forWidget.getLeft(), forWidget.getTop(), tmp );
ox += tmp.getWidth();
oy += tmp.getHeight();
Dim2i.toPool( tmp );
offsetX -= ox;
offsetY -= oy;
//width += ox;
//height += oy;
width = getWidthPX();
height = getHeightPX();
drawBackground( texCanvas, offsetX, offsetY, width, height );
}
/**
*
* @param texCanvas
* @param offsetX
* @param offsetY
* @param width
* @param height
* @param drawsSelf
*/
protected void drawChildWidgets( Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height, boolean drawsSelf )
{
if ( !isAHostedWidgetDirty() )
return;
Point2i p = Point2i.fromPool();
Dim2i d = Dim2i.fromPool();
ensureWidgetsSortedByZIndex();
//offsetX += getPaddingLeft();
//offsetY += getPaddingTop();
offsetX += childrenOffset_PX.getX();
offsetY -= childrenOffset_PX.getY();
for ( int i = 0; i < widgets.size(); i++ )
{
Widget widget = widgets.get( i );
if ( !widget.isHeavyWeight() && widget.isVisible() )
{
getRelLocationHUD2Pixels_( widget.getLeft(), widget.getTop(), p );
getSizeHUD2Pixels( widget.getWidth(), widget.getHeight(), d );
widget.drawAndUpdateWidget( texCanvas, offsetX + p.getX(), offsetY + p.getY(), d.getWidth(), d.getHeight(), false );
}
}
Dim2i.toPool( d );
Point2i.toPool( p );
}
/**
* {@inheritDoc}
*/
@Override
protected void drawWidget( Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height, boolean drawsSelf )
{
drawChildWidgets( texCanvas, offsetX, offsetY, width, height, drawsSelf );
}
protected GroupNode createChildrenGroup()
{
return ( new Group() );
}
/**
* Creates a new WidgetContainer with the given width, height and z-index.
* The WidgetContainer will have a differen coordinate system then it's parent WidgetContainer.
*
* @param isHeavyWeight
* @param hasWidgetAssembler
* @param width the new width of this Widget
* @param height the new height of this Widget
* @param backgroundColor the background color
* @param backgroundTex the background texture
* @param backgroundTileMode
*/
protected WidgetContainer( boolean isHeavyWeight, boolean hasWidgetAssembler, float width, float height, Colorf backgroundColor, Texture2D backgroundTex, TileMode backgroundTileMode )
{
super( isHeavyWeight, hasWidgetAssembler, width, height, backgroundColor, backgroundTex, backgroundTileMode );
Node.pushGlobalIgnoreBounds( true );
this.childrenGroup = createChildrenGroup();
Node.popGlobalIgnoreBounds();
( (TransformGroup)getSGNode() ).addChild( childrenGroup );
childrenGroup.setScissorRect( new ScissorRect( 0, 0, 0, 0 ) );
}
}