cone & disc distributions
[vg.git] / vg_input.h
index 87109d25e777d2d2f844472ce88e3b62b44ed3ca..cd7130d745f0aae33d608de4efed992790aa85d2 100644 (file)
 #include "common.h"
 #include "vg/vg_loader.h"
 
-VG_STATIC inline float vg_get_axis( const char *axis );
-VG_STATIC inline int vg_get_button( const char *button );
+#define VG_MAX_CONTROLLERS 4
 
-/*
- * Cannot be used in fixed update
- */
-VG_STATIC inline int vg_get_button_down( const char *button );
-VG_STATIC inline int vg_get_button_up( const char *button );
+static float controller_deadzone = 0.05f;
+typedef u32 vg_input_op;
+typedef vg_input_op *vg_input_program;
 
-VG_STATIC float g_controller_deadzone = 0.05f;
-
-enum vg_button_state
-{
-       k_button_state_down = 1,
-       k_button_state_up = 3,
-       k_button_state_pressed = 2,
-       k_button_state_none = 0
+enum vg_input_type {
+   k_vg_input_type_button_u8,
+   k_vg_input_type_axis_f32,
+   k_vg_input_type_joy_v2f
 };
 
-struct input_binding
-{
-       const char *name;
-
-   enum input_type
-   {
-      k_input_type_button,
-      k_input_type_axis,
-      k_input_type_axis_norm,
-
-      k_input_type_unknown,
-      k_input_type_keyboard_key,
-      k_input_type_mouse_button, /* ? TODO */
-      k_input_type_gamepad_axis,
-      k_input_type_gamepad_button
-   }
-   type;
-
-   union
-   {
-      struct input_axis
-      {
-         SDL_GameControllerAxis gamepad_axis;
-         SDL_Keycode            keyboard_positive,
-                                keyboard_negative;
-
-         int gamepad_inverted;
-         float value;
-      }
-      axis;
-
-      struct
-      {
-         SDL_GameControllerButton gamepad_id;
-         SDL_Keycode              keyboard_id;
-         int value, prev;
-      }
-      button;
-   };
-
-   int save_this;
+enum vg_input_op {
+   /* data source */
+   vg_keyboard,
+   vg_mouse,
+   vg_joy_button,
+   vg_joy_axis,
+   vg_joy_ls,
+   vg_joy_rs,
+
+   /* modes */
+   vg_mode_mul,
+   vg_mode_sub,
+   vg_mode_add,
+   vg_mode_absmax,
+   vg_mode_max,
+
+   /* control */
+   vg_index,
+   vg_end,
+   vg_gui_visible,
+
+   /* math */
+   vg_normalize
 };
 
-struct
-{
-   const u8            *sdl_keys;
-   struct input_binding named_inputs[ 32 ];
-   u32                  named_input_count;
+struct{
+   const u8 *sdl_keys;
+   u32 sdl_mouse;
 
-   const char          *controller_name;
-   SDL_GameController  *controller_handle; /* null if unplugged */
-   SDL_JoystickID       controller_joystick_id;
-   int                  controller_should_use_trackpad_look;
+   struct vg_controller{
+      SDL_GameController *handle; /* handle for controller. NULL if unused */
+      SDL_JoystickID instance_id; /* uid used in events */
 
-   float                controller_axises[ SDL_CONTROLLER_AXIS_MAX ];
-   int                  controller_buttons[ SDL_CONTROLLER_BUTTON_MAX ];
-}
-VG_STATIC vg_input;
-
-VG_STATIC void vg_create_unnamed_input( struct input_binding *bind,
-                                        enum input_type type )
-{
-   memset( bind, 0, sizeof(struct input_binding) );
+      float axises[ SDL_CONTROLLER_AXIS_MAX ];
+      u32   buttons[ SDL_CONTROLLER_BUTTON_MAX ];
+   }
+   controllers[4];
 
-   bind->name = "API DEFINED";
-   bind->save_this = 0;
-   bind->type = type;
+   int active_controller_index; /* most recent controller (by button press)
+                                   will be -1 if no controllers active */
 
-   bind->axis.gamepad_axis = -1;
-   bind->axis.keyboard_positive = -1;
-   bind->axis.keyboard_negative = -1;
-   bind->button.gamepad_id = -1;
-   bind->button.keyboard_id = -1;
+   /* what the user is currently using. the keyboard and controller are still
+    * active simultaneously, but this reflects what the UI should show */
+   enum input_method{
+      k_input_method_kbm,
+      k_input_method_controller
+   }
+   display_input_method;
+   SDL_GameControllerType display_input_type;
 }
+static vg_input = { .active_controller_index = -2 };
 
-VG_STATIC struct input_binding *vg_create_named_input( const char *name,
-                                                       enum input_type type )
+static u8 vg_getkey( SDL_Keycode kc )
 {
-   struct input_binding *bind = 
-      &vg_input.named_inputs[ vg_input.named_input_count ++ ];
-   memset( bind, 0, sizeof(struct input_binding) );
-
-   bind->name = name;
-   bind->save_this = 0;
-   bind->type = type;
-
-   bind->axis.gamepad_axis = -1;
-   bind->axis.keyboard_positive = -1;
-   bind->axis.keyboard_negative = -1;
-   bind->button.gamepad_id = -1;
-   bind->button.keyboard_id = -1;
-
-   return bind;
+   SDL_Scancode sc = SDL_GetScancodeFromKey( kc );
+   return vg_input.sdl_keys[sc];
 }
 
-VG_STATIC struct input_binding *vg_get_named_input( const char *name )
+/*
+ * takes SDL device index, and tries to open that on any free channel
+ */
+static int vg_open_gamecontroller( Sint32 index )
 {
-   if( name[0] == '+' || name[0] == '-' )
-      name ++;
-
-   for( u32 i=0; i<vg_input.named_input_count; i++ )
-   {
-      struct input_binding *bind = &vg_input.named_inputs[i];
-      if( !strcmp( bind->name, name ) )
-         return bind;
+   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;
    }
 
-   return NULL;
-}
-
-struct input_en
-{
-   enum input_type type;
+   for( int j=0; j<VG_MAX_CONTROLLERS; j++ ){
+      struct vg_controller *esta = &vg_input.controllers[j];
 
-   const char *alias;
-   int id;
-}
-vg_all_bindable_inputs[] =
-{
- {k_input_type_keyboard_key, "space", SDLK_SPACE},
- {k_input_type_keyboard_key, ";", SDLK_SEMICOLON},
- {k_input_type_keyboard_key, "-", SDLK_MINUS},
- {k_input_type_keyboard_key, ".", SDLK_PERIOD},
- {k_input_type_keyboard_key, ",", SDLK_COMMA},
- {k_input_type_keyboard_key, "=", SDLK_EQUALS},
- {k_input_type_keyboard_key, "[", SDLK_LEFTBRACKET},
- {k_input_type_keyboard_key, "]", SDLK_RIGHTBRACKET},
- {k_input_type_keyboard_key, "left", SDLK_LEFT},
- {k_input_type_keyboard_key, "right", SDLK_RIGHT},
- {k_input_type_keyboard_key, "up", SDLK_UP},
- {k_input_type_keyboard_key, "down", SDLK_DOWN},
- {k_input_type_keyboard_key, "shift", SDLK_LSHIFT},
- {k_input_type_keyboard_key, "control", SDLK_LCTRL},
- {k_input_type_keyboard_key, "\2enter", SDLK_RETURN},
- {k_input_type_keyboard_key, "\2escape", SDLK_ESCAPE },
- {k_input_type_gamepad_axis, "gp-lt",  SDL_CONTROLLER_AXIS_TRIGGERLEFT},
- {k_input_type_gamepad_axis, "gp-rt", SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
- {k_input_type_gamepad_axis, "gp-ls-h", SDL_CONTROLLER_AXIS_LEFTX},
- {k_input_type_gamepad_axis, "gp-ls-v", SDL_CONTROLLER_AXIS_LEFTY},
- {k_input_type_gamepad_axis, "gp-rs-h", SDL_CONTROLLER_AXIS_RIGHTX},
- {k_input_type_gamepad_axis, "gp-rs-v", SDL_CONTROLLER_AXIS_RIGHTY},
- {k_input_type_gamepad_button, "gp-a", SDL_CONTROLLER_BUTTON_A},
- {k_input_type_gamepad_button, "gp-b", SDL_CONTROLLER_BUTTON_B},
- {k_input_type_gamepad_button, "gp-x", SDL_CONTROLLER_BUTTON_X},
- {k_input_type_gamepad_button, "gp-y", SDL_CONTROLLER_BUTTON_Y},
- {k_input_type_gamepad_button, "gp-rb", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
- {k_input_type_gamepad_button, "gp-lb", SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
- {k_input_type_gamepad_button, "gp-rs", SDL_CONTROLLER_BUTTON_RIGHTSTICK},
- {k_input_type_gamepad_button, "gp-ls", SDL_CONTROLLER_BUTTON_LEFTSTICK},
- {k_input_type_gamepad_button, "gp-dpad-down", SDL_CONTROLLER_BUTTON_DPAD_DOWN},
- {k_input_type_gamepad_button, "gp-dpad-left", SDL_CONTROLLER_BUTTON_DPAD_LEFT},
- {k_input_type_gamepad_button,"gp-dpad-right",SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
- {k_input_type_gamepad_button, "gp-dpad-up", SDL_CONTROLLER_BUTTON_DPAD_UP},
- {k_input_type_gamepad_button, "\2gp-menu", SDL_CONTROLLER_BUTTON_BACK}
-};
-
-VG_STATIC const char *vg_input_to_str( u32 input, enum input_type input_type )
-{
-   if( input == -1 )
-      return NULL;
-
-   if( input_type == k_input_type_keyboard_key )
-   {
-      if( (input >= SDLK_a) && (input <= SDLK_z) )
-      {
-         return &"a\0b\0c\0d\0e\0f\0g\0h\0i\0j\0k\0l\0m\0n\0o\0p\0"
-                 "q\0r\0s\0t\0u\0v\0w\0x\0y\0z\0"[(input-SDLK_a)*2];
+      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;
+         }
       }
-
-      if( (input >= SDLK_0) && (input <= SDLK_9) )
-      {
-         return &"0\0" "1\0" "2\0" "3\0" "4\0" 
-                 "5\0" "6\0" "7\0" "8\0" "9\0"[(input-SDLK_0)*2];
+      else{
+         if( !controller ){
+            controller = &vg_input.controllers[j];
+            vg_id = j;
+         }
       }
    }
 
-   for( int i=0; i<vg_list_size(vg_all_bindable_inputs); i++ )
-   {
-      struct input_en *desc = &vg_all_bindable_inputs[i];
-
-      if( (desc->type == input_type) && (desc->id == input) )
-         return desc->alias;
-   }
-
-   return NULL;
-}
-
-VG_STATIC enum input_type vg_str_to_input( const char *str, u32 *input )
-{
-   if( !str )
-   {
-      *input = -1;
-      return k_input_type_unknown;
-   }
+   if( controller ){
+      controller->handle = SDL_GameControllerOpen( index );
+      controller->instance_id = instance_id;
 
-   u32 len = strlen(str);
-
-   if( len == 0 )
-   {
-      *input = -1;
-      return k_input_type_unknown;
-   }
-
-   if( len == 1 )
-   {
-      u8 uch = str[0];
+      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 );
+         }
 
-      if( (uch >= (u8)'a') && (uch <= (u8)'z') )
-      {
-         *input = SDLK_a + (uch-(u8)'a');
-         return k_input_type_keyboard_key;
+         return vg_id;
       }
-
-      if( (uch >= (u8)'0') && (uch <= (u8)'9') )
-      {
-         *input = SDLK_0 + (uch-(u8)'0');
-         return k_input_type_keyboard_key;
+      else{
+         vg_error( ". Failed to attach game controller '%s'. Reason: %s\n",
+                     name, SDL_GetError() );
+         return -1;
       }
    }
-
-   for( int i=0; i<vg_list_size(vg_all_bindable_inputs); i++ )
-   {
-      struct input_en *desc = &vg_all_bindable_inputs[i];
-
-      if( !strcmp( desc->alias, str ) )
-      {
-         *input = desc->id;
-         return desc->type;
-      }
+   else{
+      vg_error( ". Too many controllers open! ignoring '%s'\n", name );
+      return -1;
    }
-   
-   *input = -1;
-   return k_input_type_unknown;
 }
 
-VG_STATIC void vg_print_binding_info( struct input_binding *bind )
+static void vg_input_device_event( SDL_Event *ev )
 {
-   vg_info( "    name: %s\n", bind->name );
-   vg_info( "    type: %s\n", (const char *[]){"button","axis","axis[0-1]"}
-                                                [ bind->type ] );
-   vg_info( "    save this? %d\n", bind->save_this );
-
-   if( (bind->type == k_input_type_axis) ||
-       (bind->type == k_input_type_axis_norm) )
-   {
-      vg_info( "      gamepad_axis: %s\n", 
-         vg_input_to_str(bind->axis.gamepad_axis, k_input_type_gamepad_axis));
+   if( ev->type == SDL_CONTROLLERDEVICEADDED ){
+      int is_controller = SDL_IsGameController( ev->cdevice.which );
+      const char *name = SDL_JoystickNameForIndex( ev->cdevice.which );
 
-      vg_info( "      keyboard_positive: %s\n",
-         vg_input_to_str(bind->axis.keyboard_positive, 
-                         k_input_type_keyboard_key ));
+      Sint32 index = ev->cdevice.which;
+      SDL_JoystickID instance_id = SDL_JoystickGetDeviceInstanceID( index );
+      vg_info( "SDL_CONTROLLERDEVICEADDED | device index: %d, name: '%s'\n", 
+                  index, name );
 
-      vg_info( "      keyboard_negative: %s\n",
-         vg_input_to_str(bind->axis.keyboard_negative, 
-                         k_input_type_keyboard_key ));
+      if( is_controller ){
+         vg_open_gamecontroller( index );
+      }
    }
-   else
-   {
-      vg_info( "      gamepad_id: %s\n", 
-         vg_input_to_str(bind->button.gamepad_id, k_input_type_gamepad_button));
-      vg_info( "      keyboard_id: %s\n",
-         vg_input_to_str(bind->button.keyboard_id,
-                         k_input_type_keyboard_key));
+   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;
+            }
+         }
+      }
    }
 }
 
-VG_STATIC void vg_apply_bind_str( struct input_binding *bind,
-                                  const char *mod,
-                                  const char *str )
+static void vg_input_controller_event( SDL_Event *ev )
 {
-   int axis_mod = 0;
-   char modch = ' ';
-   if( (mod[0] == '-') || (mod[0] == '+') )
-   {
-      axis_mod = 1;
-      modch = mod[0];
-      mod ++;
-   }
-   
-   int invert = 0;
-   if( (str[0] == '-' ) )
-   {
-      invert = 1;
-      str ++;
-   }
+   if( ev->type == SDL_CONTROLLERAXISMOTION ){
+      for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+         struct vg_controller *esta = &vg_input.controllers[i];
 
-   u32 id;
-   enum input_type type = vg_str_to_input( str, &id );
+         if( ev->caxis.which == esta->instance_id ){
+            float value = (float)ev->caxis.value / 32767.0f;
 
-   if( bind->type == k_input_type_button )
-   {
-      if( axis_mod )
-      {
-         vg_error( "Cannot use axis modifiers on button input!\n" );
-         return;
-      }
-
-      if( invert )
-      {
-         vg_error( "Cannot invert button input!\n" );
-         return;
-      }
+            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));
+            }
 
-      if( type == k_input_type_keyboard_key )
-         bind->button.keyboard_id = id;
-      else if( type == k_input_type_gamepad_button )
-         bind->button.gamepad_id = id;
-      else
-      {
-         vg_error( "Unknown button or key '%s'\n", str );
-         return;
+            esta->axises[ ev->caxis.axis ] = value;
+            break;
+         }
       }
    }
-   else if( (bind->type == k_input_type_axis ) ||
-            (bind->type == k_input_type_axis_norm))
-   {
-      if( axis_mod )
-      {
-         if( type == k_input_type_keyboard_key )
-         {
-            if( invert )
-            {
-               vg_error( "Cannot invert a keyboard key!\n" );
-               return;
+   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( modch == '+' )
-               bind->axis.keyboard_positive = id;
-            else
-               bind->axis.keyboard_negative = id;
          }
-         else
-         {
-            vg_error( "You can only bind keyboard keys to +- axises\n" );
-            return;
+         
+         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 );
          }
       }
-      else
-      {
-         if( type == k_input_type_gamepad_axis )
-         {
-            bind->axis.gamepad_inverted = invert;
-            bind->axis.gamepad_axis = id;
+
+      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" );
          }
-         else
-         {
-            vg_error( "You can only bind gamepad axises to this\n" );
-            return;
+         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;
          }
       }
    }
 }
 
-/* 
- * bind x jump 
- * bind a -horizontal
- * bind d +horizontal
- * bind -gp-ls-h horizontal
- */
-
-VG_STATIC int vg_rebind_input_cmd( int argc, const char *argv[] )
+static void vg_process_inputs(void)
 {
-   if( argc == 0 )
-   {
-      vg_info( "Usage: bind jump x\n" );
-      vg_info( "       bind -steerh j\n" );
-      vg_info( "       bind steerh gp-ls-h\n" );
-      return 0;
-   }
-
-   const char *str_bind_name = argv[0];
-   struct input_binding *bind = vg_get_named_input( str_bind_name );
+   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;
+         }
+      }
 
-   if( !bind )
-   {
-      vg_error( "There is no bind with that name '%s'\n", str_bind_name );
-      return 0;
+      /* 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" );
+      }
    }
+}
 
-   if( argc == 1 )
-   {
-      vg_print_binding_info( bind );
-      return 0;
-   }
+static void async_vg_input_init( void *payload, u32 size )
+{
+   VG_VAR_F32( controller_deadzone, flags=VG_VAR_PERSISTENT );
 
-   if( argc == 2 )
-   {
-      const char *str_input_id  = argv[1];
+   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_apply_bind_str( bind, str_bind_name, str_input_id );
-      return 0;
-   }
+      vg_info( "%d: %s [controller: %d]\n", i, name, is_controller );
 
-   return 0;
+      if( is_controller ){
+         vg_open_gamecontroller( i );
+      }
+   }
 }
 
-VG_STATIC u8 vg_getkey( SDL_Keycode kc )
+static void vg_input_init(void)
 {
-   SDL_Scancode sc = SDL_GetScancodeFromKey( kc );
-   return vg_input.sdl_keys[sc];
+   vg_async_call( async_vg_input_init, NULL, 0 );
 }
 
-VG_STATIC void vg_input_update( u32 num, struct input_binding *binds )
+static void vg_input_free(void)
 {
-   if( vg_console.enabled )
-   {
-      for( i32 i=0; i<num; i++ )
-      {
-         struct input_binding *bind = &binds[i];
+   for( int i=0; i<VG_MAX_CONTROLLERS; i++ ){
+      struct vg_controller *controller = &vg_input.controllers[i];
 
-         if( bind->type == k_input_type_button )
-         {
-            bind->button.prev = bind->button.value;
-            bind->button.value = 0;
-         }
+      if( controller->handle ){
+         SDL_GameControllerClose( controller->handle );
+         controller->handle = NULL;
       }
-
-      return;
    }
+}
 
-   for( i32 i=0; i<num; i++ )
-   {
-      struct input_binding *bind = &binds[i];
-
-      if( bind->type == k_input_type_button )
-      {
-         bind->button.prev = bind->button.value;
-         bind->button.value = 0;
+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;
+}
 
-         if( bind->button.gamepad_id != -1 )
-            bind->button.value |= 
-               vg_input.controller_buttons[ bind->button.gamepad_id ];
+static u8 vg_controller_button( SDL_GameControllerButton button ){
+   struct vg_controller *c = vg_active_controller();
+   if( c ) return c->buttons[ button ];
+   else return 0;
+}
 
-         if( bind->button.keyboard_id != -1 )
-            bind->button.value |= vg_getkey( bind->button.keyboard_id );
-      }
-      else if( bind->type == k_input_type_axis )
-      {
-         float keyboard_value = 0.0f,
-               gamepad_value = 0.0f;
+static f32 vg_controller_axis( SDL_GameControllerAxis axis ){
+   struct vg_controller *c = vg_active_controller();
+   if( c ) return c->axises[ axis ];
+   else return 0;
+}
 
-         if( bind->axis.keyboard_positive != -1 )
-            if( vg_getkey( bind->axis.keyboard_positive ) )
-               keyboard_value += 1.0f;
+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 );
+}
 
-         if( bind->axis.keyboard_negative != -1 )
-            if( vg_getkey( bind->axis.keyboard_negative ) )
-               keyboard_value -= 1.0f;
+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 );
+}
 
-         if( bind->axis.gamepad_axis != -1 )
-         {
-            gamepad_value = 
-               vg_input.controller_axises[ bind->axis.gamepad_axis ];
+/*
+ * Run an input program. out_result must point to memory with sufficient
+ * storage respective to the size set by type.
+ */
+static 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;
+   }
 
-            if( bind->axis.gamepad_inverted )
-               gamepad_value *= -1.0f;
-         }
+   /* computer state */
+   vg_input_op mode = vg_mode_absmax;
+   u32 pc = 0, index = 0;
 
-         float deadz   = vg_clampf( g_controller_deadzone, 0.0f, 0.999f ),
-               high    = vg_maxf( 0.0f, fabsf(gamepad_value) - deadz ),
-               norm    = high / (1.0f-deadz);
+next_code:;
+   vg_input_op op = ops[ pc ++ ];
 
-         gamepad_value = vg_signf( gamepad_value ) * norm;
+   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( fabsf(keyboard_value) > fabsf(gamepad_value) )
-            bind->axis.value = keyboard_value;
-         else
-            bind->axis.value = gamepad_value;
-      }
-      else if( bind->type == k_input_type_axis_norm )
-      {
-         float value = 0.0f;
-         if( bind->axis.keyboard_positive != -1 )
-            if( vg_getkey( bind->axis.keyboard_positive ))
-               value = 1.0f;
-         
-         if( bind->axis.gamepad_axis != -1 )
-            value = vg_maxf( value, 
-                  vg_input.controller_axises[bind->axis.gamepad_axis] );
+      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 ++]);
 
-         bind->axis.value = value;
+      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;
 }
 
