/*==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 "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 &outPoints ) { const plDrawInterface* di = so->GetDrawInterface(); if( !di ) return; // The following uses mf's spiffy plAccessGeometry/Spans stuff, which, in // one word, 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( 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()); }