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.
1500 lines
34 KiB
1500 lines
34 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
#include <windows.h>
|
||
|
#include "AnimationBrowser.h"
|
||
|
#include "hlfaceposer.h"
|
||
|
#include "ChoreoView.h"
|
||
|
#include "StudioModel.h"
|
||
|
#include "ViewerSettings.h"
|
||
|
#include "choreowidgetdrawhelper.h"
|
||
|
#include "faceposer_models.h"
|
||
|
#include "tabwindow.h"
|
||
|
#include "inputproperties.h"
|
||
|
#include "KeyValues.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "tier1/KeyValues.h"
|
||
|
#include "tier1/UtlBuffer.h"
|
||
|
|
||
|
#define MAX_THUMBNAILSIZE 256
|
||
|
#define MIN_THUMBNAILSIZE 64
|
||
|
#define THUMBNAIL_SIZE_STEP 4
|
||
|
#define DEFAULT_THUMBNAIL_SIZE 128
|
||
|
#define TOP_GAP 70
|
||
|
|
||
|
AnimationBrowser *g_pAnimationBrowserTool = 0;
|
||
|
extern double realtime;
|
||
|
|
||
|
void CreatePath( const char *pPath );
|
||
|
|
||
|
void CCustomAnim::LoadFromFile()
|
||
|
{
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( m_Handle, fn, sizeof( fn ) ) )
|
||
|
return;
|
||
|
|
||
|
KeyValues *kv = new KeyValues( "CustomAnimation" );
|
||
|
if ( kv->LoadFromFile( filesystem, fn, "MOD" ) )
|
||
|
{
|
||
|
for ( KeyValues *sub = kv->GetFirstSubKey(); sub ; sub = sub->GetNextKey() )
|
||
|
{
|
||
|
CUtlSymbol anim;
|
||
|
anim = sub->GetString();
|
||
|
|
||
|
m_Animations.AddToTail( anim );
|
||
|
}
|
||
|
}
|
||
|
kv->deleteThis();
|
||
|
}
|
||
|
|
||
|
void CCustomAnim::SaveToFile()
|
||
|
{
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( m_Handle, fn, sizeof( fn ) ) )
|
||
|
return;
|
||
|
|
||
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
||
|
|
||
|
buf.Printf( "\"%s\"\n", m_ShortName.String() );
|
||
|
buf.Printf( "{\n" );
|
||
|
for ( int i = 0; i < m_Animations.Count(); ++i )
|
||
|
{
|
||
|
buf.Printf( "\t\"item%d\" \"%s\"\n", i + 1, m_Animations[ i ].String() );
|
||
|
}
|
||
|
buf.Printf( "}\n" );
|
||
|
|
||
|
CreatePath( fn );
|
||
|
filesystem->WriteFile( fn, "MOD", buf );
|
||
|
}
|
||
|
|
||
|
bool CCustomAnim::HasAnimation( char const *search )
|
||
|
{
|
||
|
CUtlSymbol searchSym;
|
||
|
searchSym = search;
|
||
|
if ( m_Animations.Find( searchSym ) != m_Animations.InvalidIndex() )
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CAnimBrowserTab : public CTabWindow
|
||
|
{
|
||
|
typedef CTabWindow BaseClass;
|
||
|
|
||
|
public:
|
||
|
|
||
|
|
||
|
CAnimBrowserTab( AnimationBrowser *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) :
|
||
|
CTabWindow( (mxWindow *)parent, x, y, w, h, id, style )
|
||
|
{
|
||
|
// SetInverted( true );
|
||
|
}
|
||
|
|
||
|
void Init( void )
|
||
|
{
|
||
|
add( "all" );
|
||
|
add( "gestures" );
|
||
|
add( "postures" );
|
||
|
add( "search results" );
|
||
|
}
|
||
|
|
||
|
virtual void ShowRightClickMenu( int mx, int my )
|
||
|
{
|
||
|
POINT pt;
|
||
|
GetCursorPos( &pt );
|
||
|
ScreenToClient( (HWND)getHandle(), &pt );
|
||
|
|
||
|
// New scene, edit comments
|
||
|
mxPopupMenu *pop = new mxPopupMenu();
|
||
|
|
||
|
pop->add ("&New Group...", IDC_AB_CREATE_CUSTOM );
|
||
|
|
||
|
mxPopupMenu *sub = NULL;
|
||
|
for ( int i = 0; i < m_CustomGroups.Count(); ++i )
|
||
|
{
|
||
|
if ( !sub )
|
||
|
{
|
||
|
sub = new mxPopupMenu();
|
||
|
}
|
||
|
sub->add( va( "%s", m_CustomGroups[ i ].String() ), IDC_AB_DELETEGROUPSTART + i );
|
||
|
}
|
||
|
if ( sub )
|
||
|
{
|
||
|
pop->addMenu( "Delete Group", sub );
|
||
|
}
|
||
|
|
||
|
pop->addSeparator();
|
||
|
|
||
|
sub = new mxPopupMenu();
|
||
|
for ( int i = 0; i < m_CustomGroups.Count(); ++i )
|
||
|
{
|
||
|
sub->add( va( "%s", m_CustomGroups[ i ].String() ), IDC_AB_RENAMEGROUPSTART + i );
|
||
|
}
|
||
|
|
||
|
pop->addMenu( "Rename Group", sub );
|
||
|
|
||
|
pop->popup( getParent(), pt.x, pt.y );
|
||
|
}
|
||
|
|
||
|
void UpdateCustomTabs( CUtlVector< CCustomAnim * >& list )
|
||
|
{
|
||
|
m_CustomGroups.Purge();
|
||
|
|
||
|
while ( getItemCount() > AnimationBrowser::FILTER_FIRST_CUSTOM )
|
||
|
{
|
||
|
remove( getItemCount() - 1 );
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < list.Count(); ++i )
|
||
|
{
|
||
|
const CCustomAnim *anim = list[ i ];
|
||
|
add( anim->m_ShortName.String() );
|
||
|
m_CustomGroups.AddToTail( anim->m_ShortName );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
|
||
|
CUtlVector< CUtlSymbol > m_CustomGroups;
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
AnimationBrowser::AnimationBrowser( mxWindow *parent, int id /*=0*/ )
|
||
|
: IFacePoserToolWindow( "AnimationBrowser", "Animations" ),
|
||
|
mxWindow( parent, 0, 0, 0, 0, "AnimationBrowser", id )
|
||
|
{
|
||
|
setId( id );
|
||
|
|
||
|
m_nTopOffset = 0;
|
||
|
slScrollbar = new mxScrollbar( this, 0, 0, 18, 100, IDC_AB_TRAYSCROLL, mxScrollbar::Vertical );
|
||
|
|
||
|
m_nLastNumAnimations = -1;
|
||
|
|
||
|
m_nGranularity = 10;
|
||
|
|
||
|
m_nCurCell = -1;
|
||
|
|
||
|
m_nClickedCell = -1;
|
||
|
|
||
|
m_nGap = 4;
|
||
|
m_nDescriptionHeight = 34;
|
||
|
m_nSnapshotWidth = g_viewerSettings.thumbnailsizeanim;
|
||
|
m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth );
|
||
|
m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth );
|
||
|
|
||
|
g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth;
|
||
|
|
||
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight;
|
||
|
|
||
|
m_bDragging = false;
|
||
|
m_nDragCell = -1;
|
||
|
|
||
|
m_szSearchString[0]=0;
|
||
|
|
||
|
m_pFilterTab = new CAnimBrowserTab( this, 5, 5, 240, 20, IDC_AB_FILTERTAB );
|
||
|
m_pFilterTab->Init();
|
||
|
|
||
|
m_pSearchEntry = new mxLineEdit( this, 0, 0, 0, 0, "" );
|
||
|
|
||
|
m_pThumbnailIncreaseButton = new mxButton( this, 0, 0, 18, 18, "+", IDC_AB_THUMBNAIL_INCREASE );
|
||
|
m_pThumbnailDecreaseButton = new mxButton( this, 0, 0, 18, 18, "-", IDC_AB_THUMBNAIL_DECREASE );
|
||
|
m_nCurFilter = FILTER_NONE;
|
||
|
|
||
|
m_flDragTime = 0.0f;
|
||
|
|
||
|
OnFilter();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Output : AnimationBrowser::~AnimationBrowser
|
||
|
//-----------------------------------------------------------------------------
|
||
|
AnimationBrowser::~AnimationBrowser ( void )
|
||
|
{
|
||
|
g_pAnimationBrowserTool = NULL;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::Shutdown()
|
||
|
{
|
||
|
PurgeCustom();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : cellsize -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::SetCellSize( int cellsize )
|
||
|
{
|
||
|
m_nSnapshotWidth = cellsize;
|
||
|
m_nSnapshotHeight = cellsize + m_nDescriptionHeight;
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::Deselect( void )
|
||
|
{
|
||
|
m_nCurCell = -1;
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : exp -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::Select( int sequence )
|
||
|
{
|
||
|
m_nCurCell = sequence;
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Output : int
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int AnimationBrowser::ComputePixelsNeeded( void )
|
||
|
{
|
||
|
int seqcount = GetSequenceCount();
|
||
|
|
||
|
if ( !seqcount )
|
||
|
return 100;
|
||
|
|
||
|
// Remove scroll bar
|
||
|
int w = this->w2() - 16;
|
||
|
|
||
|
int colsperrow;
|
||
|
|
||
|
colsperrow = ( w - m_nGap ) / ( m_nSnapshotWidth + m_nGap );
|
||
|
// At least one
|
||
|
colsperrow = max( 1, colsperrow );
|
||
|
|
||
|
int rowsneeded = ( ( seqcount + colsperrow - 1 ) / colsperrow );
|
||
|
return rowsneeded * ( m_nSnapshotHeight + m_nGap ) + m_nGap + TOP_GAP + GetCaptionHeight();
|
||
|
}
|
||
|
|
||
|
bool AnimationBrowser::ComputeRect( int cell, int& rcx, int& rcy, int& rcw, int& rch )
|
||
|
{
|
||
|
// Remove scroll bar
|
||
|
int w = this->w2() - 16;
|
||
|
|
||
|
int colsperrow;
|
||
|
|
||
|
colsperrow = ( w - m_nGap ) / ( m_nSnapshotWidth + m_nGap );
|
||
|
// At least one
|
||
|
colsperrow = max( 1, colsperrow );
|
||
|
|
||
|
int row, col;
|
||
|
|
||
|
row = cell / colsperrow;
|
||
|
col = cell % colsperrow;
|
||
|
|
||
|
// don't allow partial columns
|
||
|
|
||
|
rcx = m_nGap + col * ( m_nSnapshotWidth + m_nGap );
|
||
|
rcy = GetCaptionHeight() + TOP_GAP + ( -m_nTopOffset * m_nGranularity ) + m_nGap + row * ( m_nSnapshotHeight + m_nGap );
|
||
|
|
||
|
// Starts off screen
|
||
|
if ( rcx < 0 )
|
||
|
return false;
|
||
|
|
||
|
// Ends off screen
|
||
|
if ( rcx + m_nSnapshotWidth + m_nGap > this->w2() )
|
||
|
return false;
|
||
|
|
||
|
// Allow partial in y direction
|
||
|
if ( rcy > this->h2() )
|
||
|
return false;
|
||
|
|
||
|
if ( rcy + m_nSnapshotHeight + m_nGap < 0 )
|
||
|
return false;
|
||
|
|
||
|
// Some portion is onscreen
|
||
|
rcw = m_nSnapshotWidth;
|
||
|
rch = m_nSnapshotHeight;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::DrawSequenceFocusRect( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, COLORREF clr )
|
||
|
{
|
||
|
helper.DrawOutlinedRect( clr, PS_SOLID, 4, x, y, x + w, y + h );
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::DrawSequenceDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, int sequence, mstudioseqdesc_t &seqdesc )
|
||
|
{
|
||
|
int textheight = 15;
|
||
|
|
||
|
RECT textRect;
|
||
|
textRect.left = x + 5;
|
||
|
textRect.top = y + h - 2 * textheight - 12;
|
||
|
textRect.right = x + w - 10;
|
||
|
textRect.bottom = y + h - 12;
|
||
|
|
||
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 63, 63, 63 ), textRect, "%s", seqdesc.pszLabel() );
|
||
|
|
||
|
StudioModel *mdl = models->GetActiveStudioModel();
|
||
|
if ( !mdl )
|
||
|
return;
|
||
|
|
||
|
OffsetRect( &textRect, 0, textheight );
|
||
|
|
||
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 63, 63, 63 ), textRect, "%.2f seconds",
|
||
|
mdl->GetDuration( sequence ) );
|
||
|
|
||
|
textRect.top = y + h - 4 * textheight - 1;
|
||
|
textRect.bottom = textRect.top + textheight;
|
||
|
|
||
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 50, 200, 255 ), textRect, "frames %i",
|
||
|
mdl->GetNumFrames( sequence ) );
|
||
|
|
||
|
OffsetRect( &textRect, 0, textheight - 4 );
|
||
|
|
||
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 50, 200, 255 ), textRect, "fps %.2f",
|
||
|
(float)mdl->GetFPS( sequence ) );
|
||
|
|
||
|
}
|
||
|
|
||
|
bool AnimationBrowser::PaintBackground( void )
|
||
|
{
|
||
|
redraw();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::DrawThumbNail( int sequence, CChoreoWidgetDrawHelper& helper, int rcx, int rcy, int rcw, int rch )
|
||
|
{
|
||
|
HDC dc = helper.GrabDC();
|
||
|
|
||
|
helper.DrawFilledRect( GetSysColor( COLOR_BTNFACE ), rcx, rcy, rcw + rcx, rch + rcy );
|
||
|
|
||
|
mstudioseqdesc_t *pseqdesc = GetSeqDesc( sequence );
|
||
|
if ( !pseqdesc )
|
||
|
return;
|
||
|
|
||
|
mxbitmapdata_t *bm = models->GetBitmapForSequence( models->GetActiveModelIndex(), TranslateSequenceNumber( sequence ) );
|
||
|
if ( bm && bm->valid )
|
||
|
{
|
||
|
DrawBitmapToDC( dc, rcx, rcy, rcw, rch - m_nDescriptionHeight, *bm );
|
||
|
helper.DrawOutlinedRect( RGB( 127, 127, 127 ), PS_SOLID, 1, rcx, rcy, rcx + rcw, rcy + rch - m_nDescriptionHeight );
|
||
|
}
|
||
|
|
||
|
DrawSequenceDescription( helper, rcx, rcy, rcw, rch, TranslateSequenceNumber( sequence ), *pseqdesc );
|
||
|
|
||
|
if ( sequence == m_nCurCell )
|
||
|
{
|
||
|
DrawSequenceFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, RGB( 255, 100, 63 ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::redraw()
|
||
|
{
|
||
|
if ( !ToolCanDraw() )
|
||
|
return;
|
||
|
|
||
|
bool updateSelection = false;
|
||
|
|
||
|
int curcount = GetSequenceCount();
|
||
|
if ( curcount != m_nLastNumAnimations )
|
||
|
{
|
||
|
m_nTopOffset = 0;
|
||
|
RepositionSlider();
|
||
|
m_nLastNumAnimations = curcount;
|
||
|
updateSelection = true;
|
||
|
}
|
||
|
|
||
|
CChoreoWidgetDrawHelper helper( this, GetSysColor( COLOR_BTNFACE ) );
|
||
|
HandleToolRedraw( helper );
|
||
|
|
||
|
int w, h;
|
||
|
w = w2();
|
||
|
h = h2();
|
||
|
|
||
|
RECT clipRect;
|
||
|
helper.GetClientRect( clipRect );
|
||
|
|
||
|
clipRect.top += TOP_GAP + GetCaptionHeight();
|
||
|
|
||
|
helper.StartClipping( clipRect );
|
||
|
|
||
|
int rcx, rcy, rcw, rch;
|
||
|
|
||
|
EnableStickySnapshotMode( );
|
||
|
|
||
|
int c = curcount;
|
||
|
for ( int i = 0; i < c; i++ )
|
||
|
{
|
||
|
if ( !ComputeRect( i, rcx, rcy, rcw, rch ) )
|
||
|
{
|
||
|
// Cache in .bmp no matter what
|
||
|
// This was too slow, so turning it back off
|
||
|
//models->GetBitmapForSequence( models->GetActiveModelIndex(), TranslateSequenceNumber( i ) );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
DrawThumbNail( i, helper, rcx, rcy, rcw, rch );
|
||
|
}
|
||
|
|
||
|
DisableStickySnapshotMode( );
|
||
|
|
||
|
helper.StopClipping();
|
||
|
|
||
|
RECT rcText;
|
||
|
rcText.right = w2();
|
||
|
rcText.left = rcText.right - 120;
|
||
|
rcText.top = 8;
|
||
|
rcText.bottom = rcText.top + 15;
|
||
|
|
||
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 63, 63, 63 ), rcText, "%i sequences",
|
||
|
curcount );
|
||
|
|
||
|
}
|
||
|
|
||
|
int AnimationBrowser::GetCellUnderPosition( int x, int y )
|
||
|
{
|
||
|
int count = GetSequenceCount();
|
||
|
if ( !count )
|
||
|
return -1;
|
||
|
|
||
|
int rcx, rcy, rcw, rch;
|
||
|
int c = 0;
|
||
|
while ( c < count )
|
||
|
{
|
||
|
if ( !ComputeRect( c, rcx, rcy, rcw, rch ) )
|
||
|
{
|
||
|
c++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( x >= rcx && x <= rcx + rcw &&
|
||
|
y >= rcy && y <= rcy + rch )
|
||
|
{
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
c++;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::RepositionSlider( void )
|
||
|
{
|
||
|
int trueh = h2() - GetCaptionHeight();
|
||
|
|
||
|
int heightpixels = trueh / m_nGranularity;
|
||
|
int rangepixels = ComputePixelsNeeded() / m_nGranularity;
|
||
|
|
||
|
if ( rangepixels < heightpixels )
|
||
|
{
|
||
|
m_nTopOffset = 0;
|
||
|
slScrollbar->setVisible( false );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
slScrollbar->setVisible( true );
|
||
|
}
|
||
|
|
||
|
slScrollbar->setBounds( w2() - 16, GetCaptionHeight() + TOP_GAP, 16, trueh - TOP_GAP );
|
||
|
|
||
|
m_nTopOffset = max( 0, m_nTopOffset );
|
||
|
m_nTopOffset = min( rangepixels, m_nTopOffset );
|
||
|
|
||
|
slScrollbar->setRange( 0, rangepixels );
|
||
|
slScrollbar->setValue( m_nTopOffset );
|
||
|
slScrollbar->setPagesize( heightpixels );
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::SetClickedCell( int cell )
|
||
|
{
|
||
|
m_nClickedCell = cell;
|
||
|
Select( cell );
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::ShowRightClickMenu( int mx, int my )
|
||
|
{
|
||
|
mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell );
|
||
|
if ( !pseqdesc )
|
||
|
return;
|
||
|
|
||
|
mxPopupMenu *pop = new mxPopupMenu();
|
||
|
Assert( pop );
|
||
|
|
||
|
pop->add( va( "New Group..." ), IDC_AB_CREATE_CUSTOM );
|
||
|
|
||
|
if ( m_CustomAnimationTabs.Count() > 0 )
|
||
|
{
|
||
|
mxPopupMenu *ca = new mxPopupMenu();
|
||
|
Assert( ca );
|
||
|
|
||
|
for ( int i = 0; i < m_CustomAnimationTabs.Count() ; ++i )
|
||
|
{
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ i ];
|
||
|
ca->add( va( "%s", anim->m_ShortName.String() ), IDC_AB_ADDTOGROUPSTART + i );
|
||
|
}
|
||
|
|
||
|
pop->addMenu( "Add to Group", ca );
|
||
|
|
||
|
ca = new mxPopupMenu();
|
||
|
|
||
|
bool useMenu = false;
|
||
|
for ( int i = 0; i < m_CustomAnimationTabs.Count() ; ++i )
|
||
|
{
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ i ];
|
||
|
if ( anim->HasAnimation( pseqdesc->pszLabel() ) )
|
||
|
{
|
||
|
ca->add( va( "%s", anim->m_ShortName.String() ), IDC_AB_REMOVEFROMGROUPSTART + i );
|
||
|
useMenu = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( useMenu )
|
||
|
{
|
||
|
pop->addMenu( "Remove from Group", ca );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete ca;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pop->addSeparator();
|
||
|
|
||
|
pop->add( va( "Re-create thumbnail for '%s'", pseqdesc->pszLabel() ), IDC_AB_CONTEXT_CREATEBITMAP );
|
||
|
pop->add( va( "Re-create all thumbnails" ), IDC_AB_CONTEXT_CREATEALLBITMAPS );
|
||
|
|
||
|
pop->popup( this, mx, my );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::DrawFocusRect( void )
|
||
|
{
|
||
|
HDC dc = GetDC( NULL );
|
||
|
|
||
|
::DrawFocusRect( dc, &m_rcFocus );
|
||
|
|
||
|
ReleaseDC( NULL, dc );
|
||
|
}
|
||
|
|
||
|
static bool IsWindowOrChild( mxWindow *parent, HWND test )
|
||
|
{
|
||
|
HWND parentHwnd = (HWND)parent->getHandle();
|
||
|
if ( test == parentHwnd ||
|
||
|
IsChild( parentHwnd, test ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int AnimationBrowser::handleEvent (mxEvent *event)
|
||
|
{
|
||
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
|
||
|
|
||
|
int iret = 0;
|
||
|
|
||
|
if ( HandleToolEvent( event ) )
|
||
|
{
|
||
|
return iret;
|
||
|
}
|
||
|
|
||
|
switch ( event->event )
|
||
|
{
|
||
|
case mxEvent::Action:
|
||
|
{
|
||
|
iret = 1;
|
||
|
switch ( event->action )
|
||
|
{
|
||
|
default:
|
||
|
if ( event->action >= IDC_AB_ADDTOGROUPSTART && event->action <= IDC_AB_ADDTOGROUPEND )
|
||
|
{
|
||
|
int index = event->action - IDC_AB_ADDTOGROUPSTART;
|
||
|
mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell );
|
||
|
if ( pseqdesc )
|
||
|
{
|
||
|
AddAnimationToCustomFile( index, pseqdesc->pszLabel() );
|
||
|
}
|
||
|
}
|
||
|
else if ( event->action >= IDC_AB_REMOVEFROMGROUPSTART && event->action <= IDC_AB_REMOVEFROMGROUPEND )
|
||
|
{
|
||
|
int index = event->action - IDC_AB_REMOVEFROMGROUPSTART;
|
||
|
mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell );
|
||
|
if ( pseqdesc )
|
||
|
{
|
||
|
RemoveAnimationFromCustomFile( index, pseqdesc->pszLabel() );
|
||
|
}
|
||
|
}
|
||
|
else if ( event->action >= IDC_AB_DELETEGROUPSTART && event->action <= IDC_AB_DELETEGROUPEND )
|
||
|
{
|
||
|
int index = event->action - IDC_AB_DELETEGROUPSTART;
|
||
|
DeleteCustomFile( index );
|
||
|
}
|
||
|
else if ( event->action >= IDC_AB_RENAMEGROUPSTART && event->action <= IDC_AB_RENAMEGROUPEND )
|
||
|
{
|
||
|
int index = event->action - IDC_AB_RENAMEGROUPSTART;
|
||
|
RenameCustomFile( index );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
iret = 0;
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_CREATE_CUSTOM:
|
||
|
{
|
||
|
OnAddCustomAnimationFilter();
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_FILTERTAB:
|
||
|
{
|
||
|
int index = m_pFilterTab->getSelectedIndex();
|
||
|
if ( index >= 0 )
|
||
|
{
|
||
|
m_nCurFilter = index;
|
||
|
OnFilter();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_TRAYSCROLL:
|
||
|
{
|
||
|
if (event->modifiers == SB_THUMBTRACK)
|
||
|
{
|
||
|
int offset = event->height;
|
||
|
|
||
|
slScrollbar->setValue( offset );
|
||
|
|
||
|
m_nTopOffset = offset;
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
else if ( event->modifiers == SB_PAGEUP )
|
||
|
{
|
||
|
int offset = slScrollbar->getValue();
|
||
|
|
||
|
offset -= m_nGranularity;
|
||
|
offset = max( offset, slScrollbar->getMinValue() );
|
||
|
|
||
|
slScrollbar->setValue( offset );
|
||
|
InvalidateRect( (HWND)slScrollbar->getHandle(), NULL, TRUE );
|
||
|
|
||
|
m_nTopOffset = offset;
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
else if ( event->modifiers == SB_PAGEDOWN )
|
||
|
{
|
||
|
int offset = slScrollbar->getValue();
|
||
|
|
||
|
offset += m_nGranularity;
|
||
|
offset = min( offset, slScrollbar->getMaxValue() );
|
||
|
|
||
|
slScrollbar->setValue( offset );
|
||
|
InvalidateRect( (HWND)slScrollbar->getHandle(), NULL, TRUE );
|
||
|
|
||
|
m_nTopOffset = offset;
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_THUMBNAIL_INCREASE:
|
||
|
{
|
||
|
ThumbnailIncrease();
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_THUMBNAIL_DECREASE:
|
||
|
{
|
||
|
ThumbnailDecrease();
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_CONTEXT_CREATEBITMAP:
|
||
|
{
|
||
|
int current_model = models->GetActiveModelIndex();
|
||
|
|
||
|
if ( m_nClickedCell >= 0 )
|
||
|
{
|
||
|
models->RecreateAnimationBitmap( current_model, TranslateSequenceNumber( m_nClickedCell ) );
|
||
|
}
|
||
|
redraw();
|
||
|
}
|
||
|
break;
|
||
|
case IDC_AB_CONTEXT_CREATEALLBITMAPS:
|
||
|
{
|
||
|
int current_model = models->GetActiveModelIndex();
|
||
|
models->RecreateAllAnimationBitmaps( current_model );
|
||
|
redraw();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case mxEvent::MouseDown:
|
||
|
{
|
||
|
if ( !( event->buttons & mxEvent::MouseRightButton ) )
|
||
|
{
|
||
|
// Figure out cell #
|
||
|
int cell = GetCellUnderPosition( event->x, event->y );
|
||
|
if ( cell >= 0 && cell < GetSequenceCount() )
|
||
|
{
|
||
|
int cx, cy, cw, ch;
|
||
|
if ( ComputeRect( cell, cx, cy, cw, ch ) )
|
||
|
{
|
||
|
m_flDragTime = realtime;
|
||
|
m_bDragging = true;
|
||
|
m_nDragCell = cell;
|
||
|
|
||
|
m_nXStart = (short)event->x;
|
||
|
m_nYStart = (short)event->y;
|
||
|
|
||
|
m_rcFocus.left = cx;
|
||
|
m_rcFocus.top = cy;
|
||
|
m_rcFocus.right = cx + cw;
|
||
|
m_rcFocus.bottom = cy + ch - m_nDescriptionHeight;
|
||
|
|
||
|
POINT pt;
|
||
|
pt.x = pt.y = 0;
|
||
|
ClientToScreen( (HWND)getHandle(), &pt );
|
||
|
|
||
|
OffsetRect( &m_rcFocus, pt.x, pt.y );
|
||
|
|
||
|
m_rcOrig = m_rcFocus;
|
||
|
|
||
|
DrawFocusRect();
|
||
|
|
||
|
Select( cell );
|
||
|
m_nClickedCell = cell;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Deselect();
|
||
|
redraw();
|
||
|
}
|
||
|
}
|
||
|
iret = 1;
|
||
|
}
|
||
|
break;
|
||
|
case mxEvent::MouseDrag:
|
||
|
{
|
||
|
if ( m_bDragging )
|
||
|
{
|
||
|
// Draw drag line of some kind
|
||
|
DrawFocusRect();
|
||
|
|
||
|
// update pos
|
||
|
m_rcFocus = m_rcOrig;
|
||
|
OffsetRect( &m_rcFocus, ( (short)event->x - m_nXStart ),
|
||
|
( (short)event->y - m_nYStart ) );
|
||
|
|
||
|
DrawFocusRect();
|
||
|
}
|
||
|
iret = 1;
|
||
|
}
|
||
|
break;
|
||
|
case mxEvent::MouseUp:
|
||
|
{
|
||
|
iret = 1;
|
||
|
|
||
|
if ( event->buttons & mxEvent::MouseRightButton )
|
||
|
{
|
||
|
SetClickedCell( GetCellUnderPosition( (short)event->x, (short)event->y ) );
|
||
|
ShowRightClickMenu( (short)event->x, (short)event->y );
|
||
|
return iret;
|
||
|
}
|
||
|
|
||
|
if ( m_bDragging && m_nClickedCell >= 0 )
|
||
|
{
|
||
|
mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nClickedCell );
|
||
|
|
||
|
DrawFocusRect();
|
||
|
m_bDragging = false;
|
||
|
// See if we let go on top of the choreo view
|
||
|
|
||
|
// Convert x, y to screen space
|
||
|
POINT pt;
|
||
|
pt.x = (short)event->x;
|
||
|
pt.y = (short)event->y;
|
||
|
ClientToScreen( (HWND)getHandle(), &pt );
|
||
|
|
||
|
HWND maybeTool = WindowFromPoint( pt );
|
||
|
|
||
|
// Now tell choreo view
|
||
|
if ( maybeTool && pseqdesc )
|
||
|
{
|
||
|
if ( IsWindowOrChild( g_pChoreoView, maybeTool ) )
|
||
|
{
|
||
|
if ( g_pChoreoView->CreateAnimationEvent( pt.x, pt.y, pseqdesc->pszLabel() ) )
|
||
|
{
|
||
|
return iret;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case mxEvent::Size:
|
||
|
{
|
||
|
int width = w2();
|
||
|
// int height = h2();
|
||
|
|
||
|
int ch = GetCaptionHeight() + 10;
|
||
|
|
||
|
m_pSearchEntry->setBounds( 5, ch, width - 10 - 170, 18 );
|
||
|
|
||
|
m_pThumbnailIncreaseButton->setBounds( width - 40, 4 + ch, 16, 16 );
|
||
|
m_pThumbnailDecreaseButton->setBounds( width - 20, 4 + ch, 16, 16 );
|
||
|
|
||
|
m_pFilterTab->setBounds( 5, ch + 20, width - 10, 20 );
|
||
|
|
||
|
m_nTopOffset = 0;
|
||
|
RepositionSlider();
|
||
|
|
||
|
redraw();
|
||
|
iret = 1;
|
||
|
}
|
||
|
break;
|
||
|
case mxEvent::MouseWheeled:
|
||
|
{
|
||
|
// Figure out cell #
|
||
|
POINT pt;
|
||
|
|
||
|
pt.x = event->x;
|
||
|
pt.y = event->y;
|
||
|
|
||
|
ScreenToClient( (HWND)getHandle(), &pt );
|
||
|
|
||
|
if ( event->height < 0 )
|
||
|
{
|
||
|
m_nTopOffset = min( m_nTopOffset + 10, slScrollbar->getMaxValue() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_nTopOffset = max( m_nTopOffset - 10, 0 );
|
||
|
}
|
||
|
RepositionSlider();
|
||
|
redraw();
|
||
|
iret = 1;
|
||
|
}
|
||
|
break;
|
||
|
case mxEvent::KeyDown:
|
||
|
case mxEvent::KeyUp:
|
||
|
{
|
||
|
bool search = false;
|
||
|
// int n = 3;
|
||
|
if ( event->key == VK_ESCAPE && m_szSearchString[ 0 ] )
|
||
|
{
|
||
|
m_pSearchEntry->setLabel( "" );
|
||
|
m_szSearchString[ 0 ] = 0;
|
||
|
m_pFilterTab->select( FILTER_NONE );
|
||
|
m_nCurFilter = FILTER_NONE;
|
||
|
OnFilter();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Text changed?
|
||
|
char sz[ 512 ];
|
||
|
m_pSearchEntry->getText( sz, sizeof( sz ) );
|
||
|
if ( Q_stricmp( sz, m_szSearchString ) )
|
||
|
{
|
||
|
Q_strncpy( m_szSearchString, sz, sizeof( m_szSearchString ) );
|
||
|
search = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( search )
|
||
|
{
|
||
|
if ( Q_strlen( m_szSearchString ) > 0 )
|
||
|
{
|
||
|
m_pFilterTab->select( FILTER_STRING );
|
||
|
m_nCurFilter = FILTER_STRING;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pFilterTab->select( FILTER_NONE );
|
||
|
m_nCurFilter = FILTER_NONE;
|
||
|
}
|
||
|
OnFilter();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
if ( iret )
|
||
|
{
|
||
|
SetActiveTool( this );
|
||
|
}
|
||
|
return iret;
|
||
|
}
|
||
|
|
||
|
// HACK HACK: VS2005 is generating bogus code for this little operation in the function below...
|
||
|
#pragma optimize( "g", off )
|
||
|
float roundcycle( float cycle )
|
||
|
{
|
||
|
int rounded = (int)(cycle);
|
||
|
float cy2 = cycle - rounded;
|
||
|
return cy2;
|
||
|
}
|
||
|
#pragma optimize( "", on )
|
||
|
|
||
|
void AnimationBrowser::Think( float dt )
|
||
|
{
|
||
|
if ( !m_bDragging )
|
||
|
return;
|
||
|
|
||
|
if ( m_nClickedCell < 0 )
|
||
|
return;
|
||
|
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( model )
|
||
|
{
|
||
|
int iSequence = TranslateSequenceNumber( m_nClickedCell );
|
||
|
float dur = model->GetDuration( iSequence );
|
||
|
if ( dur > 0.0f )
|
||
|
{
|
||
|
float elapsed = (float)realtime - m_flDragTime;
|
||
|
|
||
|
float flFrameRate = 0.0f;
|
||
|
float flGroundSpeed = 0.0f;
|
||
|
model->GetSequenceInfo( iSequence, &flFrameRate, &flGroundSpeed );
|
||
|
|
||
|
float cycle = roundcycle( elapsed * flFrameRate );
|
||
|
|
||
|
// This should be the only thing on the model!!!
|
||
|
model->ClearAnimationLayers();
|
||
|
|
||
|
// FIXME: shouldn't sequences always be lower priority than gestures?
|
||
|
int iLayer = model->GetNewAnimationLayer( 0 );
|
||
|
model->SetOverlaySequence( iLayer, iSequence, 1.0f );
|
||
|
model->SetOverlayRate( iLayer, cycle, 0.0f );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::ThumbnailIncrease( void )
|
||
|
{
|
||
|
if ( m_nSnapshotWidth + THUMBNAIL_SIZE_STEP <= MAX_THUMBNAILSIZE )
|
||
|
{
|
||
|
m_nSnapshotWidth += THUMBNAIL_SIZE_STEP;
|
||
|
g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth;
|
||
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight;
|
||
|
|
||
|
Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth );
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::ThumbnailDecrease( void )
|
||
|
{
|
||
|
if ( m_nSnapshotWidth - THUMBNAIL_SIZE_STEP >= MIN_THUMBNAILSIZE )
|
||
|
{
|
||
|
m_nSnapshotWidth -= THUMBNAIL_SIZE_STEP;
|
||
|
g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth;
|
||
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight;
|
||
|
|
||
|
Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth );
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AnimationBrowser::RestoreThumbnailSize( void )
|
||
|
{
|
||
|
m_nSnapshotWidth = g_viewerSettings.thumbnailsizeanim;
|
||
|
m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth );
|
||
|
m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth );
|
||
|
|
||
|
g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth;
|
||
|
|
||
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight;
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::ReloadBitmaps( void )
|
||
|
{
|
||
|
Assert( 0 );
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : *model -
|
||
|
// sequence -
|
||
|
// Output : static bool
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static bool IsTypeOfSequence( StudioModel *model, int sequence, char const *typestring )
|
||
|
{
|
||
|
bool match = false;
|
||
|
|
||
|
if ( !model->GetStudioHdr() )
|
||
|
return match;
|
||
|
|
||
|
KeyValues *seqKeyValues = new KeyValues("");
|
||
|
if ( seqKeyValues->LoadFromBuffer( model->GetFileName( ), model->GetKeyValueText( sequence ) ) )
|
||
|
{
|
||
|
// Do we have a build point section?
|
||
|
KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer");
|
||
|
if ( pkvAllFaceposer )
|
||
|
{
|
||
|
char const *t = pkvAllFaceposer->GetString( "type", "" );
|
||
|
if ( t && !Q_stricmp( t, typestring ) )
|
||
|
{
|
||
|
match = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
seqKeyValues->deleteThis();
|
||
|
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
bool AnimationBrowser::SequencePassesFilter( StudioModel *model, int sequence, mstudioseqdesc_t &seqdesc )
|
||
|
{
|
||
|
if (model->IsHidden( sequence ))
|
||
|
return false;
|
||
|
|
||
|
switch ( m_nCurFilter )
|
||
|
{
|
||
|
default:
|
||
|
{
|
||
|
|
||
|
int offset = m_nCurFilter - FILTER_FIRST_CUSTOM;
|
||
|
if ( offset >= 0 && offset < m_CustomAnimationTabs.Count() )
|
||
|
{
|
||
|
// Find the name
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ offset ];
|
||
|
return anim->HasAnimation( seqdesc.pszLabel() );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
case FILTER_NONE:
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
case FILTER_GESTURES:
|
||
|
if ( IsTypeOfSequence( model, sequence, "gesture" ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
case FILTER_POSTURES:
|
||
|
if ( IsTypeOfSequence( model, sequence, "posture" ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
case FILTER_STRING:
|
||
|
if ( Q_stristr( seqdesc.pszLabel(), m_szSearchString ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::OnFilter()
|
||
|
{
|
||
|
m_Filtered.RemoveAll();
|
||
|
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( !model )
|
||
|
return;
|
||
|
|
||
|
CStudioHdr *hdr = model->GetStudioHdr();
|
||
|
if ( !hdr )
|
||
|
return;
|
||
|
|
||
|
int count = hdr->GetNumSeq();
|
||
|
|
||
|
for ( int i = 0; i < count; i++ )
|
||
|
{
|
||
|
mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( i );
|
||
|
|
||
|
// if it passes the filter, add it
|
||
|
if ( SequencePassesFilter( model, i, seqdesc ) )
|
||
|
{
|
||
|
m_Filtered.AddToTail( i );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
redraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Output : int
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int AnimationBrowser::GetSequenceCount()
|
||
|
{
|
||
|
return m_Filtered.Count();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : index -
|
||
|
// Output : mstudioseqdesc_t
|
||
|
//-----------------------------------------------------------------------------
|
||
|
mstudioseqdesc_t *AnimationBrowser::GetSeqDesc( int index )
|
||
|
{
|
||
|
CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr();
|
||
|
if ( !hdr )
|
||
|
return NULL;
|
||
|
|
||
|
index = TranslateSequenceNumber( index );
|
||
|
|
||
|
if ( index < 0 || index >= hdr->GetNumSeq() )
|
||
|
return NULL;
|
||
|
|
||
|
return &hdr->pSeqdesc( index );
|
||
|
}
|
||
|
|
||
|
int AnimationBrowser::TranslateSequenceNumber( int index )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_Filtered.Count() )
|
||
|
return NULL;
|
||
|
|
||
|
// Lookup the true index
|
||
|
index = m_Filtered[ index ];
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::FindCustomFiles( char const *subdir, CUtlVector< FileNameHandle_t >& files )
|
||
|
{
|
||
|
char search[ 512 ];
|
||
|
Q_snprintf( search, sizeof( search ), "%s/*.txt", subdir );
|
||
|
|
||
|
FileFindHandle_t findHandle;
|
||
|
const char *pFileName = filesystem->FindFirst( search, &findHandle );
|
||
|
while( pFileName )
|
||
|
{
|
||
|
if( !filesystem->FindIsDirectory( findHandle ) )
|
||
|
{
|
||
|
// Strip off the 'sound/' part of the name.
|
||
|
char fn[ 512 ];
|
||
|
Q_snprintf( fn, sizeof( fn ), "%s/%s", subdir, pFileName );
|
||
|
|
||
|
FileNameHandle_t fh;
|
||
|
fh = filesystem->FindOrAddFileName( fn );
|
||
|
files.AddToTail( fh );
|
||
|
}
|
||
|
pFileName = filesystem->FindNext( findHandle );
|
||
|
}
|
||
|
|
||
|
filesystem->FindClose( findHandle );
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::PurgeCustom()
|
||
|
{
|
||
|
for ( int i = 0; i < m_CustomAnimationTabs.Count(); ++i )
|
||
|
{
|
||
|
if ( m_CustomAnimationTabs[ i ]->m_bDirty )
|
||
|
{
|
||
|
m_CustomAnimationTabs[ i ]->SaveToFile();
|
||
|
}
|
||
|
delete m_CustomAnimationTabs[ i ];
|
||
|
}
|
||
|
m_CustomAnimationTabs.Purge();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::BuildCustomFromFiles( CUtlVector< FileNameHandle_t >& files )
|
||
|
{
|
||
|
PurgeCustom();
|
||
|
|
||
|
for ( int i = 0; i < files.Count(); ++i )
|
||
|
{
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( files[ i ], fn, sizeof( fn ) ) )
|
||
|
continue;
|
||
|
|
||
|
Q_FixSlashes( fn );
|
||
|
Q_strlower( fn );
|
||
|
|
||
|
char basename[ 128 ];
|
||
|
Q_FileBase( fn, basename, sizeof( basename ) );
|
||
|
|
||
|
CCustomAnim *anim = new CCustomAnim( files[ i ] );
|
||
|
anim->m_ShortName = basename;
|
||
|
anim->LoadFromFile();
|
||
|
|
||
|
m_CustomAnimationTabs.AddToTail( anim );
|
||
|
}
|
||
|
|
||
|
UpdateCustomTabs();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::RenameCustomFile( int index )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_CustomAnimationTabs.Count() )
|
||
|
return;
|
||
|
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ index ];
|
||
|
CInputParams params;
|
||
|
memset( ¶ms, 0, sizeof( params ) );
|
||
|
Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Custom Animation Group" );
|
||
|
Q_strcpy( params.m_szPrompt, "Group Name:" );
|
||
|
Q_strcpy( params.m_szInputText, anim->m_ShortName.String() );
|
||
|
|
||
|
if ( !InputProperties( ¶ms ) )
|
||
|
return;
|
||
|
|
||
|
if ( !params.m_szInputText[ 0 ] )
|
||
|
return;
|
||
|
|
||
|
// No change
|
||
|
if ( !Q_stricmp( anim->m_ShortName.String(), params.m_szInputText ) )
|
||
|
return;
|
||
|
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( anim->m_Handle, fn, sizeof( fn ) ) )
|
||
|
{
|
||
|
Assert( 0 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( !model )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CStudioHdr *hdr = model->GetStudioHdr();
|
||
|
if ( !hdr )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Delete the old file
|
||
|
filesystem->RemoveFile( fn, "MOD" );
|
||
|
|
||
|
anim->m_ShortName = params.m_szInputText;
|
||
|
|
||
|
char basename[ 128 ];
|
||
|
Q_StripExtension( hdr->pszName(), basename, sizeof( basename ) );
|
||
|
Q_snprintf( fn, sizeof( fn ), "expressions/%s/animation/%s.txt", basename, params.m_szInputText );
|
||
|
Q_FixSlashes( fn );
|
||
|
Q_strlower( fn );
|
||
|
CreatePath( fn );
|
||
|
|
||
|
anim->m_Handle = filesystem->FindOrAddFileName( fn );
|
||
|
|
||
|
anim->m_bDirty = true;
|
||
|
UpdateCustomTabs();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::AddCustomFile( const FileNameHandle_t& handle )
|
||
|
{
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( handle, fn, sizeof( fn ) ) )
|
||
|
return;
|
||
|
|
||
|
Q_FixSlashes( fn );
|
||
|
Q_strlower( fn );
|
||
|
char basename[ 128 ];
|
||
|
Q_FileBase( fn, basename, sizeof( basename ) );
|
||
|
|
||
|
CCustomAnim *anim = new CCustomAnim( handle );
|
||
|
anim->m_ShortName = basename;
|
||
|
anim->LoadFromFile();
|
||
|
anim->m_bDirty = true;
|
||
|
|
||
|
if ( m_nCurCell != -1 )
|
||
|
{
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( model )
|
||
|
{
|
||
|
CStudioHdr *hdr = model->GetStudioHdr();
|
||
|
if ( hdr )
|
||
|
{
|
||
|
mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( m_nCurCell );
|
||
|
CUtlSymbol sym;
|
||
|
sym = seqdesc.pszLabel();
|
||
|
anim->m_Animations.AddToTail( sym );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_CustomAnimationTabs.AddToTail( anim );
|
||
|
|
||
|
UpdateCustomTabs();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::DeleteCustomFile( int index )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_CustomAnimationTabs.Count() )
|
||
|
return;
|
||
|
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ index ];
|
||
|
|
||
|
char fn[ 512 ];
|
||
|
if ( !filesystem->String( anim->m_Handle, fn, sizeof( fn ) ) )
|
||
|
return;
|
||
|
|
||
|
m_CustomAnimationTabs.Remove( index );
|
||
|
filesystem->RemoveFile( fn );
|
||
|
delete anim;
|
||
|
|
||
|
UpdateCustomTabs();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::UpdateCustomTabs()
|
||
|
{
|
||
|
m_pFilterTab->UpdateCustomTabs( m_CustomAnimationTabs );
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::OnModelChanged()
|
||
|
{
|
||
|
CUtlVector< FileNameHandle_t > files;
|
||
|
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( model )
|
||
|
{
|
||
|
CStudioHdr *hdr = model->GetStudioHdr();
|
||
|
if ( hdr )
|
||
|
{
|
||
|
char subdir[ 512 ];
|
||
|
char basename[ 512 ];
|
||
|
Q_StripExtension( hdr->pszName(), basename, sizeof( basename ) );
|
||
|
Q_snprintf( subdir, sizeof( subdir ), "expressions/%s/animation", basename );
|
||
|
Q_FixSlashes( subdir );
|
||
|
Q_strlower( subdir );
|
||
|
FindCustomFiles( subdir, files );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BuildCustomFromFiles( files );
|
||
|
|
||
|
RestoreThumbnailSize();
|
||
|
|
||
|
// Just reapply filter
|
||
|
OnFilter();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::OnAddCustomAnimationFilter()
|
||
|
{
|
||
|
StudioModel *model = models->GetActiveStudioModel();
|
||
|
if ( !model )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CStudioHdr *hdr = model->GetStudioHdr();
|
||
|
if ( !hdr )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CInputParams params;
|
||
|
memset( ¶ms, 0, sizeof( params ) );
|
||
|
Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Custom Animation Group" );
|
||
|
Q_strcpy( params.m_szPrompt, "Group Name:" );
|
||
|
Q_strcpy( params.m_szInputText, "" );
|
||
|
|
||
|
if ( !InputProperties( ¶ms ) )
|
||
|
return;
|
||
|
|
||
|
if ( !params.m_szInputText[ 0 ] )
|
||
|
return;
|
||
|
|
||
|
if ( FindCustomFile( params.m_szInputText ) != -1 )
|
||
|
{
|
||
|
Warning( "Can't add duplicate tab '%s'\n", params.m_szInputText );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Create it
|
||
|
char fn[ 512 ];
|
||
|
char basename[ 512 ];
|
||
|
Q_StripExtension( hdr->pszName(), basename, sizeof( basename ) );
|
||
|
Q_snprintf( fn, sizeof( fn ), "expressions/%s/animation/%s.txt", basename, params.m_szInputText );
|
||
|
Q_FixSlashes( fn );
|
||
|
Q_strlower( fn );
|
||
|
CreatePath( fn );
|
||
|
|
||
|
FileNameHandle_t fh = filesystem->FindOrAddFileName( fn );
|
||
|
AddCustomFile( fh );
|
||
|
}
|
||
|
|
||
|
int AnimationBrowser::FindCustomFile( char const *shortName )
|
||
|
{
|
||
|
CUtlSymbol search;
|
||
|
search = shortName;
|
||
|
|
||
|
for ( int i = 0; i < m_CustomAnimationTabs.Count(); ++i )
|
||
|
{
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ i ];
|
||
|
if ( anim->m_ShortName == search )
|
||
|
return i;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::AddAnimationToCustomFile( int index, char const *animationName )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_CustomAnimationTabs.Count() )
|
||
|
return;
|
||
|
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ index ];
|
||
|
|
||
|
CUtlSymbol search;
|
||
|
search = animationName;
|
||
|
|
||
|
if ( anim->m_Animations.Find( search ) == anim->m_Animations.InvalidIndex() )
|
||
|
{
|
||
|
anim->m_Animations.AddToTail( search );
|
||
|
anim->m_bDirty = true;
|
||
|
}
|
||
|
|
||
|
OnFilter();
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::RemoveAnimationFromCustomFile( int index, char const *animationName )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_CustomAnimationTabs.Count() )
|
||
|
return;
|
||
|
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ index ];
|
||
|
|
||
|
CUtlSymbol search;
|
||
|
search = animationName;
|
||
|
|
||
|
int slot = anim->m_Animations.Find( search );
|
||
|
if ( slot != anim->m_Animations.InvalidIndex() )
|
||
|
{
|
||
|
anim->m_Animations.Remove( slot );
|
||
|
anim->m_bDirty = true;
|
||
|
OnFilter();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AnimationBrowser::RemoveAllAnimationsFromCustomFile( int index )
|
||
|
{
|
||
|
if ( index < 0 || index >= m_CustomAnimationTabs.Count() )
|
||
|
return;
|
||
|
|
||
|
CCustomAnim *anim = m_CustomAnimationTabs[ index ];
|
||
|
anim->m_Animations.Purge();
|
||
|
anim->m_bDirty = true;
|
||
|
|
||
|
OnFilter();
|
||
|
}
|