diff options
Diffstat (limited to 'utils/hlfaceposer/expressiontool.cpp')
| -rw-r--r-- | utils/hlfaceposer/expressiontool.cpp | 4816 |
1 files changed, 4816 insertions, 0 deletions
diff --git a/utils/hlfaceposer/expressiontool.cpp b/utils/hlfaceposer/expressiontool.cpp new file mode 100644 index 0000000..b303e7c --- /dev/null +++ b/utils/hlfaceposer/expressiontool.cpp @@ -0,0 +1,4816 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "ExpressionTool.h" +#include "mdlviewer.h" +#include "choreowidgetdrawhelper.h" +#include "TimelineItem.h" +#include "expressions.h" +#include "expclass.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "InputProperties.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "ExpressionProperties.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "iscenetokenprocessor.h" +#include "MatSysWin.h" +#include "choreoviewcolors.h" +#include "scriplib.h" +#include "EdgeProperties.h" + +ExpressionTool *g_pExpressionTool = 0; + +#define TRAY_HEIGHT 55 + +#define TRAY_ITEM_INSET 10 + +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +void SetupFlexControllerTracks( CStudioHdr *hdr, CChoreoEvent *event ); + +class CExpressionToolWorkspace : public mxWindow +{ +public: + CExpressionToolWorkspace( mxWindow *parent ); + ~CExpressionToolWorkspace(); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground( void ) + { + redraw(); + return false; + } + + void RepositionVSlider( void ); + int ComputeVPixelsNeeded( void ); + // Playback tick + void Think( float dt ); + + void LayoutItems( bool force = false ); + + void HideTimelines( void ); + void CollapseAll( TimelineItem *keepExpanded ); + + void ExpandAll( void ); + void ExpandValid( void ); + void DisableAllExcept( void ); + void EnableValid( void ); + + TimelineItem *GetItem( int number ); + TimelineItem *GetClickedItem( void ); + void ClearClickedItem( void ); + + void OnSnapAll(); + void OnDeleteColumn(); + + void MoveSelectedSamples( float dfdx, float dfdy, bool snap ); + void DeleteSelectedSamples( void ); + int CountSelectedSamples( void ); + void DeselectAll( void ); + void SelectPoints( float start, float end ); + + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + + void OnSortByUsed( void ); + void OnSortByName( void ); + +private: + + int GetItemUnderMouse( int mx, int my ); + + void MouseToToolMouse( int& mx, int& my, char *reason ); + + TimelineItem *m_pItems[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + // The scroll bars + mxScrollbar *m_pVertScrollBar; + int m_nLastVPixelsNeeded; + + int m_nTopOffset; + int m_nScrollbarHeight; + + int m_nItemGap; + int m_nFocusItem; +}; + +CExpressionToolWorkspace::CExpressionToolWorkspace( mxWindow *parent ) : + mxWindow( parent, 0, 0, 0, 0 ) +{ + HWND wnd = (HWND)getHandle(); + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + SetWindowLong( wnd, GWL_STYLE, style ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + m_pItems[ i ] = new TimelineItem( this ); + } + + m_nItemGap = 2; + + m_nScrollbarHeight = 12; + m_nTopOffset = 0; + + m_nLastVPixelsNeeded = -1; + + m_pVertScrollBar = new mxScrollbar( this, 0, 0, 12, 100, IDC_EXPRESSIONTOOLVSCROLL, mxScrollbar::Vertical ); + + m_nFocusItem = -1; + + HideTimelines(); + LayoutItems(); +} + +CExpressionToolWorkspace::~CExpressionToolWorkspace() +{ +} + +void CExpressionToolWorkspace::redraw() +{ + CChoreoWidgetDrawHelper drawHelper( this ); + + DrawEventEnd( drawHelper ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item ) + continue; + + if ( !item->GetVisible() ) + continue; + + RECT rcBounds; + item->GetBounds( rcBounds ); + + if ( rcBounds.bottom < 0 ) + continue; + if ( rcBounds.top > h2() ) + continue; + + item->Draw( drawHelper ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *elem1 - +// *elem2 - +// Output : int +//----------------------------------------------------------------------------- +int SortFuncByUse(const void *elem1, const void *elem2 ) +{ + TimelineItem *item1 = *( TimelineItem ** )elem1; + TimelineItem *item2 = *( TimelineItem ** )elem2; + + if ( item1->IsValid() == item2->IsValid() ) + return 0; + + if ( !item2->IsValid() && item1->IsValid() ) + return -1; + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *elem1 - +// *elem2 - +// Output : int +//----------------------------------------------------------------------------- +int SortFuncByName(const void *elem1, const void *elem2 ) +{ + TimelineItem *item1 = *( TimelineItem ** )elem1; + TimelineItem *item2 = *( TimelineItem ** )elem2; + + CFlexAnimationTrack *track1 = item1->GetSafeTrack(); + CFlexAnimationTrack *track2 = item2->GetSafeTrack(); + + if ( !track1 || !track2 ) + { + if ( track1 ) + return -1; + if ( track2 ) + return 1; + return 0; + } + + return stricmp( track1->GetFlexControllerName(), track2->GetFlexControllerName() ); +} + +void CExpressionToolWorkspace::OnSortByUsed( void ) +{ + qsort( m_pItems, GLOBAL_STUDIO_FLEX_CONTROL_COUNT, sizeof( TimelineItem * ), SortFuncByUse ); + LayoutItems( false ); +} + +void CExpressionToolWorkspace::OnSortByName( void ) +{ + qsort( m_pItems, GLOBAL_STUDIO_FLEX_CONTROL_COUNT, sizeof( TimelineItem * ), SortFuncByName ); + LayoutItems( false ); +} + +void CExpressionToolWorkspace::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !g_pExpressionTool ) + return; + + CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = g_pExpressionTool->GetPixelForTimeValue( duration ) -5; + if ( leftx >= w2() ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcClient.top, leftx, rcClient.bottom ); + +} + +int CExpressionToolWorkspace::GetItemUnderMouse( int mx, int my ) +{ + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item ) + continue; + + if ( !item->GetVisible() ) + continue; + + RECT rc; + item->GetBounds( rc ); + + if ( PtInRect( &rc, pt ) ) + { + return i; + } + } + + return -1; +} + +void CExpressionToolWorkspace::MouseToToolMouse( int& mx, int& my, char *reason ) +{ + POINT pt; + pt.x = mx; + pt.y = my; + + ClientToScreen( (HWND)getHandle(), &pt ); + ScreenToClient( (HWND)getParent()->getHandle(), &pt ); + + mx = pt.x; + my = pt.y; +} + +int CExpressionToolWorkspace::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + switch ( event->event ) + { + case mxEvent::MouseDown: + { + HWND wnd = (HWND)getParent()->getHandle(); + SetFocus( wnd ); + SetWindowPos( wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + { + int mx = (short)event->x; + int my = (short)event->y; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousedown" ); + + g_pExpressionTool->SetClickedPos( mx, my ); + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + + } + + int oldFocus = m_nFocusItem; + m_nFocusItem = GetItemUnderMouse( (short)event->x, (short)event->y ); + + if ( oldFocus != -1 && + oldFocus != m_nFocusItem ) + { + TimelineItem *item = GetItem( oldFocus ); + if ( item ) + { + item->DrawSelf(); + } + } + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + } + } + + iret = 1; + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + // + bool handled = false; + + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + + if ( event->event == mxEvent::MouseDrag ) + { + int mx, my; + + item->GetLastMouse( mx, my ); + mx += rc.left; + my += rc.top; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousedrag" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + handled = true; + } + } + } + + if ( !handled ) + { + int mx = (short)event->x; + int my = (short)event->y; + + mx += TRAY_ITEM_INSET; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousemove" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + } + } + break; + case mxEvent::MouseUp: + { + // + { + int mx = (short)event->x; + int my = (short)event->y; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mouseup" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + + } + + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + } + } + } + break; + case mxEvent::Size: + { + RepositionVSlider(); + LayoutItems(); + iret = 1; + } + break; + case mxEvent::MouseWheeled: + // Tell parent + { + if ( event->modifiers & mxEvent::KeyShift ) + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( g_pExpressionTool->GetToolName() ); + + // Zoom time in / out + if ( event->height > 0 ) + { + g_pChoreoView->SetTimeZoom( g_pExpressionTool->GetToolName(), min( tz + TIME_ZOOM_STEP, MAX_TIME_ZOOM ), false ); + } + else + { + g_pChoreoView->SetTimeZoom( g_pExpressionTool->GetToolName(), min( tz - TIME_ZOOM_STEP, MAX_TIME_ZOOM ), false ); + } + g_pExpressionTool->RepositionHSlider(); + } + redraw(); + iret = 1; + return iret; + } + + int offset = 0; + int jump = 50; + + if ( event->height < 0 ) + { + offset = m_pVertScrollBar->getValue(); + offset += jump; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + } + else + { + offset = m_pVertScrollBar->getValue(); + offset -= jump; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + } + + m_pVertScrollBar->setValue( offset ); + InvalidateRect( (HWND)m_pVertScrollBar->getHandle(), NULL, TRUE ); + m_nTopOffset = offset; + LayoutItems(); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_EXPRESSIONTOOLVSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 100; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 100; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + m_pVertScrollBar->setValue( offset ); + InvalidateRect( (HWND)m_pVertScrollBar->getHandle(), NULL, TRUE ); + m_nTopOffset = offset; + LayoutItems(); + } + } + } + } + break; + } + return iret; +} + +void CExpressionToolWorkspace::HideTimelines( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + Assert( item ); + item->SetVisible( false ); + } + + redraw(); +} + + +TimelineItem *CExpressionToolWorkspace::GetItem( int number ) +{ + if ( number < 0 || number >= GLOBAL_STUDIO_FLEX_CONTROL_COUNT ) + { + return NULL; + } + return m_pItems[ number ]; +} + +TimelineItem *CExpressionToolWorkspace::GetClickedItem( void ) +{ + return GetItem( m_nFocusItem ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ClearClickedItem( void ) +{ + m_nFocusItem = -1; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : force - force vert scrollbar recomputation +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::LayoutItems( bool force /* = false */ ) +{ + int x = TRAY_ITEM_INSET; + int y = - m_nTopOffset; + int width = w2() - 2 * TRAY_ITEM_INSET - m_nScrollbarHeight; + int height; + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item || !item->GetVisible() ) + continue; + + height = item->GetHeight(); + + RECT rcBounds; + rcBounds.left = x; + rcBounds.top = y; + rcBounds.right = x + width; + rcBounds.bottom = y + height; + + item->SetBounds( rcBounds ); + y += height + m_nItemGap; + } + + if ( force || ( ComputeVPixelsNeeded() != m_nLastVPixelsNeeded ) ) + { + RepositionVSlider(); + } + + redraw(); +} + +int CExpressionToolWorkspace::ComputeVPixelsNeeded( void ) +{ + int pixels = 0; + + // Count visible + int c = 0; + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item || !item->GetVisible() ) + continue; + + c += item->GetHeight(); + c += m_nItemGap; + } + + pixels += c; + + return pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::RepositionVSlider( void ) +{ + int pixelsneeded = ComputeVPixelsNeeded(); + + if ( pixelsneeded <= ( h2() )) + { + m_pVertScrollBar->setVisible( false ); + m_nTopOffset = 0; + } + else + { + m_pVertScrollBar->setVisible( true ); + } + + m_pVertScrollBar->setBounds( + w2() - m_nScrollbarHeight, + 0, + m_nScrollbarHeight, + h2() ); + + m_nTopOffset = max( 0, m_nTopOffset ); + m_nTopOffset = min( pixelsneeded, m_nTopOffset ); + + m_pVertScrollBar->setRange( 0, pixelsneeded ); + m_pVertScrollBar->setValue( m_nTopOffset ); + m_pVertScrollBar->setPagesize( h2() ); + + m_nLastVPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::DisableAllExcept( void ) +{ + TimelineItem *keepExpanded = GetClickedItem(); + if ( !keepExpanded ) + return; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Disable All Except" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetActive( item == keepExpanded ? true : false ); + } + + LayoutItems(); + g_pChoreoView->PushRedo( "Disable All Except" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::EnableValid( void ) +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Enable Valid" ); + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetActive( item->IsValid() ); + } + + LayoutItems(); + g_pChoreoView->PushRedo( "Enable Valid" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::CollapseAll( TimelineItem *keepExpanded ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetCollapsed( item == keepExpanded ? false : true ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ExpandAll( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->SetCollapsed( false ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::OnSnapAll() +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap All" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->SnapAll(); + } + + g_pChoreoView->PushRedo( "Snap All" ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::OnDeleteColumn() +{ + float t = g_pExpressionTool->GetTimeForClickedPos(); + + float snapped = FacePoser_SnapTime( t ); + int scenefps = FacePoser_GetSceneFPS(); + + if ( scenefps <= 0 ) + { + Con_Printf( "Can't delete column, scene fps is <= 0 (%i)\n", scenefps ); + return; + } + + int clickedframe = ( int ) ( scenefps * snapped + 0.5f ); + + // One half of 1/fps on each side + float epsilon = epsilon = 0.5f / (float)scenefps; + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Delete Column" ); + strcpy( params.m_szPrompt, "Frame(s) to delete [e.g., 82 or 81-91 ]:" ); + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%i", clickedframe ); + + if ( !InputProperties( ¶ms ) ) + return; + + int deleteframestart; + int deleteframeend; + + char *sep = Q_strstr( params.m_szInputText, "-" ); + if ( sep ) + { + *sep = 0; + deleteframestart = atoi( params.m_szInputText ); + deleteframeend = atoi( sep + 1 ); + deleteframeend = max( deleteframestart, deleteframeend ); + } + else + { + deleteframestart = atoi( params.m_szInputText ); + deleteframeend = deleteframestart; + } + + float start, end; + + start = (float)deleteframestart / (float)scenefps; + end = (float)deleteframeend / (float)scenefps; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Delete Column" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->DeletePoints( start - epsilon, end + epsilon ); + } + + g_pChoreoView->PushRedo( "Delete Column" ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ExpandValid( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + bool valid = item->IsValid(); + item->SetCollapsed( !valid ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpressionToolWorkspace::CountSelectedSamples( void ) +{ + int c = 0; + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + Assert( item ); + item->CountSelected(); + c += item->GetNumSelected(); + } + return c; +} + +void CExpressionToolWorkspace::MoveSelectedSamples( float dfdx, float dfdy, bool snap ) +{ + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); + if ( !e ) + return; + + float eventduration = e->GetDuration(); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + // If the track is a combo type track, then move any underlying selected samples, too + for ( int edittype = 0; edittype <= ( track->IsComboType() ? 1 : 0 ); edittype++ ) + { + for ( int i = 0; i < (int)track->GetNumSamples( edittype ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, edittype ); + if ( !sample || !sample->selected ) + continue; + + sample->time += dfdx; + sample->time = clamp( sample->time, 0.0f, eventduration ); + + if ( snap ) + { + sample->time = FacePoser_SnapTime( sample->time ); + } + + sample->value -= dfdy; + sample->value = clamp( sample->value, 0.0f, 1.0f ); + } + } + + track->Resort(); + + item->DrawSelf(); + } +} + +void CExpressionToolWorkspace::DeleteSelectedSamples( void ) +{ + int i, t; + + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Delete points" ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( "Delete points" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::DeselectAll( void ) +{ + int i, t; + + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + sample->selected = false; + } + } + + item->DrawSelf(); + } +} + +void CExpressionToolWorkspace::SelectPoints( float start, float end ) +{ + int i, t; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + bool inrange = ( sample->time >= start && sample->time <= end ); + sample->selected = inrange; + } + } + + item->DrawSelf(); + } +} + +ExpressionTool::ExpressionTool( mxWindow *parent ) +: IFacePoserToolWindow( "ExpressionTool", "Flex Animation" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_bSuppressLayout = false; + + SetAutoProcess( true ); + + m_pWorkspace = new CExpressionToolWorkspace( this ); + + m_nFocusEventGlobalID = -1; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + m_nDragType = DRAGTYPE_NONE; + + m_nClickedX = 0; + m_nClickedY = 0; + + m_hPrevCursor = 0; + + m_nStartX = 0; + m_nStartY = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_pLastEvent = NULL; + + m_nMousePos[ 0 ] = m_nMousePos[ 1 ] = 0; + + m_flSelection[ 0 ] = m_flSelection[ 1 ] = 0.0f; + m_bSelectionActive = false; + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 500.0f; + + m_flLastDuration = 0.0f; + m_nScrollbarHeight = 12; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_FLEXHSCROLL, mxScrollbar::Horizontal ); + m_pHorzScrollBar->setVisible( false ); + + m_bInSetEvent = false; + m_flScrubberTimeOffset = 0.0f; +} + +ExpressionTool::~ExpressionTool( void ) +{ +} + +void ExpressionTool::DoTrackLookup( CChoreoEvent *event ) +{ + if ( !event || !models->GetActiveStudioModel() ) + return; + + //if ( event->GetTrackLookupSet() ) + // return; + + // Force recompute + SetEvent( event ); +} + +#pragma optimize( "g", off ) + +void ExpressionTool::SetEvent( CChoreoEvent *event ) +{ + if ( m_bInSetEvent ) + return; + + m_bInSetEvent = true; + + if ( event == m_pLastEvent ) + { + if ( event ) + { + float dur = event->GetDuration(); + if ( dur != m_flLastDuration ) + { + m_flLastDuration = dur; + m_nLastHPixelsNeeded = -1; + m_flLeftOffset = 0.0f; + InvalidateLayout(); + } + + m_nFocusEventGlobalID = event->GetGlobalID(); + } + m_bInSetEvent = false; + return; + } + + m_pLastEvent = event; + + m_pWorkspace->HideTimelines(); + + m_nFocusEventGlobalID = -1; + if ( event ) + { + m_nFocusEventGlobalID = event->GetGlobalID(); + + if ( models->GetActiveStudioModel() ) + { + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + // Force re-lookup + event->SetTrackLookupSet( false ); + + SetupFlexControllerTracks( hdr, event ); + + int itemCount = 0; + + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + Assert( track ); + if ( !track ) + continue; + + TimelineItem *item = m_pWorkspace->GetItem( itemCount++ ); + item->SetExpressionInfo( track, track->GetFlexControllerIndex( 0 ) ); + item->SetCollapsed( track->GetNumSamples( 0 ) <= 0 ); + item->SetVisible( true ); + } + + m_pWorkspace->LayoutItems( true ); + } + } + } + + DeselectAll(); + + if ( event ) + { + m_flLastDuration = event->GetDuration(); + } + else + { + m_flLastDuration = 0.0f; + } + + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + + m_bInSetEvent = false; +} + +#pragma optimize( "g", on ) + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::HasCopyData( void ) +{ + return ( m_CopyData[0].Size() != 0 ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *source - +//----------------------------------------------------------------------------- +void ExpressionTool::Copy( CFlexAnimationTrack *source ) +{ + for ( int t = 0; t < 2; t++ ) + { + m_CopyData[ t ].RemoveAll(); + + if ( t == 0 || source->IsComboType() ) + { + for ( int i = 0 ; i < source->GetNumSamples( t ); i++ ) + { + CExpressionSample *s = source->GetSample( i, t ); + m_CopyData[ t ].AddToTail( *s ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *destination - +//----------------------------------------------------------------------------- +void ExpressionTool::Paste( CFlexAnimationTrack *destination ) +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Paste" ); + + destination->Clear(); + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = 0; i < m_CopyData[ t ].Size() ; i++ ) + { + CExpressionSample *s = &m_CopyData[ t ][ i ]; + + if ( t == 0 || destination->IsComboType() ) + { + destination->AddSample( s->time, s->value, t ); + } + } + + destination->Resort( t ); + } + g_pChoreoView->PushRedo( "Paste" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent *ExpressionTool::GetSafeEvent( void ) +{ + if ( m_nFocusEventGlobalID == -1 ) + return NULL; + + if ( !g_pChoreoView ) + return NULL; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return NULL; + + // look to see if it's focused any any event + for ( int i = 0; i < scene->GetNumEvents() ; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e || e->GetType() != CChoreoEvent::FLEXANIMATION ) + continue; + + if ( e->GetGlobalID() == m_nFocusEventGlobalID ) + { + DoTrackLookup( e ); + return e; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void ExpressionTool::GetScrubHandleRect( RECT& rcHandle, bool clipped ) +{ + float pixel = 0.0f; + if ( m_pWorkspace->w2() > 0 ) + { + pixel = GetPixelForTimeValue( m_flScrub ); + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); + } + } + + rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle ) +{ + HBRUSH br = CreateSolidBrush( RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", m_flScrub ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + float st, ed; + st = ev->GetStartTime(); + ed = ev->GetEndTime(); + + float dt = ed - st; + if ( dt > 0.0f ) + { + sprintf( sz, "%.3f", st + m_flScrub ); + } + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsProcessing( void ) +{ + if ( !GetSafeEvent() ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +bool ExpressionTool::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +void ExpressionTool::Think( float dt ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + bool scrubbing = IsScrubbing(); + + ScrubThink( dt, scrubbing ); +} + +void ExpressionTool::SetScrubTime( float t ) +{ + m_flScrub = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + m_flScrub; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->DrawScrubHandle(); + } +} + +void ExpressionTool::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + m_flScrubTarget; + + g_pChoreoView->SetScrubTargetTime( realtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void ExpressionTool::ScrubThink( float dt, bool scrubbing ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub + maxmove ); + } + } + else + { + if ( -d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub - maxmove ); + } + } + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void ExpressionTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + RECT rcSelection; + GetWorkspaceRect( rcSelection ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcSelection.top, w2(), rcSelection.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcSelection.bottom, w2(), rcSelection.bottom ); + + if ( m_bSelectionActive ) + { + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + int left, right; + left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + + rcSelection.left = left; + rcSelection.right = right; + rcSelection.bottom = TRAY_HEIGHT; + + drawHelper.DrawFilledRect( RGB( 200, 220, 230 ), rcSelection ); + + drawHelper.DrawColoredLine( RGB( 100, 100, 255 ), PS_SOLID, 3, rcSelection.left, rcSelection.top, rcSelection.left, rcSelection.bottom ); + drawHelper.DrawColoredLine( RGB( 100, 100, 255 ), PS_SOLID, 3, rcSelection.right, rcSelection.top, rcSelection.right, rcSelection.bottom ); + + } + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + RECT rcText; + drawHelper.GetClientRect( rcText ); + rcText.top += GetCaptionHeight()+1; + rcText.bottom = rcText.top + 13; + rcText.left += 5; + rcText.right -= 5; + + OffsetRect( &rcText, 0, 12 ); + + int current, total; + + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + RECT rcUndo = rcText; + OffsetRect( &rcUndo, 0, 2 ); + + drawHelper.DrawColoredText( "Small Fonts", 8, FW_NORMAL, RGB( 0, 100, 0 ), rcUndo, + "Undo: %i/%i", current, total ); + } + + rcText.left += 60; + + // Found it, write out description + // + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 150, 100 ), rcText, + "Event: %s", + ev->GetName() ); + + OffsetRect( &rcText, 0, 30 ); + + rcText.left = 5; + + RECT timeRect = rcText; + + timeRect.right = timeRect.left + 100; + + char sz[ 32 ]; + + float st, ed; + + GetStartAndEndTime( st, ed ); + + st += ev->GetStartTime(); + ed += ev->GetStartTime(); + + Q_snprintf( sz, sizeof( sz ), "%.2f", st ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + + timeRect = rcText; + + Q_snprintf( sz, sizeof( sz ), "%.2f", ed ); + + int textW = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + timeRect.right = w2() - 10; + timeRect.left = timeRect.right - textW; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + } + + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + DrawScrubHandle( drawHelper, rcHandle ); + + DrawRelativeTags( drawHelper ); + + RECT rcPos; + GetMouseOverPosRect( rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); + + DrawEventEnd( drawHelper ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +//----------------------------------------------------------------------------- +bool ExpressionTool::GetTimingTagRect( RECT& rcClient, CChoreoEvent *event, CFlexTimingTag *tag, RECT& rcTag ) +{ + rcTag = rcClient; + + int tagx = GetPixelForTimeValue( tag->GetStartTime() - event->GetStartTime() ); + + rcTag.top = rcClient.bottom - 6; + rcTag.bottom = rcTag.top + 6; + rcTag.left = tagx - 3; + rcTag.right = tagx + 3; + + return true; +} + +// Get workspace min, max point in terms of tool window +void ExpressionTool::GetWorkspaceLeftRight( int& left, int& right ) +{ + POINT pt; + pt.x = TRAY_ITEM_INSET; + pt.y = 0; + + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + left = (short)pt.x; + + pt.x = m_pWorkspace->w2() - TRAY_ITEM_INSET - 12; + pt.y = 0; + + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + right = (short)pt.x; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : CFlexTimingTag +//----------------------------------------------------------------------------- +CFlexTimingTag *ExpressionTool::IsMouseOverTag( int mx, int my ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return NULL; + + RECT rcClient; + GetClientRect( (HWND)getHandle(), &rcClient ); + + int left, right; + + GetWorkspaceLeftRight( left, right ); + + rcClient.left = left; + rcClient.right = right; + rcClient.top = GetCaptionHeight(); + rcClient.bottom = rcClient.top + TRAY_HEIGHT; + + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int i = 0 ; i < event->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *tag = event->GetTimingTag( i ); + if ( !tag ) + continue; + + RECT rcTag; + + if ( !GetTimingTagRect( rcClient, event, tag, rcTag ) ) + continue; + + if ( !PtInRect( &rcTag, pt ) ) + continue; + + return tag; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( event->GetDuration() <= 0.0f ) + return; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + int left, right; + + GetWorkspaceLeftRight( left, right ); + + rcClient.top += GetCaptionHeight(); + + rcClient.left = left; + rcClient.right = right; + + rcClient.bottom = rcClient.top + TRAY_HEIGHT; + + // Iterate relative tags + for ( int i = 0; i < scene->GetNumActors(); i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + // add each tag to combo box + for ( int t = 0; t < e->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *tag = e->GetRelativeTag( t ); + if ( !tag ) + continue; + + //SendMessage( control, CB_ADDSTRING, 0, (LPARAM)va( "\"%s\" \"%s\"", tag->GetName(), e->GetParameters() ) ); + bool clipped; + int tagx = GetPixelForTimeValue( tag->GetStartTime() - event->GetStartTime(), &clipped ); + if ( clipped ) + continue; + + //drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); + + RECT rcMark; + rcMark = rcClient; + rcMark.top = rcClient.bottom - 6; + rcMark.left = tagx - 3; + rcMark.right = tagx + 3; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 0, 100, 250 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = tagx - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, tag->GetName() ); + + } + } + } + } + + for ( int t = 0; t < event->GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tag = event->GetTimingTag( t ); + if ( !tag ) + continue; + + RECT rcMark; + + if ( !GetTimingTagRect( rcClient, event, tag, rcMark ) ) + continue; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 250, 100, 0 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 20; + + char text[ 256 ]; + sprintf( text, "%s", tag->GetName() ); + if ( tag->GetLocked() ) + { + strcat( text, " - locked" ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, text ); + rcText.left = ( rcMark.left + rcMark.right ) / 2 - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 100, 0 ), rcText, text ); + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::ShowContextMenu( mxEvent *event, bool include_track_menus ) +{ + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + TimelineItem *item = NULL; + CFlexAnimationTrack *track = NULL; + + if ( include_track_menus ) + { + item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->CountSelected(); + track = item->GetSafeTrack(); + } + } + + int current, total; + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + if ( current > 0 ) + { + pop->add( va( "Undo %s", g_pChoreoView->GetUndoDescription() ), IDC_UNDO_FA ); + } + + if ( current <= total - 1 ) + { + pop->add( va( "Redo %s", g_pChoreoView->GetRedoDescription() ), IDC_REDO_FA ); + } + pop->addSeparator(); + } + + // Create expand menu + mxPopupMenu *expand = new mxPopupMenu(); + if ( item && track && item->IsCollapsed() ) + { + expand->add( va( "Track '%s'", track->GetFlexControllerName() ), IDC_TL_EXPAND ); + } + expand->add( "All tracks", IDC_EXPANDALL ); + expand->add( "Used tracks", IDC_EXPANDVALID ); + + pop->addMenu( "Expand", expand ); + + mxPopupMenu *collapse = new mxPopupMenu; + + if ( item && track && !item->IsCollapsed() ) + { + collapse->add( va( "Track '%s'", track->GetFlexControllerName() ), IDC_TL_COLLAPSE ); + collapse->add( va( "All tracks except '%s'", track->GetFlexControllerName() ), IDC_COLLAPSE_ALL_EXCEPT ); + } + + collapse->add( "All tracks", IDC_COLLAPSEALL ); + + pop->addMenu( "Collapse", collapse ); + + pop->addSeparator(); + + pop->add( va( "Enable all valid" ), IDC_ENABLE_ALL_VALID ); + + if ( item && track ) + { + if ( item->IsActive() ) + { + pop->add( va( "Disable '%s'", track->GetFlexControllerName() ), IDC_TL_DISABLE ); + } + else + { + pop->add( va( "Enable '%s'", track->GetFlexControllerName() ), IDC_TL_ENABLE ); + } + pop->add( va( "Disable all except '%s'", track->GetFlexControllerName() ), IDC_DISABLE_ALL_EXCEPT ); + + pop->addSeparator(); + pop->add( "Copy", IDC_TL_COPY ); + if ( HasCopyData() ) + { + pop->add( "Paste", IDC_TL_PASTE ); + } + + pop->addSeparator(); + if ( item->GetNumSelected() > 0 ) + { + pop->add( va( "Delete" ), IDC_TL_DELETE ); + pop->add( "Deselect all", IDC_TL_DESELECT ); + pop->add( va( "Scale selected..." ), IDC_FLEX_SCALESAMPLES ); + } + pop->add( "Select all", IDC_TL_SELECTALL ); + + if ( FacePoser_IsSnapping() ) + { + mxPopupMenu *snap = new mxPopupMenu(); + + snap->add( va( "All points" ), IDC_TL_SNAPALL ); + snap->add( va( "All points in '%s'", track->GetFlexControllerName() ), IDC_TL_SNAPPOINTS ); + snap->add( va( "Selected points in '%s'", track->GetFlexControllerName() ), IDC_TL_SNAPSELECTED ); + + pop->addSeparator(); + + pop->addMenu( "Snap", snap ); + } + + if ( track->IsComboType() ) + { + pop->addSeparator(); + + if ( item->GetEditType() == 0 ) + { + pop->add( "Edit <left/right>", IDC_TL_EDITLEFTRIGHT ); + } + else + { + pop->add( "Edit <amount>", IDC_TL_EDITNORMAL ); + } + } + + pop->addSeparator(); + mxPopupMenu *heightMenu = new mxPopupMenu(); + heightMenu->add( va( "Reset '%s'", track->GetFlexControllerName() ) , IDC_ET_RESET_ITEM_SIZE ); + heightMenu->add( "Reset All", IDC_ET_RESET_ALL_ITEM_SIZES ); + pop->addMenu( "Height", heightMenu ); + + pop->addSeparator(); + pop->add( "Edge Properties...", IDC_ET_EDGEPROPERTIES ); + } + pop->addSeparator(); + + mxPopupMenu *tagmenu = new mxPopupMenu(); + + CFlexTimingTag *tag = IsMouseOverTag( (short)event->x, (short)event->y ); + if ( tag ) + { + if ( tag->GetLocked() ) + { + tagmenu->add( va( "Unlock tag '%s'...", tag->GetName() ), IDC_UNLOCK_TIMING_TAG ); + } + else + { + tagmenu->add( va( "Lock tag '%s'...", tag->GetName() ), IDC_LOCK_TIMING_TAG ); + } + tagmenu->addSeparator(); + tagmenu->add( va( "Delete tag '%s'...", tag->GetName() ), IDC_DELETE_TIMING_TAG ); + } + else + { + tagmenu->add( "Insert...", IDC_INSERT_TIMING_TAG ); + } + + bool bMouseOverSelection = IsMouseOverSelection( (short)event->x, (short)event->y ); + + if ( bMouseOverSelection || HasCopiedColumn() ) + { + mxPopupMenu *selectionMenu = new mxPopupMenu(); + + if ( bMouseOverSelection ) + { + selectionMenu->add( "Copy samples", IDC_ET_SELECTION_COPY ); + } + + if ( HasCopiedColumn() ) + { + selectionMenu->add( "Paste samples", IDC_ET_SELECTION_PASTE ); + } + + if ( bMouseOverSelection ) + { + selectionMenu->addSeparator(); + selectionMenu->add( "Delete samples", IDC_ET_SELECTION_DELETE ); + selectionMenu->add( "Delete samples and shift remainder", IDC_ET_SELECTION_EXCISE ); + } + pop->addMenu( "Column", selectionMenu ); + } + + + + pop->addMenu( "Timing Tags", tagmenu ); + + if ( FacePoser_IsSnapping() ) + { + pop->addSeparator(); + pop->add( "Delete keys by frame", IDC_TL_DELETECOLUMN ); + pop->addSeparator(); + } + + mxPopupMenu *flexmenu = new mxPopupMenu(); + + flexmenu->add( "Copy to sliders", IDC_COPY_TO_FLEX ); + flexmenu->add( "Copy from sliders", IDC_COPY_FROM_FLEX ); + pop->addMenu( "Flex", flexmenu ); + + pop->add( "Create expression...", IDC_NEW_EXPRESSION_FROM_FLEXANIMATION ); + + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + mxPopupMenu *importexport = new mxPopupMenu(); + + importexport->add( "Export flex animation...", IDC_EXPORT_FA ); + importexport->add( "Import flex animation...", IDC_IMPORT_FA ); + + pop->addMenu( "Import/Export", importexport ); + } + + pop->add( va( "Change scale..." ), IDC_FLEX_CHANGESCALE ); + + mxPopupMenu *sortmenu = new mxPopupMenu(); + sortmenu->add( "Sort by name", IDC_ET_SORT_BY_NAME ); + sortmenu->add( "Sort by used", IDC_ET_SORT_BY_USED ); + + pop->addMenu( "Sort", sortmenu ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +void ExpressionTool::SetClickedPos( int x, int y ) +{ + m_nClickedX = x; + m_nClickedY = y; +} + +float ExpressionTool::GetTimeForClickedPos( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return 0.0f; + + float t = GetTimeValueForMouse( m_nClickedX ); + + // Get spline intensity for controller + float faketime = e->GetStartTime() + t; + return faketime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void ExpressionTool::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rc; + GetWorkspaceRect( rc ); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + default: + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.top = rcScrub.bottom; + + rcStart.bottom = h2(); + } + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + rcStart.top = rc.top; + rcStart.bottom = h2(); + } + break; + case DRAGTYPE_SELECTSAMPLES: + { + float st = GetTimeValueForMouse( startx ); + rcStart.left = GetPixelForTimeValue( st ); + rcStart.right = rcStart.left; + + m_nStartX = rcStart.left; + m_nLastX = rcStart.left; + } + case DRAGTYPE_MOVESELECTIONSTART: + case DRAGTYPE_MOVESELECTIONEND: + { + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + } + break; + case DRAGTYPE_MOVESELECTION: + { + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + + // Compute left/right pixels for selection + rcStart.left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + rcStart.right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + } + break; + } + + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect(); +} + +void ExpressionTool::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + int my = (short)event->y; + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + { + OffsetRect( &f->m_rcFocus, ( (short)event->x - m_nStartX ), 0 ); + } + break; + case DRAGTYPE_SELECTSAMPLES: + { + float st = GetTimeValueForMouse( mx ); + int snapx = GetPixelForTimeValue( st ); + f->m_rcFocus.left = min( snapx, m_nStartX ); + f->m_rcFocus.right = max( snapx, m_nStartY ); + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &f->m_rcFocus, offset.x, 0 ); + + } + break; + } + } + + DrawFocusRect(); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + if ( IsMouseOverScrubHandle( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( mx, my ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( event->modifiers & mxEvent::KeyShift ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + } + + switch ( m_nDragType ) + { + default: + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + ApplyBounds( mx, my ); + } + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + } + break; + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +int ExpressionTool::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int w, h; + w = event->width; + h = event->height; + + m_pWorkspace->setBounds( 5, TRAY_HEIGHT + GetCaptionHeight(), w - 10, h - ( TRAY_HEIGHT + 5 + GetCaptionHeight() ) - m_nScrollbarHeight ); + + m_nLastHPixelsNeeded = 0; + InvalidateLayout(); + + iret = 1; + } + break; + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + g_pChoreoView->SetPreservedTimeZoom( this, tz ); + } + //RepositionHSlider(); + m_pWorkspace->redraw(); + redraw(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { +// bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + iret = 1; + + int mx = (short)event->x; + int my = (short)event->y; + + SetClickedPos( mx, my ); + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + return iret; + } + + if ( m_nDragType == DRAGTYPE_NONE ) + { + if ( IsMouseOverScrubHandle( event ) ) + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + StartDragging( DRAGTYPE_SCRUBBER, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( m_nClickedX, m_nClickedY ) ) + { + StartDragging( DRAGTYPE_FLEXTIMINGTAG, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverPoints( m_nClickedX, m_nClickedY ) ) + { + if ( !m_bSelectionActive ) + { + StartDragging( DRAGTYPE_SELECTSAMPLES, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + // Either move, move edge if ctrl key is held, or deselect + if ( IsMouseOverSelection( m_nClickedX,m_nClickedY ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONSTART, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONEND, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( shiftdown ) + { + StartDragging( DRAGTYPE_MOVESELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + else + { + m_bSelectionActive = false; + redraw(); + return iret; + } + } + } + else + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + SetScrubTargetTime( t ); + } + } + + CalcBounds( m_nDragType ); + } + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + int mx = (short)event->x; + int my = (short)event->y; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + OnMouseMove( event ); + + iret = 1; + } + break; + case mxEvent::MouseUp: + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + return 1; + } + + int mx = (short)event->x; + int my = (short)event->y; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_NONE: + break; + case DRAGTYPE_SELECTSAMPLES: + FinishSelect( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTION: + FinishMoveSelection( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTIONSTART: + FinishMoveSelectionStart( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTIONEND: + FinishMoveSelectionEnd( m_nStartX, mx ); + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + +// int dx = mx - m_nStartX; +// int dy = my = m_nStartY; + + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + t += m_flScrubberTimeOffset; + m_flScrubberTimeOffset = 0.0f; + ForceScrubPosition( t ); + } + } + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + ApplyBounds( mx, my ); + +// int dx = mx - m_nStartX; +// int dy = my = m_nStartY; + + // Compute dx, dy and apply to sections + //Con_Printf( "dx == %i\n", dx ); + CFlexTimingTag *tag = IsMouseOverTag( m_nStartX, m_nStartY ); + CChoreoEvent *ev = GetSafeEvent(); + if ( tag && g_pChoreoView && ev && ev->GetDuration() ) + { + float t = GetTimeValueForMouse( mx ); + + float percent = t / ev->GetDuration(); + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Move Timing Tag" ); + + if ( tag->GetLocked() ) + { + // Resample all control points on right/left + // of locked tags all the way to the next lock or edge + ResampleControlPoints( tag, percent ); + } + + tag->SetPercentage( percent ); + + g_pChoreoView->PushRedo( "Move Timing Tag" ); + } + + LayoutItems( true ); + redraw(); + } + break; + } + + m_nDragType = DRAGTYPE_NONE; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_ET_RESET_ITEM_SIZE: + OnResetItemSize(); + break; + case IDC_ET_RESET_ALL_ITEM_SIZES: + OnResetAllItemSizes(); + break; + case IDC_ET_SELECTION_DELETE: + OnDeleteSelection( false ); + break; + case IDC_ET_SELECTION_EXCISE: + OnDeleteSelection( true ); + break; + case IDC_ET_SELECTION_COPY: + OnCopyColumn(); + break; + case IDC_ET_SELECTION_PASTE: + OnPasteColumn(); + break; + case IDC_ET_SORT_BY_USED: + OnSortByUsed(); + break; + case IDC_ET_SORT_BY_NAME: + OnSortByName(); + break; + case IDC_EXPORT_FA: + OnExportFlexAnimation(); + break; + case IDC_IMPORT_FA: + OnImportFlexAnimation(); + break; + case IDC_LOCK_TIMING_TAG: + LockTimingTag(); + break; + case IDC_UNLOCK_TIMING_TAG: + UnlockTimingTag(); + break; + case IDC_DELETE_TIMING_TAG: + DeleteFlexTimingTag( m_nClickedX, m_nClickedY ); + break; + case IDC_INSERT_TIMING_TAG: + AddFlexTimingTag( m_nClickedX ); + break; + case IDC_EXPANDALL: + m_pWorkspace->ExpandAll(); + break; + case IDC_COLLAPSEALL: + m_pWorkspace->CollapseAll( NULL ); + break; + case IDC_COLLAPSE_ALL_EXCEPT: + m_pWorkspace->CollapseAll( m_pWorkspace->GetClickedItem() ); + break; + case IDC_EXPANDVALID: + m_pWorkspace->ExpandValid(); + break; + case IDC_COPY_TO_FLEX: + OnCopyToFlex( true ); + break; + case IDC_COPY_FROM_FLEX: + OnCopyFromFlex( false ); + break; + case IDC_NEW_EXPRESSION_FROM_FLEXANIMATION: + OnNewExpression(); + break; + case IDC_UNDO_FA: + OnUndo(); + break; + case IDC_REDO_FA: + OnRedo(); + break; + case IDC_TL_EDITNORMAL: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetEditType( 0 ); + item->DrawSelf(); + } + } + break; + case IDC_TL_EDITLEFTRIGHT: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetEditType( 1 ); + item->DrawSelf(); + } + } + break; + case IDC_TL_EXPAND: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetCollapsed( false ); + } + LayoutItems(); + } + break; + case IDC_TL_COLLAPSE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetCollapsed( true ); + } + LayoutItems(); + } + break; + case IDC_TL_ENABLE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Enable item" ); + + item->SetActive( true ); + + g_pChoreoView->PushRedo( "Enable item" ); + + item->DrawSelf(); + } + } + break; + case IDC_TL_DISABLE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Disable item" ); + + item->SetActive( false ); + + g_pChoreoView->PushRedo( "Disable item" ); + + item->DrawSelf(); + } + } + break; + case IDC_TL_COPY: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Copy(); + } + } + break; + case IDC_TL_PASTE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Paste(); + item->DrawSelf(); + } + } + break; + case IDC_TL_DELETE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Delete(); + item->DrawSelf(); + } + } + break; + case IDC_TL_DESELECT: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->DeselectAll(); + item->DrawSelf(); + } + } + break; + case IDC_TL_SELECTALL: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SelectAll(); + item->DrawSelf(); + } + } + break; + case IDC_DISABLE_ALL_EXCEPT: + { + m_pWorkspace->DisableAllExcept(); + } + break; + case IDC_ENABLE_ALL_VALID: + { + m_pWorkspace->EnableValid(); + } + break; + case IDC_TL_SNAPSELECTED: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap Selected" ); + + item->SnapSelected(); + + g_pChoreoView->PushRedo( "Snap Selected" ); + item->DrawSelf(); + } + } + break; + case IDC_TL_SNAPPOINTS: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap Item" ); + + item->SnapAll(); + + g_pChoreoView->PushRedo( "Snap Item" ); + item->DrawSelf(); + } + } + break; + case IDC_TL_DELETECOLUMN: + { + m_pWorkspace->OnDeleteColumn(); + } + break; + case IDC_TL_SNAPALL: + { + m_pWorkspace->OnSnapAll(); + } + break; + case IDC_FLEXHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_FLEX_CHANGESCALE: + { + OnChangeScale(); + } + break; + case IDC_FLEX_SCALESAMPLES: + { + OnScaleSamples(); + } + break; + case IDC_ET_EDGEPROPERTIES: + { + OnEdgeProperties(); + } + break; + } + } + break; + case mxEvent::KeyDown: + case mxEvent::KeyUp: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + iret = item->handleEvent( event ); + } + + if ( !iret ) + { + switch ( event->key ) + { + default: + break; + case VK_ESCAPE: + { + DeselectAll(); + iret = 1; + } + break; + } + } + } + break; + } + return iret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : false - +//----------------------------------------------------------------------------- +void ExpressionTool::LayoutItems( bool force /*= false*/ ) +{ + m_pWorkspace->LayoutItems( force ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void ExpressionTool::AddFlexTimingTag( int mx ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( event->GetType() != CChoreoEvent::FLEXANIMATION ) + { + Con_ErrorPrintf( "Timing Tag: Can only tag FLEXANIMATION events\n" ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Event Tag Name" ); + strcpy( params.m_szPrompt, "Name:" ); + + strcpy( params.m_szInputText, "" ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Timing Tag Name: No name entered!\n" ); + return; + } + + // Convert click to frac + float t = GetTimeValueForMouse( mx ); + float frac = 0.0f; + if ( event->GetDuration() ) + { + frac = t / event->GetDuration(); + frac = clamp( frac, 0.0f, 1.0f ); + } + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Add Timing Tag" ); + + event->AddTimingTag( params.m_szInputText, frac, true ); + + g_pChoreoView->PushRedo( "Add Timing Tag" ); + + // Redraw this window + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::DeleteFlexTimingTag( int mx, int my ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( mx, my ); + if ( !tag ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Delete Timing Tag" ); + + event->RemoveTimingTag( tag->GetName() ); + + g_pChoreoView->PushRedo( "Delete Timing Tag" ); + + LayoutItems( true ); + // Redraw this window + redraw(); + +} + +void ExpressionTool::LockTimingTag( void ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( !tag ) + return; + + if ( tag->GetLocked() ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Lock Timing Tag" ); + + tag->SetLocked( true ); + + g_pChoreoView->PushRedo( "Lock Timing Tag" ); + + redraw(); +} + +void ExpressionTool::UnlockTimingTag( void ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( !tag ) + return; + + if ( !tag->GetLocked() ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Unlock Timing Tag" ); + + tag->SetLocked( false ); + + g_pChoreoView->PushRedo( "Unlock Timing Tag" ); + + redraw(); +} + +void ExpressionTool::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +void ExpressionTool::CalcBounds( int movetype ) +{ + switch ( movetype ) + { + default: + case DRAGTYPE_NONE: + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + break; + case DRAGTYPE_SCRUBBER: + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + m_bUseBounds = true; + + int left, right; + GetWorkspaceLeftRight( left, right ); + + m_nMinX = left; + m_nMaxX = right; + + RECT rcClient; + rcClient.left = left; + rcClient.right = right; + rcClient.top = 0; + rcClient.bottom = TRAY_HEIGHT; + + CFlexTimingTag *tag = IsMouseOverTag( m_nStartX, m_nStartY ); + if ( tag && + tag->GetOwner() ) + { + CChoreoEvent *e = tag->GetOwner(); + + float st = e->GetStartTime(); + float ed = e->GetEndTime(); + + if ( ed > st ) + { + + + // Find previous tag, if any + CFlexTimingTag *prev = NULL; + CFlexTimingTag *next = NULL; + + for ( int i = 0; i < e->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *test = e->GetTimingTag( i ); + if ( test != tag ) + continue; + + // Found it + if ( i > 0 ) + { + prev = e->GetTimingTag( i - 1 ); + } + + if ( i + 1 < e->GetNumTimingTags() ) + { + next = e->GetTimingTag( i + 1 ); + } + break; + } + + if ( prev ) + { + // Compute x pixel of prev tag + float frac = ( prev->GetStartTime() - st ) / ( ed - st ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + int tagx = rcClient.left + (int)( frac * (float)( rcClient.right - rcClient.left ) ); + + m_nMinX = max( m_nMinX, tagx + 5 ); + } + } + + if ( next ) + { + // Compute x pixel of next tag + float frac = ( next->GetStartTime() - st ) / ( ed - st ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + int tagx = rcClient.left + (int)( frac * (float)( rcClient.right - rcClient.left ) ); + m_nMaxX = min( m_nMaxX, tagx - 5 ); + } + } + } + + } + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +// newposition - +//----------------------------------------------------------------------------- +void ExpressionTool::ResampleControlPoints( CFlexTimingTag *tag, float newposition ) +{ + CChoreoEvent *e = tag->GetOwner(); + if ( !e ) + return; + + float duration = e->GetDuration(); + + float leftedge = 0.0f; + float rightedge = duration; + + // Find neighboring locked tags, if any + CFlexTimingTag *prev = NULL; + CFlexTimingTag *next = NULL; + + int i; + for ( i = 0; i < e->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *test = e->GetTimingTag( i ); + if ( test != tag ) + continue; + + // Found it + if ( i > 0 ) + { + int i1 = i - 1; + while ( 1 ) + { + if ( i1 < 0 ) + { + prev = NULL; + break; + } + + prev = e->GetTimingTag( i1 ); + if ( prev->GetLocked() ) + break; + + i1--; + } + } + + if ( i + 1 < e->GetNumTimingTags() ) + { + int i1 = i + 1; + while ( 1 ) + { + if ( i1 >= e->GetNumTimingTags() ) + { + next = NULL; + break; + } + + next = e->GetTimingTag( i1 ); + if ( next->GetLocked() ) + break; + + i1++; + } + } + break; + } + + if ( prev ) + { + leftedge = prev->GetPercentage() * duration; + } + + if ( next ) + { + rightedge = next->GetPercentage() * duration; + } + + // Now, using the tags old position as a pivot, rescale intervening + // sample points based on size delta of new vs old range + float oldpivot = tag->GetPercentage() * duration; + float newpivot = newposition * duration; + + float oldleftrange = oldpivot - leftedge; + float oldrightrange = rightedge - oldpivot; + + float newleftrange = newpivot - leftedge; + float newrightrange = rightedge - newpivot; + + if ( oldleftrange <= 0.0f || + oldrightrange <= 0.0f || + newleftrange <= 0.0f || + newrightrange <= 0.0f ) + { + Con_Printf( "Range problem!!! avoiding division by zero\n" ); + return; + } + + for ( i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int t = 0; t < ( track->IsComboType() ? 2 : 1 ); t++ ) + { + for ( int j = 0; j < track->GetNumSamples( t ); j++ ) + { + CExpressionSample *s = track->GetSample( j, t ); + if ( !s ) + continue; + + float oldtime = s->time; + + // In old range? + if ( oldtime < leftedge ) + continue; + if ( oldtime > rightedge ) + continue; + + // In left or right side( tiebreak toward left ) + float newtime = oldtime; + + if ( oldtime <= oldpivot ) + { + float n = ( oldtime - leftedge ) / oldleftrange; + newtime = leftedge + n * newleftrange; + } + else + { + float n = ( oldtime - oldpivot ) / oldrightrange; + newtime = newpivot + n * newrightrange; + } + + //newtime = FacePoser_SnapTime( newtime ); + + s->time = newtime; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnNewExpression( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "ExpressionTool::OnNewExpression: Can't create new face pose, must load a model first!\n" ); + return; + } + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + { + Con_ErrorPrintf( "ExpressionTool::OnNewExpression: Can't create new face pose, must load an expression file first!\n" ); + return; + } + + g_pExpressionTrayTool->Deselect(); + + float t = GetTimeValueForMouse( m_nClickedX ); + + // Get spline intensity for controller + float faketime = e->GetStartTime() + t; + + float settings[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weights[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + memset( settings, 0, sizeof( settings ) ); + memset( weights, 0, sizeof( settings ) ); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( faketime, side ); + + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + else + { + int controller = track->GetFlexControllerIndex( 0 ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( faketime, 0 ); + + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + + CExpressionParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Add Expression" ); + strcpy( params.m_szName, "" ); + strcpy( params.m_szDescription, "" ); + + if ( !ExpressionProperties( ¶ms ) ) + return; + + if ( ( strlen( params.m_szName ) <= 0 ) || + !stricmp( params.m_szName, "unnamed" ) ) + { + Con_ErrorPrintf( "You must type in a valid name\n" ); + return; + } + + if ( ( strlen( params.m_szDescription ) <= 0 ) || + !stricmp( params.m_szDescription, "description" ) ) + { + Con_ErrorPrintf( "You must type in a valid description\n" ); + return; + } + + active->AddExpression( params.m_szName, params.m_szDescription, settings, weights, true, true ); +} + +LocalFlexController_t FindFlexControllerIndexByName( StudioModel *model, char const *searchname ) +{ + if ( !model ) + return LocalFlexController_t(-1); + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return LocalFlexController_t(-1); + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + char const *name = hdr->pFlexcontroller( i )->pszName(); + if ( !name ) + continue; + + if ( strcmp( name, searchname ) ) + continue; + + return i; + } + return LocalFlexController_t(-1); +} + +void ExpressionTool::OnCopyToFlex( bool isEdited ) +{ + // local time in the expression tool for the last mouse click + float t = GetTimeValueForMouse( m_nClickedX ); + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float scenetime = e->GetStartTime() + t; + + OnCopyToFlex( scenetime, isEdited ); + + return; +} + + +void ExpressionTool::OnCopyToFlex( float scenetime, bool isEdited ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + bool needundo = false; + + float *settings = NULL; + float *weights = NULL; + CExpression *exp = NULL; + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + exp = active->GetExpression( index ); + if ( exp ) + { + needundo = true; + settings = exp->GetSettings(); + weights = exp->GetWeights(); + } + } + } + + if ( needundo && exp ) + { + exp->PushUndoInformation(); + active->SetDirty( true ); + } + + g_pFlexPanel->ResetSliders( false, true ); + + StudioModel *model = models->GetActiveStudioModel(); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, side ); + + g_pFlexPanel->SetSlider( controller, flIntensity ); + g_pFlexPanel->SetInfluence( controller, 1.0f ); + g_pFlexPanel->SetEdited( controller, isEdited ); + if( model ) + { + LocalFlexController_t raw = track->GetRawFlexControllerIndex( side ); + if ( raw != LocalFlexController_t(-1) ) + { + model->SetFlexController( raw, flIntensity ); + } + } + if ( settings && weights ) + { + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + } + + if ( needundo && exp ) + { + exp->PushRedoInformation(); + } +} + +void ExpressionTool::OnCopyFromFlex( bool isEdited ) +{ + // local time in the expression tool for the last mouse click + float t = GetTimeValueForMouse( m_nClickedX ); + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float scenetime = e->GetStartTime() + t; + + OnCopyFromFlex( scenetime, isEdited ); + + return; +} + +void ExpressionTool::OnSetSingleKeyFromFlex( char const *sliderName ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float scenetime = g_pChoreoView->GetScene()->GetTime(); + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + scenetime = FacePoser_SnapTime( scenetime ); + + float relativetime = scenetime - e->GetStartTime(); + + // Get spline intensity for controller + + float setting; + float influence; + float minvalue, maxvalue; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Set Single Key" ); + + for (int j = 0; j < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; j++) + { + if ( !g_pFlexPanel->IsValidSlider( j ) ) + continue; + + if ( Q_stricmp( g_pFlexPanel->getLabel(), sliderName ) ) + continue; + + setting = g_pFlexPanel->GetSliderRawValue( j ); + influence = g_pFlexPanel->GetInfluence( j ); + + // g_pFlexPanel->SetEdited( j, isEdited ); + + g_pFlexPanel->GetSliderRange( j, minvalue, maxvalue ); + + bool found = false; + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks() && !found; i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + if ( track->GetFlexControllerIndex( side ) != j ) + continue; + + float normalized = setting; + if ( side == 0 ) + { + if ( minvalue != maxvalue ) + { + normalized = ( setting - minvalue ) / ( maxvalue - minvalue ); + } + if (track->IsInverted()) + { + normalized = 1.0 - normalized; + } + } + + found = true; + + int nSampleCount = track->GetNumSamples( side ); + + int j = 0; + for ( ; j < nSampleCount; ++j ) + { + CExpressionSample *s = track->GetSample( j, side ); + if ( s->time == relativetime ) + break; + } + + if ( j >= nSampleCount ) + { + track->AddSample( relativetime, normalized, side ); + track->Resort( side ); + } + else + { + CExpressionSample *s = track->GetSample( j, side ); + s->value = normalized; + } + + track->SetTrackActive( true ); + + break; + } + } + } + + g_pChoreoView->PushRedo( "Set Single Key" ); + + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::OnCopyFromFlex( float scenetime, bool isEdited ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + scenetime = FacePoser_SnapTime( scenetime ); + + float relativetime = scenetime - e->GetStartTime(); + + // Get spline intensity for controller + + float setting; + float influence; + float minvalue, maxvalue; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Copy from Flex" ); + + for (int j = 0; j < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; j++) + { + if ( !g_pFlexPanel->IsValidSlider( j ) ) + continue; + + setting = g_pFlexPanel->GetSliderRawValue( j ); + //setting = g_pFlexPanel->GetSlider( j ); + influence = g_pFlexPanel->GetInfluence( j ); + + g_pFlexPanel->SetEdited( j, isEdited ); + + g_pFlexPanel->GetSliderRange( j, minvalue, maxvalue ); + + // Found it + if ( !influence ) + { + continue; + } + + bool found = false; + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks() && !found; i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + if ( track->GetFlexControllerIndex( side ) != j ) + continue; + + float normalized = setting; + if ( side == 0 ) + { + if ( minvalue != maxvalue ) + { + normalized = ( setting - minvalue ) / ( maxvalue - minvalue ); + } + if (track->IsInverted()) + { + normalized = 1.0 - normalized; + } + } + + found = true; + + track->AddSample( relativetime, normalized, side ); + track->Resort( side ); + track->SetTrackActive( true ); + + break; + } + } + } + + g_pChoreoView->PushRedo( "Copy from Flex" ); + + m_pWorkspace->redraw(); + redraw(); +} + +bool ExpressionTool::SetFlexAnimationTrackFromExpression( int mx, int my, CExpClass *cl, CExpression *exp ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e | !e->GetDuration() ) + { + return false; + } + + if ( !exp ) + { + return false; + } + + // Convert screen to client + POINT pt; + pt.x = mx; + pt.y = my; + + ScreenToClient( (HWND)getHandle(), &pt ); + + if ( pt.x < 0 || pt.y < 0 ) + { + return false; + } + + if ( pt.x > w2() || pt.y > h2() ) + { + return false; + } + + float t = GetTimeValueForMouse( (short)pt.x ); + + // Get spline intensity for controller + // Get spline intensity for controller + float relativetime = t; + float faketime = e->GetStartTime() + relativetime; + + faketime = FacePoser_SnapTime( faketime ); + + float *settings = exp->GetSettings(); + float *influence = exp->GetWeights(); + + if ( !settings || !influence ) + return false; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Copy from Expression" ); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + if ( track->IsComboType() ) + { + int left = track->GetFlexControllerIndex( 0 ); + int right = track->GetFlexControllerIndex( 1 ); + + float leftval = settings[ left ]; + float leftinfluence = influence[ left ]; + float rightval = settings[ right ]; + float rightinfluence = influence[ right ]; + + if ( leftinfluence || rightinfluence ) + { + + //Con_Printf( "%s %i(side %i): amount %f inf %f\n", track->GetFlexControllerName(), j, side, s, inf ); + + float mag, leftright; + + if (leftval < rightval) + { + mag = rightval; + leftright = 1.0 - (leftval / rightval) * 0.5; + } + else if (leftval > rightval) + { + mag = leftval; + leftright = (rightval / leftval) * 0.5; + } + else + { + mag = leftval; + leftright = 0.5; + } + + track->AddSample( relativetime, mag * leftinfluence, 0 ); + track->AddSample( relativetime, leftright, 1 ); + + track->Resort( 0 ); + track->Resort( 1 ); + + track->SetTrackActive( true ); + } + } + else + { + int j = track->GetFlexControllerIndex( 0 ); + + float s = settings[ j ]; + float inf = influence[ j ]; + + if ( inf ) + { + track->AddSample( relativetime, s, 0 ); + + track->Resort( 0 ); + + track->SetTrackActive( true ); + } + } + } + + g_pChoreoView->PushRedo( "Copy from Expression" ); + + m_pWorkspace->redraw(); + redraw(); + + return true; +} + +bool ExpressionTool::PaintBackground() +{ + redraw(); + return false; +} + +void ExpressionTool::OnExportFlexAnimation( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + // Create flexanimations dir + CreatePath( "flexanimations/foo" ); + + char fafilename[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( fafilename, sizeof( fafilename ), "flexanimations", "*.vfa" ) ) + { + return; + } + + Q_DefaultExtension( fafilename, ".vfa", sizeof( fafilename ) ); + + Con_Printf( "Exporting events to %s\n", fafilename ); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + CChoreoScene::FileSaveFlexAnimations( buf, 0, event ); + + // Write it out baby + FileHandle_t fh = filesystem->Open( fafilename, "wt" ); + if (fh) + { + filesystem->Write( buf.Base(), buf.TellPut(), fh ); + filesystem->Close(fh); + } + else + { + Con_Printf( "Unable to write file %s!!!\n", fafilename ); + } +} + +void ExpressionTool::OnImportFlexAnimation( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + char fafilename[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( fafilename, sizeof( fafilename ), "flexanimations", "*.vfa" ) ) + { + return; + } + + if ( !filesystem->FileExists( fafilename ) ) + return; + + char fullpath[ 512 ]; + filesystem->RelativePathToFullPath( fafilename, "MOD", fullpath, sizeof( fullpath ) ); + + LoadScriptFile( (char *)fullpath ); + + tokenprocessor->GetToken( true ); + if ( stricmp( tokenprocessor->CurrentToken(), "flexanimations" ) ) + { + Con_Printf( "ExpressionTool::OnImportFlexAnimation: %s, expecting \"flexanimations\"\n", + fullpath ); + } + else + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Import flex animations" ); + + CChoreoScene::ParseFlexAnimations( tokenprocessor, event, true ); + + // Force a full reset + m_pLastEvent = NULL; + SetEvent( event ); + + g_pChoreoView->PushRedo( "Import flex animations" ); + + Con_Printf( "Parsed flex animations from %s\n", fullpath ); + } +} + +void ExpressionTool::OnUndo( void ) +{ + g_pChoreoView->Undo(); +} + +void ExpressionTool::OnRedo( void ) +{ + g_pChoreoView->Redo(); +} + +void ExpressionTool::ForceScrubPositionFromSceneTime( float scenetime ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float t = scenetime - e->GetStartTime(); + m_flScrub = t; + m_flScrubTarget = t; + + DrawScrubHandles(); +} + +void ExpressionTool::ForceScrubPosition( float frac ) +{ + m_flScrub = frac; + m_flScrubTarget = frac; + + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + frac; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->SetScrubTargetTime( realtime ); + + g_pChoreoView->DrawScrubHandle(); + } + + DrawScrubHandles(); +} + +void ExpressionTool::DrawScrubHandles() +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + + RECT rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle ); +} + +void ExpressionTool::SetMouseOverPos( int x, int y ) +{ + m_nMousePos[ 0 ] = x; + m_nMousePos[ 1 ] = y; +} + +void ExpressionTool::GetMouseOverPos( int &x, int& y ) +{ + x = m_nMousePos[ 0 ]; + y = m_nMousePos[ 1 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcPos - +//----------------------------------------------------------------------------- +void ExpressionTool::GetMouseOverPosRect( RECT& rcPos ) +{ + rcPos.top = GetCaptionHeight() + 12; + rcPos.left = w2() - 200; + rcPos.right = w2() - 5; + rcPos.bottom = rcPos.top + 13; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcPos - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ) +{ + // Compute time for pixel x + float t = GetTimeValueForMouse( m_nMousePos[ 0 ] ); + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + t += e->GetStartTime(); + + float snapped = FacePoser_SnapTime( t ); + + // Found it, write out description + // + char sz[ 128 ]; + if ( t != snapped ) + { + Q_snprintf( sz, sizeof( sz ), "%s", FacePoser_DescribeSnappedTime( t ) ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%.3f", t ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 11, 900, sz ); + + RECT rcText = rcPos; + rcText.left = max( rcPos.left, rcPos.right - len ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 255, 50, 70 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::DrawMouseOverPos() +{ + RECT rcPos; + GetMouseOverPosRect( rcPos ); + + CChoreoWidgetDrawHelper drawHelper( this, rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +int ExpressionTool::CountSelectedSamples( void ) +{ + return m_pWorkspace->CountSelectedSamples(); +} + +void ExpressionTool::MoveSelectedSamples( float dfdx, float dfdy, bool snap ) +{ + m_pWorkspace->MoveSelectedSamples( dfdx, dfdy, snap ); +} + +void ExpressionTool::DeleteSelectedSamples( void ) +{ + m_pWorkspace->DeleteSelectedSamples(); +} + +void ExpressionTool::DeselectAll( void ) +{ + m_pWorkspace->DeselectAll(); + m_bSelectionActive = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +//----------------------------------------------------------------------------- +void ExpressionTool::SelectPoints( float starttime, float endtime ) +{ + // Make sure order is correct + if ( endtime < starttime ) + { + float temp = endtime; + endtime = starttime; + starttime = temp; + } + + DeselectAll(); + + m_flSelection[ 0 ] = starttime; + m_flSelection[ 1 ] = endtime; + m_bSelectionActive = true; + + // Select any words that span the selection + // + m_pWorkspace->SelectPoints( starttime, endtime ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelection( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + for ( int i = 0; i < 2; i++ ) + { + m_flSelection[ i ] += delta; + } + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelectionStart( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + m_flSelection[ 0 ] += delta; + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelectionEnd( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + m_flSelection[ 1 ] += delta; + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : startx - +// mx - +//----------------------------------------------------------------------------- +void ExpressionTool::FinishSelect( int startx, int mx ) +{ + // Don't select really small areas + if ( abs( startx - mx ) < 1 ) + return; + + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + SelectPoints( start, end ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverPoints( int mx, int my ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + // Over tag + if ( my > TRAY_HEIGHT ) + return false; + + if ( my <= 12 + GetCaptionHeight() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverSelection( int mx, int my ) +{ + if ( !m_bSelectionActive ) + return false; + + if ( !IsMouseOverPoints( mx, my ) ) + return false; + + float t = GetTimeValueForMouse( mx ); + + if ( t >= m_flSelection[ 0 ] && + t <= m_flSelection[ 1 ] ) + { + return true; + } + + return false; +} + +bool ExpressionTool::IsMouseOverSelectionStartEdge( mxEvent *event ) +{ + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int left; + + left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + + if ( abs( left - mx ) <= 2 ) + { + return true; + } + + return false; +} + +bool ExpressionTool::IsMouseOverSelectionEndEdge( mxEvent *event ) +{ + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int right; + + right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + + if ( abs( right - mx ) <= 2 ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &rc - +//----------------------------------------------------------------------------- +void ExpressionTool::GetWorkspaceRect( RECT &rc ) +{ + GetClientRect( (HWND)getHandle(), &rc ); + + rc.top = TRAY_HEIGHT - 17; + rc.bottom = TRAY_HEIGHT - 1; + //InflateRect( &rc, -1, -1 ); +} + +void ExpressionTool::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int ExpressionTool::ComputeHPixelsNeeded( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return 0; + + int pixels = 0; + float maxtime = event->GetDuration(); + pixels = (int)( ( maxtime + 5.0 ) * GetPixelsPerSecond() + 10 ); + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w2(), m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( m_flLeftOffset ); + m_pHorzScrollBar->setPagesize( w2() ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void ExpressionTool::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = x; + m_pHorzScrollBar->setValue( m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + m_bLayoutIsValid = false; + m_pWorkspace->redraw(); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int ExpressionTool::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + int left, right; + + GetWorkspaceLeftRight( left, right ); + + if ( clipped ) + { + *clipped = false; + } + + float st, ed; + GetStartAndEndTime( st, ed ); + + float frac = ( time - st ) / ( ed - st ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = left + ( int )( frac * (right - left ) ); + return pixel; +} + +void ExpressionTool::OnChangeScale( void ) +{ + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + g_pChoreoView->SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", g_pChoreoView->GetTimeZoom( GetToolName() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : st - +// ed - +//----------------------------------------------------------------------------- +void ExpressionTool::GetStartAndEndTime( float& st, float& ed ) +{ + st = m_flLeftOffset / GetPixelsPerSecond(); + int left, right; + GetWorkspaceLeftRight( left, right ); + if ( right <= left ) + { + ed = st; + } + else + { + ed = st + (float)( right - left ) / GetPixelsPerSecond(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetEventEndTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 1.0f; + + return ev->GetDuration(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + int left, right; + + GetWorkspaceLeftRight( left, right ); + + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( clip ) + { + if ( mx < 0 ) + { + return st; + } + if ( mx > w2() ) + { + return ed; + } + } + + float frac = (float)( mx - left ) / (float)( right - left ); + return st + frac * ( ed - st ); +} + +void ExpressionTool::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = GetPixelForTimeValue( duration ); + if ( leftx >= w2() ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcClient.top + TRAY_HEIGHT, leftx, rcClient.bottom ); + +} + +void ExpressionTool::OnSortByUsed( void ) +{ + m_pWorkspace->OnSortByUsed(); +} + +void ExpressionTool::OnSortByName( void ) +{ + m_pWorkspace->OnSortByName(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *item - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsFocusItem( TimelineItem *item ) +{ + return m_pWorkspace->GetClickedItem() == item; +} + +//----------------------------------------------------------------------------- +// Purpose: Delete a vertical column of samples between the selection +// markers. If excise_time is true, shifts remaining samples left +// Input : excise_time - +//----------------------------------------------------------------------------- +void ExpressionTool::OnDeleteSelection( bool excise_time ) +{ + if ( !m_bSelectionActive ) + return; + + // Force selection of everything again! + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + int i, t; + + char const *undotext = excise_time ? "Excise column" : "Delete column"; + + float shift_left_time = m_flSelection[ 1 ] - m_flSelection[ 0 ]; + Assert( shift_left_time > 0.0f ); + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + + if ( !excise_time ) + continue; + + + // Now shift things after m_flSelection[0] to the left + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( sample->time < m_flSelection[ 1 ] ) + continue; + + // Shift it + sample->time -= shift_left_time; + } + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( undotext ); + + // Clear selection and redraw() + DeselectAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnResetItemSize() +{ + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( !item ) + return; + + item->ResetHeight(); + m_pWorkspace->LayoutItems( true ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnResetAllItemSizes() +{ + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + item->ResetHeight(); + } + + m_pWorkspace->LayoutItems( true ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnScaleSamples() +{ + int t, i; + + //Scale samples + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Scale selected samples" ); + strcpy( params.m_szPrompt, "Factor:" ); + strcpy( params.m_szInputText, "1.0" ); + + if ( !InputProperties( ¶ms ) ) + return; + + float scale_factor = atof( params.m_szInputText ); + if( scale_factor <= 0.0f ) + { + Con_Printf( "Can't scale to %.2f\n", scale_factor ); + } + + char const *undotext = "Scale samples"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + // Scale it + float curvalue = sample->value; + curvalue *= scale_factor; + // Clamp it + curvalue = clamp( curvalue, 0.0f, 1.0f ); + sample->value = curvalue; + } + } + } + + g_pChoreoView->PushRedo( undotext ); + + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::OnModelChanged() +{ + SetEvent( NULL ); + redraw(); +} + +void ExpressionTool::OnEdgeProperties() +{ + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( !item ) + return; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + return; + + CEdgePropertiesParams params; + Q_memset( ¶ms, 0, sizeof( params ) ); + Q_strcpy( params.m_szDialogTitle, "Edge Properties" ); + + params.SetFromFlexTrack( track ); + + if ( !EdgeProperties( ¶ms ) ) + { + return; + } + + char const *undotext = "Change Edge Properties"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + // Apply changes. + params.ApplyToTrack( track ); + + g_pChoreoView->PushRedo( undotext ); + + m_pWorkspace->redraw(); + redraw(); +} + +float ExpressionTool::GetScrubberSceneTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 0.0f; + + float curtime = GetScrub(); + curtime += ev->GetStartTime(); + return curtime; +} + +void ExpressionTool::GetTimelineItems( CUtlVector< TimelineItem * >& list ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( i ); + if ( !item ) + continue; + + list.AddToTail( item ); + } +} + + + +bool ExpressionTool::HasCopiedColumn() +{ + return m_ColumnCopy.m_bActive; +} + +void ExpressionTool::OnCopyColumn() +{ + m_ColumnCopy.Reset(); + + m_ColumnCopy.m_bActive = true; + m_ColumnCopy.m_flCopyTimes[ 0 ] = m_flSelection[ 0 ]; + m_ColumnCopy.m_flCopyTimes[ 1 ] = m_flSelection[ 1 ]; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; ++controller ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + // Add to dictionary + CExpressionSample copy( *sample ); + copy.selected = false; + + int tIndex = m_ColumnCopy.m_Data.Find( track->GetFlexControllerName() ); + if ( tIndex == m_ColumnCopy.m_Data.InvalidIndex() ) + { + tIndex = m_ColumnCopy.m_Data.Insert( track->GetFlexControllerName() ); + } + + CColumnCopier::CTrackData &data = m_ColumnCopy.m_Data[ tIndex ]; + data.m_Samples[ t ].AddToTail( copy ); + } + } + } +} + +void ExpressionTool::OnPasteColumn() +{ + if ( !m_ColumnCopy.m_bActive ) + { + Msg( "Nothing to paste\n" ); + return; + } + + float flPasteTime = GetTimeForClickedPos(); + + float flPasteEndTime = flPasteTime + m_ColumnCopy.m_flCopyTimes[ 1 ] - m_ColumnCopy.m_flCopyTimes[ 0 ]; + + // Clear selection and redraw() + DeselectAll(); + + // Select everthing in the paste region so we can delete the existing stuff + SelectPoints( flPasteTime, flPasteEndTime ); + + int i, t; + + char const *undotext = "Paste column"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + int tIndex = m_ColumnCopy.m_Data.Find( track->GetFlexControllerName() ); + + for ( t = 0; t < 2; t++ ) + { + // Remove all selected samples + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + + // Now add the new samples, if any in the time selection + if ( tIndex != m_ColumnCopy.m_Data.InvalidIndex() ) + { + CColumnCopier::CTrackData &data = m_ColumnCopy.m_Data[ tIndex ]; + + for ( int j = 0; j < data.m_Samples[ t ].Count(); ++j ) + { + CExpressionSample *s = &data.m_Samples[ t ][ j ]; + CExpressionSample *newSample = track->AddSample( s->time - m_ColumnCopy.m_flCopyTimes[ 0 ] + flPasteTime, s->value, t ); + newSample->selected = true; + } + } + track->Resort( t ); + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( undotext ); +} + +void ExpressionTool::ClearColumnCopy() +{ + m_ColumnCopy.Reset(); +}
\ No newline at end of file |