Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1763 lines
42 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "hlfaceposer.h"
#include <stdio.h>
#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 );
}
}