source-engine/vguimatsurface/Input.cpp

560 lines
14 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implementation of the VGUI ISurface interface using the
// material system to implement it
//
//===========================================================================//
#if defined( WIN32 ) && !defined( _X360 )
#include <windows.h>
#include <zmouse.h>
#endif
#include "inputsystem/iinputsystem.h"
#include "tier2/tier2.h"
#include "Input.h"
#include "vguimatsurface.h"
#include "../vgui2/src/VPanel.h"
#include <vgui/KeyCode.h>
#include <vgui/MouseCode.h>
#include <vgui/IVGui.h>
#include <vgui/IPanel.h>
#include <vgui/ISurface.h>
#include <vgui/IClientPanel.h>
#include "inputsystem/ButtonCode.h"
#include "Cursor.h"
#include "tier0/dbg.h"
#include "../vgui2/src/vgui_key_translation.h"
#include <vgui/IInputInternal.h>
#include "tier0/icommandline.h"
#ifdef _X360
#include "xbox/xbox_win32stubs.h"
#endif
#include "MatSystemSurface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
extern CMatSystemSurface g_MatSystemSurface;
//-----------------------------------------------------------------------------
// Vgui input events
//-----------------------------------------------------------------------------
enum VguiInputEventType_t
{
IE_Close = IE_FirstVguiEvent,
IE_LocateMouseClick,
IE_SetCursor,
IE_KeyTyped,
IE_KeyCodeTyped,
IE_InputLanguageChanged,
IE_IMESetWindow,
IE_IMEStartComposition,
IE_IMEComposition,
IE_IMEEndComposition,
IE_IMEShowCandidates,
IE_IMEChangeCandidates,
IE_IMECloseCandidates,
IE_IMERecomputeModes,
};
void InitInput()
{
EnableInput( true );
}
static bool s_bInputEnabled = true;
#ifdef WIN32
//-----------------------------------------------------------------------------
// Translates actual keys into VGUI ids
//-----------------------------------------------------------------------------
static WNDPROC s_ChainedWindowProc = NULL;
extern HWND thisWindow;
//-----------------------------------------------------------------------------
// Initializes the input system
//-----------------------------------------------------------------------------
static bool s_bIMEComposing = false;
static HWND s_hLastHWnd = 0;
//-----------------------------------------------------------------------------
// Handles input messages
//-----------------------------------------------------------------------------
static LRESULT CALLBACK MatSurfaceWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
if ( !s_bInputEnabled )
goto chainWndProc;
InputEvent_t event;
memset( &event, 0, sizeof(event) );
event.m_nTick = g_pInputSystem->GetPollTick();
if ( hwnd != s_hLastHWnd )
{
s_hLastHWnd = hwnd;
event.m_nType = IE_IMESetWindow;
event.m_nData = (int)s_hLastHWnd;
g_pInputSystem->PostUserEvent( event );
}
switch(uMsg)
{
case WM_QUIT:
// According to windows docs, WM_QUIT should never be passed to wndprocs
Assert( 0 );
break;
case WM_CLOSE:
// Handle close messages
{
LONG_PTR wndProc = GetWindowLongPtrW( hwnd, GWLP_WNDPROC );
if ( wndProc == (LONG_PTR)MatSurfaceWindowProc )
{
event.m_nType = IE_Close;
g_pInputSystem->PostUserEvent( event );
}
}
return 0;
// All mouse messages need to mark where the click occurred before chaining down
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case MS_WM_XBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case MS_WM_XBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case MS_WM_XBUTTONDBLCLK:
event.m_nType = IE_LocateMouseClick;
event.m_nData = (short)LOWORD(lParam);
event.m_nData2 = (short)HIWORD(lParam);
g_pInputSystem->PostUserEvent( event );
break;
case WM_SETCURSOR:
event.m_nType = IE_SetCursor;
g_pInputSystem->PostUserEvent( event );
break;
case WM_XCONTROLLER_KEY:
if ( IsX360() )
{
// First have to insert the edge case event
int nRetVal = 0;
if ( s_ChainedWindowProc )
{
nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );
}
// xboxissue - as yet HL2 input hasn't been made aware of analog inputs or ports
// so just digital step on the sample range
int sample = LOWORD( lParam );
if ( sample )
{
event.m_nType = IE_KeyCodeTyped;
event.m_nData = (vgui::KeyCode)wParam;
g_pInputSystem->PostUserEvent( event );
}
}
break;
// Need to deal with key repeat for keydown since inputsystem doesn't
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
// First have to insert the edge case event
int nRetVal = 0;
if ( s_ChainedWindowProc )
{
nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );
}
int nKeyRepeat = LOWORD( lParam );
for ( int i = 0; i < nKeyRepeat; ++i )
{
event.m_nType = IE_KeyCodeTyped;
event.m_nData = KeyCode_VirtualKeyToVGUI( wParam );
g_pInputSystem->PostUserEvent( event );
}
return nRetVal;
}
case WM_SYSCHAR:
case WM_CHAR:
if ( !s_bIMEComposing )
{
event.m_nType = IE_KeyTyped;
event.m_nData = (int)wParam;
g_pInputSystem->PostUserEvent( event );
}
break;
case WM_INPUTLANGCHANGE:
event.m_nType = IE_InputLanguageChanged;
g_pInputSystem->PostUserEvent( event );
break;
case WM_IME_STARTCOMPOSITION:
s_bIMEComposing = true;
event.m_nType = IE_IMEStartComposition;
g_pInputSystem->PostUserEvent( event );
return TRUE;
case WM_IME_COMPOSITION:
event.m_nType = IE_IMEComposition;
event.m_nData = (int)lParam;
g_pInputSystem->PostUserEvent( event );
return TRUE;
case WM_IME_ENDCOMPOSITION:
s_bIMEComposing = false;
event.m_nType = IE_IMEEndComposition;
g_pInputSystem->PostUserEvent( event );
return TRUE;
case WM_IME_NOTIFY:
{
switch (wParam)
{
default:
break;
case 14: // Chinese Traditional IMN_PRIVATE...
break;
case IMN_OPENCANDIDATE:
event.m_nType = IE_IMEShowCandidates;
g_pInputSystem->PostUserEvent( event );
return 1;
case IMN_CHANGECANDIDATE:
event.m_nType = IE_IMEChangeCandidates;
g_pInputSystem->PostUserEvent( event );
return 0;
case IMN_CLOSECANDIDATE:
event.m_nType = IE_IMECloseCandidates;
g_pInputSystem->PostUserEvent( event );
break;
// To detect the change of IME mode, or the toggling of Japanese IME
case IMN_SETCONVERSIONMODE:
case IMN_SETSENTENCEMODE:
case IMN_SETOPENSTATUS:
event.m_nType = IE_IMERecomputeModes;
g_pInputSystem->PostUserEvent( event );
if ( wParam == IMN_SETOPENSTATUS )
return 0;
break;
case IMN_CLOSESTATUSWINDOW:
case IMN_GUIDELINE:
case IMN_OPENSTATUSWINDOW:
case IMN_SETCANDIDATEPOS:
case IMN_SETCOMPOSITIONFONT:
case IMN_SETCOMPOSITIONWINDOW:
case IMN_SETSTATUSWINDOWPOS:
break;
}
}
case WM_IME_SETCONTEXT:
// We draw all IME windows ourselves
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
lParam &= ~ISC_SHOWUIGUIDELINE;
lParam &= ~ISC_SHOWUIALLCANDIDATEWINDOW;
break;
case WM_IME_CHAR:
// We need to process this message so that the IME doesn't double convert the unicode IME characters into garbage characters and post
// them to our window... (get ? marks after text entry ).
return 0;
}
chainWndProc:
if ( s_ChainedWindowProc )
return CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );
// This means the application is driving the messages (calling our window procedure manually)
// rather than us hooking their window procedure. The engine needs to do this in order for VCR
// mode to play back properly.
return 0;
}
#endif
//-----------------------------------------------------------------------------
// Enables/disables input (enabled by default)
//-----------------------------------------------------------------------------
void EnableInput( bool bEnable )
{
#if 0 // #ifdef BENCHMARK
s_bInputEnabled = false;
#else
s_bInputEnabled = bEnable;
#endif
}
#ifdef WIN32
//-----------------------------------------------------------------------------
// Hooks input listening up to a window
//-----------------------------------------------------------------------------
void InputAttachToWindow(void *hwnd)
{
#if !defined( USE_SDL )
s_ChainedWindowProc = (WNDPROC)GetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC );
SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)MatSurfaceWindowProc );
#endif
}
void InputDetachFromWindow(void *hwnd)
{
if (!hwnd)
return;
if ( s_ChainedWindowProc )
{
SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR) s_ChainedWindowProc );
s_ChainedWindowProc = NULL;
}
}
#else
void InputAttachToWindow(void *hwnd)
{
#if !defined( OSX ) && !defined( LINUX )
if ( hwnd && !HushAsserts() )
{
// under OSX we use the Cocoa mgr to route events rather than hooking winprocs
// and under Linux we use SDL
Assert( !"Implement me" );
}
#endif
}
void InputDetachFromWindow(void *hwnd)
{
#if !defined( OSX ) && !defined( LINUX )
if ( hwnd && !HushAsserts() )
{
// under OSX we use the Cocoa mgr to route events rather than hooking winprocs
// and under Linux we use SDL
Assert( !"Implement me" );
}
#endif
}
#endif
//-----------------------------------------------------------------------------
// Converts an input system button code to a vgui key code
// FIXME: Remove notion of vgui::KeyCode + vgui::MouseCode altogether
//-----------------------------------------------------------------------------
static vgui::KeyCode ButtonCodeToKeyCode( ButtonCode_t buttonCode )
{
return ( vgui::KeyCode )buttonCode;
}
static vgui::MouseCode ButtonCodeToMouseCode( ButtonCode_t buttonCode )
{
return ( vgui::MouseCode )buttonCode;
}
//-----------------------------------------------------------------------------
// Handles an input event, returns true if the event should be filtered
// from the rest of the game
//-----------------------------------------------------------------------------
bool InputHandleInputEvent( const InputEvent_t &event )
{
switch( event.m_nType )
{
case IE_ButtonPressed:
{
// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
ButtonCode_t code = (ButtonCode_t)event.m_nData2;
if ( IsKeyCode( code ) || IsJoystickCode( code ) )
{
vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
return g_pIInput->InternalKeyCodePressed( keyCode );
}
if ( IsJoystickCode( code ) )
{
vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
return g_pIInput->InternalKeyCodePressed( keyCode );
}
if ( IsMouseCode( code ) )
{
vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
return g_pIInput->InternalMousePressed( mouseCode );
}
}
break;
case IE_ButtonReleased:
{
// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
ButtonCode_t code = (ButtonCode_t)event.m_nData2;
if ( IsKeyCode( code ) || IsJoystickCode( code ) )
{
vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
return g_pIInput->InternalKeyCodeReleased( keyCode );
}
if ( IsJoystickCode( code ) )
{
vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
return g_pIInput->InternalKeyCodeReleased( keyCode );
}
if ( IsMouseCode( code ) )
{
vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
return g_pIInput->InternalMouseReleased( mouseCode );
}
}
break;
case IE_FingerDown:
{
int w,h,x,y; g_MatSystemSurface.GetScreenSize(w, h);
float _x, _y;
memcpy( &_x, &event.m_nData2, sizeof(_x) );
memcpy( &_y, &event.m_nData3, sizeof(_y) );
x = w*_x; y = h*_y;
g_pIInput->UpdateCursorPosInternal( x, y );
g_pIInput->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_PRESSED );
g_pIInput->InternalMousePressed( MOUSE_LEFT );
}
return true;
case IE_FingerUp:
{
int w,h,x,y; g_MatSystemSurface.GetScreenSize(w, h);
float _x, _y;
memcpy( &_x, &event.m_nData2, sizeof(_x) );
memcpy( &_y, &event.m_nData3, sizeof(_y) );
x = w*_x; y = h*_y;
g_pIInput->UpdateCursorPosInternal( x, y );
g_pIInput->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_RELEASED );
g_pIInput->InternalMouseReleased( MOUSE_LEFT );
}
return true;
case IE_FingerMotion:
{
int w,h,x,y; g_MatSystemSurface.GetScreenSize(w, h);
float _x, _y;
memcpy( &_x, &event.m_nData2, sizeof(_x) );
memcpy( &_y, &event.m_nData3, sizeof(_y) );
x = w*_x; y = h*_y;
g_pIInput->InternalCursorMoved( x, y );
}
return true;
case IE_ButtonDoubleClicked:
{
// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
ButtonCode_t code = (ButtonCode_t)event.m_nData2;
if ( IsMouseCode( code ) )
{
vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
return g_pIInput->InternalMouseDoublePressed( mouseCode );
}
}
break;
case IE_AnalogValueChanged:
{
if ( event.m_nData == MOUSE_WHEEL )
return g_pIInput->InternalMouseWheeled( event.m_nData3 );
if ( event.m_nData == MOUSE_XY )
return g_pIInput->InternalCursorMoved( event.m_nData2, event.m_nData3 );
}
break;
case IE_KeyCodeTyped:
{
vgui::KeyCode code = (vgui::KeyCode)event.m_nData;
g_pIInput->InternalKeyCodeTyped( code );
}
return true;
case IE_KeyTyped:
{
vgui::KeyCode code = (vgui::KeyCode)event.m_nData;
g_pIInput->InternalKeyTyped( code );
}
return true;
case IE_Quit:
g_pVGui->Stop();
#if defined( USE_SDL )
return false; // also let higher layers consume it
#else
return true;
#endif
case IE_Close:
// FIXME: Change this so we don't stop until 'save' occurs, etc.
g_pVGui->Stop();
return true;
case IE_SetCursor:
ActivateCurrentCursor();
return true;
case IE_IMESetWindow:
g_pIInput->SetIMEWindow( (void *)(intp)event.m_nData );
return true;
case IE_LocateMouseClick:
g_pIInput->InternalCursorMoved( event.m_nData, event.m_nData2 );
return true;
case IE_InputLanguageChanged:
g_pIInput->OnInputLanguageChanged();
return true;
case IE_IMEStartComposition:
g_pIInput->OnIMEStartComposition();
return true;
case IE_IMEComposition:
g_pIInput->OnIMEComposition( event.m_nData );
return true;
case IE_IMEEndComposition:
g_pIInput->OnIMEEndComposition();
return true;
case IE_IMEShowCandidates:
g_pIInput->OnIMEShowCandidates();
return true;
case IE_IMEChangeCandidates:
g_pIInput->OnIMEChangeCandidates();
return true;
case IE_IMECloseCandidates:
g_pIInput->OnIMECloseCandidates();
return true;
case IE_IMERecomputeModes:
g_pIInput->OnIMERecomputeModes();
return true;
}
return false;
}