You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1021 lines
29 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 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, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// pfGUIControlMod Definition //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "pfGUIControlMod.h"
#include "pfGameGUIMgr.h"
#include "pfGUIDialogMod.h"
#include "pfGUIControlHandlers.h"
#include "pfGUIDialogHandlers.h"
#include "pfGUIListElement.h" // Includes dropTargetProc
#include "../pnMessage/plRefMsg.h"
#include "../pnMessage/plEnableMsg.h"
#include "../pfMessage/pfGameGUIMsg.h"
#include "../plMessage/plDeviceRecreateMsg.h"
#include "../pnSceneObject/plDrawInterface.h"
#include "../pnSceneObject/plCoordinateInterface.h"
#include "../pnSceneObject/plAudioInterface.h"
#include "../plGImage/plDynamicTextMap.h"
#include "../plSurface/plLayer.h"
#include "../plMessage/plRenderMsg.h"
#include "../pnMessage/plSoundMsg.h"
#include "plPipeline.h"
#include "../plDrawable/plAccessGeometry.h"
#include "../plDrawable/plAccessSpan.h"
#include "../plDrawable/plAccessVtxSpan.h"
#include "pfGUIPopUpMenu.h" // For skin, can we move that please? Thank you
#include "plgDispatch.h"
#include "hsResMgr.h"
//// pfGUIColorScheme Functions //////////////////////////////////////////////
void pfGUIColorScheme::IReset( void )
{
fForeColor.Set( 1, 1, 1, 1 );
fBackColor.Set( 0, 0, 0, 1 );
fSelForeColor.Set( 1, 1, 1, 1 );
fSelBackColor.Set( 0, 0, 1, 1 );
fTransparent = false;
fFontFace = hsStrcpy( "Times New Roman" );
fFontSize = 10;
fFontFlags = 0;
}
pfGUIColorScheme::pfGUIColorScheme()
{
IReset();
}
pfGUIColorScheme::~pfGUIColorScheme()
{
delete [] fFontFace;
}
pfGUIColorScheme::pfGUIColorScheme( hsColorRGBA &foreColor, hsColorRGBA &backColor )
{
IReset();
fForeColor = foreColor;
fBackColor = backColor;
}
pfGUIColorScheme::pfGUIColorScheme( const char *face, UInt8 size, UInt8 fontFlags )
{
IReset();
fFontFace = hsStrcpy( face );
fFontSize = size;
fFontFlags = fontFlags;
}
void pfGUIColorScheme::SetFontFace( const char *face )
{
delete [] fFontFace;
fFontFace = hsStrcpy( face );
}
void pfGUIColorScheme::Read( hsStream *s )
{
fForeColor.Read( s );
fBackColor.Read( s );
fSelForeColor.Read( s );
fSelBackColor.Read( s );
s->ReadSwap( &fTransparent );
delete [] fFontFace;
fFontFace = s->ReadSafeString();
s->ReadSwap( &fFontSize );
s->ReadSwap( &fFontFlags );
}
void pfGUIColorScheme::Write( hsStream *s )
{
fForeColor.Write( s );
fBackColor.Write( s );
fSelForeColor.Write( s );
fSelBackColor.Write( s );
s->WriteSwap( fTransparent );
s->WriteSafeString( fFontFace );
s->WriteSwap( fFontSize );
s->WriteSwap( fFontFlags );
}
//// Constructor/Destructor //////////////////////////////////////////////////
pfGUIControlMod::pfGUIControlMod()
{
fEnabled = true;
fDialog = nil;
fBoundsValid = false;
fCenterValid = false;
fFocused = false;
fInteresting = false;
fVisible = true;
fHandler = nil;
fTagID = 0;
fDropTargetHdlr = nil;
fDynTextMap = nil;
fProxy = nil;
fColorScheme = nil;
fSkin = nil;
fNotifyOnInteresting = false;
}
pfGUIControlMod::~pfGUIControlMod()
{
ISetHandler( nil );
SetDropTargetHdlr( nil );
SetColorScheme( nil );
}
//// IEval ///////////////////////////////////////////////////////////////////
hsBool pfGUIControlMod::IEval( double secs, hsScalar del, UInt32 dirty )
{
// UpdateBounds();
return false;
}
//// GetBounds ///////////////////////////////////////////////////////////////
const hsBounds3 &pfGUIControlMod::GetBounds( void )
{
UpdateBounds();
return fBounds;
}
//// SetTransform ////////////////////////////////////////////////////////////
// Override from plModifier so we can update our bounds
void pfGUIControlMod::SetTransform( const hsMatrix44 &l2w, const hsMatrix44 &w2l )
{
fBoundsValid = false;
}
//// GetVectorAngle //////////////////////////////////////////////////////////
static hsScalar GetVectorAngle( const hsPoint3 &basePt, const hsPoint3 &pointA, const hsPoint3 &pointB )
{
hsVector3 vectorA( &pointA, &basePt ), vectorB( &pointB, &basePt );
hsScalar dot = vectorA * vectorB;
hsVector3 cross = vectorA % vectorB;
hsScalar crossLen = cross.fZ;
return atan2( crossLen, dot );
}
//// CreateConvexHull ////////////////////////////////////////////////////////
// Algorithm is Graham's scan algorithm:
// R.L. Graham, "An efficient algorithm for determining the convex hull of a finite
// planar set", Info. Proc. Lett. 1, 132-133 (1972).
// Note: THIS WILL DESTROY YOUR INPOINTS ARRAY.
static hsBool CreateConvexHull( hsPoint3 *inPoints, int &numPoints )
{
int i, j, pointA, pointB, pointC;
hsScalar *angles;
if( numPoints < 3 )
return false;
// Step 1: Find a point interior to our hull. Easiest is average of all our input points...
// (plus: set the Zs of all the points to the Z of the first point, since we want to be
// working in 2D)
hsPoint3 avgPoint = inPoints[ 0 ];
for( i = 1; i < numPoints; i++ )
{
avgPoint += inPoints[ i ];
inPoints[ i ].fZ = inPoints[ 0 ].fZ;
}
avgPoint.fX /= numPoints;
avgPoint.fY /= numPoints;
avgPoint.fZ /= numPoints;
// Step 2: Sort all the in points by the angle to the X axis (vector <1,0>).
// Step A: Calculate all the angles
angles = TRACKED_NEW hsScalar[ numPoints ];
hsPoint3 xAxisPoint( avgPoint.fX + 1, avgPoint.fY, avgPoint.fZ );
for( i = 0; i < numPoints; i++ )
angles[ i ] = GetVectorAngle( avgPoint, inPoints[ i ], xAxisPoint );
// Step B: Bubble sort by the angles
for( i = 0; i < numPoints - 1; i++ )
{
for( j = i + 1; j < numPoints; j++ )
{
if( angles[ j ] < angles[ i ] )
{
hsScalar tempAngle = angles[ j ];
angles[ j ] = angles[ i ];
angles[ i ] = tempAngle;
hsPoint3 tempPt = inPoints[ j ];
inPoints[ j ] = inPoints[ i ];
inPoints[ i ] = tempPt;
}
}
}
// Step 3: Eliminate non-convex points to form the hull
for( pointA = 0, pointB = 1, pointC = 2; pointA < numPoints && numPoints > 3; )
{
// Two cases of wrap-around...
if( pointC >= numPoints )
pointC -= numPoints;
else if( pointC < 0 )
pointC += numPoints;
if( pointB >= numPoints )
pointB -= numPoints;
else if( pointB < 0 )
pointB += numPoints;
// For points A, B, and C, find the interior angle between them
hsScalar angle = GetVectorAngle( inPoints[ pointB ], inPoints[ pointA ], inPoints[ pointC ] );
// If the angle is < 180, then it's a good angle and we can advance all our points by 1...
// Note: we have a tolerance so that we don't get points that form edges that are pretty darned close...
const hsScalar tolerance = hsScalarPI / 90.f;
if( angle > tolerance && angle < hsScalarPI - tolerance )
{
pointA++;
pointB++;
pointC++;
}
else
{
// Angle is > 180 degrees, this is bad. This means our middle point doesn't belong,
// so we need to remove it
for( i = pointB; i < numPoints - 1; i++ )
inPoints[ i ] = inPoints[ i + 1 ];
numPoints--;
if( pointC > pointB )
pointC--;
// There's one case where point B and C could've wrapped around and so deleting that point
// actually moves point A down by 1...
if( pointA > pointB )
pointA--;
// Back up the points by 1 if possible (so we can keep checking to make sure we're still convex).
// If not, just increment C up
if( pointA > 0 )
{
pointA--;
pointB--;
}
else
pointC++;
}
}
delete [] angles;
return true;
}
//// GetObjectPoints /////////////////////////////////////////////////////////
// Retrieves ALL of the points of a sceneObject's meshes. And I mean ALL of
// 'em...
static void GetObjectPoints( plSceneObject *so, hsTArray<hsPoint3> &outPoints )
{
const plDrawInterface* di = so->GetDrawInterface();
if( !di )
return;
// The following uses mf's spiffy plAccessGeometry/Spans stuff, which, in
// one word, kicksAss.
hsTArray<plAccessSpan> spans;
plAccessGeometry::Instance()->OpenRO( di, spans );
int i;
outPoints.Reset();
for( i = 0; i < spans.GetCount(); i++ )
{
plAccessVtxSpan& vtxSrc = spans[ i ].AccessVtx();
plAccPositionIterator iterSrc( &vtxSrc );
for( iterSrc.Begin(); iterSrc.More(); iterSrc.Advance() )
outPoints.Append( *iterSrc.Position() );
}
if (plAccessGeometry::Instance())
plAccessGeometry::Instance()->Close( spans );
}
//// PointsOnSameSide ////////////////////////////////////////////////////////
// Given two ends of a line segment and two points, tells you whether the
// two points are on the same side of the line. Used in PointInTriangle().
static hsBool PointsOnSameSide( const hsPoint3 &line1, const hsPoint3 &line2, const hsPoint3 &pointA, const hsPoint3 &pointB )
{
hsVector3 baseVec( &line2, &line1 );
hsVector3 cp1 = hsVector3( &pointA, &line1 ) % baseVec;
hsVector3 cp2 = hsVector3( &pointB, &line1 ) % baseVec;
return ( cp1.fZ * cp2.fZ > 0 ) ? true : false;
}
//// PointInTriangle /////////////////////////////////////////////////////////
// Given three points that define a triangle and a fourth point, tells you
// whether the fourth point is inside the triangle.
static hsBool PointInTriangle( hsPoint3 tri1, hsPoint3 tri2, hsPoint3 tri3, const hsPoint3 &testPoint )
{
tri1.fZ = tri2.fZ = tri3.fZ = testPoint.fZ;
if( PointsOnSameSide( tri1, tri2, testPoint, tri3 ) &&
PointsOnSameSide( tri2, tri3, testPoint, tri1 ) &&
PointsOnSameSide( tri3, tri1, testPoint, tri2 ) )
return true;
return false;
}
//// PointInBounds ///////////////////////////////////////////////////////////
// Tells you whether said point is in the control's bounds.
hsBool pfGUIControlMod::PointInBounds( const hsPoint3 &point )
{
UpdateBounds();
if( fBounds.GetType() != kBoundsEmpty && fBounds.GetType() != kBoundsUninitialized && fBounds.IsInside( &point ) )
{
if( fBoundsPoints.GetCount() > 0 )
{
// We have a more-accurate bounds set, so use it
int i;
for( i = 1; i < fBoundsPoints.GetCount() - 1; i++ )
{
// Test the triangle (0,i,i+1)
if( PointInTriangle( fBoundsPoints[ 0 ], fBoundsPoints[ i ], fBoundsPoints[ i + 1 ], point ) )
return true;
}
return false;
}
else
return true;
}
return false;
}
//// CalcInitialBounds ///////////////////////////////////////////////////////
// Called by the dialog once as soon as the dialog adds the control, so that
// initial bounds for the control can be calced. This is used for initing
// any dynmaic text maps, since we want to use the initial bounds to do so
// instead of any currently animated state of the bounds.
void pfGUIControlMod::CalcInitialBounds( void )
{
UpdateBounds( nil, true );
fInitialBounds = fBounds;
}
//// UpdateBounds ////////////////////////////////////////////////////////////
void pfGUIControlMod::UpdateBounds( hsMatrix44 *invXformMatrix, hsBool force )
{
hsMatrix44 xformMatrix, projMatrix;
hsPoint3 corners[ 8 ];
int i;
if( ( !fBoundsValid || force ) && fDialog && GetTarget() )
{
plDrawInterface *DI = IGetTargetDrawInterface( 0 );
if( DI == nil )
return;
if( HasFlag( kBetterHitTesting ) )
{
hsTArray<hsPoint3> scrnPoints;
// Create a list of points to make a 2D convex hull from
GetObjectPoints( GetTarget(), scrnPoints );
hsMatrix44 l2w = GetTarget()->GetLocalToWorld();
for( i = 0; i < scrnPoints.GetCount(); i++ )
{
scrnPoints[ i ] = l2w * scrnPoints[ i ];
scrnPoints[ i ] = fDialog->WorldToScreenPoint( scrnPoints[ i ] );
}
// Now create a convex hull from them, assuming the Zs are all the same
int numPoints = scrnPoints.GetCount();
if( !CreateConvexHull( scrnPoints.AcquireArray(), numPoints ) )
return;
// Copy & store. Also recalc our bounding box just for fun
fBounds.MakeEmpty();
fBoundsPoints.SetCount( numPoints );
for( i = 0; i < numPoints; i++ )
{
fBoundsPoints[ i ] = scrnPoints[ i ];
fBounds.Union( &fBoundsPoints[ i ] );
}
}
else
{
fBounds.MakeEmpty();
hsBounds3Ext worldBounds = DI->GetLocalBounds();
hsMatrix44 l2w = GetTarget()->GetLocalToWorld();
worldBounds.Transform( &l2w );
worldBounds.GetCorners( corners );
for( i = 0; i < 8; i++ )
{
hsPoint3 scrnPt = fDialog->WorldToScreenPoint( corners[ i ] );
fBounds.Union( &scrnPt );
}
}
// Calc center Z
// if( !fCenterValid )
{
#if 0
corners[ 1 ] = GetTarget()->GetLocalToWorld().GetTranslate();
float w = corners[ 1 ].fX * fXformMatrix.fMap[3][0]
+ corners[ 1 ].fY * fXformMatrix.fMap[3][1]
+ corners[ 1 ].fZ * fXformMatrix.fMap[3][2]
+ 1.f * fXformMatrix.fMap[3][3];
corners[ 1 ] = fXformMatrix * corners[ 1 ];
corners[ 1 ].fX = ( ( corners[ 1 ].fX / corners[ 1 ].fZ ) + 1.f ) / 2.f;
corners[ 1 ].fY = ( ( corners[ 1 ].fY / corners[ 1 ].fZ ) + 1.f ) / 2.f;
fScreenCenter = corners[ 1 ];
// fScreenCenter.fZ = w;
corners[ 1 ] = GetTarget()->GetLocalToWorld().GetTranslate();
fDialog->WorldToScreenPoint( corners[ 1 ].fX, corners[ 1 ].fY, corners[ 1 ].fZ, fScreenCenter );
fCenterValid = true;
#else
corners[ 1 ] = GetTarget()->GetLocalToWorld().GetTranslate();
fScreenCenter = fDialog->WorldToScreenPoint( corners[ 1 ] );
corners[ 1 ] = fScreenCenter;
fCenterValid = true;
#endif
}
fScreenMinZ = fBounds.GetMins().fZ;
// Manually change the bounds so we know the z ranges from at least -1 to 1, suitable for us testing against for clicks
corners[ 0 ] = fBounds.GetCenter();
corners[ 0 ].fZ = -1.f;
fBounds.Union( &corners[ 0 ] );
corners[ 0 ].fZ = 1.f;
fBounds.Union( &corners[ 0 ] );
fBoundsValid = true;
}
}
//// SetObjectCenter /////////////////////////////////////////////////////////
// Given the x/y coordinates in 0..1 space, recalcs the sceneObject position
// and moves the object to match, retaining the stored fCenterZ coordinate
void pfGUIControlMod::SetObjectCenter( hsScalar x, hsScalar y )
{
hsMatrix44 xformMatrix, l2p, p2l;
hsPoint3 center, corners[ 8 ];
if( x > 1.f )
x = 1.f;
else if( x < 0.f )
x = 0.f;
if( y > 1.f )
y = 1.f;
else if( y < 0.f )
y = 0.f;
if( fDialog && GetTarget() )
{
plCoordinateInterface *CI = IGetTargetCoordinateInterface( 0 );
if( CI == nil )
return;
// if( !fInvXformValid )
// UpdateBounds();
l2p = GetTarget()->GetLocalToWorld();
hsPoint3 oldPt = l2p.GetTranslate();
hsPoint3 oldScrnPt = fDialog->WorldToScreenPoint( oldPt );
hsPoint3 oldPtRedux;
fDialog->ScreenToWorldPoint( oldScrnPt.fX, oldScrnPt.fY, oldScrnPt.fZ, oldPtRedux );
fDialog->ScreenToWorldPoint( x, y, fScreenCenter.fZ, center );
l2p.SetTranslate( &center );
l2p.GetInverse( &p2l );
GetTarget()->SetTransform( l2p, p2l );
fScreenCenter.fX = x;
fScreenCenter.fY = y;
}
}
void pfGUIControlMod::SetTarget( plSceneObject *object )
{
plSingleModifier::SetTarget( object );
UpdateBounds();
}
//// MsgReceive //////////////////////////////////////////////////////////////
#include "plProfile.h"
plProfile_CreateTimer("Gui", "RenderSetup", GUITime);
hsBool pfGUIControlMod::MsgReceive( plMessage *msg )
{
plRenderMsg* rend = plRenderMsg::ConvertNoRef( msg );
plDeviceRecreateMsg* device = plDeviceRecreateMsg::ConvertNoRef(msg);
if (rend || device) {
plPipeline* pipe = rend ? rend->Pipeline() : device->Pipeline();
plProfile_BeginLap(GUITime, this->GetKey()->GetUoid().GetObjectName());
ISetUpDynTextMap(pipe);
plProfile_EndLap(GUITime, this->GetKey()->GetUoid().GetObjectName());
if (rend)
plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());
return true;
}
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( msg );
if( refMsg != nil )
{
if( refMsg->fType == kRefDynTextMap )
{
if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
{
fDynTextMap = plDynamicTextMap::ConvertNoRef( refMsg->GetRef() );
// These tell us when we need to (re-)initialize the DTM
plgDispatch::Dispatch()->RegisterForExactType( plRenderMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plDeviceRecreateMsg::Index(), GetKey() );
}
else
{
fDynTextMap = nil;
plgDispatch::Dispatch()->UnRegisterForExactType( plDeviceRecreateMsg::Index(), GetKey() );
}
return true;
}
else if( refMsg->fType == kRefDynTextLayer )
{
if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
fDynTextLayer = plLayerInterface::ConvertNoRef( refMsg->GetRef() );
else
fDynTextLayer = nil;
return true;
}
else if( refMsg->fType == kRefProxy )
{
if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
fProxy = plSceneObject::ConvertNoRef( refMsg->GetRef() );
else
fProxy = nil;
return true;
}
else if( refMsg->fType == kRefSkin )
{
if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
fSkin = pfGUISkin::ConvertNoRef( refMsg->GetRef() );
else
fSkin = nil;
return true;
}
}
return plSingleModifier::MsgReceive( msg );
}
//// ISetUpDynTextMap ////////////////////////////////////////////////////////
// Given a pointer to a dynamic text map, regurgitates it so it matches our
// screen res and fun stuff like that. Also sets the layer transform to give
// us a 1:1 textel-pixel ratio, which we like.
hsBool pfGUIControlMod::ISetUpDynTextMap( plPipeline *pipe )
{
if( fDynTextMap == nil )
{
hsAssert( false, "Trying to set up a nil dynamicTextMap in a GUI control" );
return true;
}
if( fDynTextLayer == nil || fInitialBounds.GetType() == kBoundsUninitialized )//|| fDialog == nil )
return false;
UInt32 scrnWidth, scrnHeight;
if( !HasFlag( kScaleTextWithResolution ) )
{
// Scale so that there is a 1:1 pixel:textel ratio
scrnWidth = pipe->Width();
scrnHeight = pipe->Height();
}
else
{
// Scale with the resolution so that we take up the same % of screen space no matter what resolution
// Assume a base "resolution" of 1024xX, where X is such that the ratio "1024/X = scrnWidth/scrnHt" holds
const int kBaseScaleRes = 1024;
const int kBaseScaleHeightRes = 768;
scrnWidth = kBaseScaleRes;
scrnHeight = kBaseScaleHeightRes;
// we are going to just force things to be in 4 by 3 ratio...
// ...cause it seems to work better.
/////// scrnHeight = ( pipe->Height() * kBaseScaleRes ) / pipe->Width();
}
const hsBounds3 &bounds = fInitialBounds;//GetBounds();
UInt16 width = (UInt16)(( bounds.GetMaxs().fX - bounds.GetMins().fX ) * scrnWidth);
UInt16 height = (UInt16)(( bounds.GetMaxs().fY - bounds.GetMins().fY ) * scrnHeight);
// Allow derived controls to allocate some extra scratch space if desired
// (Do it this way so we can pass in our current calculated dimensions for them to play with)
UInt16 extraW = width, extraH = height;
IGrowDTMDimsToDesiredSize( extraW, extraH );
extraW -= width;
extraH -= height;
fDynTextMap->Reset();
fDynTextMap->Create( width, height, HasFlag( kXparentBgnd ), extraW, extraH, true );
fDynTextMap->SetFont( GetColorScheme()->fFontFace, GetColorScheme()->fFontSize, GetColorScheme()->fFontFlags,
HasFlag( kXparentBgnd ) ? false : true );
fDynTextMap->SetTextColor( GetColorScheme()->fForeColor,
( HasFlag( kXparentBgnd ) && GetColorScheme()->fBackColor.a == 0.f ) ? true : false );
// Now we gotta set the texture transform on the layer so our texture comes
// out with 1:1 mapping from textel to pixel
plLayer *layer = (plLayer *)fDynTextLayer;
layer->SetTransform( fDynTextMap->GetLayerTransform() );
layer->SetBlendFlags( layer->GetBlendFlags() | hsGMatState::kBlendAlphaPremultiplied );
// Let the derived classes do their things
IPostSetUpDynTextMap();
// Do our first update
IUpdate();
return true;
}
//// Get/SetColorScheme //////////////////////////////////////////////////////
pfGUIColorScheme *pfGUIControlMod::GetColorScheme( void ) const
{
if( fColorScheme == nil )
return fDialog->GetColorScheme();
return fColorScheme;
}
void pfGUIControlMod::SetColorScheme( pfGUIColorScheme *newScheme )
{
if( fColorScheme != nil )
{
hsRefCnt_SafeUnRef( fColorScheme );
fColorScheme = nil;
}
fColorScheme = newScheme;
if( fColorScheme != nil )
hsRefCnt_SafeRef( fColorScheme );
}
//// SetDynTextMap ///////////////////////////////////////////////////////////
// EXPORT ONLY
void pfGUIControlMod::SetDynTextMap( plLayerInterface *layer, plDynamicTextMap *dynText )
{
hsgResMgr::ResMgr()->AddViaNotify( layer->GetKey(), TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, pfGUIControlMod::kRefDynTextLayer ), plRefFlags::kActiveRef );
hsgResMgr::ResMgr()->AddViaNotify( dynText->GetKey(), TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, pfGUIControlMod::kRefDynTextMap ), plRefFlags::kActiveRef );
}
//// SetEnabled //////////////////////////////////////////////////////////////
void pfGUIControlMod::SetEnabled( hsBool e )
{
if( e == fEnabled )
return;
fEnabled = e;
IUpdate();
}
//// SetFocused //////////////////////////////////////////////////////////////
void pfGUIControlMod::SetFocused( hsBool e )
{
if( e == fFocused )
return;
fFocused = e;
IUpdate();
}
//// SetInteresting //////////////////////////////////////////////////////////
void pfGUIControlMod::SetInteresting( hsBool i )
{
if( i == fInteresting )
return;
fInteresting = i;
IUpdate();
if ( fNotifyOnInteresting && fDialog && fDialog->GetHandler() )
fDialog->GetHandler()->OnInterestingEvent(this);
}
//// SetVisible //////////////////////////////////////////////////////////////
void pfGUIControlMod::SetVisible( hsBool vis )
{
if( vis == fVisible )
return;
fVisible = vis;
if (fTarget)
{
plEnableMsg *msg = TRACKED_NEW plEnableMsg();
msg->SetCmd( fVisible ? plEnableMsg::kEnable : plEnableMsg::kDisable );
msg->SetCmd( plEnableMsg::kDrawable );
msg->AddReceiver( fTarget->GetKey() );
plgDispatch::MsgSend( msg );
}
if( !fVisible && fFocused )
fDialog->SetFocus( nil );
}
void pfGUIControlMod::Refresh( void )
{
IUpdate();
}
//// Read/Write //////////////////////////////////////////////////////////////
void pfGUIControlMod::Read( hsStream *s, hsResMgr *mgr )
{
plSingleModifier::Read(s, mgr);
s->ReadSwap( &fTagID );
fVisible = s->ReadBool();
// Read the handler in
ISetHandler( pfGUICtrlProcWriteableObject::Read( s ) );
// Read in the dynTextMap if there is one
if( s->ReadBool() )
{
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefDynTextLayer ), plRefFlags::kActiveRef );
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefDynTextMap ), plRefFlags::kActiveRef );
}
else
{
fDynTextLayer = nil;
fDynTextMap = nil;
}
if( s->ReadBool() )
{
SetColorScheme( nil );
fColorScheme = TRACKED_NEW pfGUIColorScheme();
fColorScheme->Read( s );
}
// Read in our sound indices
UInt8 i, count = s->ReadByte();
if( count == 0 )
fSoundIndices.Reset();
else
{
fSoundIndices.SetCountAndZero( count );
for( i = 0; i < count; i++ )
fSoundIndices[ i ] = (int)s->ReadSwap32();
}
if( HasFlag( kHasProxy ) )
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefProxy ), plRefFlags::kActiveRef );
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefSkin ), plRefFlags::kActiveRef );
}
void pfGUIControlMod::Write( hsStream *s, hsResMgr *mgr )
{
if( HasFlag( kHasProxy ) && !fProxy )
ClearFlag( kHasProxy );
plSingleModifier::Write( s, mgr );
s->WriteSwap( fTagID );
s->WriteBool( fVisible );
// Write the handler out (if it's not a writeable, damn you)
pfGUICtrlProcWriteableObject::Write( (pfGUICtrlProcWriteableObject *)fHandler, s );
// Write out the dynTextMap
if( fDynTextMap != nil )
{
s->WriteBool( true );
mgr->WriteKey( s, fDynTextLayer->GetKey() );
mgr->WriteKey( s, fDynTextMap->GetKey() );
}
else
s->WriteBool( false );
if( fColorScheme != nil )
{
s->WriteBool( true );
fColorScheme->Write( s );
}
else
s->WriteBool( false );
// Write out our sound indices
s->WriteByte( fSoundIndices.GetCount() );
UInt8 i;
for( i = 0; i < fSoundIndices.GetCount(); i++ )
s->WriteSwap32( fSoundIndices[ i ] );
if( HasFlag( kHasProxy ) )
mgr->WriteKey( s, fProxy->GetKey() );
mgr->WriteKey( s, fSkin );
}
//// HandleKeyPress/Event ////////////////////////////////////////////////////
hsBool pfGUIControlMod::HandleKeyPress( char key, UInt8 modifiers )
{
return false;
}
hsBool pfGUIControlMod::HandleKeyEvent( pfGameGUIMgr::EventType event, plKeyDef key, UInt8 modifiers )
{
return false;
}
//// IScreenToLocalPt ////////////////////////////////////////////////////////
void pfGUIControlMod::IScreenToLocalPt( hsPoint3 &pt )
{
const hsBounds3 &bnds = GetBounds();
pt.fX -= bnds.GetMins().fX;
pt.fY -= bnds.GetMins().fY;
pt.fX /= bnds.GetMaxs().fX - bnds.GetMins().fX;
pt.fY /= bnds.GetMaxs().fY - bnds.GetMins().fY;
}
//// ISetHandler /////////////////////////////////////////////////////////////
void pfGUIControlMod::ISetHandler( pfGUICtrlProcObject *h, hsBool clearInheritFlag )
{
if( fHandler && fHandler->DecRef() )
delete fHandler;
fHandler = h;
if( fHandler )
fHandler->IncRef();
if( clearInheritFlag )
ClearFlag( kInheritProcFromDlg );
}
//// DoSomething /////////////////////////////////////////////////////////////
void pfGUIControlMod::DoSomething( void )
{
if( fEnabled && fHandler != nil )
fHandler->DoSomething( this );
}
//// HandleExtendedEvent /////////////////////////////////////////////////////
void pfGUIControlMod::HandleExtendedEvent( UInt32 event )
{
if( fEnabled && fHandler != nil )
fHandler->HandleExtendedEvent( this, event );
}
//// SetDropTargetHdlr ///////////////////////////////////////////////////////
void pfGUIControlMod::SetDropTargetHdlr( pfGUIDropTargetProc *h )
{
if( fDropTargetHdlr && fDropTargetHdlr->DecRef() )
delete fDropTargetHdlr;
fDropTargetHdlr = h;
if( fDropTargetHdlr )
fDropTargetHdlr->IncRef();
}
//// SetSoundIndex ///////////////////////////////////////////////////////////
// Associates the given GUI event with an index of a sound on the target SO's
// audioInterface. The guiCtrlEvent is specific to each type of control.
void pfGUIControlMod::SetSoundIndex( UInt8 guiCtrlEvent, int soundIndex )
{
if( fSoundIndices.GetCount() < guiCtrlEvent + 1 )
fSoundIndices.ExpandAndZero( guiCtrlEvent + 1 );
fSoundIndices[ guiCtrlEvent ] = soundIndex + 1; // We +1, since 0 means no sound
}
//// IPlaySound //////////////////////////////////////////////////////////////
// Sends a sound play message with the soundIndex associated with the given
// event.
void pfGUIControlMod::IPlaySound( UInt8 guiCtrlEvent, hsBool loop /* = false */ )
{
if( guiCtrlEvent >= fSoundIndices.GetCount() || fSoundIndices[ guiCtrlEvent ] == 0 )
return;
if( GetTarget() == nil || GetTarget()->GetAudioInterface() == nil )
return;
plSoundMsg *msg = TRACKED_NEW plSoundMsg;
msg->fIndex = fSoundIndices[ guiCtrlEvent ] - 1;
msg->SetCmd( plSoundMsg::kGoToTime );
msg->fTime = 0.f;
msg->SetCmd( plSoundMsg::kPlay );
if (loop)
{
msg->fLoop = true;
msg->SetCmd( plSoundMsg::kSetLooping );
}
msg->Send( GetTarget()->GetAudioInterface()->GetKey() );
}
void pfGUIControlMod::IStopSound(UInt8 guiCtrlEvent)
{
if (guiCtrlEvent >= fSoundIndices.GetCount() || fSoundIndices[guiCtrlEvent] == 0)
return;
if (GetTarget() == nil || GetTarget()->GetAudioInterface() == nil )
return;
plSoundMsg *msg = TRACKED_NEW plSoundMsg;
msg->fIndex = fSoundIndices[guiCtrlEvent] - 1;
msg->SetCmd(plSoundMsg::kStop);
msg->Send(GetTarget()->GetAudioInterface()->GetKey());
}