//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "hlfaceposer.h" #include #include "TimelineItem.h" #include "choreowidgetdrawhelper.h" #include "mathlib/mathlib.h" #include "expressions.h" #include "StudioModel.h" #include "expclass.h" #include "mathlib/mathlib.h" #include "ExpressionTool.h" #include "choreoevent.h" #include "choreoscene.h" #include "choreoactor.h" #include "choreochannel.h" #include "ChoreoView.h" #include "ControlPanel.h" #include "faceposer_models.h" #include "MatSysWin.h" #include "choreoviewcolors.h" #include "ifaceposersound.h" #include "curveeditorhelpers.h" extern double realtime; #define DOUBLE_CLICK_TIME 0.2 #define GROW_HANDLE_WIDTH 66 #define GROW_HANDLE_HEIGHT 8 #define GROW_HANDLE_INSETPIXELS 8 #define TIMELINEITEM_DEFAULT_HEIGHT 100 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- TimelineItem::TimelineItem( mxWindow *workspace ) { m_pHelper = new CCurveEditorHelper< TimelineItem >( this ); m_pWorkspace = workspace; m_nDragging = DRAGTYPE_NONE; m_nLastX = 0; m_nLastY = 0; m_nStartX = 0; m_nStartY = 0; SetExpressionInfo( NULL, 0 ); m_nNumSelected = 0; m_nEditType = 0; SetCollapsed( false ); SetActive( false ); m_nUndoSetup = 0; m_rcBounds.top = 0; m_rcBounds.bottom = 0; m_rcBounds.left = 0; m_rcBounds.right = 0; m_bVisible = false; m_flLastClickTime = -1; m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT; } TimelineItem::~TimelineItem( void ) { delete m_pHelper; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::ResetHeight() { m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT; } //----------------------------------------------------------------------------- // Purpose: // Input : mx - // Output : float //----------------------------------------------------------------------------- float TimelineItem::GetTimeForMouse( int mx, bool clip /*= false*/ ) { float start, end; g_pExpressionTool->GetStartAndEndTime( start, end ); if ( clip ) { if ( mx < m_rcBounds.left ) { return start; } else if ( mx >= m_rcBounds.right ) { return end; } } float frac = (float)( mx - m_rcBounds.left ) / (float)( m_rcBounds.right - m_rcBounds.left ); float t = start + frac * ( end - start ); return t; } //----------------------------------------------------------------------------- // Purpose: // Input : t - // Output : int //----------------------------------------------------------------------------- int TimelineItem::GetMouseForTime( float t, bool *clipped /*= NULL*/ ) { float start, end; g_pExpressionTool->GetStartAndEndTime( start, end ); float frac = ( t - start ) / ( end - start ); if ( frac < 0.0 || frac > 1.0 ) { if ( clipped ) { *clipped = true; } } int mx = m_rcBounds.left + ( int ) ( frac * (float)( m_rcBounds.right - m_rcBounds.left ) ); return mx; } int TimelineItem::NumSamples() { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return 0; // Aggregate both types of tracks together return track->GetNumSamples( 0 ) + track->GetNumSamples( 1 ); // return track->GetNumSamples( m_nEditType ); } CExpressionSample *TimelineItem::GetSample( int idx ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return NULL; if ( idx >= track->GetNumSamples( 0 ) ) { // Rebase and look at left/right track instead idx -= track->GetNumSamples( 0 ); return track->GetSample( idx, 1 ); } return track->GetSample( idx, 0 ); } CExpressionSample *TimelineItem::GetSampleUnderMouse( int mx, int my, float tolerance /*= FP_TL_SELECTION_TOLERANCE*/ ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return NULL; CChoreoEvent *e = track->GetEvent(); if ( !e ) return NULL; float closest_dist = 9999999.f; CExpressionSample *bestsample = NULL; // Add a sample point int height = m_rcBounds.bottom - m_rcBounds.top; mx += m_rcBounds.left; for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ ) { CExpressionSample *sample = track->GetSample( i, m_nEditType ); bool clipped = false; int px = GetMouseForTime( sample->time, &clipped ); int py = height * ( 1.0f - sample->value ); int dx = px - mx; int dy = py - my; float dist = sqrt( (float)(dx * dx + dy * dy) ); if ( dist < closest_dist ) { bestsample = sample; closest_dist = dist; } } // Not close to any of them!!! if ( ( tolerance != 0.0f ) && ( closest_dist > tolerance ) ) return NULL; return bestsample; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::DeselectAll( void ) { g_pExpressionTool->DeselectAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::SelectAll( void ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; for ( int t = 0; t < 2; t++ ) { for ( int i = 0; i < track->GetNumSamples( t ); i++ ) { CExpressionSample *sample = track->GetSample( i, t ); sample->selected = true; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::Delete( void ) { g_pExpressionTool->DeleteSelectedSamples(); } //----------------------------------------------------------------------------- // Purpose: // Input : sample - //----------------------------------------------------------------------------- void TimelineItem::AddSample( CExpressionSample const& sample ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; PreDataChanged( "Add sample point" ); track->AddSample( sample.time, sample.value, m_nEditType ); track->Resort( m_nEditType ); SetActive( true ); PostDataChanged( "Add sample point" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int TimelineItem::CountSelected( void ) { m_nNumSelected = m_pHelper->CountSelected( false ); return m_nNumSelected; } void TimelineItem::SetMousePositionForEvent( mxEvent *event ) { POINT pt; GetCursorPos( &pt ); ScreenToClient( (HWND)m_pWorkspace->getHandle(), &pt ); pt.x -= m_rcBounds.left; pt.y -= m_rcBounds.top; event->x = pt.x; event->y = pt.y; } int TimelineItem::handleEvent( mxEvent *event ) { int iret = 0; // Give helper a shot at the event if ( m_pHelper->HelperHandleEvent( event ) ) { return 1; } switch ( event->event ) { case mxEvent::KeyDown: { switch ( event->key ) { default: iret = g_pChoreoView->HandleZoomKey( g_pExpressionTool, event->key ); break; case VK_ESCAPE: DeselectAll(); DrawSelf(); break; case VK_DELETE: Delete(); DrawSelf(); break; case 'C': Copy(); DrawSelf(); break; case 'V': Paste(); DrawSelf(); break; case 'J': { g_pExpressionTool->OnCopyToFlex( g_pExpressionTool->GetScrubberSceneTime(), true ); } break; case 'K': { g_pExpressionTool->OnCopyFromFlex( g_pExpressionTool->GetScrubberSceneTime(), false ); } break; case 188: // VK_OEM_COMMA: { g_pExpressionTool->SetScrubTargetTime( 0.0f ); } break; case 190: // VK_OEM_PERIOD: { CChoreoScene *scene = g_pChoreoView->GetScene(); if ( scene ) { g_pExpressionTool->SetScrubTargetTime( scene->FindStopTime() ); } } break; case VK_LEFT: { CChoreoScene *scene = g_pChoreoView->GetScene(); if ( scene && scene->GetSceneFPS() > 0 ) { float curscrub = g_pExpressionTool->GetScrub(); curscrub -= ( 1.0f / (float)scene->GetSceneFPS() ); curscrub = max( curscrub, 0.0f ); g_pExpressionTool->SetScrubTargetTime( curscrub ); } } break; case VK_RIGHT: { CChoreoScene *scene = g_pChoreoView->GetScene(); if ( scene && scene->GetSceneFPS() > 0 ) { float curscrub = g_pExpressionTool->GetScrub(); curscrub += ( 1.0f / (float)scene->GetSceneFPS() ); curscrub = min( curscrub, scene->FindStopTime() ); g_pExpressionTool->SetScrubTargetTime( curscrub ); } } break; case 191: { if ( g_pChoreoView->IsPlayingScene() ) { g_pChoreoView->StopScene(); } } break; } iret = 1; } break; case mxEvent::KeyUp: { switch ( event->key ) { case VK_SPACE: { CFlexAnimationTrack *track = GetSafeTrack(); if ( track && track->IsComboType() ) { SetEditType( m_nEditType == 0 ? 1 : 0 ); DrawSelf(); } } break; } iret = 1; } break; case mxEvent::MouseDown: { sound->Flush(); SetFocus( (HWND)g_pExpressionTool->getHandle() ); int height = m_rcBounds.bottom - m_rcBounds.top; bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; if ( m_nDragging == DRAGTYPE_NONE ) { bool ctrlDown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; CExpressionSample *sample = GetSampleUnderMouse( event->x, event->y, ctrlDown ? FP_TL_ADDSAMPLE_TOLERANCE : FP_TL_SELECTION_TOLERANCE ); if ( IsMouseOverGrowHandle( (short)event->x, (short)event->y ) ) { m_nDragging = DRAGTYPE_GROW; m_nLastX = (short)event->x; m_nLastY = (short)event->y; m_nStartX = m_nLastX; m_nStartY = m_nLastY; MouseDrag( (short)event->x, (short)event->y, event->modifiers ); DrawGrowRect(); } else if ( sample ) { if ( event->modifiers & mxEvent::KeyShift ) { sample->selected = !sample->selected; DrawSelf(); } else if ( sample->selected ) { m_nDragging = rightbutton ? DRAGTYPE_MOVEPOINTS_TIME : DRAGTYPE_MOVEPOINTS_VALUE; m_nLastX = (short)event->x; m_nLastY = (short)event->y; m_nStartX = m_nLastX; m_nStartY = m_nLastY; PreDataChanged( "Move sample point(s)" ); MouseDrag( (short)event->x, (short)event->y, event->modifiers ); DrawSelf(); } else { if ( !( event->modifiers & mxEvent::KeyShift ) ) { DeselectAll(); DrawSelf(); } m_nDragging = DRAGTYPE_SELECTION; m_nLastX = (short)event->x; m_nLastY = (short)event->y; m_nStartX = m_nLastX; m_nStartY = m_nLastY; MouseDrag( (short)event->x, (short)event->y, event->modifiers ); DrawFocusRect(); } } else if ( event->modifiers & mxEvent::KeyCtrl ) { CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); if ( e ) { // Add a sample point float t = GetTimeForMouse( (short)event->x + m_rcBounds.left ); CExpressionSample sample; sample.time = FacePoser_SnapTime( t ); sample.value = 1.0f - (float)( (short)( event->y ) ) / (float)height; sample.selected = false; AddSample( sample ); DrawSelf(); } } else { if ( rightbutton ) { POINT pt; pt.x = event->x; pt.y = event->y; ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); ScreenToClient( (HWND)g_pExpressionTool->getHandle(), &pt ); event->x = pt.x; event->y = pt.y; g_pExpressionTool->ShowContextMenu( event, true ); return iret; } if ( !( event->modifiers & mxEvent::KeyShift ) ) { DeselectAll(); DrawSelf(); } m_nDragging = DRAGTYPE_SELECTION; m_nLastX = (short)event->x; m_nLastY = (short)event->y; m_nStartX = m_nLastX; m_nStartY = m_nLastY; MouseDrag( (short)event->x, (short)event->y, event->modifiers ); DrawFocusRect(); } } iret = 1; } break; case mxEvent::MouseDrag: case mxEvent::MouseMove: { if ( m_nDragging != DRAGTYPE_NONE ) { if ( m_nDragging == DRAGTYPE_SELECTION ) { DrawFocusRect(); } else if ( m_nDragging == DRAGTYPE_GROW ) { DrawGrowRect(); } MouseDrag( (short)event->x, (short)event->y, event->modifiers ); if ( m_nDragging == DRAGTYPE_SELECTION ) { DrawFocusRect(); } else if ( m_nDragging == DRAGTYPE_GROW ) { DrawGrowRect(); } if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME || m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) { DrawSelf(); } } else { // See if anything is selected CountSelected(); if ( m_nNumSelected <= 0 && g_pExpressionTool->IsFocusItem( this ) ) { // Nothing selected // Draw auto highlight DrawAutoHighlight( event ); } } iret = 1; } break; case mxEvent::MouseUp: { bool overgrow = IsMouseOverGrowHandle( (short)event->x, (short)event->y ); if ( m_nDragging != DRAGTYPE_NONE ) { if ( m_nDragging == DRAGTYPE_SELECTION ) { DrawFocusRect(); } else if ( m_nDragging == DRAGTYPE_GROW ) { DrawGrowRect(); } MouseDrag( (short)event->x, (short)event->y, event->modifiers, true ); if ( m_nDragging == DRAGTYPE_GROW ) { // Finish grow by resizing control int desiredheight = m_nCurrentHeight + event->y - m_nStartY; if ( desiredheight >= 10 ) { m_nCurrentHeight = desiredheight; g_pExpressionTool->LayoutItems( true ); } } else if ( m_nDragging != DRAGTYPE_MOVEPOINTS_VALUE && m_nDragging != DRAGTYPE_MOVEPOINTS_TIME ) { SelectPoints(); } else { PostDataChanged( "Move sample point(s)" ); } m_nDragging = DRAGTYPE_NONE; DrawSelf(); } bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; bool shift = ( event->modifiers & mxEvent::KeyShift ) ? true : false; bool ctrl = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; if ( !rightbutton && !shift && !ctrl ) { if ( realtime - m_flLastClickTime < DOUBLE_CLICK_TIME ) { if ( overgrow || IsCollapsed() ) { OnDoubleClicked(); } } m_flLastClickTime = realtime; } iret = 1; } break; } return iret; } void TimelineItem::MouseDrag( int x, int y, int modifiers, bool snap /*=false*/ ) { if ( m_nDragging == DRAGTYPE_NONE ) return; int width = m_rcBounds.right - m_rcBounds.left; int height = m_rcBounds.bottom - m_rcBounds.top; if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME || m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) { int dx = x - m_nLastX; int dy = y - m_nLastY; if ( !( modifiers & mxEvent::KeyCtrl ) ) { // Zero out motion on other axis if ( m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) { dx = 0; x = m_nLastX; } else { dy = 0; y = m_nLastY; } } float dfdx = (float)dx / g_pExpressionTool->GetPixelsPerSecond(); float dfdy = (float)dy / (float)height; g_pExpressionTool->MoveSelectedSamples( dfdx, dfdy, snap ); // Update the scrubber if ( (float)width > 0 ) { float t = GetTimeForMouse( x + m_rcBounds.left ); g_pExpressionTool->ForceScrubPosition( t ); g_pMatSysWindow->Frame(); } } m_nLastX = x; m_nLastY = y; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::DrawFocusRect( void ) { RECT rcFocus; rcFocus.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; rcFocus.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; rcFocus.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; rcFocus.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; POINT offset; offset.x = m_rcBounds.left; offset.y = m_rcBounds.top; ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset ); OffsetRect( &rcFocus, offset.x, offset.y ); HDC dc = GetDC( NULL ); ::DrawFocusRect( dc, &rcFocus ); ReleaseDC( NULL, dc ); } void TimelineItem::DrawSelf( void ) { CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds ); Draw( drawHelper ); } void TimelineItem::Draw( CChoreoWidgetDrawHelper& drawHelper ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; CChoreoEvent *e = track->GetEvent(); if ( !e ) return; Assert( e->HasEndTime() ); bool active = track && ( IsValid() || IsActive() ); float starttime; float endtime; g_pExpressionTool->GetStartAndEndTime( starttime, endtime ); CountSelected(); int scount = GetNumSelected(); COLORREF bgColor = RGB( 230, 230, 200 ); if ( IsCollapsed() && active ) { bgColor = RGB( 200, 230, 200 ); } RECT rcClient = m_rcBounds; drawHelper.DrawFilledRect( bgColor, rcClient ); COLORREF gray = RGB( 200, 200, 200 ); DrawEventEnd( drawHelper ); DrawRelativeTags( drawHelper ); if ( !IsCollapsed() && track ) { if ( m_nEditType == 1 ) { float zero = track->GetZeroValue( m_nEditType, true ); drawHelper.DrawColoredLine( RGB( 180, 200, 220 ), PS_SOLID, 1, rcClient.left, ( rcClient.top * zero + rcClient.bottom * (1 - zero)) , rcClient.right, ( rcClient.top * zero + rcClient.bottom * (1 - zero)) ); } drawHelper.DrawOutlinedRect( RGB( 100, 150, 200 ), PS_SOLID, 1, rcClient ); // Draw grow handle into background... if ( CanHaveGrowHandle() ) { RECT handleRect; GetGrowHandleRect( handleRect ); DrawGrowHandle( drawHelper, handleRect ); } // Draw left/right underneath amount so go backbard for ( int type = ( track->IsComboType() ? 1 : 0 ); type >= 0; type-- ) { COLORREF lineColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray; COLORREF shadowColor = ( type == m_nEditType ) ? RGB( 150, 150, 250 ) : gray; COLORREF dotColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray; COLORREF dotColorSelected = ( type == m_nEditType ) ? RGB( 240, 80, 20 ) : gray; int height = rcClient.bottom - rcClient.top; int bottom = rcClient.bottom; // Fixme, could look at 1st derivative and do more sampling at high rate of change? // or near actual sample points! float linelength = g_pExpressionTool->IsFocusItem( this ) ? 2.0f : 8.0f; float timestepperpixel = linelength / g_pExpressionTool->GetPixelsPerSecond(); float stoptime = min( endtime, e->GetDuration() ); float prev_t = starttime; float prev_value = track->GetFracIntensity( prev_t, type ); CUtlVector< POINT > segments; /* if (type == m_nEditType) { // draw hermite version of time step float i0, i1, i2; float time10hz = starttime; i0 = track->GetFracIntensity( time10hz, type ); i1 = i0; time10hz = starttime + 0.1; i2 = track->GetFracIntensity( time10hz, type );; for ( float t = starttime; t <= stoptime; t += timestepperpixel ) { while (t >= time10hz) { time10hz += 0.1; i0 = i1; i1 = i2; i2 = track->GetFracIntensity( time10hz, type );; } float value = Hermite_Spline( i0, i1, i2, (t - time10hz + 0.1) / 0.1 ); int prevx, x; bool clipped1, clipped2; x = GetMouseForTime( t, &clipped1 ); prevx = GetMouseForTime( prev_t, &clipped2 ); //if ( !clipped1 && !clipped2 ) { // Draw segment //drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, // prevx, bottom - prev_value * height, // x, bottom - value * height ); POINT pt; if ( segments.Count() == 0 ) { pt.x = prevx; pt.y = bottom - prev_value * height; segments.AddToTail( pt ); } pt.x = x; pt.y = bottom - value * height; segments.AddToTail( pt ); } prev_t = t; prev_value = value; } if ( segments.Count() >= 2 ) { drawHelper.DrawColoredPolyLine( shadowColor, PS_SOLID, 1, segments ); } segments.RemoveAll(); } */ for ( float t = starttime; t <= stoptime; t += timestepperpixel ) { float value = track->GetFracIntensity( t, type ); int prevx, x; bool clipped1, clipped2; x = GetMouseForTime( t, &clipped1 ); prevx = GetMouseForTime( prev_t, &clipped2 ); //if ( !clipped1 && !clipped2 ) { // Draw segment //drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, // prevx, bottom - prev_value * height, // x, bottom - value * height ); POINT pt; if ( segments.Count() == 0 ) { pt.x = prevx; pt.y = bottom - prev_value * height; segments.AddToTail( pt ); } pt.x = x; pt.y = bottom - value * height; segments.AddToTail( pt ); } prev_t = t; prev_value = value; } if ( segments.Count() >= 2 ) { drawHelper.DrawColoredPolyLine( lineColor, PS_SOLID, 1, segments ); } for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ ) { bool dummy; CExpressionSample *start = track->GetBoundedSample( sample, dummy, type ); /* int pixel = (int)( ( start->time / event_time ) * width + 0.5f); int x = m_rcBounds.left + pixel; float roundedfrac = (float)pixel / (float)width; */ float value = start->value; // track->GetFracIntensity( start->time, type ); bool clipped = false; int x = GetMouseForTime( start->time, &clipped ); if ( clipped ) continue; int y = bottom - value * height; int dotsize = 6; int dotSizeSelected = 6; COLORREF clr = dotColor; COLORREF clrSelected = dotColorSelected; drawHelper.DrawCircle( start->selected ? clrSelected : clr, x, y, start->selected ? dotSizeSelected : dotsize, true ); if ( !start->selected ) continue; if ( start->GetCurveType() == CURVE_DEFAULT ) continue; // Draw curve type indicator... char sz[ 128 ]; Q_snprintf( sz, sizeof( sz ), "%s", Interpolator_NameForCurveType( start->GetCurveType(), true ) ); RECT rc; int fontSize = 9; rc.top = clamp( y + 5, rcClient.top + 2, rcClient.bottom - 2 - fontSize ); rc.bottom = rc.top + fontSize + 1; rc.left = x - 75; rc.right = x + 175; drawHelper.DrawColoredText( "Arial", fontSize, 500, shadowColor, rc, sz ); } } } if ( track && track->IsComboType() && !IsCollapsed() ) { RECT title = rcClient; title.left += 10; title.top += 14; title.bottom = title.top + 9; char sz[ 128 ]; if ( m_nEditType == 1 ) { sprintf( sz, "left" ); drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); sprintf( sz, "right" ); title.top = rcClient.bottom - 22; title.bottom = rcClient.bottom; drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); } int mid = ( rcClient.top + rcClient.bottom ) / 2; title.top = mid - 10; title.bottom = mid; sprintf( sz, "editmode: <%s>", m_nEditType == 0 ? "amount" : "left/right" ); drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); } if ( track ) { RECT title = rcClient; title.left += 2; title.top += 2; title.bottom = title.top + 9; char const *name = track->GetFlexControllerName(); char sz[ 128 ]; if ( scount > 0 ) { sprintf( sz, "{%i} - ", scount ); int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 120, 120, 0 ), title, sz ); title.left += len + 2; } sprintf( sz, "%s -", name ); int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); drawHelper.DrawColoredText( "Arial", 9, 500, active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), title, sz ); sprintf( sz, "%s", IsActive() ? "enabled" : "disabled" ); title.left += len + 2; len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); drawHelper.DrawColoredText( "Arial", 9, 500, active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), title, sz ); if ( active ) { title.left += len + 2; sprintf( sz, " <%i>", track->GetNumSamples( 0 ) ); len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 220, 0, 00 ), title, sz ); } } } void TimelineItem::DrawAutoHighlight( mxEvent *event ) { if ( IsCollapsed() ) return; CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; CExpressionSample *hover = GetSampleUnderMouse( event->x, event->y, 0.0f ); CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds, true ); RECT rcClient = m_rcBounds; // Draw left/right underneath amount so go backbard int type = m_nEditType; COLORREF dotColor = RGB( 0, 0, 255 ); COLORREF dotColorSelected = RGB( 240, 80, 20 ); COLORREF clrHighlighted = RGB( 0, 200, 0 ); int height = rcClient.bottom - rcClient.top; int bottom = rcClient.bottom; int dotsize = 6; int dotSizeSelected = 6; int dotSizeHighlighted = 6; COLORREF clr = dotColor; COLORREF clrSelected = dotColorSelected; COLORREF bgColor = RGB( 230, 230, 200 ); // Fixme, could look at 1st derivative and do more sampling at high rate of change? // or near actual sample points! for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ ) { bool dummy; CExpressionSample *start = track->GetBoundedSample( sample, dummy, type ); float value = start->value; bool clipped = false; int x = GetMouseForTime( start->time, &clipped ); if ( clipped ) continue; int y = bottom - value * height; if ( hover == start ) { drawHelper.DrawCircle( bgColor, x, y, dotSizeHighlighted, true ); drawHelper.DrawCircle( clrHighlighted, x, y, dotSizeHighlighted, false ); } else { drawHelper.DrawCircle( start->selected ? clrSelected : clr, x, y, start->selected ? dotSizeSelected : dotsize, true ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : drawHelper - //----------------------------------------------------------------------------- void TimelineItem::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; CChoreoEvent *event = track->GetEvent(); if ( !event ) return; float duration = event->GetDuration(); if ( duration <= 0.0f ) return; CChoreoScene *scene = g_pChoreoView->GetScene(); if ( !scene ) return; RECT rcClient = m_rcBounds;; //drawHelper.GetClientRect( rcClient ); // 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 = false; int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped ); if ( clipped ) continue; drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); } } } } for ( int t = 0; t < event->GetNumTimingTags(); t++ ) { CFlexTimingTag *tag = event->GetTimingTag( t ); if ( !tag ) continue; bool clipped = false; int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped ); if ( clipped ) continue; // Draw relative tag marker drawHelper.DrawColoredLine( RGB( 220, 180, 180 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *exp - // flexnum - //----------------------------------------------------------------------------- void TimelineItem::SetExpressionInfo( CFlexAnimationTrack *track, int flexnum ) { m_szTrackName[ 0 ] = 0; if ( track ) { V_strcpy_safe( m_szTrackName, track->GetFlexControllerName() ); SetActive( track->IsTrackActive() ); } m_nFlexNum = flexnum; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::Copy( void ) { if ( !g_pExpressionTool ) return; CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; g_pExpressionTool->Copy( track ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::Paste( void ) { if ( !g_pExpressionTool ) return; if ( !g_pExpressionTool->HasCopyData() ) return; CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; g_pExpressionTool->Paste( track ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::Clear( bool preserveundo ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; if ( preserveundo ) { PreDataChanged( "Clear" ); } track->Clear(); if ( preserveundo ) { PostDataChanged( "Clear" ); } } //----------------------------------------------------------------------------- // Purpose: // Input : state - //----------------------------------------------------------------------------- void TimelineItem::SetCollapsed( bool state ) { m_bCollapsed = state; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool TimelineItem::IsCollapsed( void ) const { return m_bCollapsed; } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int TimelineItem::GetHeight( void ) { return ( IsCollapsed() ? 12 : m_nCurrentHeight ); } //----------------------------------------------------------------------------- // Purpose: // Input : state - //----------------------------------------------------------------------------- void TimelineItem::SetActive( bool state ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; track->SetTrackActive( state ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool TimelineItem::IsActive( void ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return false; return track->IsTrackActive(); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool TimelineItem::IsValid( void ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return false; if ( track->GetNumSamples( 0 ) > 0 ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: // Output : CFlexAnimationTrack //----------------------------------------------------------------------------- CFlexAnimationTrack *TimelineItem::GetSafeTrack( void ) { if ( !g_pExpressionTool ) return NULL; CChoreoEvent *ev = g_pExpressionTool->GetSafeEvent(); if ( !ev ) return NULL; // Find track by name for ( int i = 0; i < ev->GetNumFlexAnimationTracks() ; i++ ) { CFlexAnimationTrack *track = ev->GetFlexAnimationTrack( i ); if ( track && !stricmp( track->GetFlexControllerName(), m_szTrackName ) ) { return track; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : type - //----------------------------------------------------------------------------- void TimelineItem::SetEditType( int type ) { Assert( type == 0 || type == 1 ); CFlexAnimationTrack *track = GetSafeTrack(); if ( !track || !track->IsComboType() ) { type = 0; } m_nEditType = type; } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int TimelineItem::GetEditType( void ) { return m_nEditType; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::SelectPoints( void ) { RECT rcSelection; rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; int selW = rcSelection.right - rcSelection.left; int selH = rcSelection.bottom - rcSelection.top; float tolerance = FP_TL_SELECTION_RECTANGLE_TOLERANCE; // If they are just clicking and releasing in one spot, capture any items w/in a larger tolerance if ( selW <= 2 && selH <= 2 ) { tolerance = FP_TL_SELECTION_TOLERANCE; CExpressionSample *sample = GetSampleUnderMouse( rcSelection.left + selW * 0.5f, rcSelection.top + selH * 0.5f ); if ( sample ) { sample->selected = true; return; } } else { InflateRect( &rcSelection, 3, 3 ); } int width = m_rcBounds.right - m_rcBounds.left; int height = m_rcBounds.bottom - m_rcBounds.top; CFlexAnimationTrack *track = GetSafeTrack(); if ( !track || !width || !height ) return; CChoreoEvent *e = track->GetEvent(); Assert( e ); if ( !e ) return; float duration = e->GetDuration(); float fleft = (float)GetTimeForMouse( rcSelection.left + m_rcBounds.left ); float fright = (float)GetTimeForMouse( rcSelection.right + m_rcBounds.left ); //fleft *= duration; //fright *= duration; float ftop = (float)rcSelection.top / (float)height; float fbottom = (float)rcSelection.bottom / (float)height; fleft = clamp( fleft, 0.0f, duration ); fright = clamp( fright, 0.0f, duration ); ftop = clamp( ftop, 0.0f, 1.0f ); fbottom = clamp( fbottom, 0.0f, 1.0f ); float timestepperpixel = 1.0f / g_pExpressionTool->GetPixelsPerSecond(); float yfracstepperpixel = 1.0f / (float)height; float epsx = tolerance*timestepperpixel; float epsy = tolerance*yfracstepperpixel; for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ ) { CExpressionSample *sample = track->GetSample( i, m_nEditType ); if ( sample->time + epsx < fleft ) continue; if ( sample->time - epsx > fright ) continue; if ( (1.0f - sample->value ) + epsy < ftop ) continue; if ( (1.0f - sample->value ) - epsy > fbottom ) continue; sample->selected = true; } } //----------------------------------------------------------------------------- // Purpose: // Input : *undodescription - //----------------------------------------------------------------------------- void TimelineItem::PreDataChanged( char const *undodescription ) { if ( m_nUndoSetup == 0 ) { g_pChoreoView->SetDirty( true ); g_pChoreoView->PushUndo( undodescription ); } ++m_nUndoSetup; } //----------------------------------------------------------------------------- // Purpose: // Input : *redodescription - //----------------------------------------------------------------------------- void TimelineItem::PostDataChanged( char const *redodescription ) { --m_nUndoSetup; if ( m_nUndoSetup == 0 ) { g_pChoreoView->PushRedo( redodescription ); g_pExpressionTool->InvalidateLayout(); } } void TimelineItem::SetBounds( const RECT& rect ) { m_rcBounds = rect; } void TimelineItem::GetBounds( RECT& rect ) { rect = m_rcBounds; } void TimelineItem::SetVisible( bool vis ) { m_bVisible = vis; } bool TimelineItem::GetVisible( void ) const { return m_bVisible; } int TimelineItem::GetNumSelected( void ) { return m_nNumSelected; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::SnapAll() { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; for ( int t = 0; t < 2; t++ ) { for ( int i = 0; i < track->GetNumSamples( t ); i++ ) { CExpressionSample *sample = track->GetSample( i, t ); sample->time = FacePoser_SnapTime( sample->time ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::SnapSelected() { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; for ( int t = 0; t < 2; t++ ) { for ( int i = 0; i < track->GetNumSamples( t ); i++ ) { CExpressionSample *sample = track->GetSample( i, t ); if ( !sample->selected ) continue; sample->time = FacePoser_SnapTime( sample->time ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : start - // end - //----------------------------------------------------------------------------- void TimelineItem::DeletePoints( float start, float end ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; for ( int t = 0; t < 2; t++ ) { int num = track->GetNumSamples( t ); for ( int i = num - 1; i >= 0 ; i-- ) { CExpressionSample *sample = track->GetSample( i, t ); if ( sample->time < start || sample->time > end ) continue; track->RemoveSample( i, t ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TimelineItem::OnDoubleClicked() { // Disabled for now by request of BillF CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; SetCollapsed( !IsCollapsed() ); g_pExpressionTool->LayoutItems( true ); } void TimelineItem::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) { CFlexAnimationTrack *track = GetSafeTrack(); if ( !track ) return; CChoreoEvent *e = track->GetEvent(); if ( !e ) return; float duration = e->GetDuration(); if ( !duration ) return; int leftx = GetMouseForTime( duration ); if ( leftx > m_rcBounds.right ) return; drawHelper.DrawColoredLine( COLOR_CHOREO_ENDTIME, PS_SOLID, 1, leftx, m_rcBounds.top, leftx, m_rcBounds.bottom ); } //----------------------------------------------------------------------------- // Purpose: // Input : helper - // handleRect - //----------------------------------------------------------------------------- void TimelineItem::DrawGrowHandle( CChoreoWidgetDrawHelper& helper, RECT& handleRect ) { Assert(CanHaveGrowHandle()); RECT useRect = handleRect; helper.OffsetSubRect( useRect ); POINT region[4]; int cPoints = 4; region[ 0 ].x = useRect.left + GROW_HANDLE_INSETPIXELS; region[ 0 ].y = useRect.top; region[ 1 ].x = useRect.right - GROW_HANDLE_INSETPIXELS; region[ 1 ].y = useRect.top; region[ 2 ].x = useRect.right; region[ 2 ].y = useRect.bottom; region[ 3 ].x = useRect.left; region[ 3 ].y = useRect.bottom; HDC dc = helper.GrabDC(); HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE ); int oldPF = SetPolyFillMode( dc, ALTERNATE ); HBRUSH brBg = CreateSolidBrush( RGB( 150, 150, 150 ) ); HBRUSH brBorder = CreateSolidBrush( RGB( 200, 200, 200 ) ); FillRgn( dc, rgn, brBg ); FrameRgn( dc, rgn, brBorder, 1, 1 ); SetPolyFillMode( dc, oldPF ); DeleteObject( rgn ); DeleteObject( brBg ); DeleteObject( brBorder ); // draw a line in the middle int midy = ( handleRect.bottom + handleRect.top ) * 0.5f; int lineinset = GROW_HANDLE_INSETPIXELS *1.5; helper.DrawColoredLine( RGB( 63, 63, 63 ), PS_SOLID, 1, handleRect.left + lineinset, midy, handleRect.right - lineinset, midy ); } //----------------------------------------------------------------------------- // Purpose: // Input : rc - //----------------------------------------------------------------------------- void TimelineItem::GetGrowHandleRect( RECT& rc ) { rc = m_rcBounds; rc.bottom -= 1; rc.top = rc.bottom - GROW_HANDLE_HEIGHT; rc.left = ( rc.right + rc.left ) / 2 - GROW_HANDLE_WIDTH / 2; rc.right = rc.left + GROW_HANDLE_WIDTH; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool TimelineItem::CanHaveGrowHandle() { if ( IsCollapsed() ) return false; if ( !g_pExpressionTool->IsFocusItem( this ) ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : x - // y - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool TimelineItem::IsMouseOverGrowHandle( int x, int y) { if ( !CanHaveGrowHandle() ) return false; RECT rcGrowHandle; GetGrowHandleRect( rcGrowHandle ); POINT pt; pt.x = x + m_rcBounds.left; pt.y = y + m_rcBounds.top; return PtInRect( &rcGrowHandle, pt ) ? true : false; } void TimelineItem::DrawGrowRect() { RECT rcFocus = m_rcBounds; rcFocus.bottom = m_rcBounds.top + m_nLastY; OffsetRect( &rcFocus, -m_rcBounds.left, -m_rcBounds.top ); POINT offset; offset.x = m_rcBounds.left; offset.y = m_rcBounds.top; ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset ); OffsetRect( &rcFocus, offset.x, offset.y ); HDC dc = GetDC( NULL ); ::DrawFocusRect( dc, &rcFocus ); ReleaseDC( NULL, dc ); } void TimelineItem::GetWorkList( bool reflect, CUtlVector< TimelineItem * >& list ) { if ( !reflect ) { list.AddToTail( this ); } else { g_pExpressionTool->GetTimelineItems( list ); } }