/*==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 ); fTransparent = s->ReadBOOL(); 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->WriteBOOL( 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 /////////////////////////////////////////////////////////////////// bool 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 bool 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 bool 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 bool 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. bool 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, bool 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); bool 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. bool 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( bool e ) { if( e == fEnabled ) return; fEnabled = e; IUpdate(); } //// SetFocused ////////////////////////////////////////////////////////////// void pfGUIControlMod::SetFocused( bool e ) { if( e == fFocused ) return; fFocused = e; IUpdate(); } //// SetInteresting ////////////////////////////////////////////////////////// void pfGUIControlMod::SetInteresting( bool i ) { if( i == fInteresting ) return; fInteresting = i; IUpdate(); if ( fNotifyOnInteresting && fDialog && fDialog->GetHandler() ) fDialog->GetHandler()->OnInterestingEvent(this); } //// SetVisible ////////////////////////////////////////////////////////////// void pfGUIControlMod::SetVisible( bool 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 //////////////////////////////////////////////////// bool pfGUIControlMod::HandleKeyPress( wchar_t key, uint8_t modifiers ) { return false; } bool 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, bool 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, bool 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()); }