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.
1212 lines
28 KiB
1212 lines
28 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
#include "hlfaceposer.h" |
|
#include <windows.h> |
|
#include <stdio.h> |
|
#include <mxtk/mxWindow.h> |
|
#include <mxtk/mxScrollBar.h> |
|
#include "mxexpressiontray.h" |
|
#include "expressions.h" |
|
#include "expclass.h" |
|
#include "ControlPanel.h" |
|
#include "FlexPanel.h" |
|
#include <mxtk/mxPopupMenu.h> |
|
#include "ChoreoView.h" |
|
#include "StudioModel.h" |
|
#include "ExpressionProperties.h" |
|
#include "InputProperties.h" |
|
#include "ViewerSettings.h" |
|
#include "mxExpressionTab.h" |
|
#include "choreowidgetdrawhelper.h" |
|
#include "ExpressionTool.h" |
|
#include "faceposer_models.h" |
|
#include "tier0/icommandline.h" |
|
#include "filesystem.h" |
|
|
|
#define MAX_THUMBNAILSIZE 256 |
|
#define MIN_THUMBNAILSIZE 64 |
|
#define THUMBNAIL_SIZE_STEP 4 |
|
#define DEFAULT_THUMBNAIL_SIZE 128 |
|
#define TOP_GAP 45 |
|
|
|
mxExpressionTray *g_pExpressionTrayTool = 0; |
|
|
|
mxExpressionTray::mxExpressionTray( mxWindow *parent, int id /*=0*/ ) |
|
: IFacePoserToolWindow( "ExpressionTrayTool", "Expressions" ), mxWindow( parent, 0, 0, 0, 0, "ExpressionTrayTool", id ) |
|
{ |
|
setId( id ); |
|
|
|
m_nTopOffset = 0; |
|
slScrollbar = new mxScrollbar( this, 0, 0, 18, 100, IDC_TRAYSCROLL, mxScrollbar::Vertical ); |
|
|
|
m_nLastNumExpressions = -1; |
|
|
|
m_nGranularity = 10; |
|
|
|
m_nPrevCell = -1; |
|
m_nCurCell = -1; |
|
|
|
m_nClickedCell = -1; |
|
|
|
m_nButtonSquare = 16; |
|
|
|
m_nGap = 4; |
|
m_nDescriptionHeight = 34; |
|
m_nSnapshotWidth = g_viewerSettings.thumbnailsize; |
|
m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); |
|
m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); |
|
|
|
g_viewerSettings.thumbnailsize = m_nSnapshotWidth; |
|
|
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; |
|
|
|
m_pButtons = NULL; |
|
|
|
m_nPreviousExpressionCount = -1; |
|
|
|
m_bDragging = false; |
|
m_nDragCell = -1; |
|
|
|
CreateButtons(); |
|
|
|
g_pExpressionClass = new mxExpressionTab( this, 5, 5, 500, 20, IDC_EXPRESSIONCLASS ); |
|
|
|
m_pABButton = new mxButton( this, 520, 8, 50, 18, "A/B", IDC_AB ); |
|
m_pThumbnailIncreaseButton = new mxButton( this, 0, 0, 18, 18, "+", IDC_THUMBNAIL_INCREASE ); |
|
m_pThumbnailDecreaseButton = new mxButton( this, 0, 0, 18, 18, "-", IDC_THUMBNAIL_DECREASE ); |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : mxExpressionTray::~mxExpressionTray |
|
//----------------------------------------------------------------------------- |
|
mxExpressionTray::~mxExpressionTray ( void ) |
|
{ |
|
DeleteAllButtons(); |
|
g_pExpressionTrayTool = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : cellsize - |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::SetCellSize( int cellsize ) |
|
{ |
|
m_nSnapshotWidth = cellsize; |
|
m_nSnapshotHeight = cellsize + m_nDescriptionHeight; |
|
|
|
DeleteAllButtons(); |
|
CreateButtons(); |
|
|
|
redraw(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::Deselect( void ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( active ) |
|
{ |
|
for ( int i = 0 ; i < active->GetNumExpressions(); i++ ) |
|
{ |
|
CExpression *exp = active->GetExpression( i ); |
|
if ( exp ) |
|
{ |
|
exp->SetSelected( false ); |
|
} |
|
} |
|
} |
|
|
|
m_nCurCell = m_nPrevCell = -1; |
|
redraw(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : exp - |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::Select( int exp, bool deselect /*=true*/ ) |
|
{ |
|
int oldcell = m_nCurCell; |
|
|
|
if ( deselect ) |
|
{ |
|
Deselect(); |
|
} |
|
|
|
m_nPrevCell = oldcell; |
|
m_nCurCell = exp; |
|
|
|
if ( m_nCurCell >= 0 ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( active ) |
|
{ |
|
CExpression *exp = active->GetExpression( m_nCurCell ); |
|
if ( exp ) |
|
{ |
|
exp->SetSelected( true ); |
|
} |
|
} |
|
} |
|
|
|
redraw(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *btn - |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::AddButton( const char *name, const char *tooltip, const char *bitmap, ETMEMBERFUNC pfnCallback, |
|
bool active, int x, int y, int w, int h ) |
|
{ |
|
mxETButton *btn = new mxETButton; |
|
strcpy( btn->m_szName, name ); |
|
strcpy( btn->m_szToolTip, tooltip ); |
|
btn->m_bActive = active; |
|
btn->m_rc.left = x; |
|
btn->m_rc.top = y; |
|
btn->m_rc.right = x + w; |
|
btn->m_rc.bottom = y + h; |
|
|
|
btn->m_pImage = new mxbitmapdata_t; |
|
Assert( btn->m_pImage ); |
|
btn->m_pImage->valid = false; |
|
LoadBitmapFromFile( bitmap, *btn->m_pImage ); |
|
|
|
btn->m_fnCallback = pfnCallback; |
|
|
|
btn->next = m_pButtons; |
|
m_pButtons = btn; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::CreateButtons( void ) |
|
{ |
|
int x = m_nSnapshotWidth - 2 * ( m_nButtonSquare + 4 ); |
|
int y = 4; |
|
|
|
AddButton( "undo", "Undo", "gfx/hlfaceposer/undo.bmp", &mxExpressionTray::ET_Undo, true, x, y, m_nButtonSquare, m_nButtonSquare ); |
|
|
|
x += ( m_nButtonSquare + 4 ); |
|
|
|
AddButton( "redo", "Redo", "gfx/hlfaceposer/redo.bmp", &mxExpressionTray::ET_Redo, true, x, y, m_nButtonSquare, m_nButtonSquare ); |
|
} |
|
|
|
void mxExpressionTray::ActivateButton( const char *name, bool active ) |
|
{ |
|
mxETButton *btn = FindButton( name ); |
|
if ( !name ) |
|
return; |
|
|
|
btn->m_bActive = active; |
|
} |
|
|
|
mxExpressionTray::mxETButton *mxExpressionTray::FindButton( const char *name ) |
|
{ |
|
mxETButton *p = m_pButtons; |
|
while ( p ) |
|
{ |
|
if ( !stricmp( p->m_szName, name ) ) |
|
return p; |
|
p = p->next; |
|
} |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::DeleteAllButtons( void ) |
|
{ |
|
mxETButton *p = m_pButtons, *n; |
|
while ( p ) |
|
{ |
|
n = p->next; |
|
delete p->m_pImage; |
|
delete p; |
|
p = n; |
|
} |
|
m_pButtons = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : x - |
|
// y - |
|
// Output : mxExpressionTray::mxETButton |
|
//----------------------------------------------------------------------------- |
|
mxExpressionTray::mxETButton *mxExpressionTray::GetItemUnderCursor( int x, int y ) |
|
{ |
|
// Convert to cell space |
|
int cell = GetCellUnderPosition( x, y ); |
|
if ( cell == -1 ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// Cell is off screen? |
|
int cx, cy, cw, ch; |
|
if ( !ComputeRect( cell, cx, cy, cw, ch ) ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
|
|
mxETButton *p = m_pButtons; |
|
while ( p ) |
|
{ |
|
if ( p->m_bActive && |
|
x >= cx && |
|
x <= cx + cw && |
|
y >= cy && |
|
y <= cy + ch ) |
|
{ |
|
// In-side cell |
|
int cellx = x - cx; |
|
int celly = y - cy; |
|
|
|
if ( cellx >= p->m_rc.left && |
|
cellx <= p->m_rc.right && |
|
celly >= p->m_rc.top && |
|
celly <= p->m_rc.bottom ) |
|
{ |
|
return p; |
|
} |
|
} |
|
p = p->next; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void mxExpressionTray::DrawButton( CChoreoWidgetDrawHelper& helper, int cell, mxETButton *btn ) |
|
{ |
|
if ( !btn || !btn->m_pImage || !btn->m_pImage->valid ) |
|
return; |
|
|
|
if ( !btn->m_bActive ) |
|
return; |
|
|
|
int x, y, w, h; |
|
if ( !ComputeRect( cell, x, y, w, h ) ) |
|
return; |
|
|
|
x += btn->m_rc.left; |
|
y += btn->m_rc.top; |
|
w = btn->m_rc.right - btn->m_rc.left; |
|
h = btn->m_rc.bottom - btn->m_rc.top; |
|
|
|
HDC dc = helper.GrabDC(); |
|
|
|
DrawBitmapToDC( dc, x, y, w, h, *btn->m_pImage ); |
|
helper.DrawOutlinedRect( RGB( 170, 170, 170 ), PS_SOLID, 1, x, y, x + w, y + h ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int mxExpressionTray::ComputePixelsNeeded( void ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( !active ) |
|
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 = ( ( active->GetNumExpressions() + colsperrow - 1 ) / colsperrow ); |
|
return rowsneeded * ( m_nSnapshotHeight + m_nGap ) + m_nGap + TOP_GAP + GetCaptionHeight(); |
|
} |
|
|
|
bool mxExpressionTray::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 mxExpressionTray::DrawExpressionFocusRect( 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 mxExpressionTray::DrawExpressionDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, const char *expressionname, const char *description ) |
|
{ |
|
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( 0, 0, 0 ), textRect, "%s", expressionname ); |
|
|
|
// DrawText( hdc, expressionname, strlen( expressionname ), &textRect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_WORD_ELLIPSIS ); |
|
|
|
OffsetRect( &textRect, 0, textheight ); |
|
|
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 63, 63, 63 ), textRect, "%s", description ); |
|
|
|
// DrawText( hdc, description, strlen( description ), &textRect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_WORD_ELLIPSIS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : dc - |
|
// current - |
|
// rcx - |
|
// rcy - |
|
// rcw - |
|
// rch - |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::DrawDirtyFlag( CChoreoWidgetDrawHelper& helper, CExpression *current, int rcx, int rcy, int rcw, int rch ) |
|
{ |
|
// Not dirty |
|
if ( !current || ( !current->CanUndo() && !current->GetDirty() ) ) |
|
return; |
|
|
|
int fontsize = 14; |
|
|
|
RECT textRect; |
|
textRect.left = rcx + 5; |
|
textRect.right = rcx + rcw; |
|
textRect.top = rcy + 5; |
|
textRect.bottom = textRect.top + fontsize + 2; |
|
|
|
helper.DrawColoredText( "Arial", fontsize, FW_NORMAL, RGB( 100, 240, 255 ), textRect, "*" ); |
|
} |
|
|
|
bool mxExpressionTray::PaintBackground( void ) |
|
{ |
|
redraw(); |
|
return false; |
|
} |
|
|
|
void mxExpressionTray::DrawThumbNail( CExpClass *active, CExpression *current, CChoreoWidgetDrawHelper& helper, int rcx, int rcy, int rcw, int rch, int c, int selected, bool updateselection ) |
|
{ |
|
if ( !current ) |
|
return; |
|
|
|
HDC dc = helper.GrabDC(); |
|
|
|
helper.DrawFilledRect( GetSysColor( COLOR_BTNFACE ), rcx, rcy, rcw + rcx, rch + rcy ); |
|
|
|
if ( current->m_Bitmap[ models->GetActiveModelIndex() ].valid ) |
|
{ |
|
DrawBitmapToDC( dc, rcx, rcy, rcw, rch - m_nDescriptionHeight, current->m_Bitmap[ models->GetActiveModelIndex() ] ); |
|
helper.DrawOutlinedRect( RGB( 127, 127, 127 ), PS_SOLID, 1, rcx, rcy, rcx + rcw, rcy + rch - m_nDescriptionHeight ); |
|
} |
|
|
|
DrawDirtyFlag( helper, current, rcx, rcy, rcw, rch ); |
|
|
|
DrawExpressionDescription( helper, rcx, rcy, rcw, rch, current->name, current->description ); |
|
|
|
if ( c == selected ) |
|
{ |
|
DrawExpressionFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, RGB( 255, 100, 63 ) ); |
|
|
|
if ( updateselection ) |
|
{ |
|
m_nPrevCell = -1; |
|
m_nCurCell = c; |
|
} |
|
|
|
if ( current->CanUndo() || current->CanRedo() ) |
|
{ |
|
if ( current->CanUndo() ) |
|
{ |
|
DrawButton( helper, c, FindButton( "undo" ) ); |
|
} |
|
|
|
if ( current->CanRedo() ) |
|
{ |
|
DrawButton( helper, c, FindButton( "redo" ) ); |
|
} |
|
|
|
RECT rc; |
|
rc.left = rcx + rcw - 2 * ( m_nButtonSquare + 4 ); |
|
rc.top = rcy + m_nButtonSquare + 6; |
|
rc.right = rc.left + 2 * ( m_nButtonSquare + 4 ); |
|
rc.bottom = rc.top + 15; |
|
|
|
helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 200, 200 ), rc, |
|
"%i/%i", current->UndoCurrent(), current->UndoLevels() ); |
|
} |
|
|
|
} |
|
else |
|
{ |
|
if ( current->GetSelected() ) |
|
{ |
|
DrawExpressionFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, RGB( 127, 127, 220 ) ); |
|
} |
|
} |
|
} |
|
|
|
void mxExpressionTray::redraw() |
|
{ |
|
if ( !ToolCanDraw() ) |
|
return; |
|
|
|
bool updateSelection = false; |
|
|
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( active && active->GetNumExpressions() != m_nPreviousExpressionCount ) |
|
{ |
|
m_nTopOffset = 0; |
|
|
|
RepositionSlider(); |
|
m_nPreviousExpressionCount = active->GetNumExpressions(); |
|
} |
|
|
|
CChoreoWidgetDrawHelper helper( this, GetSysColor( COLOR_BTNFACE ) ); |
|
HandleToolRedraw( helper ); |
|
|
|
int w, h; |
|
w = w2(); |
|
h = h2(); |
|
|
|
if ( active ) |
|
{ |
|
RECT clipRect; |
|
helper.GetClientRect( clipRect ); |
|
|
|
clipRect.top += TOP_GAP + GetCaptionHeight(); |
|
|
|
helper.StartClipping( clipRect ); |
|
|
|
if ( m_nLastNumExpressions != active->GetNumExpressions() ) |
|
{ |
|
m_nTopOffset = 0; |
|
m_nLastNumExpressions = active->GetNumExpressions(); |
|
RepositionSlider(); |
|
updateSelection = true; |
|
} |
|
|
|
int selected = active->GetSelectedExpression(); |
|
|
|
int rcx, rcy, rcw, rch; |
|
|
|
int c = 0; |
|
while ( c < active->GetNumExpressions() ) |
|
{ |
|
if ( !ComputeRect( c, rcx, rcy, rcw, rch ) ) |
|
{ |
|
c++; |
|
continue; |
|
} |
|
|
|
CExpression *current = active->GetExpression( c ); |
|
if ( !current ) |
|
break; |
|
|
|
DrawThumbNail( active, current, helper, rcx, rcy, rcw, rch, c, selected, updateSelection ); |
|
|
|
c++; |
|
} |
|
|
|
helper.StopClipping(); |
|
|
|
} |
|
else |
|
{ |
|
|
|
RECT rc; |
|
helper.GetClientRect( rc ); |
|
|
|
// Arial 36 normal |
|
char sz[ 256 ]; |
|
sprintf( sz, "No expression file loaded" ); |
|
|
|
int pointsize = 18; |
|
|
|
int textlen = helper.CalcTextWidth( "Arial", pointsize, FW_NORMAL, sz ); |
|
|
|
RECT rcText; |
|
rcText.top = ( rc.bottom - rc.top ) / 2 - pointsize / 2; |
|
rcText.bottom = rcText.top + pointsize + 10; |
|
int fullw = rc.right - rc.left; |
|
|
|
rcText.left = rc.left + ( fullw - textlen ) / 2; |
|
rcText.right = rcText.left + textlen; |
|
|
|
helper.DrawColoredText( "Arial", pointsize, FW_NORMAL, RGB( 80, 80, 80 ), rcText, sz ); |
|
} |
|
|
|
|
|
// ValidateRect( (HWND)getHandle(), &rc ); |
|
} |
|
|
|
int mxExpressionTray::GetCellUnderPosition( int x, int y ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( !active ) |
|
return -1; |
|
|
|
int rcx, rcy, rcw, rch; |
|
int c = 0; |
|
while ( c < active->GetNumExpressions() ) |
|
{ |
|
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 mxExpressionTray::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 mxExpressionTray::AB( void ) |
|
{ |
|
if ( m_nPrevCell == -1 && m_nCurCell == -1 ) |
|
return; |
|
|
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( !active ) |
|
return; |
|
|
|
if ( m_nPrevCell >= 0 && m_nPrevCell < active->GetNumExpressions() ) |
|
{ |
|
active->SelectExpression( m_nPrevCell ); |
|
} |
|
} |
|
|
|
int mxExpressionTray::CountSelected( void ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( !active ) |
|
return 0; |
|
|
|
int c = 0; |
|
for ( int i = 0; i < active->GetNumExpressions(); i++ ) |
|
{ |
|
CExpression *exp = active->GetExpression( i ); |
|
if ( !exp ) |
|
continue; |
|
|
|
if ( exp->GetSelected() ) |
|
{ |
|
c++; |
|
} |
|
} |
|
|
|
return c; |
|
} |
|
|
|
void mxExpressionTray::SetClickedCell( int cell ) |
|
{ |
|
m_nClickedCell = cell; |
|
} |
|
|
|
void mxExpressionTray::ShowRightClickMenu( int mx, int my ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( !active ) |
|
return; |
|
|
|
mxPopupMenu *pop = new mxPopupMenu(); |
|
Assert( pop ); |
|
|
|
CExpression *exp = NULL; |
|
if ( m_nClickedCell != -1 ) |
|
{ |
|
exp = active->GetExpression( m_nClickedCell ); |
|
} |
|
|
|
pop->add( "New Expression...", IDC_CONTEXT_NEWEXP ); |
|
if ( exp ) |
|
{ |
|
pop->addSeparator(); |
|
pop->add( va( "Edit '%s'...", exp->name ), IDC_CONTEXT_EDITEXP ); |
|
pop->add( va( "Save '%s'", exp->name ), IDC_CONTEXT_SAVEEXP ); |
|
|
|
if ( exp->CanUndo() || exp->CanRedo() ) |
|
{ |
|
pop->add( va( "Revert '%s'", exp->name ), IDC_CONTEXT_REVERT ); |
|
} |
|
pop->addSeparator(); |
|
pop->add( va( "Delete '%s'", exp->name ), IDC_CONTEXT_DELETEXP ); |
|
pop->addSeparator(); |
|
pop->add( va( "Re-create thumbnail for '%s'", exp->name ), IDC_CONTEXT_CREATEBITMAP ); |
|
} |
|
|
|
pop->popup( this, mx, my ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::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 mxExpressionTray::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: |
|
iret = 0; |
|
break; |
|
case IDC_EXPRESSIONCLASS: |
|
{ |
|
int index = g_pExpressionClass->getSelectedIndex(); |
|
if ( index >= 0 ) |
|
{ |
|
CExpClass *current = expressions->GetClass( index ); |
|
if ( current ) |
|
{ |
|
// Switch classname |
|
expressions->ActivateExpressionClass( current ); |
|
current->SelectExpression( 0 ); |
|
} |
|
} |
|
} |
|
break; |
|
case IDC_CONTEXT_NEWEXP: |
|
g_pFlexPanel->NewExpression(); |
|
break; |
|
case IDC_CONTEXT_EDITEXP: |
|
if ( m_nClickedCell != -1 ) |
|
{ |
|
g_pFlexPanel->EditExpression(); |
|
} |
|
break; |
|
case IDC_CONTEXT_REVERT: |
|
if ( m_nClickedCell != -1 ) |
|
{ |
|
g_pFlexPanel->RevertExpression( m_nClickedCell ); |
|
} |
|
break; |
|
case IDC_CONTEXT_SAVEEXP: |
|
if ( m_nClickedCell != -1 ) |
|
{ |
|
g_pFlexPanel->SaveExpression( m_nClickedCell ); |
|
} |
|
break; |
|
case IDC_CONTEXT_DELETEXP: |
|
if ( m_nClickedCell != -1 ) |
|
{ |
|
g_pControlPanel->DeleteExpression( m_nClickedCell ); |
|
} |
|
break; |
|
case IDC_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: |
|
{ |
|
AB(); |
|
} |
|
break; |
|
case IDC_THUMBNAIL_INCREASE: |
|
{ |
|
ThumbnailIncrease(); |
|
} |
|
break; |
|
case IDC_THUMBNAIL_DECREASE: |
|
{ |
|
ThumbnailDecrease(); |
|
} |
|
break; |
|
case IDC_CONTEXT_CREATEBITMAP: |
|
{ |
|
if ( m_nClickedCell >= 0 ) |
|
{ |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( active ) |
|
{ |
|
CExpression *exp = active->GetExpression( m_nClickedCell ); |
|
if ( exp ) |
|
{ |
|
active->SelectExpression( m_nClickedCell ); |
|
exp->CreateNewBitmap( models->GetActiveModelIndex() ); |
|
redraw(); |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
break; |
|
} |
|
case mxEvent::MouseDown: |
|
{ |
|
if ( !( event->buttons & mxEvent::MouseRightButton ) ) |
|
{ |
|
// Figure out cell # |
|
int cell = GetCellUnderPosition( event->x, event->y ); |
|
CExpClass *active = expressions->GetActiveClass(); |
|
if ( active ) |
|
{ |
|
|
|
if ( cell == m_nCurCell && cell >= 0 && cell < active->GetNumExpressions() ) |
|
{ |
|
mxETButton *btn = GetItemUnderCursor( event->x, event->y ); |
|
if ( btn && btn->m_fnCallback ) |
|
{ |
|
(this->*(btn->m_fnCallback))( cell ); |
|
return iret; |
|
} |
|
} |
|
|
|
if ( cell >= 0 && cell < active->GetNumExpressions() ) |
|
{ |
|
active->SelectExpression( cell, event->modifiers & mxEvent::KeyShift ? false : true ); |
|
|
|
int cx, cy, cw, ch; |
|
if ( ComputeRect( cell, cx, cy, cw, ch ) ) |
|
{ |
|
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(); |
|
} |
|
} |
|
else |
|
{ |
|
Deselect(); |
|
active->DeselectExpression(); |
|
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; |
|
} |
|
|
|
int cell = GetCellUnderPosition( event->x, event->y ); |
|
CExpClass *active = expressions->GetActiveClass(); |
|
|
|
if ( m_bDragging ) |
|
{ |
|
DrawFocusRect(); |
|
m_bDragging = false; |
|
// See if we let go on top of the choreo view |
|
|
|
if ( active ) |
|
{ |
|
// 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 |
|
CExpression *exp = active->GetExpression( m_nDragCell ); |
|
if ( exp && maybeTool ) |
|
{ |
|
if ( IsWindowOrChild( g_pChoreoView, maybeTool ) ) |
|
{ |
|
if ( g_pChoreoView->CreateExpressionEvent( pt.x, pt.y, active, exp ) ) |
|
{ |
|
return iret; |
|
} |
|
} |
|
|
|
if ( IsWindowOrChild( g_pExpressionTool, maybeTool ) ) |
|
{ |
|
if ( g_pExpressionTool->SetFlexAnimationTrackFromExpression( pt.x, pt.y, active, exp ) ) |
|
{ |
|
return iret; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( active ) |
|
{ |
|
// Over a new cell |
|
if ( cell >= 0 && |
|
cell < active->GetNumExpressions() && |
|
cell != m_nCurCell && |
|
m_nCurCell != -1 ) |
|
{ |
|
// Swap cells |
|
CExpression *exp = active->GetExpression( m_nCurCell ); |
|
if ( exp ) |
|
{ |
|
active->SwapExpressionOrder( m_nCurCell, cell ); |
|
active->SetDirty( true ); |
|
active->SelectExpression( cell ); |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
case mxEvent::Size: |
|
{ |
|
int width = w2(); |
|
|
|
int ch = GetCaptionHeight(); |
|
|
|
g_pExpressionClass->setBounds( 5, 5 + ch, width - 120, 20 ); |
|
|
|
m_pABButton->setBounds( width - 60, 4 + ch, 60, 16 ); |
|
m_pThumbnailIncreaseButton->setBounds( width - 60 - 40, 4 + ch, 16, 16 ); |
|
m_pThumbnailDecreaseButton->setBounds( width - 60 - 20, 4 + ch, 16, 16 ); |
|
|
|
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; |
|
}; |
|
|
|
if ( iret ) |
|
{ |
|
SetActiveTool( this ); |
|
} |
|
return iret; |
|
} |
|
|
|
void mxExpressionTray::ET_Undo( int cell ) |
|
{ |
|
g_pControlPanel->UndoExpression( cell ); |
|
} |
|
|
|
void mxExpressionTray::ET_Redo( int cell ) |
|
{ |
|
g_pControlPanel->RedoExpression( cell ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::ThumbnailIncrease( void ) |
|
{ |
|
if ( m_nSnapshotWidth + THUMBNAIL_SIZE_STEP <= MAX_THUMBNAILSIZE ) |
|
{ |
|
m_nSnapshotWidth += THUMBNAIL_SIZE_STEP; |
|
g_viewerSettings.thumbnailsize = m_nSnapshotWidth; |
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; |
|
|
|
Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); |
|
|
|
redraw(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::ThumbnailDecrease( void ) |
|
{ |
|
if ( m_nSnapshotWidth - THUMBNAIL_SIZE_STEP >= MIN_THUMBNAILSIZE ) |
|
{ |
|
m_nSnapshotWidth -= THUMBNAIL_SIZE_STEP; |
|
g_viewerSettings.thumbnailsize = m_nSnapshotWidth; |
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; |
|
|
|
Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); |
|
|
|
redraw(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void mxExpressionTray::RestoreThumbnailSize( void ) |
|
{ |
|
m_nSnapshotWidth = g_viewerSettings.thumbnailsize; |
|
m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); |
|
m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); |
|
|
|
g_viewerSettings.thumbnailsize = m_nSnapshotWidth; |
|
|
|
m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; |
|
|
|
redraw(); |
|
} |
|
|
|
void mxExpressionTray::ReloadBitmaps( void ) |
|
{ |
|
CExpClass *cl; |
|
int c = expressions->GetNumClasses(); |
|
EnableStickySnapshotMode(); |
|
for ( int i = 0 ; i < c; i++ ) |
|
{ |
|
cl = expressions->GetClass( i ); |
|
if ( !cl ) |
|
continue; |
|
|
|
cl->ReloadBitmaps(); |
|
} |
|
DisableStickySnapshotMode(); |
|
redraw(); |
|
} |
|
|
|
bool IsUsingPerPlayerExpressions() |
|
{ |
|
bool bPerPlayerExpressions = false; |
|
if ( CommandLine()->CheckParm( "-perplayerexpressions" ) ) |
|
{ |
|
bPerPlayerExpressions = true; |
|
} |
|
else |
|
{ |
|
// Returns the search path, each path is separated by ;s. Returns the length of the string returned |
|
char pSearchPath[2048]; |
|
if ( g_pFullFileSystem->GetSearchPath( "GAME", false, pSearchPath, sizeof(pSearchPath) ) ) |
|
{ |
|
Q_FixSlashes( pSearchPath ); |
|
if ( Q_stristr( pSearchPath, "\\tf" ) ) |
|
{ |
|
bPerPlayerExpressions = true; |
|
} |
|
} |
|
} |
|
return bPerPlayerExpressions; |
|
} |
|
|
|
void mxExpressionTray::OnModelChanged() |
|
{ |
|
if ( IsUsingPerPlayerExpressions() ) |
|
{ |
|
Msg( "Closing current phoneme set\n" ); |
|
|
|
if ( !g_pControlPanel->Closeall() ) |
|
return; |
|
|
|
// See if per-model overrides exist for this model |
|
char fn[ MAX_PATH ]; |
|
Q_snprintf( fn, sizeof( fn ), "expressions/%s/phonemes/phonemes.txt", models->GetActiveModelName() ); |
|
|
|
// Load appropriate classes |
|
char rootDir[ MAX_PATH ]; |
|
Q_snprintf( rootDir, sizeof( rootDir ), "%s/phonemes/", models->GetActiveModelName() ); |
|
FacePoser_SetPhonemeRootDir( rootDir ); |
|
|
|
FacePoser_EnsurePhonemesLoaded(); |
|
} |
|
|
|
ReloadBitmaps(); |
|
RestoreThumbnailSize(); |
|
} |