--- /dev/null
+/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */
+
+#include "vg_input.h"
+#include "vg_loader.h"
+#include "vg_engine.h"
+#include "vg_async.h"
+#include "vg_string.h"
+
+f32 controller_deadzone = 0.05f;
+
+struct vg_input vg_input = {
+ .active_controller_index = -2
+};
+
+u8 vg_getkey( SDL_Keycode kc )
+{
+ SDL_Scancode sc = SDL_GetScancodeFromKey( kc );
+ return vg_input.sdl_keys[sc];
+}
+
+/*
+ * takes SDL device index, and tries to open that on any free channel
+ */
+static int vg_open_gamecontroller( Sint32 index )
+{
+ struct vg_controller *controller = NULL;
+ int vg_id = 0;
+ const char *name = SDL_GameControllerNameForIndex( index );
+ SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index );
+
+ if( instance_id == -1 ){
+ vg_error( ". Invalid device index (vg_open_gamecontroller)\n" );
+ return -1;
+ }
+
+ for( int j=0; j<VG_MAX_CONTROLLERS; j++ ){
+ struct vg_controller *esta = &vg_input.controllers[j];
+
+ if( esta->handle ){
+ if( esta->instance_id == instance_id ){
+ vg_warn( " . SDL_JoystickID[%d] is already in open at index #%d\n",
+ esta->instance_id, j );
+ return -1;
+ }
+ }
+ else{
+ if( !controller ){
+ controller = &vg_input.controllers[j];
+ vg_id = j;
+ }
+ }
+ }
+
+ if( controller ){
+ controller->handle = SDL_GameControllerOpen( index );
+ controller->instance_id = instance_id;
+
+ if( controller->handle ){
+ vg_success(
+ " . opened SDL_JoystickID[%d] as controller '%s' at index #%d\n",
+ instance_id, name, vg_id );
+
+ for( u32 i=0; i< SDL_CONTROLLER_BUTTON_MAX; i++ )
+ controller->buttons[i] = 0;
+
+ for( u32 i=0; i< SDL_CONTROLLER_AXIS_MAX; i++ )
+ controller->axises[i] = 0.0f;
+
+ if( vg_input.active_controller_index == -2 ){
+ vg_input.active_controller_index = vg_id;
+ vg_input.display_input_method = k_input_method_controller;
+ vg_input.display_input_type =
+ SDL_GameControllerGetType( controller->handle );
+ }
+
+ return vg_id;
+ }
+ else{
+ vg_error( ". Failed to attach game controller '%s'. Reason: %s\n",
+ name, SDL_GetError() );
+ return -1;
+ }
+ }
+ else{
+ vg_error( ". Too many controllers open! ignoring '%s'\n", name );
+ return -1;
+ }
+}
+
+void vg_input_device_event( SDL_Event *ev )
+{
+ if( ev->type == SDL_CONTROLLERDEVICEADDED ){
+ int is_controller = SDL_IsGameController( ev->cdevice.which );
+ const char *name = SDL_JoystickNameForIndex( ev->cdevice.which );
+
+ Sint32 index = ev->cdevice.which;
+ SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index );
+ vg_info( "SDL_CONTROLLERDEVICEADDED | device index: %d, name: '%s'\n",
+ index, name );
+
+ if( is_controller ){
+ vg_open_gamecontroller( index );
+ }
+ }
+ else if( ev->type == SDL_CONTROLLERDEVICEREMOVED ){
+ vg_info( "SDL_CONTROLLERDEVICEREMOVED | instance_id: %d\n",
+ ev->cdevice.which );
+
+ for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+ struct vg_controller *controller = &vg_input.controllers[i];
+
+ if( controller->handle ){
+ if( controller->instance_id == ev->cdevice.which ){
+ vg_info( " . closing controller at index #%d\n", i );
+ SDL_GameControllerClose( controller->handle );
+ controller->handle = NULL;
+ controller->instance_id = -1;
+
+ if( vg_input.active_controller_index == i ){
+ vg_input.active_controller_index = -1;
+ vg_input.display_input_method = k_input_method_kbm;
+ vg_info( "display_input: k_input_method_kbm\n" );
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void vg_input_controller_event( SDL_Event *ev )
+{
+ if( ev->type == SDL_CONTROLLERAXISMOTION ){
+ for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+ struct vg_controller *esta = &vg_input.controllers[i];
+
+ if( ev->caxis.which == esta->instance_id ){
+ float value = (float)ev->caxis.value / 32767.0f;
+
+ if( ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX ||
+ ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY ||
+ ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX ||
+ ev->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY )
+ {
+ float deadz = vg_clampf( controller_deadzone, 0.0f, 0.999f ),
+ high = vg_maxf( 0.0f, fabsf(value) - deadz );
+
+ value = vg_signf(value) * (high / (1.0f-deadz));
+ }
+
+ esta->axises[ ev->caxis.axis ] = value;
+ break;
+ }
+ }
+ }
+ else if( ev->type == SDL_CONTROLLERBUTTONDOWN ){
+ struct vg_controller *active = NULL;
+
+ if( vg_input.active_controller_index >= 0 )
+ active = &vg_input.controllers[vg_input.active_controller_index];
+
+ if( !active || (ev->cbutton.which != active->instance_id) ){
+ active = NULL;
+ vg_input.active_controller_index = -1;
+ vg_input.display_input_method = k_input_method_kbm;
+
+ for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+ if( vg_input.controllers[i].instance_id == ev->cbutton.which ){
+ active = &vg_input.controllers[i];
+ vg_input.active_controller_index = i;
+ vg_input.display_input_type =
+ SDL_GameControllerGetType(active->handle);
+ break;
+ }
+ }
+
+ if( active ){
+ vg_info( "Switching active controller index to #%d\n",
+ vg_input.active_controller_index );
+ }
+ else{
+ vg_error( "Input out of range (SDL_JoystickID#%d)\n",
+ ev->cbutton.which );
+ }
+ }
+
+ if( active ){
+ if( vg_input.display_input_method != k_input_method_controller ){
+ vg_input.display_input_method = k_input_method_controller;
+ vg_info( "display_input: k_input_method_controller\n" );
+ }
+ active->buttons[ ev->cbutton.button ] = 1;
+ }
+ }
+ else if( ev->type == SDL_CONTROLLERBUTTONUP ){
+ for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+ struct vg_controller *esta = &vg_input.controllers[i];
+
+ if( ev->cbutton.which == esta->instance_id ){
+ esta->buttons[ ev->cbutton.button ] = 0;
+ break;
+ }
+ }
+ }
+}
+
+void vg_process_inputs(void)
+{
+ int count;
+ vg_input.sdl_keys = SDL_GetKeyboardState( &count );
+ vg_input.sdl_mouse = SDL_GetMouseState(NULL,NULL);
+
+ if( vg_input.display_input_method != k_input_method_kbm ){
+ /* check for giving keyboard priority */
+ for( int i=0; i<count; i++ ){
+ if( vg_input.sdl_keys[i] ){
+ vg_input.display_input_method = k_input_method_kbm;
+ vg_info( "display_input: k_input_method_kbm (keyboard %d)\n", i );
+ break;
+ }
+ }
+
+ /* check for giving mouse priority */
+ if( vg_input.sdl_mouse &
+ (SDL_BUTTON(SDL_BUTTON_LEFT)|SDL_BUTTON(SDL_BUTTON_RIGHT)|
+ SDL_BUTTON(SDL_BUTTON_MIDDLE)) )
+ {
+ vg_input.display_input_method = k_input_method_kbm;
+ vg_info( "display_input: k_input_method_kbm (mouse)\n" );
+ }
+ }
+}
+
+void async_vg_input_init( void *payload, u32 size )
+{
+ vg_info( "Checking for controllers\n" );
+ SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
+
+ int joy_count = SDL_NumJoysticks();
+ for( int i=0; i<joy_count; i++ ) {
+ const char *name = SDL_JoystickNameForIndex( i );
+ int is_controller = SDL_IsGameController(i);
+
+ vg_info( "%d: %s [controller: %d]\n", i, name, is_controller );
+
+ if( is_controller ){
+ vg_open_gamecontroller( i );
+ }
+ }
+}
+
+void vg_input_init(void)
+{
+ VG_VAR_F32( controller_deadzone, flags=VG_VAR_PERSISTENT );
+ vg_async_call( async_vg_input_init, NULL, 0 );
+}
+
+void vg_input_free(void)
+{
+ for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+ struct vg_controller *controller = &vg_input.controllers[i];
+
+ if( controller->handle ){
+ SDL_GameControllerClose( controller->handle );
+ controller->handle = NULL;
+ }
+ }
+}
+
+struct vg_controller *vg_active_controller(void)
+{
+ if( vg_input.active_controller_index >= 0 )
+ return &vg_input.controllers[vg_input.active_controller_index];
+ else
+ return NULL;
+}
+
+u8 vg_controller_button( SDL_GameControllerButton button )
+{
+ struct vg_controller *c = vg_active_controller();
+ if( c ) return c->buttons[ button ];
+ else return 0;
+}
+
+f32 vg_controller_axis( SDL_GameControllerAxis axis )
+{
+ struct vg_controller *c = vg_active_controller();
+ if( c ) return c->axises[ axis ];
+ else return 0;
+}
+
+static void vg_input_apply_to_u8( vg_input_op mode, u8 data, u8 *inout_result ){
+ if ( mode == vg_mode_absmax ) *inout_result |= data;
+ else if( mode == vg_mode_mul ) *inout_result &= data;
+ else vg_fatal_error( "mode not supported for destination type (%d)", mode );
+}
+
+static void vg_input_apply_to_f32( vg_input_op mode, f32 data,
+ f32 *inout_result ){
+ if ( mode == vg_mode_absmax ){
+ if( fabsf(data) > fabsf(*inout_result) )
+ *inout_result = data;
+ }
+ else if( mode == vg_mode_max ) *inout_result = vg_maxf(*inout_result,data);
+ else if( mode == vg_mode_mul ) *inout_result *= (f32)data;
+ else if( mode == vg_mode_sub ) *inout_result -= (f32)data;
+ else if( mode == vg_mode_add ) *inout_result += (f32)data;
+ else vg_fatal_error( "mode not supported for destination type (%d)", mode );
+}
+
+/*
+ * Run an input program. out_result must point to memory with sufficient
+ * storage respective to the size set by type.
+ */
+void vg_exec_input_program( enum vg_input_type type, vg_input_op *ops,
+ void *out_result ){
+ u8 *out_button = NULL;
+ f32 *out_joy = NULL;
+
+ if( type == k_vg_input_type_button_u8 ){
+ out_button = out_result;
+ *out_button = 0;
+ }
+ else if( type == k_vg_input_type_axis_f32 ){
+ out_joy = out_result;
+ out_joy[0] = 0.0f;
+ }
+ else if( type == k_vg_input_type_joy_v2f ){
+ out_joy = out_result;
+ out_joy[0] = 0.0f;
+ out_joy[1] = 0.0f;
+ }
+
+ /* computer state */
+ vg_input_op mode = vg_mode_absmax;
+ u32 pc = 0, index = 0;
+
+next_code:;
+ vg_input_op op = ops[ pc ++ ];
+
+ if( (op >= vg_mode_mul) && (op <= vg_mode_max) )
+ mode = op;
+ else if( (op == vg_keyboard) || (op == vg_mouse) || (op == vg_joy_button) ){
+ u8 state = 0;
+
+ if( op == vg_keyboard )
+ state = vg_getkey(ops[pc ++]);
+ else if( op == vg_mouse )
+ state = (vg_input.sdl_mouse & SDL_BUTTON(ops[pc ++]))?1:0;
+ else
+ state = vg_controller_button(ops[pc ++]);
+
+ if( type == k_vg_input_type_button_u8 )
+ vg_input_apply_to_u8( mode, state, out_button );
+ else
+ vg_input_apply_to_f32( mode, (f32)state, &out_joy[index] );
+ }
+ else if( op == vg_joy_axis ){
+ f32 state = vg_controller_axis( ops[pc ++] );
+ if( type == k_vg_input_type_button_u8 )
+ vg_input_apply_to_u8( mode, state>0.5f?1:0, out_button );
+ else
+ vg_input_apply_to_f32( mode, state, &out_joy[index] );
+ }
+ else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){
+ if( type == k_vg_input_type_joy_v2f ){
+ vg_input_apply_to_f32( mode,
+ vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTX:
+ SDL_CONTROLLER_AXIS_RIGHTX),
+ &out_joy[0] );
+ vg_input_apply_to_f32( mode,
+ vg_controller_axis( op==vg_joy_ls? SDL_CONTROLLER_AXIS_LEFTY:
+ SDL_CONTROLLER_AXIS_RIGHTY),
+ &out_joy[1] );
+ }
+ }
+ else if( op == vg_index )
+ index = ops[pc ++];
+ else if( op == vg_end )
+ return;
+ else if( op == vg_normalize )
+ v2_normalize( out_joy );
+ else if( op == vg_gui_visible )
+ pc ++;
+ else
+ vg_fatal_error( "unknown op\n" );
+
+ goto next_code;
+}
+
+/*
+ * Get vendor specific button glyphs based on SDL button ID
+ */
+const char *controller_button_str( SDL_GameControllerButton button )
+{
+ static const char *controller_glyphs[ SDL_CONTROLLER_BUTTON_MAX ][2] = {
+ /* xbox/generic playstation */
+ [ SDL_CONTROLLER_BUTTON_A ] = { "\x1e\x85","\x1e\x82" },
+ [ SDL_CONTROLLER_BUTTON_B ] = { "\x1e\x86","\x1e\x81" },
+ [ SDL_CONTROLLER_BUTTON_X ] = { "\x1e\x83","\x1e\x7f" },
+ [ SDL_CONTROLLER_BUTTON_Y ] = { "\x1e\x84","\x1e\x80" },
+ [ SDL_CONTROLLER_BUTTON_LEFTSTICK ] = { "\x87", "\x87" },
+ [ SDL_CONTROLLER_BUTTON_RIGHTSTICK ] = { "\x8b", "\x8b" },
+ [ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ] = { "\x91", "\x91" },
+ [ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ]= { "\x92", "\x92" },
+ [ SDL_CONTROLLER_BUTTON_DPAD_LEFT ] = { "\x1e\x93","\x1e\x93" },
+ [ SDL_CONTROLLER_BUTTON_DPAD_UP ] = { "\x1e\x94","\x1e\x94" },
+ [ SDL_CONTROLLER_BUTTON_DPAD_RIGHT ] = { "\x1e\x95","\x1e\x95" },
+ [ SDL_CONTROLLER_BUTTON_DPAD_DOWN ] = { "\x1e\x96","\x1e\x96" },
+ [ SDL_CONTROLLER_BUTTON_GUIDE ] = { "\x91", "\x91" },
+ };
+
+ if( vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3 ||
+ vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4 ||
+ vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5 )
+ {
+ return controller_glyphs[ button ][ 1 ];
+ }
+ else if( vg_input.display_input_type ==
+ SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO ||
+ vg_input.display_input_type ==
+ SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
+ vg_input.display_input_type ==
+ SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR ||
+ vg_input.display_input_type ==
+ SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT )
+ {
+ return NULL;
+ }
+ else
+ return controller_glyphs[ button ][ 0 ];
+}
+
+/*
+ * Cat keyboard key string. special_glyphs include SR glyphs
+ */
+void vg_keyboard_key_string( vg_str *str, u32 key, int special_glyphs )
+{
+ if( (key >= SDLK_a) && (key <= SDLK_z) ){
+ key = (key-SDLK_a)+(u32)'A';
+
+ if( special_glyphs ){
+ vg_strcatch( str, '\x1f' );
+ vg_strcatch( str, key );
+ vg_strcatch( str, ' ' );
+ }
+ else
+ vg_strcatch( str, key );
+ }
+ else if( (key == SDLK_LSHIFT) || (key == SDLK_RSHIFT) )
+ vg_strcat( str, special_glyphs? "\x9e": "shift" );
+ else if( (key == SDLK_LCTRL) || (key == SDLK_RCTRL) )
+ vg_strcat( str, special_glyphs? "\x9f": "ctrl" );
+ else if( (key == SDLK_LALT) || (key == SDLK_RALT) )
+ vg_strcat( str, special_glyphs? "\xa0": "alt" );
+ else if( key == SDLK_SPACE )
+ vg_strcat( str, special_glyphs? "\xa1": "space" );
+ else if( (key == SDLK_RETURN) || (key == SDLK_RETURN2) )
+ vg_strcat( str, special_glyphs? "\xa2": "return" );
+ else if( key == SDLK_ESCAPE )
+ vg_strcat( str, special_glyphs? "\xa3": "escape" );
+ else if( key == SDLK_RIGHT )
+ vg_strcat( str, special_glyphs? "\x1f\x95 ": "right" );
+ else if( key == SDLK_LEFT )
+ vg_strcat( str, special_glyphs? "\x1f\x93 ": "left" );
+ else if( key == SDLK_UP )
+ vg_strcat( str, special_glyphs? "\x1f\x94 ": "up" );
+ else if( key == SDLK_DOWN )
+ vg_strcat( str, special_glyphs? "\x1f\x96 ": "down" );
+ else {
+ vg_strcat( str, "keyboard key #" );
+ vg_strcati32( str, key );
+ }
+}
+
+/*
+ * Cat mouse button string. special_glyphs include SR glyphs
+ */
+void vg_mouse_button_string( vg_str *str, u32 button, int special_glyphs )
+{
+ if ( button == SDL_BUTTON_LEFT )
+ vg_strcat( str, special_glyphs? "\x99": "left mouse" );
+ else if( button == SDL_BUTTON_RIGHT )
+ vg_strcat( str, special_glyphs? "\x9a": "right mouse" );
+ else if( button == SDL_BUTTON_MIDDLE )
+ vg_strcat( str, special_glyphs? "\x9c": "middle mouse" );
+ else{
+ vg_strcat( str, "mouse button #" );
+ vg_strcati32( str, button );
+ }
+}
+
+/*
+ * Cat string represeinting single axis
+ */
+void vg_joy_axis_string( vg_str *str, SDL_GameControllerAxis axis,
+ int special_glyphs )
+{
+ if( axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT )
+ vg_strcat( str, special_glyphs?"\x8f":"left trigger" );
+ else if( axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT )
+ vg_strcat( str, special_glyphs?"\x90":"right trigger" );
+ else if( axis == SDL_CONTROLLER_AXIS_LEFTX )
+ vg_strcat( str, special_glyphs?"\x88":"left stick horizontal" );
+ else if( axis == SDL_CONTROLLER_AXIS_LEFTY )
+ vg_strcat( str, special_glyphs?"\x89":"left stick vertical" );
+ else if( axis == SDL_CONTROLLER_AXIS_RIGHTX )
+ vg_strcat( str, special_glyphs?"\x8c":"right stick horizontal" );
+ else if( axis == SDL_CONTROLLER_AXIS_RIGHTY )
+ vg_strcat( str, special_glyphs?"\x8d":"right stick vertical" );
+ else{
+ vg_strcat( str, "axis " );
+ vg_strcati32( str, axis );
+ }
+}
+
+/*
+ * Cat string represeinting whole joystick
+ */
+void vg_joy_string( vg_str *str, vg_input_op op, int special_glyphs )
+{
+ if( op == vg_joy_ls )
+ vg_strcat( str, special_glyphs? "\x87": "left stick" );
+ else
+ vg_strcat( str, special_glyphs? "\x8b": "right stick" );
+}
+
+/*
+ * Convert an input program into a readable string
+ */
+void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs )
+{
+ u32 pc = 0;
+ int applicable = 0, visible = 1;
+
+next_code:;
+ vg_input_op op = ops[ pc ++ ];
+
+ if( (op == vg_keyboard) || (op == vg_mouse) ){
+ if( (vg_input.display_input_method == k_input_method_kbm) && visible ){
+ applicable = 1;
+
+ if( op == vg_keyboard )
+ vg_keyboard_key_string( str, ops[pc], glyphs );
+ else
+ vg_mouse_button_string( str, ops[pc], glyphs );
+ }
+ else applicable = 0;
+ pc ++;
+ }
+ else if( (op == vg_joy_button) || (op == vg_joy_axis) ){
+ if( (vg_input.display_input_method == k_input_method_controller)
+ && visible ){
+ applicable = 1;
+
+ if( op == vg_joy_button )
+ vg_strcat( str, controller_button_str(ops[pc]) );
+ else
+ vg_joy_axis_string( str, ops[pc], glyphs );
+ }
+ else applicable = 0;
+ pc ++;
+ }
+ else if( (op == vg_joy_ls) || (op == vg_joy_rs) ){
+ if( (vg_input.display_input_method == k_input_method_controller)
+ && visible ){
+ applicable = 1;
+ vg_joy_string( str, op, glyphs );
+ }
+ else applicable = 0;
+ }
+ else if( op == vg_mode_mul ){
+ if( applicable && visible )
+ vg_strcat( str, " + " );
+ }
+ else if( op == vg_index )
+ pc ++;
+ else if( op == vg_gui_visible )
+ visible = ops[pc++];
+ else if( op == vg_end )
+ return;
+
+ goto next_code;
+}