/*==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 .
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 "HeadSpin.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 "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_t size, uint8_t 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->ReadLE( &fTransparent );
delete [] fFontFace;
fFontFace = s->ReadSafeString();
s->ReadLE( &fFontSize );
s->ReadLE( &fFontFlags );
}
void pfGUIColorScheme::Write( hsStream *s )
{
fForeColor.Write( s );
fBackColor.Write( s );
fSelForeColor.Write( s );
fSelBackColor.Write( s );
s->WriteLE( fTransparent );
s->WriteSafeString( fFontFace );
s->WriteLE( fFontSize );
s->WriteLE( 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, float del, uint32_t 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 float GetVectorAngle( const hsPoint3 &basePt, const hsPoint3 &pointA, const hsPoint3 &pointB )
{
hsVector3 vectorA( &pointA, &basePt ), vectorB( &pointB, &basePt );
float dot = vectorA * vectorB;
hsVector3 cross = vectorA % vectorB;
float 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;
float *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 = new float[ 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 ] )
{
float 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
float 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 float tolerance = M_PI / 90.f;
if( angle > tolerance && angle < M_PI - 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 &outPoints )
{
const plDrawInterface* di = so->GetDrawInterface();
if( !di )
return;
// The following uses mf's spiffy plAccessGeometry/Spans stuff, which, in
// one uint16_t, kicksAss.
hsTArray 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 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( float x, float 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 );
if( rend )
{
plProfile_BeginLap(GUITime, this->GetKey()->GetUoid().GetObjectName().c_str());
// Only need it once
if( ISetUpDynTextMap( rend->Pipeline() ) )
plgDispatch::Dispatch()->UnRegisterForExactType( plRenderMsg::Index(), GetKey() );
plProfile_EndLap(GUITime, this->GetKey()->GetUoid().GetObjectName().c_str());
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() );
// Register for a render msg so we can leech the material when we finally
// have a pipeline to work with
plgDispatch::Dispatch()->RegisterForExactType( plRenderMsg::Index(), GetKey() );
}
else
fDynTextMap = nil;
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_t 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_t width = (uint16_t)(( bounds.GetMaxs().fX - bounds.GetMins().fX ) * scrnWidth);
uint16_t height = (uint16_t)(( 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_t extraW = width, extraH = height;
IGrowDTMDimsToDesiredSize( extraW, extraH );
extraW -= width;
extraH -= height;
fDynTextMap->Reset();
fDynTextMap->Create( width, height, HasFlag( kXparentBgnd ), extraW, extraH );
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() );
// 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(), new plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, pfGUIControlMod::kRefDynTextLayer ), plRefFlags::kActiveRef );
hsgResMgr::ResMgr()->AddViaNotify( dynText->GetKey(), 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 = 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->ReadLE( &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, new plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefDynTextLayer ), plRefFlags::kActiveRef );
mgr->ReadKeyNotifyMe( s, new plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefDynTextMap ), plRefFlags::kActiveRef );
}
else
{
fDynTextLayer = nil;
fDynTextMap = nil;
}
if( s->ReadBool() )
{
SetColorScheme( nil );
fColorScheme = new pfGUIColorScheme();
fColorScheme->Read( s );
}
// Read in our sound indices
uint8_t i, count = s->ReadByte();
if( count == 0 )
fSoundIndices.Reset();
else
{
fSoundIndices.SetCountAndZero( count );
for( i = 0; i < count; i++ )
fSoundIndices[ i ] = (int)s->ReadLE32();
}
if( HasFlag( kHasProxy ) )
mgr->ReadKeyNotifyMe( s, new plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefProxy ), plRefFlags::kActiveRef );
mgr->ReadKeyNotifyMe( s, 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->WriteLE( 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_t i;
for( i = 0; i < fSoundIndices.GetCount(); i++ )
s->WriteLE32( fSoundIndices[ i ] );
if( HasFlag( kHasProxy ) )
mgr->WriteKey( s, fProxy->GetKey() );
mgr->WriteKey( s, fSkin );
}
//// HandleKeyPress/Event ////////////////////////////////////////////////////
hsBool pfGUIControlMod::HandleKeyPress( wchar_t key, uint8_t modifiers )
{
return false;
}
hsBool pfGUIControlMod::HandleKeyEvent( pfGameGUIMgr::EventType event, plKeyDef key, uint8_t 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_t 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_t 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_t guiCtrlEvent, hsBool loop /* = false */ )
{
if( guiCtrlEvent >= fSoundIndices.GetCount() || fSoundIndices[ guiCtrlEvent ] == 0 )
return;
if( GetTarget() == nil || GetTarget()->GetAudioInterface() == nil )
return;
plSoundMsg *msg = 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_t guiCtrlEvent)
{
if (guiCtrlEvent >= fSoundIndices.GetCount() || fSoundIndices[guiCtrlEvent] == 0)
return;
if (GetTarget() == nil || GetTarget()->GetAudioInterface() == nil )
return;
plSoundMsg *msg = new plSoundMsg;
msg->fIndex = fSoundIndices[guiCtrlEvent] - 1;
msg->SetCmd(plSoundMsg::kStop);
msg->Send(GetTarget()->GetAudioInterface()->GetKey());
}