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
1021 lines
29 KiB
4 years ago
|
/*==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( ¢er );
|
||
|
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());
|
||
|
}
|