//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "dme_controls/attributeslider.h" #include "materialsystem/imesh.h" #include "movieobjects/dmeanimationset.h" #include "vgui/IInput.h" #include "vgui/ISurface.h" #include "vgui_controls/TextEntry.h" #include "vgui_controls/TextImage.h" #include "vgui_controls/subrectimage.h" #include "vgui_controls/CheckButton.h" #include "dme_controls/BaseAnimSetAttributeSliderPanel.h" #include "dme_controls/BaseAnimationSetEditor.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; //----------------------------------------------------------------------------- // Enums //----------------------------------------------------------------------------- #define SLIDER_PIXEL_SPACING 3 #define CIRCULAR_CONTROL_RADIUS 6.0f #define UNDO_CHAIN_MOUSEWHEEL_ATTRIBUTE_SLIDER 9876 #define FRAC_PER_PIXEL 0.0025f #define ANIM_SET_ATTRIBUTE_SLIDER_BALANCE_INSET 30 #define ANIM_SET_ATTRIBUTE_SLIDER_LEFT_BORDER 5 #define ANIM_SET_ATTRIBUTE_SLIDER_GRAPH_BUTTON_WIDTH 16 #define ANIM_SET_ATTRIBUTE_SLIDER_MULTILEVEL_INSET 30 static ConVar ifm_attributeslider_sensitivity( "ifm_attributeslider_sensitivity", "3.0", 0 ); //----------------------------------------------------------------------------- // Globals //----------------------------------------------------------------------------- static Color s_TextColor( 200, 200, 200, 192 ); static Color s_TextColorFocus( 208, 143, 40, 192 ); // NOTE: Index with [preview][selected] static Color s_BarColor[2][2] = { { Color( 45, 45, 45, 255 ), Color( 150, 80, 0, 255 ) }, { Color( 30, 255, 255, 80 ), Color( 30, 180, 255, 255 ) } }; static Color s_ZeroColor[2][2] = { { Color( 33, 33, 33, 255 ), Color( 0, 255, 255, 60 ) }, { Color( 100, 80, 0, 255 ), Color( 0, 180, 255, 255 ) } }; static Color s_DraggingBarColor( 142, 142, 142, 255 ); static Color s_PreviewTickColor( 255, 164, 8, 255 ); static Color s_OldValueTickColor( 100, 100, 100, 63 ); static Color s_MidpointColor( 115, 115, 115, 255 ); //----------------------------------------------------------------------------- // Blends flex values in left-right space instead of balance/value space //----------------------------------------------------------------------------- static void BlendFlexValues( AttributeValue_t *pResult, const AttributeValue_t &src, const AttributeValue_t &dest, float flBlend, float flBalanceFilter = 0.5f ) { // Apply the left-right balance to the target float flLeftFilter, flRightFilter; ValueBalanceToLeftRight( &flLeftFilter, &flRightFilter, flBlend, flBalanceFilter ); // Do the math in 'left-right' space because we filter in that space float flSrcLeft, flSrcRight; ValueBalanceToLeftRight( &flSrcLeft, &flSrcRight, src.m_pValue[ANIM_CONTROL_VALUE], src.m_pValue[ANIM_CONTROL_BALANCE] ); float flDestLeft, flDestRight; ValueBalanceToLeftRight( &flDestLeft, &flDestRight, dest.m_pValue[ANIM_CONTROL_VALUE], dest.m_pValue[ANIM_CONTROL_BALANCE] ); float flTargetLeft = flSrcLeft + flLeftFilter * ( flDestLeft - flSrcLeft ); float flTargetRight = flSrcRight + flRightFilter * ( flDestRight - flSrcRight ); LeftRightToValueBalance( &pResult->m_pValue[ANIM_CONTROL_VALUE], &pResult->m_pValue[ANIM_CONTROL_BALANCE], flTargetLeft, flTargetRight, ( flBlend <= 0.5f ) ? src.m_pValue[ANIM_CONTROL_BALANCE] : dest.m_pValue[ANIM_CONTROL_BALANCE] ); pResult->m_pValue[ANIM_CONTROL_MULTILEVEL] = src.m_pValue[ANIM_CONTROL_MULTILEVEL] + ( dest.m_pValue[ANIM_CONTROL_MULTILEVEL] - src.m_pValue[ANIM_CONTROL_MULTILEVEL] ) * flBlend; } //----------------------------------------------------------------------------- // The panel used to do text entry when double-clicking in the slider //----------------------------------------------------------------------------- class CAttributeSliderTextEntry : public TextEntry { DECLARE_CLASS_SIMPLE( CAttributeSliderTextEntry, TextEntry ); public: CAttributeSliderTextEntry( CAttributeSlider *slider, const char *panelName ) : BaseClass( (Panel *)slider, panelName ), m_pSlider( slider ) { Assert( m_pSlider ); } MESSAGE_FUNC_PARAMS( OnKillFocus, "KillFocus", kv ); virtual void OnMouseWheeled( int delta ); private: CAttributeSlider *m_pSlider; }; //----------------------------------------------------------------------------- // // CAttributeSlider begins here // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CAttributeSlider::CAttributeSlider( CBaseAnimSetAttributeSliderPanel *parent, const char *panelName, CDmElement *pControl ) : BaseClass( (Panel *)parent, panelName ), m_pParent( parent ), m_pWhite( NULL ), m_bPreviewEnabled( false ), m_bSimplePreviewOnly( true ), m_bCursorInsidePanel( false ), m_flPreviewGoalTime( -1.0f ), m_bRampUp( false ), m_bFaderBeingDragged( false ), m_flFaderAmount( 1.0f ), m_bIsLogPreviewControl( false ), m_bSelected( false ), m_pRightTextField( 0 ) { m_SliderMode = SLIDER_MODE_NONE; m_hControl = pControl; // Cache off control information since this state should never change // NOTE: If it ever does, just change the implementations of // IsTransform + GetMidpoint to always read these values from the attributes m_bTransform = pControl->GetValue< bool >( "transform" ); m_nDragStartPosition[ 0 ] = m_nDragStartPosition[ 1 ] = 0; m_nAccum[ 0 ] = m_nAccum[ 1 ] = 0; m_flDragStartValue = 1.0f; m_flDragStartBalance = 0.5f; SetPaintBackgroundEnabled( true ); m_pName = new TextImage( panelName ); m_pValues[ 0 ] = new TextImage( "" ); m_pValues[ 1 ] = new TextImage( "" ); m_pValues[ 2 ] = new TextImage( "" ); m_pCircleImage = new CSubRectImage( "tools/ifm/icon_balance", false, 7, 8, 19, 15 ); // Allocate a white material KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); pVMTKeyValues->SetInt( "$vertexcolor", 1 ); pVMTKeyValues->SetInt( "$vertexalpha", 1 ); pVMTKeyValues->SetInt( "$ignorez", 1 ); pVMTKeyValues->SetInt( "$no_fullbright", 1 ); pVMTKeyValues->SetInt( "$nocull", 1 ); m_pWhite.Init( "AttributeSlider_White", NULL, pVMTKeyValues ); SetBgColor( Color( 42, 42, 42, 255 ) ); m_bIsControlActive[ANIM_CONTROL_VALUE] = true; m_bIsControlActive[ANIM_CONTROL_BALANCE] = false; m_bIsControlActive[ANIM_CONTROL_MULTILEVEL] = false; m_pTextField = new CAttributeSliderTextEntry( this, panelName ); m_pTextField->SetVisible( false ); m_pTextField->SetEnabled( false ); m_pTextField->SelectAllOnFocusAlways( true ); SetPaintBorderEnabled( false ); } CAttributeSlider::~CAttributeSlider() { m_pWhite.Shutdown(); delete m_pCircleImage; delete m_pName; delete m_pValues[ 0 ]; delete m_pValues[ 1 ]; delete m_pValues[ 2 ]; } //----------------------------------------------------------------------------- // Scheme //----------------------------------------------------------------------------- void CAttributeSlider::ApplySchemeSettings( IScheme *scheme ) { BaseClass::ApplySchemeSettings( scheme ); m_pName->SetFont( scheme->GetFont( "Default" ) ); m_pName->SetColor( s_TextColor ); m_pName->ResizeImageToContent(); m_pValues[ 0 ]->SetColor( s_TextColor ); m_pValues[ 0 ]->SetFont( scheme->GetFont( "Default" ) ); m_pValues[ 1 ]->SetColor( s_TextColorFocus ); m_pValues[ 1 ]->SetFont( scheme->GetFont( "Default" ) ); m_pValues[ 2 ]->SetColor( s_TextColor ); m_pValues[ 2 ]->SetFont( scheme->GetFont( "Default" ) ); m_pCircleImage->SetColor( Color( 255, 255, 255, 255 ) ); SetBgColor( Color( 42, 42, 42, 255 ) ); SetFgColor( Color( 194, 120, 0, 255 ) ); } //----------------------------------------------------------------------------- // Gets/sets the slider value. // NOTE: This may not match the value pushed into the control because of fading //----------------------------------------------------------------------------- static const char *s_pChangeMessage[ANIM_CONTROL_COUNT] = { "SliderMoved", "BalanceChanged", "MultiLevelChanged", }; static const char *s_pChangeKeyValue[ANIM_CONTROL_COUNT] = { "position", "balance", "level", }; void CAttributeSlider::ActivateControl( AnimationControlType_t type, bool bActive ) { if ( m_bIsControlActive[type] != bActive ) { m_bIsControlActive[type] = bActive; if ( bActive ) { PostActionSignal( new KeyValues( s_pChangeMessage[type], s_pChangeKeyValue[type], m_Control.m_pValue[type] ) ); } } } bool CAttributeSlider::IsControlActive( AnimationControlType_t type ) { return m_bIsControlActive[type]; } void CAttributeSlider::SetValue( AnimationControlType_t type, float flValue ) { if ( m_Control.m_pValue[type] != flValue ) { m_Control.m_pValue[type] = flValue; if ( m_bIsControlActive[type] ) { PostActionSignal( new KeyValues( s_pChangeMessage[type], s_pChangeKeyValue[type], flValue ) ); } } } void CAttributeSlider::SetValue( const AttributeValue_t& value ) { for ( int i = 0; i < ANIM_CONTROL_COUNT; ++i ) { SetValue( (AnimationControlType_t)i, value.m_pValue[i] ); } } float CAttributeSlider::GetValue( AnimationControlType_t type ) const { return m_Control.m_pValue[type]; } const AttributeValue_t& CAttributeSlider::GetValue() const { return m_Control; } //----------------------------------------------------------------------------- // Returns the default value for the control //----------------------------------------------------------------------------- float CAttributeSlider::GetControlDefaultValue( AnimationControlType_t type ) const { if ( IsTransform() ) return 0.0f; Assert( m_hControl.Get() ); if ( !m_hControl.Get() ) return 0.0f; switch ( type ) { case ANIM_CONTROL_VALUE: return m_hControl->GetValue( "defaultValue" ); case ANIM_CONTROL_BALANCE: return m_hControl->GetValue( "defaultBalance" ); case ANIM_CONTROL_MULTILEVEL: return m_hControl->GetValue( "defaultMultilevel" ); } return 0.0f; } //----------------------------------------------------------------------------- // Given a mouse position in (x,y) in local coordinates, which animation control is it over? //----------------------------------------------------------------------------- AnimationControlType_t CAttributeSlider::DetermineControl( int x, int y ) { if ( IsControlActive( ANIM_CONTROL_MULTILEVEL ) ) { Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_MULTILEVEL ); if ( x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height ) return ANIM_CONTROL_MULTILEVEL; } return ANIM_CONTROL_VALUE; } void CAttributeSlider::SetSelected( bool state ) { m_bSelected = state; } bool CAttributeSlider::IsSelected() const { return m_bSelected; } void CAttributeSlider::SetIsLogPreviewControl( bool state ) { m_bIsLogPreviewControl = state; } void CAttributeSlider::OnCursorEntered() { BaseClass::OnCursorEntered(); m_bCursorInsidePanel = true; } void CAttributeSlider::OnCursorExited() { BaseClass::OnCursorExited(); m_bCursorInsidePanel = false; } //----------------------------------------------------------------------------- // Mouse event handlers //----------------------------------------------------------------------------- void CAttributeSlider::OnMousePressed( MouseCode code ) { if ( !IsEnabled() || IsInTextEntry() || IsDragging() ) return; // Deal with transform sliders if ( m_bTransform ) { bool bCtrlDown = ( input()->IsKeyDown( KEY_LCONTROL ) || input()->IsKeyDown( KEY_RCONTROL ) ); m_pParent->SetLogPreviewControl( m_hControl ); if ( !bCtrlDown ) { m_pParent->ClearSelectedControls(); } m_pParent->SetControlSelected( this, !IsSelected() ); return; } // Determine which control we clicked on int x,y; input()->GetCursorPosition( x, y ); ScreenToLocal( x, y ); AnimationControlType_t type = DetermineControl( x, y ); // Right click sets the value to match the default value if ( code == MOUSE_RIGHT ) { SetValue( type, GetControlDefaultValue( type ) ); CUndoScopeGuard guard( "Set Slider Value To Default" ); StampValueIntoLogs( type, GetControlDefaultValue( type ) ); return; } if ( code != MOUSE_LEFT ) return; // Cache off the value at the click point // in case we end up receiving a double-click m_InitialTextEntryValue = m_Control; // Enter drag mode m_SliderMode = (SliderMode_t)( SLIDER_MODE_FIRST_DRAG_MODE + type ); m_nDragStartPosition[ 0 ] = x; m_nDragStartPosition[ 1 ] = y; m_nAccum[ 0 ] = m_nAccum[ 1 ] = 0; m_flDragStartValue = GetValue( type ); m_flDragStartBalance = GetValue( ANIM_CONTROL_BALANCE ); input()->SetMouseCapture( GetVPanel() ); SetCursor( dc_blank ); m_pParent->RecomputePreview(); } void CAttributeSlider::OnCursorMoved( int x, int y ) { if ( !IsEnabled() || !IsDragging() || m_bTransform ) return; // NOTE: This works because we always slam the mouse to be back at the start position // at the end of this function // Accumulate the total mouse movement int dx = x - m_nDragStartPosition[ 0 ]; m_nAccum[ 0 ] += dx; float flFactor = FRAC_PER_PIXEL * ifm_attributeslider_sensitivity.GetFloat(); bool bInRecordMode = m_pParent->GetEditor()->GetRecordingState() == AS_RECORD; float flMinVal = bInRecordMode ? -1.0f : 0.0f; float flMaxVal = bInRecordMode ? 2.0f : 1.0f; // Clamp accum so we never generate values < -1 or > 2 int nMinVal = floor( ( -m_flDragStartValue + flMinVal ) / flFactor ); int nMaxVal = ceil( ( -m_flDragStartValue + flMaxVal ) / flFactor ); m_nAccum[ 0 ] = clamp( m_nAccum[ 0 ], nMinVal, nMaxVal ); float flDelta = flFactor * m_nAccum[ 0 ]; if ( GetDragControl() == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { // do the hacky conversion from the ui's left/right to the underlying value/balance float flLeftValue, flRightValue; ValueBalanceToLeftRight( &flLeftValue, &flRightValue, m_flDragStartValue, m_flDragStartBalance ); float flLeftDelta, flRightDelta; ValueBalanceToLeftRight( &flLeftDelta, &flRightDelta, flDelta, m_pParent->GetBalanceSliderValue() ); flLeftValue = clamp( flLeftValue + flLeftDelta, flMinVal, flMaxVal ); flRightValue = clamp( flRightValue + flRightDelta, flMinVal, flMaxVal ); float flValue, flBalance; LeftRightToValueBalance( &flValue, &flBalance, flLeftValue, flRightValue ); SetValue( GetDragControl(), flValue ); SetValue( ANIM_CONTROL_BALANCE, flBalance ); // TODO - add balance for multi control as well } else { float flValue = clamp( m_flDragStartValue + flDelta, flMinVal, flMaxVal ); SetValue( GetDragControl(), flValue ); } // Slam the cursor back to the drag start point if ( x != m_nDragStartPosition[ 0 ] || y != m_nDragStartPosition[ 1 ] ) { x = m_nDragStartPosition[ 0 ]; y = m_nDragStartPosition[ 1 ]; LocalToScreen( x, y ); input()->SetCursorPos( x, y ); } } void CAttributeSlider::OnMouseReleased( MouseCode code ) { if ( !IsEnabled() || !IsDragging() || m_bTransform ) return; m_SliderMode = SLIDER_MODE_NONE; input()->SetMouseCapture( NULL ); SetCursor( dc_arrow ); m_pParent->RecomputePreview(); } //----------------------------------------------------------------------------- // // Methods related to text entry mode // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Called by the text entry code to enter the value into the logs //----------------------------------------------------------------------------- void CAttributeSlider::StampValueIntoLogs( AnimationControlType_t type, float flValue ) { Assert( !m_bTransform ); m_pParent->StampValueIntoLogs( m_hControl, type, flValue ); } //----------------------------------------------------------------------------- // Key typed key handler //----------------------------------------------------------------------------- void CAttributeSlider::OnKeyCodeTyped( KeyCode code ) { if ( !IsInTextEntry() ) { BaseClass::OnKeyCodeTyped( code ); return; } switch ( code ) { default: BaseClass::OnKeyCodeTyped( code ); break; case KEY_ESCAPE: DiscardTextEntryValue(); break; case KEY_ENTER: AcceptTextEntryValue(); break; } } //----------------------------------------------------------------------------- // Methods to entry text entry mode //----------------------------------------------------------------------------- void CAttributeSlider::EnterTextEntryMode( AnimationControlType_t type, bool bRelatchValues ) { if ( m_bTransform ) return; m_SliderMode = (SliderMode_t)( SLIDER_MODE_FIRST_TEXT_MODE + type ); // For double-clicking, ignore the value set by the first single mouse click if ( !bRelatchValues ) { SetValue( m_InitialTextEntryValue ); } m_pTextField->SetVisible( true ); m_pTextField->SetEnabled( true ); if ( type == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { if ( !m_pRightTextField ) { m_pRightTextField = new CAttributeSliderTextEntry( this, GetName() ); m_pRightTextField->SetVisible( false ); m_pRightTextField->SetEnabled( false ); m_pRightTextField->SelectAllOnFocusAlways( true ); InvalidateLayout(); } m_pRightTextField->SetVisible( true ); m_pRightTextField->SetEnabled( true ); float flValue = m_InitialTextEntryValue.m_pValue[ ANIM_CONTROL_VALUE ]; float flBalance = m_InitialTextEntryValue.m_pValue[ ANIM_CONTROL_BALANCE ]; float flLeftValue, flRightValue; ValueBalanceToLeftRight( &flLeftValue, &flRightValue, flValue, flBalance ); char val[ 64 ]; V_snprintf( val, sizeof( val ), "%f", flLeftValue ); m_pTextField->SetText( val ); V_snprintf( val, sizeof( val ), "%f", flRightValue ); m_pRightTextField->SetText( val ); m_pRightTextField->GotoTextEnd(); m_pRightTextField->RequestFocus(); } else { char val[ 64 ]; Q_snprintf( val, sizeof( val ), "%f", m_InitialTextEntryValue.m_pValue[ type ] ); m_pTextField->SetText( val ); } m_pTextField->GotoTextEnd(); m_pTextField->RequestFocus(); } //----------------------------------------------------------------------------- // Methods to accept or discard the value in the text entry field //----------------------------------------------------------------------------- void CAttributeSlider::AcceptTextEntryValue() { if ( !IsInTextEntry() ) return; Assert( !m_bTransform ); // Get the value in the text entry field char buf[ 64 ]; m_pTextField->GetText( buf, sizeof( buf ) ); float flValue = Q_atof( buf ); // Hide the text entry m_pTextField->SetVisible( false ); m_pTextField->SetEnabled( false ); if ( m_pRightTextField && GetTextEntryControl() == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { float flLeftValue = flValue; // Get the value in the text entry field buf[0] = 0; m_pRightTextField->GetText( buf, sizeof( buf ) ); float flRightValue = Q_atof( buf ); // Hide the text entry m_pRightTextField->SetVisible( false ); m_pRightTextField->SetEnabled( false ); float flBalance; LeftRightToValueBalance( &flValue, &flBalance, flLeftValue, flRightValue ); SetValue( ANIM_CONTROL_BALANCE, flBalance ); StampValueIntoLogs( ANIM_CONTROL_BALANCE, flBalance ); } // Apply the change AnimationControlType_t type = GetTextEntryControl(); SetValue( type, flValue ); StampValueIntoLogs( type, flValue ); m_SliderMode = SLIDER_MODE_NONE; RequestFocus(); } void CAttributeSlider::DiscardTextEntryValue() { if ( !IsInTextEntry() ) return; Assert( !m_bTransform ); // Hide the text entry m_pTextField->SetVisible( false ); m_pTextField->SetEnabled( false ); if ( m_pRightTextField && GetTextEntryControl() == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { m_pRightTextField->SetVisible( false ); m_pRightTextField->SetEnabled( false ); } m_SliderMode = SLIDER_MODE_NONE; RequestFocus(); } //----------------------------------------------------------------------------- // Methods of the text entry widget //----------------------------------------------------------------------------- void CAttributeSliderTextEntry::OnKillFocus( KeyValues *pParams ) { Assert( m_pSlider ); SelectNone(); VPANEL hPanel = (VPANEL)pParams->GetPtr( "newPanel" ); if ( hPanel != INVALID_PANEL && vgui::ipanel()->GetParent( hPanel ) == m_pSlider->GetVPanel() ) return; m_pSlider->AcceptTextEntryValue(); } void CAttributeSliderTextEntry::OnMouseWheeled( int delta ) { if ( m_pSlider->m_bTransform ) return; float deltaFactor; if ( input()->IsKeyDown(KEY_LSHIFT) ) { deltaFactor = ((float)delta) * 10.0f; } else if ( input()->IsKeyDown(KEY_LCONTROL) ) { deltaFactor = ((float)delta) / 100.0; } else { deltaFactor = ((float)delta) / 10.0; } char sz[ 64 ]; GetText( sz, sizeof( sz ) ); float val = Q_atof( sz ) + deltaFactor; if ( input()->IsKeyDown(KEY_LALT) ) { val = clamp( val, 0.0f, 1.0f ); } Q_snprintf( sz, sizeof( sz ), "%f", val ); SetText( sz ); m_pSlider->SetValue( ANIM_CONTROL_VALUE, val ); CUndoScopeGuard guard( UNDO_CHAIN_MOUSEWHEEL_ATTRIBUTE_SLIDER, "Set Slider Value" ); m_pSlider->StampValueIntoLogs( m_pSlider->GetTextEntryControl(), val ); } void CAttributeSlider::OnMouseDoublePressed( MouseCode code ) { if ( !IsEnabled() || IsDragging() ) return; if ( code != MOUSE_LEFT ) return; int x,y; input()->GetCursorPosition( x, y ); ScreenToLocal( x, y ); AnimationControlType_t type = DetermineControl( x, y ); EnterTextEntryMode( type, false ); } //----------------------------------------------------------------------------- // // Methods related to preview // //----------------------------------------------------------------------------- void CAttributeSlider::EnablePreview( bool state, bool simple, bool faderdrag ) { m_bPreviewEnabled = state; m_bSimplePreviewOnly = simple; m_bFaderBeingDragged = faderdrag; } bool CAttributeSlider::IsPreviewEnabled() const { return m_bPreviewEnabled; } bool CAttributeSlider::IsSimplePreview() const { return m_bSimplePreviewOnly; } #define ATTRIBUTE_SLIDER_RAMP_TIME 0.5f bool CAttributeSlider::IsRampingTowardPreview() const { if ( m_flPreviewGoalTime == -1.0f ) return false; return true; } void CAttributeSlider::RampDown() { if ( m_flPreviewGoalTime == -1.0f ) return; m_Previous.m_Current = GetValue(); m_Previous.m_Full = GetValue( ); m_bRampUp = false; } void CAttributeSlider::UpdateFaderAmount( float flAmount ) { m_flFaderAmount = flAmount; AttributeValue_t current = GetValue(); if ( m_flPreviewGoalTime == -1.0f ) { BlendFlexValues( &m_Preview.m_Current, current, m_Preview.m_Full, flAmount ); return; } BlendFlexValues( &m_Next.m_Current, current, m_Next.m_Full, flAmount ); } void CAttributeSlider::UpdateTime( float dt ) { if ( m_flPreviewGoalTime == -1.0f ) return; // Move toward goal if ( m_bRampUp ) { if ( m_flPreviewGoalTime < ATTRIBUTE_SLIDER_RAMP_TIME ) { m_flPreviewGoalTime += dt; } } else { m_flPreviewGoalTime -= dt; } if ( m_flPreviewGoalTime >= ATTRIBUTE_SLIDER_RAMP_TIME ) { m_Preview = m_Next; m_flPreviewGoalTime = ATTRIBUTE_SLIDER_RAMP_TIME; } else if ( m_flPreviewGoalTime <= 0.0f ) { m_flPreviewGoalTime = -1.0f; m_Preview = m_Previous; } else { float frac = m_flPreviewGoalTime / ATTRIBUTE_SLIDER_RAMP_TIME; BlendFlexValues( &m_Preview.m_Current, m_Previous.m_Current, m_Next.m_Current, frac ); BlendFlexValues( &m_Preview.m_Full, m_Previous.m_Full, m_Next.m_Full, frac ); } } void CAttributeSlider::SetPreview( const AttributeValue_t &value, const AttributeValue_t &full, bool instantaneous, bool startfromcurrent ) { m_bRampUp = true; if ( instantaneous ) { m_Next.m_Current = value; m_Next.m_Full = full; m_Preview = m_Previous = m_Next; m_flPreviewGoalTime = -1.0f; } else { // Current becomes previous, next becomes goal and preview starts moving toward that goal if ( startfromcurrent ) { m_Previous.m_Current = GetValue( ); m_Previous.m_Full = GetValue( ); } else { m_Previous = m_Preview; } m_Next.m_Current = value; m_Next.m_Full = full; m_flPreviewGoalTime = 0.0f; } } const AttributeValue_t &CAttributeSlider::GetPreview() const { return m_Preview.m_Current; } float CAttributeSlider::GetPreview( AnimationControlType_t type ) const { return m_Preview.m_Current.m_pValue[type]; } // Estimates the value of the control given a local coordinate float CAttributeSlider::EstimateValueAtPos( int nLocalX, int nLocalY ) const { Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); float flFactor = rect.width > 1 ? (float)( nLocalX - rect.x ) / (float)( rect.width - 1 ) : 0.5f; flFactor = clamp( flFactor, 0.0f, 1.0f ); return flFactor; } //----------------------------------------------------------------------------- // Layout //----------------------------------------------------------------------------- void CAttributeSlider::PerformLayout() { BaseClass::PerformLayout(); Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); // Place the text entry along the main attribute track rectangle if ( m_pRightTextField && GetTextEntryControl() == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { m_pTextField ->SetBounds( rect.x, rect.y, rect.width / 2, rect.height ); m_pRightTextField->SetBounds( rect.x + rect.width / 2, rect.y, rect.width / 2, rect.height ); } else { m_pTextField->SetBounds( rect.x, rect.y, rect.width, rect.height ); } } void CAttributeSlider::GetControlRect( Rect_t *pRect, AnimationControlType_t type ) const { int sw, sh; const_cast( this )->GetSize( sw, sh ); int cw, ch; m_pCircleImage->GetSize( cw, ch ); switch ( type ) { case ANIM_CONTROL_VALUE: pRect->x = 2 * SLIDER_PIXEL_SPACING + cw; pRect->y = SLIDER_PIXEL_SPACING; pRect->width = sw - pRect->x * 2; pRect->height = max( 0, sh - SLIDER_PIXEL_SPACING * 2 ); break; /* case ANIM_CONTROL_BALANCE: pRect->x = SLIDER_PIXEL_SPACING; pRect->y = max( 0, sh - ch ) / 2; pRect->width = cw; pRect->height = min( ch, sh ); break; */ case ANIM_CONTROL_MULTILEVEL: pRect->x = sw - SLIDER_PIXEL_SPACING - cw; pRect->y = max( 0, sh - ch ) / 2; pRect->width = cw; pRect->height = min( ch, sh ); break; } } bool CAttributeSlider::IsFaderBeingDragged() { return IsPreviewEnabled() && m_bFaderBeingDragged; } //----------------------------------------------------------------------------- // // Methods related to painting start here // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Used to control how fader-driven ticks look //----------------------------------------------------------------------------- float CAttributeSlider::GetPreviewAlphaScale() const { return max( m_flFaderAmount, 0.1f ); } //----------------------------------------------------------------------------- // Draws a tick on the main control //----------------------------------------------------------------------------- void CAttributeSlider::DrawTick( const Color& clr, float frac, int width, int inset ) { // Get the control position Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); // Inset by 1 pixel rect.x++; rect.y++; rect.width -= 2; rect.height -= 2; surface()->DrawSetColor( clr ); int previewx = (int)( frac * (float)rect.width + 0.5f ) + rect.x; int previewtall = rect.height - 2 * inset; int ypos = rect.y + ( rect.height - previewtall ) / 2; int xpos = previewx - width / 2; xpos = clamp( xpos, rect.x, rect.x + rect.width - width ); surface()->DrawFilledRect( xpos, ypos, xpos + width, ypos + previewtall ); } //----------------------------------------------------------------------------- // Draws a preview tick on the main control //----------------------------------------------------------------------------- void CAttributeSlider::DrawPreviewTick( bool bMainTick ) { Color col = s_PreviewTickColor; col[ 3 ] *= bMainTick ? GetPreviewAlphaScale() : 0.5f; DrawTick( col, m_Next.m_Full.m_pValue[ ANIM_CONTROL_VALUE ], 2, 2 ); } //----------------------------------------------------------------------------- // Draws a tick on a circular control //----------------------------------------------------------------------------- void CAttributeSlider::DrawCircularTick( const Color& clr, float flValue, int nCenterX, int nCenterY, float flRadius ) { float flFraction = 1.0f; float flAngle = 0.0f; if ( flValue < 0.5f ) { flFraction = ( flValue / 0.5f ); flAngle = 180.0f + flFraction * 180.0f; } else { flFraction = ( flValue - 0.5f ) * 2.0f; flAngle = flFraction * 180.0f; } float flRadians = DEG2RAD( flAngle ); float ca = cos( flRadians ); float sa = sin( flRadians ); int nEndX = nCenterX + flRadius * sa; int nEndY = nCenterY - flRadius * ca; surface()->DrawSetColor( clr ); surface()->DrawLine( nCenterX, nCenterY, nEndX, nEndY ); } //----------------------------------------------------------------------------- // Draws a preview of a circular control //----------------------------------------------------------------------------- void CAttributeSlider::DrawCircularPreview( AnimationControlType_t type, bool bMainTick, float flRadius ) { Rect_t rect; GetControlRect( &rect, type ); // Fill left from top float flPreview = m_Next.m_Full.m_pValue[type]; float flCurrent = GetValue( type ); Color clr = s_PreviewTickColor; clr[ 3 ] *= bMainTick ? GetPreviewAlphaScale() : 0.5f; int nCenterX = rect.x + rect.width / 2; int nCenterY = rect.y + rect.height / 2; DrawCircularTick( clr, flPreview, nCenterX, nCenterY, flRadius ); if ( m_bSimplePreviewOnly && !m_bFaderBeingDragged ) return; clr = s_OldValueTickColor; if ( !bMainTick ) { clr[ 3 ] *= 0.5f; } DrawCircularTick( clr, flCurrent, nCenterX, nCenterY, flRadius ); } //----------------------------------------------------------------------------- // Paints ticks //----------------------------------------------------------------------------- void CAttributeSlider::Paint() { DrawTick( s_OldValueTickColor, GetValue( ANIM_CONTROL_VALUE ), 1, 0 ); if ( m_bPreviewEnabled ) { DrawPreviewTick( true ); if ( IsControlActive( ANIM_CONTROL_BALANCE ) ) { DrawCircularPreview( ANIM_CONTROL_BALANCE, true, CIRCULAR_CONTROL_RADIUS ); } if ( IsControlActive( ANIM_CONTROL_MULTILEVEL ) ) { DrawCircularPreview( ANIM_CONTROL_MULTILEVEL, true, CIRCULAR_CONTROL_RADIUS ); } } } //----------------------------------------------------------------------------- // Draws the min, current, and max values for the slider //----------------------------------------------------------------------------- void CAttributeSlider::DrawValueLabel( float flValue ) { float flMinVal = 0.0f; float flMaxVal = 1.0f; flValue = clamp( flValue, flMinVal, flMaxVal ); Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); int cw, ch; char sz[ 32 ]; Q_snprintf( sz, sizeof( sz ), "%.1f", flMinVal ); m_pValues[ 0 ]->SetText( sz ); m_pValues[ 0 ]->ResizeImageToContent(); m_pValues[ 0 ]->GetContentSize( cw, ch ); m_pValues[ 0 ]->SetPos( rect.x + 5, rect.y + ( rect.height - ch ) * 0.5f ); m_pValues[ 0 ]->Paint(); Q_snprintf( sz, sizeof( sz ), "%.1f", flMaxVal ); m_pValues[ 2 ]->SetText( sz ); m_pValues[ 2 ]->ResizeImageToContent(); m_pValues[ 2 ]->GetContentSize( cw, ch ); m_pValues[ 2 ]->SetPos( rect.x + rect.width - cw - 5, rect.y + ( rect.height - ch ) * 0.5f ); m_pValues[ 2 ]->Paint(); Q_snprintf( sz, sizeof( sz ), "%.3f", flValue ); m_pValues[ 1 ]->SetText( sz ); m_pValues[ 1 ]->ResizeImageToContent(); m_pValues[ 1 ]->GetContentSize( cw, ch ); m_pValues[ 1 ]->SetPos( rect.x + ( rect.width - cw ) * 0.5f, rect.y + ( rect.height - ch ) * 0.5f ); m_pValues[ 1 ]->Paint(); } //----------------------------------------------------------------------------- // Draws the text for the slider. It's either the slider name, or its value if dragging is happening //----------------------------------------------------------------------------- void CAttributeSlider::DrawNameLabel() { if ( IsDragging() ) { float flValue = GetValue( GetDragControl() ); DrawValueLabel( flValue ); return; } if ( IsInTextEntry() ) return; int w, h; GetSize( w, h ); int cw, ch; Color clr = m_bCursorInsidePanel ? s_TextColorFocus : s_TextColor; m_pName->SetColor( clr ); m_pName->GetContentSize( cw, ch ); Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); m_pName->SetPos( rect.x + ( rect.width - cw ) * 0.5f, rect.y + ( rect.height - ch ) * 0.5f ); m_pName->Paint(); } //----------------------------------------------------------------------------- // Draws the midpoint value for the slider //----------------------------------------------------------------------------- void CAttributeSlider::DrawMidpoint( int x, int ty, int ttall ) { surface()->DrawSetColor( s_MidpointColor ); surface()->DrawFilledRect( x, ty, x + 1, ty + ttall ); } //----------------------------------------------------------------------------- // Paints circular controls used for balance + multilevel controls //----------------------------------------------------------------------------- void CAttributeSlider::PaintCircularControl( float flValue, const Rect_t& rect ) { flValue = clamp( flValue, 0.0f, 1.0f ); m_pCircleImage->SetPos( rect.x, rect.y ); m_pCircleImage->Paint(); int ofs[ 2 ] = { 0 }; LocalToScreen( ofs[ 0 ], ofs[ 1 ] ); int nCenterX = ofs[ 0 ] + rect.x + rect.width / 2; int nCenterY = ofs[ 1 ] + rect.y + rect.height / 2; float maxTrianges = 36.0f; float frac = 0.0f; float step = 180.0f / (float)( maxTrianges ); float ang = 0.0f; float clamp = 360.0f; int numTriangles = 0; float radius = CIRCULAR_CONTROL_RADIUS; float zpos = vgui::surface()->GetZPos(); Vector centerVert( nCenterX, nCenterY, zpos ); Vector top; top = centerVert; top.y -= radius; // Fill left from top if ( flValue < 0.5f ) { frac = 1.0f - ( flValue / 0.5f ); numTriangles = (int)( frac * ( maxTrianges ) + 0.5f ); clamp = 180.0f; step = -step; ang = 360.0f; } else { frac = ( flValue - 0.5f ) / 0.5f; numTriangles = (int)( frac * ( maxTrianges ) + 0.5f ); clamp = 180.0f; } if ( numTriangles == 0 ) return; CMatRenderContextPtr pRenderContext( materials ); IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_pWhite ); Color clr( 102, 102, 102, 255 ); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, numTriangles ); Vector next; next.Init(); for ( int j = 0; j < numTriangles; j++ ) { ang += step; //ang = min( ang, clamp ); float flRadians = DEG2RAD( ang ); float ca = cos( flRadians ); float sa = sin( flRadians ); meshBuilder.Position3fv( centerVert.Base() ); meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a() ); meshBuilder.TexCoord2f( 0, 0, 0 ); meshBuilder.AdvanceVertex(); next.Init(); next.x = radius * sa; next.y = -radius * ca; next += centerVert; if ( step > 0 ) { meshBuilder.Position3fv( top.Base() ); meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a() ); meshBuilder.TexCoord2f( 0, 0, 1 ); meshBuilder.AdvanceVertex(); meshBuilder.Position3fv( next.Base() ); meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a() ); meshBuilder.TexCoord2f( 0, 1, 0 ); meshBuilder.AdvanceVertex(); } else { meshBuilder.Position3fv( next.Base() ); meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a() ); meshBuilder.TexCoord2f( 0, 0, 1 ); meshBuilder.AdvanceVertex(); meshBuilder.Position3fv( top.Base() ); meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a() ); meshBuilder.TexCoord2f( 0, 1, 0 ); meshBuilder.AdvanceVertex(); } top = next; } meshBuilder.End(); pMesh->Draw(); } //----------------------------------------------------------------------------- // Paints the slider //----------------------------------------------------------------------------- void CAttributeSlider::PaintBackground() { Rect_t rect; GetControlRect( &rect, ANIM_CONTROL_VALUE ); // Paint the border surface()->DrawSetColor( Color( 24, 24, 24, 255 ) ); // top and left surface()->DrawOutlinedRect( rect.x, rect.y, rect.x + rect.width, rect.y + 1 ); surface()->DrawOutlinedRect( rect.x, rect.y, rect.x + 1, rect.y + rect.height ); // right surface()->DrawSetColor( Color( 33, 33, 33, 255 ) ); surface()->DrawOutlinedRect( rect.x + rect.width - 1, rect.y, rect.x + rect.width, rect.y + rect.height ); // bottom surface()->DrawSetColor( Color( 56, 56, 56, 255 ) ); surface()->DrawOutlinedRect( rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height ); // Inset the rect by 1 pixel ++rect.x; ++rect.y; rect.width -= 2; rect.height -= 2; int y0 = rect.y; int y1 = rect.y + rect.height / 2; int y2 = rect.y + rect.height; // Draw the main bar background surface()->DrawSetColor( s_ZeroColor[ m_bIsLogPreviewControl ][ IsSelected() ] ); surface()->DrawFilledRect( rect.x, y0, rect.x + rect.width, y2 ); AnimationControlType_t viewType = ANIM_CONTROL_VALUE; if ( IsDragging() ) { viewType = GetDragControl(); } else if ( IsInTextEntry() ) { viewType = GetTextEntryControl(); } bool bUsePreview = m_bPreviewEnabled && ( !m_bSimplePreviewOnly || m_bFaderBeingDragged ); float flMidPoint = GetControlDefaultValue( viewType ); int nMidPoint = (int)( (float)rect.width * clamp( flMidPoint, 0.0f, 1.0f ) + 0.5f ); float flValue = bUsePreview ? m_Preview.m_Current.m_pValue[viewType] : GetValue( viewType ); if ( viewType == ANIM_CONTROL_VALUE && IsControlActive( ANIM_CONTROL_BALANCE ) ) { float flBalance = bUsePreview ? m_Preview.m_Current.m_pValue[ ANIM_CONTROL_BALANCE ] : GetValue( ANIM_CONTROL_BALANCE ); float flLeftValue, flRightValue; ValueBalanceToLeftRight( &flLeftValue, &flRightValue, flValue, flBalance ); int nLeftValue = (int)( (float)rect.width * clamp( flLeftValue, 0.0f, 1.0f ) + 0.5f ); int nRightValue = (int)( (float)rect.width * clamp( flRightValue, 0.0f, 1.0f ) + 0.5f ); // Draw the current value as a bar from the midpoint surface()->DrawSetColor( IsDragging() ? s_DraggingBarColor : s_BarColor[ m_bIsLogPreviewControl ][ IsSelected() ] ); surface()->DrawFilledRect( rect.x + min( nLeftValue, nMidPoint ), y0, rect.x + max( nLeftValue, nMidPoint ), y1 ); surface()->DrawFilledRect( rect.x + min( nRightValue, nMidPoint ), y1, rect.x + max( nRightValue, nMidPoint ), y2 ); } else { Assert( viewType != ANIM_CONTROL_BALANCE ); int nValue = (int)( (float)rect.width * clamp( flValue, 0.0f, 1.0f ) + 0.5f ); // Draw the current value as a bar from the midpoint surface()->DrawSetColor( IsDragging() ? s_DraggingBarColor : s_BarColor[ m_bIsLogPreviewControl ][ IsSelected() ] ); surface()->DrawFilledRect( rect.x + min( nValue, nMidPoint ), y0, rect.x + max( nValue, nMidPoint ), y2 ); } // Draw the midpoint over the top of the current value DrawMidpoint( rect.x + nMidPoint, rect.y, rect.height ); // Draw the name or value over the top of that DrawNameLabel(); // Paints the circular controls if ( IsControlActive( ANIM_CONTROL_MULTILEVEL ) ) { float flMultiValue = bUsePreview ? m_Preview.m_Current.m_pValue[ANIM_CONTROL_MULTILEVEL] : GetValue( ANIM_CONTROL_MULTILEVEL ); GetControlRect( &rect, ANIM_CONTROL_MULTILEVEL ); PaintCircularControl( flMultiValue, rect ); // Draws the midpoint for the circular controls int nCenterX = rect.x + rect.width / 2; int nCenterY = rect.y + rect.height / 2; DrawCircularTick( s_MidpointColor, GetControlDefaultValue( ANIM_CONTROL_MULTILEVEL ), nCenterX, nCenterY, CIRCULAR_CONTROL_RADIUS ); } }