//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include #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 ", IDC_TL_EDITLEFTRIGHT ); } else { pop->add( "Edit ", 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(); }