-VG_STATIC void vg_input_controller_event( SDL_Event *ev )
-{
-   if( ev->type == SDL_CONTROLLERAXISMOTION )
-   {
-      if( ev->caxis.which == vg_input.controller_joystick_id )
-      {
-         vg_input.controller_axises[ ev->caxis.axis ] = 
-            (float)ev->caxis.value / 32767.0f;
-      }
-   }
-   else if( ev->type == SDL_CONTROLLERBUTTONDOWN )
+/*
+ * Get vendor specific button glyphs based on SDL button ID
+ */
+static 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 )
    {
-      if( ev->cbutton.which == vg_input.controller_joystick_id )
-         vg_input.controller_buttons[ ev->cbutton.button ] = 1;
+      return controller_glyphs[ button ][ 1 ];
    }
-   else if( ev->type == SDL_CONTROLLERBUTTONUP )
+   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 )
    {
-      if( ev->cbutton.which == vg_input.controller_joystick_id )
-         vg_input.controller_buttons[ ev->cbutton.button ] = 0;
+      return NULL;
    }
+   else
+      return controller_glyphs[ button ][ 0 ];
 }
 
-VG_STATIC void vg_try_attach_controller(void)
-{
-   int joy_count = SDL_NumJoysticks();
-   for( int i=0; i<joy_count; i++ ) 
-   {
-      if( SDL_IsGameController(i) ) 
-      {
-         vg_input.controller_handle = SDL_GameControllerOpen(i);
-         vg_input.controller_joystick_id = i;
-         vg_success( "Attached game controller with joystick ID %d\n", i );
-         return;
+/*
+ * Cat keyboard key string. special_glyphs include SR glyphs
+ */
+static 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 );
    }
 }
 
-
-VG_STATIC void vg_update_inputs(void)
-{
-   vg_input.sdl_keys = SDL_GetKeyboardState(NULL);
-
-   if( vg_input.controller_handle )
-   {
-      if( !SDL_GameControllerGetAttached( vg_input.controller_handle ) )
-      {
-         SDL_GameControllerClose( vg_input.controller_handle );
-         vg_input.controller_handle = NULL;
-      }
+/*
+ * Cat mouse button string. special_glyphs include SR glyphs
+ */
+static 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 );
    }
+}
 
-   if( !vg_input.controller_handle )
-   {
-      vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERLEFT ] = -1.0f;
-      vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERRIGHT ] = -1.0f;
-      vg_try_attach_controller();
+/*
+ * Cat string represeinting single axis 
+ */
+static 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 );
    }
-
-   /* update all inputs */
-   vg_input_update( vg_input.named_input_count, vg_input.named_inputs );
 }
 
-VG_STATIC int vg_console_enabled(void);
-VG_STATIC int vg_input_button_down( struct input_binding *bind )
-{
-   if( bind->button.value && !bind->button.prev )
-      return 1;
-   return 0;
+/*
+ * Cat string represeinting whole joystick
+ */
+static 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" );
 }
 
-VG_STATIC void vg_input_init(void)
-{
-   vg_acquire_thread_sync();
-
-   vg_function_push( (struct vg_cmd)
-   {
-      .name = "bind",
-      .function = vg_rebind_input_cmd
-   });
-
-   vg_convar_push( (struct vg_convar){
-      .name = "controller_deadzone",
-      .data = &g_controller_deadzone,
-      .data_type = k_convar_dtype_f32,
-      .opt_f32 = { .clamp = 0 },
-      .persistent = 1
-   });
-
-   vg_info( "Checking for controller\n" );
-   SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
-   
-   int joy_count = SDL_NumJoysticks();
-   for( int i=0; i<joy_count; i++ ) 
-   {
-      vg_info( "joystick %d: %s [gamecontroller: %d]\n", 
-            i, SDL_JoystickNameForIndex( i ),
-            SDL_IsGameController(i) );
-   }
-
-   vg_try_attach_controller();
+/*
+ * Convert an input program into a readable string
+ */
+static void vg_input_string( vg_str *str, vg_input_op *ops, int glyphs ){
+   u32 pc = 0;
+   int applicable = 0, visible = 1;
 
-   vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERLEFT ] = -1.0f;
-   vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERRIGHT ] = -1.0f;
+next_code:;
+   vg_input_op op = ops[ pc ++ ];
 
-   vg_release_thread_sync();
-}
+   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;
 
-VG_STATIC void vg_input_free(void)
-{
-   if( vg_input.controller_handle )
-   {
-      SDL_GameControllerClose( vg_input.controller_handle );
-      vg_input.controller_handle = NULL;
+         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;
 }
 
 #endif