Browse Source

Merge branch 'input-overhaul' of github.com:grimfang/panda3d into input-overhaul

rdb 9 years ago
parent
commit
d3008c56e0
43 changed files with 1669 additions and 90 deletions
  1. 83 0
      direct/src/showbase/ShowBase.py
  2. 3 0
      panda/src/device/config_device.cxx
  3. 2 0
      panda/src/device/config_device.h
  4. 116 36
      panda/src/device/evdevInputDevice.cxx
  5. 80 16
      panda/src/device/inputDevice.I
  6. 16 16
      panda/src/device/inputDevice.cxx
  7. 24 7
      panda/src/device/inputDevice.h
  8. 23 6
      panda/src/device/inputDeviceManager.cxx
  9. 5 5
      panda/src/device/inputDeviceManager.h
  10. 71 0
      panda/src/device/inputDeviceNode.cxx
  11. 66 0
      panda/src/device/inputDeviceNode.h
  12. 14 4
      panda/src/device/linuxJoystickDevice.cxx
  13. 1 0
      panda/src/device/p3device_composite1.cxx
  14. 12 0
      panda/src/putil/gamepadButton.cxx
  15. 6 0
      panda/src/putil/gamepadButton.h
  16. 131 0
      samples/gamepad/gamepad.py
  17. 342 0
      samples/gamepad/mappingGUI.py
  18. 117 0
      samples/gamepad/models/button_map.egg
  19. BIN
      samples/gamepad/models/click.png
  20. BIN
      samples/gamepad/models/dec_click.png
  21. BIN
      samples/gamepad/models/dec_disabled.png
  22. BIN
      samples/gamepad/models/dec_hover.png
  23. 117 0
      samples/gamepad/models/dec_map.egg
  24. BIN
      samples/gamepad/models/dec_ready.png
  25. BIN
      samples/gamepad/models/dialog.png
  26. BIN
      samples/gamepad/models/disabled.png
  27. BIN
      samples/gamepad/models/hover.png
  28. BIN
      samples/gamepad/models/inc_click.png
  29. BIN
      samples/gamepad/models/inc_disabled.png
  30. BIN
      samples/gamepad/models/inc_hover.png
  31. 117 0
      samples/gamepad/models/inc_map.egg
  32. BIN
      samples/gamepad/models/inc_ready.png
  33. BIN
      samples/gamepad/models/li_ready_even.png
  34. BIN
      samples/gamepad/models/li_ready_odd.png
  35. 33 0
      samples/gamepad/models/list_item_even.egg
  36. 33 0
      samples/gamepad/models/list_item_odd.egg
  37. BIN
      samples/gamepad/models/ready.png
  38. BIN
      samples/gamepad/models/thumb_click.png
  39. BIN
      samples/gamepad/models/thumb_disabled.png
  40. BIN
      samples/gamepad/models/thumb_hover.png
  41. 117 0
      samples/gamepad/models/thumb_map.egg
  42. BIN
      samples/gamepad/models/thumb_ready.png
  43. 140 0
      samples/gamepad/steeringWheel.py

+ 83 - 0
direct/src/showbase/ShowBase.py

@@ -173,6 +173,7 @@ class ShowBase(DirectObject.DirectObject):
         self.trackball = None
         self.trackball = None
         self.texmem = None
         self.texmem = None
         self.showVertices = None
         self.showVertices = None
+        self.deviceButtonThrowers = []
 
 
         ## This is a NodePath pointing to the Camera object set up for the 3D scene.
         ## This is a NodePath pointing to the Camera object set up for the 3D scene.
         ## This is usually a child of self.camera.
         ## This is usually a child of self.camera.
@@ -297,6 +298,7 @@ class ShowBase(DirectObject.DirectObject):
         ## The global job manager, as imported from JobManagerGlobal.
         ## The global job manager, as imported from JobManagerGlobal.
         self.jobMgr = jobMgr
         self.jobMgr = jobMgr
 
 
+
         ## Particle manager
         ## Particle manager
         self.particleMgr = None
         self.particleMgr = None
         self.particleMgrEnabled = 0
         self.particleMgrEnabled = 0
@@ -309,6 +311,12 @@ class ShowBase(DirectObject.DirectObject):
         ## This is the global input device manager, which keeps track of
         ## This is the global input device manager, which keeps track of
         ## connected input devices.
         ## connected input devices.
         self.devices = InputDeviceManager.getGlobalPtr()
         self.devices = InputDeviceManager.getGlobalPtr()
+        # add existing devices to the data graph
+        for device in self.devices.devices:
+            self.connectDevice(device)
+        # Checks for device connection and disconnection
+        self.accept('connect-device', self.connectDevice)
+        self.accept('disconnect-device', self.disconnectDevice)
 
 
         self.createStats()
         self.createStats()
 
 
@@ -1660,6 +1668,77 @@ class ShowBase(DirectObject.DirectObject):
         return self.mouseWatcherNode.getModifierButtons().isDown(
         return self.mouseWatcherNode.getModifierButtons().isDown(
             KeyboardButton.meta())
             KeyboardButton.meta())
 
 
+    def connectDevice(self, device):
+        """
+        This function will get called each time a new device got
+        connected and will add that new device to the data graph.
+
+        Each device class will get a specific prefix for thrown events. Those
+        are currently as follow
+
+        gamepad
+        flight_stick
+        steering_wheel
+        dance_pad
+        mouse
+        keyboard
+        unclassified_device
+
+        In addition, the index of that device will appended to the prefix,
+        so for example if you hit the A button of the first connected gamepad
+        you will get an event like "gamepad0-action_a" the second gamepad will
+        then be catchable via "gamepad1-button_event" and so on.
+        Note, each device class will have a separate 0 based index, this way
+        you can have a gamepad0 as well as a steering_wheel0 and flight_stick0.
+
+        All newly created button throwers will be stored in
+        the deviceButtonThrowers lsit
+        """
+        idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName()))
+        prefix = "unclassified_device"
+        if device.getDeviceClass() == InputDevice.DC_gamepad:
+            prefix = "gamepad"
+        elif device.getDeviceClass() == InputDevice.DC_flight_stick:
+            prefix = "flight_stick"
+        elif device.getDeviceClass() == InputDevice.DC_steering_wheel:
+            prefix = "steering_wheel"
+        elif device.getDeviceClass() == InputDevice.DC_dance_pad:
+            prefix = "dance_pad"
+        elif device.getDeviceClass() == InputDevice.DC_mouse:
+            prefix = "mouse"
+        elif device.getDeviceClass() == InputDevice.DC_keyboard:
+            prefix = "keyboard"
+
+        currentPrefixes = []
+        for np in self.dataRoot.findAllMatches("**/{}".format(prefix)):
+            bt = np.node()
+            currentPrefixes.append(bt.getPrefix())
+
+        id = 0
+        # Find the next free ID for the newly connected device
+        while "{}{}-".format(prefix, id) in currentPrefixes:
+            id+=1
+        # Setup the button thrower for that device and register it's event prefix
+        bt = idn.attachNewNode(ButtonThrower(prefix))
+        assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id))
+        bt.node().setPrefix("{}{}-".format(prefix, id))
+        # append the new button thrower to the list of device button throwers
+        self.deviceButtonThrowers.append(bt)
+
+    def disconnectDevice(self, device):
+        """
+        This function will get called each time a new device got
+        connected. It is then used to clean up the given device from the
+        data graph.
+        """
+        self.notify.debug("Disconnect device {}".format(device.getName()))
+        idn = self.dataRoot.find("**/{}".format(device.getName()))
+        for bt in list(self.deviceButtonThrowers):
+            if bt.getName() == idn.getName():
+                self.deviceButtonThrowers.remove(bt)
+                break
+        idn.removeNode()
+
     def addAngularIntegrator(self):
     def addAngularIntegrator(self):
         if not self.physicsMgrAngular:
         if not self.physicsMgrAngular:
             physics = importlib.import_module('panda3d.physics')
             physics = importlib.import_module('panda3d.physics')
@@ -1852,6 +1931,10 @@ class ShowBase(DirectObject.DirectObject):
         # Check if there were newly connected devices.
         # Check if there were newly connected devices.
         self.devices.update()
         self.devices.update()
 
 
+        # Poll all connected devices.
+        for device in self.devices.devices:
+            device.poll()
+
         # traverse the data graph.  This reads all the control
         # traverse the data graph.  This reads all the control
         # inputs (from the mouse and keyboard, for instance) and also
         # inputs (from the mouse and keyboard, for instance) and also
         # directly acts upon them (for instance, to move the avatar).
         # directly acts upon them (for instance, to move the avatar).

+ 3 - 0
panda/src/device/config_device.cxx

@@ -36,6 +36,9 @@ NotifyCategoryDef(device, "");
 ConfigVariableBool asynchronous_clients
 ConfigVariableBool asynchronous_clients
 ("asynchronous-clients", true);
 ("asynchronous-clients", true);
 
 
+ConfigVariableInt low_battery_level
+("low-battery-level", 15);
+
 ConfigureFn(config_device) {
 ConfigureFn(config_device) {
   init_libdevice();
   init_libdevice();
 }
 }

+ 2 - 0
panda/src/device/config_device.h

@@ -17,10 +17,12 @@
 #include "pandabase.h"
 #include "pandabase.h"
 #include "notifyCategoryProxy.h"
 #include "notifyCategoryProxy.h"
 #include "configVariableBool.h"
 #include "configVariableBool.h"
+#include "configVariableInt.h"
 
 
 NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE);
 NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE);
 
 
 extern ConfigVariableBool asynchronous_clients;
 extern ConfigVariableBool asynchronous_clients;
+extern ConfigVariableInt low_battery_level;
 
 
 extern EXPCL_PANDA_DEVICE void init_libdevice();
 extern EXPCL_PANDA_DEVICE void init_libdevice();
 
 

+ 116 - 36
panda/src/device/evdevInputDevice.cxx

@@ -227,6 +227,7 @@ init_device() {
   }
   }
 
 
   _name.assign(name);
   _name.assign(name);
+  //cerr << "##### Now initializing device " << name << "\n";
 
 
   struct input_id id;
   struct input_id id;
   if (ioctl(_fd, EVIOCGID, &id) >= 0) {
   if (ioctl(_fd, EVIOCGID, &id) >= 0) {
@@ -237,12 +238,106 @@ init_device() {
   bool all_values_zero = true;
   bool all_values_zero = true;
   bool emulate_dpad = true;
   bool emulate_dpad = true;
 
 
+  bool has_keys = false;
+  bool has_axes = false;
+
+  uint8_t keys[(KEY_CNT + 7) >> 3];
   if (test_bit(EV_KEY, evtypes)) {
   if (test_bit(EV_KEY, evtypes)) {
     // Check which buttons are on the device.
     // Check which buttons are on the device.
-    uint8_t keys[(KEY_CNT + 7) >> 3];
     memset(keys, 0, sizeof(keys));
     memset(keys, 0, sizeof(keys));
     ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys);
     ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys);
+    has_keys = true;
+
+    if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) {
+      _flags |= IDF_has_keyboard;
+    }
+  }
+
+  int num_bits = 0;
+  uint8_t axes[(ABS_CNT + 7) >> 3];
+  if (test_bit(EV_ABS, evtypes)) {
+    // Check which axes are on the device.
+    memset(axes, 0, sizeof(axes));
+    num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
+    has_axes = true;
+  }
+
+
+  // Try to detect which type of device we have here
+  int device_scores[DC_COUNT];
+  memset(device_scores, 0, sizeof(device_scores));
+
+  // Test for specific keys
+  if (test_bit(BTN_GAMEPAD, keys)) {
+    device_scores[DC_gamepad] += 5;
+    device_scores[DC_steering_wheel] += 5;
+    device_scores[DC_flight_stick] += 5;
+  }
+
+  if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) {
+    device_scores[DC_steering_wheel] += 10;
+  }
+  if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) {
+    device_scores[DC_steering_wheel] += 10;
+  }
+  if (test_bit(BTN_JOYSTICK, keys)) {
+    device_scores[DC_flight_stick] += 10;
+  }
+  if (test_bit(BTN_MOUSE, keys)) {
+    device_scores[DC_mouse] += 20;
+  }
+  uint8_t unknown_keys[] = {KEY_POWER};
+  for (int i = 0; i < 1; i++) {
+    if (test_bit(unknown_keys[i], keys)) {
+      if (unknown_keys[i] == KEY_POWER) {
+      }
+      device_scores[DC_unknown] += 20;
+    }
+  }
+  if (_flags & IDF_has_keyboard) {
+    device_scores[DC_keyboard] += 20;
+  }
+
+  // Test for specific name tags
+  string lowercase_name = _name;
+  for(int x=0; x<_name.length(); x++) {
+    lowercase_name[x]=tolower(lowercase_name[x]);
+  }
+  if (lowercase_name.find("gamepad") != string::npos) {
+    device_scores[DC_gamepad] += 10;
+  }
+  if (lowercase_name.find("wheel") != string::npos) {
+    device_scores[DC_steering_wheel] += 10;
+  }
+  if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) {
+    device_scores[DC_mouse] += 10;
+  }
+  if (lowercase_name.find("keyboard") != string::npos) {
+    device_scores[DC_keyboard] += 10;
+  }
+  // List of lowercase names that occur in unknown devices
+  string unknown_names[] = {"video bus", "power button", "sleep button"};
+  for(int i = 0; i < 3; i++) {
+    if (lowercase_name.find(unknown_names[i]) != string::npos) {
+      device_scores[DC_unknown] += 20;
+    }
+  }
+
+  // Check which device type got the most points
+  size_t highest_score = 0;
+  for (size_t i = 0; i < DC_COUNT; i++) {
+    if (device_scores[i] > highest_score) {
+      highest_score = device_scores[i];
+      _device_class = (DeviceClass)i;
+    }
+  }
+  //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n";
 
 
+  if (_device_class != DC_gamepad) {
+    emulate_dpad = false;
+  }
+
+  if (has_keys) {
     // Also check whether the buttons are currently pressed.
     // Also check whether the buttons are currently pressed.
     uint8_t states[(KEY_CNT + 7) >> 3];
     uint8_t states[(KEY_CNT + 7) >> 3];
     memset(states, 0, sizeof(states));
     memset(states, 0, sizeof(states));
@@ -252,56 +347,28 @@ init_device() {
     for (int i = 0; i < KEY_CNT; ++i) {
     for (int i = 0; i < KEY_CNT; ++i) {
       if (test_bit(i, keys)) {
       if (test_bit(i, keys)) {
         set_button_map(bi, map_button(i));
         set_button_map(bi, map_button(i));
+        //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n";
         if (test_bit(i, states)) {
         if (test_bit(i, states)) {
-          _buttons[bi]._state = S_down;
+          _buttons[bi].state = S_down;
           all_values_zero = false;
           all_values_zero = false;
         } else {
         } else {
-          _buttons[bi]._state = S_up;
+          _buttons[bi].state = S_up;
         }
         }
-        if (_buttons[bi]._handle == GamepadButton::dpad_left()) {
+        if (_buttons[bi].handle == GamepadButton::dpad_left()) {
           emulate_dpad = false;
           emulate_dpad = false;
         }
         }
         ++bi;
         ++bi;
       }
       }
     }
     }
-
-    if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) {
-      _flags |= IDF_has_keyboard;
-    }
-
-    // Check device type.
-    if (test_bit(BTN_GAMEPAD, keys)) {
-      _device_class = DC_gamepad;
-
-    } else if (test_bit(BTN_JOYSTICK, keys)) {
-      _device_class = DC_flight_stick;
-
-    } else if (test_bit(BTN_MOUSE, keys)) {
-      _device_class = DC_mouse;
-
-    } else if (test_bit(BTN_WHEEL, keys)) {
-      _device_class = DC_steering_wheel;
-
-    } else if (_flags & IDF_has_keyboard) {
-      _device_class = DC_keyboard;
-    }
-  }
-
-  if (_device_class != DC_gamepad) {
-    emulate_dpad = false;
   }
   }
 
 
-  if (test_bit(EV_ABS, evtypes)) {
-    // Check which axes are on the device.
-    uint8_t axes[(ABS_CNT + 7) >> 3];
-    memset(axes, 0, sizeof(axes));
-
+  if (has_axes) {
     AxisRange range;
     AxisRange range;
     range._scale = 1.0;
     range._scale = 1.0;
     range._bias = 0.0;
     range._bias = 0.0;
     _axis_ranges.resize(ABS_CNT, range);
     _axis_ranges.resize(ABS_CNT, range);
 
 
-    int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
+
     for (int i = 0; i < num_bits; ++i) {
     for (int i = 0; i < num_bits; ++i) {
       if (test_bit(i, axes)) {
       if (test_bit(i, axes)) {
         if (i >= ABS_HAT0X) {
         if (i >= ABS_HAT0X) {
@@ -319,9 +386,14 @@ init_device() {
             _dpad_up_button = (int)_buttons.size();
             _dpad_up_button = (int)_buttons.size();
             _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
             _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
             _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
             _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
+          } else if (i == ABS_HAT0X) {
+            set_control_map(i, C_hat_x);
+          } else if (i == ABS_HAT0Y) {
+            set_control_map(i, C_hat_y);
           }
           }
         } else {
         } else {
           set_control_map(i, axis_map[i]);
           set_control_map(i, axis_map[i]);
+          //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n";
         }
         }
 
 
         // Check the initial value and ranges.
         // Check the initial value and ranges.
@@ -346,7 +418,7 @@ init_device() {
 
 
           _axis_ranges[i]._scale = factor;
           _axis_ranges[i]._scale = factor;
           _axis_ranges[i]._bias = bias;
           _axis_ranges[i]._bias = bias;
-          _controls[i]._state = fma(absinfo.value, factor, bias);
+          _controls[i].state = fma(absinfo.value, factor, bias);
 
 
           if (absinfo.value != 0) {
           if (absinfo.value != 0) {
             all_values_zero = false;
             all_values_zero = false;
@@ -693,10 +765,18 @@ map_button(int code) {
   case BTN_TR2:
   case BTN_TR2:
     return GamepadButton::rtrigger();
     return GamepadButton::rtrigger();
 
 
+  case BTN_1:
+    return GamepadButton::action_1();
+
+  case BTN_2:
+    return GamepadButton::action_2();
+
   case BTN_SELECT:
   case BTN_SELECT:
+  case KEY_PREVIOUS:
     return GamepadButton::back();
     return GamepadButton::back();
 
 
   case BTN_START:
   case BTN_START:
+  case KEY_NEXT:
     return GamepadButton::start();
     return GamepadButton::start();
 
 
   case BTN_MODE:
   case BTN_MODE:

+ 80 - 16
panda/src/device/inputDevice.I

@@ -11,6 +11,8 @@
  * @date 2015-12-11
  * @date 2015-12-11
  */
  */
 
 
+#include "config_device.h"
+
 /**
 /**
  *
  *
  */
  */
@@ -197,7 +199,7 @@ set_button_map(int index, ButtonHandle button) {
     _buttons.resize(index + 1, ButtonState());
     _buttons.resize(index + 1, ButtonState());
   }
   }
 
 
-  _buttons[index]._handle = button;
+  _buttons[index].handle = button;
 }
 }
 
 
 /**
 /**
@@ -208,7 +210,7 @@ set_button_map(int index, ButtonHandle button) {
 INLINE ButtonHandle InputDevice::
 INLINE ButtonHandle InputDevice::
 get_button_map(int index) const {
 get_button_map(int index) const {
   if (index >= 0 && index < (int)_buttons.size()) {
   if (index >= 0 && index < (int)_buttons.size()) {
-    return _buttons[index]._handle;
+    return _buttons[index].handle;
   } else {
   } else {
     return ButtonHandle::none();
     return ButtonHandle::none();
   }
   }
@@ -221,7 +223,7 @@ get_button_map(int index) const {
 INLINE bool InputDevice::
 INLINE bool InputDevice::
 get_button_state(int index) const {
 get_button_state(int index) const {
   if (index >= 0 && index < (int)_buttons.size()) {
   if (index >= 0 && index < (int)_buttons.size()) {
-    return (_buttons[index]._state == S_down);
+    return (_buttons[index].state == S_down);
   } else {
   } else {
     return false;
     return false;
   }
   }
@@ -234,12 +236,43 @@ get_button_state(int index) const {
 INLINE bool InputDevice::
 INLINE bool InputDevice::
 is_button_known(int index) const {
 is_button_known(int index) const {
   if (index >= 0 && index < (int)_buttons.size()) {
   if (index >= 0 && index < (int)_buttons.size()) {
-    return _buttons[index]._state != S_unknown;
+    return _buttons[index].state != S_unknown;
   } else {
   } else {
     return false;
     return false;
   }
   }
 }
 }
 
 
+/**
+ * Returns the ButtonState that is set at the given index, or throw an assection
+ * if the index was not found in the list.
+ */
+INLINE InputDevice::ButtonState InputDevice::
+get_button(size_t index) const {
+  if (index >= 0 && index < (int)_buttons.size()) {
+    return _buttons[index];
+  } else {
+    device_cat.error()
+      << "Index " << index<< " was not found in the controls list\n";
+    nassertr(false, ButtonState());
+  }
+}
+
+/**
+ * Returns the first ButtonState found with the given axis, or throw an assection
+ * if the button handle was not found in the list.
+ */
+INLINE InputDevice::ButtonState InputDevice::
+find_button(ButtonHandle handle) const {
+  for (int i; i < (int)_buttons.size(); i++) {
+    if (_buttons[i].handle == handle) {
+      return _buttons[i];
+    }
+  }
+  device_cat.error()
+    << "Handle " << handle.get_name() << " was not found in the controls list\n";
+  nassertr(false, ButtonState());
+}
+
 /**
 /**
  * Returns the number of analog controls known to the InputDevice.  This number
  * Returns the number of analog controls known to the InputDevice.  This number
  * may change as more controls are discovered.
  * may change as more controls are discovered.
@@ -257,14 +290,14 @@ get_num_controls() const {
  * the various controls by index number.
  * the various controls by index number.
  */
  */
 INLINE void InputDevice::
 INLINE void InputDevice::
-set_control_map(int index, ControlAxis axis) {
+set_control_map(int index, InputDevice::ControlAxis axis) {
   LightMutexHolder holder(_lock);
   LightMutexHolder holder(_lock);
   nassertv(index >= 0);
   nassertv(index >= 0);
   if (index >= (int)_controls.size()) {
   if (index >= (int)_controls.size()) {
     _controls.resize(index + 1, AnalogState());
     _controls.resize(index + 1, AnalogState());
   }
   }
 
 
-  _controls[index]._axis = axis;
+  _controls[index].axis = axis;
 }
 }
 
 
 /**
 /**
@@ -275,7 +308,7 @@ set_control_map(int index, ControlAxis axis) {
 INLINE InputDevice::ControlAxis InputDevice::
 INLINE InputDevice::ControlAxis InputDevice::
 get_control_map(int index) const {
 get_control_map(int index) const {
   if (index >= 0 && index < (int)_controls.size()) {
   if (index >= 0 && index < (int)_controls.size()) {
-    return _controls[index]._axis;
+    return _controls[index].axis;
   } else {
   } else {
     return C_none;
     return C_none;
   }
   }
@@ -289,12 +322,43 @@ get_control_map(int index) const {
 INLINE double InputDevice::
 INLINE double InputDevice::
 get_control_state(int index) const {
 get_control_state(int index) const {
   if (index >= 0 && index < (int)_controls.size()) {
   if (index >= 0 && index < (int)_controls.size()) {
-    return _controls[index]._state;
+    return _controls[index].state;
   } else {
   } else {
     return 0.0;
     return 0.0;
   }
   }
 }
 }
 
 
+/**
+ * Returns the AnalogAxis that is set at the given index, or throw an assection
+ * if the index was not found in the list.
+ */
+INLINE InputDevice::AnalogState InputDevice::
+get_control(size_t index) const {
+  if (index >= 0 && index < (int)_controls.size()) {
+    return _controls[index];
+  } else {
+    device_cat.error()
+      << "Index " << index<< " was not found in the controls list\n";
+    nassertr(false, AnalogState());
+  }
+}
+
+/**
+ * Returns the first AnalogAxis found with the given axis, or throw an assection
+ * if the axis was not found in the list.
+ */
+INLINE InputDevice::AnalogState InputDevice::
+find_control(InputDevice::ControlAxis axis) const {
+  for (int i; i < (int)_controls.size(); i++) {
+    if (_controls[i].axis == axis) {
+      return _controls[i];
+    }
+  }
+  device_cat.error()
+    << "Axis " << axis << " was not found in the controls list\n";
+  nassertr(false, AnalogState());
+}
+
 /**
 /**
  * Returns true if the state of the indicated analog control is known, or false
  * Returns true if the state of the indicated analog control is known, or false
  * if we have never heard anything about this particular control.
  * if we have never heard anything about this particular control.
@@ -302,7 +366,7 @@ get_control_state(int index) const {
 INLINE bool InputDevice::
 INLINE bool InputDevice::
 is_control_known(int index) const {
 is_control_known(int index) const {
   if (index >= 0 && index < (int)_controls.size()) {
   if (index >= 0 && index < (int)_controls.size()) {
-    return _controls[index]._known;
+    return _controls[index].known;
   } else {
   } else {
     return false;
     return false;
   }
   }
@@ -377,8 +441,8 @@ operator < (const InputDevice &) const {
  */
  */
 INLINE InputDevice::ButtonState::
 INLINE InputDevice::ButtonState::
 ButtonState() :
 ButtonState() :
-  _handle(ButtonHandle::none()),
-  _state(S_unknown)
+  handle(ButtonHandle::none()),
+  state(S_unknown)
 {
 {
 }
 }
 
 
@@ -387,8 +451,8 @@ ButtonState() :
  */
  */
 INLINE InputDevice::ButtonState::
 INLINE InputDevice::ButtonState::
 ButtonState(ButtonHandle handle) :
 ButtonState(ButtonHandle handle) :
-  _handle(handle),
-  _state(S_unknown)
+  handle(handle),
+  state(S_unknown)
 {
 {
 }
 }
 
 
@@ -397,8 +461,8 @@ ButtonState(ButtonHandle handle) :
  */
  */
 INLINE InputDevice::AnalogState::
 INLINE InputDevice::AnalogState::
 AnalogState() :
 AnalogState() :
-  _axis(C_none),
-  _state(0.0),
-  _known(false)
+  axis(C_none),
+  state(0.0),
+  known(false)
 {
 {
 }
 }

+ 16 - 16
panda/src/device/inputDevice.cxx

@@ -171,12 +171,12 @@ set_button_state(int index, bool down) {
   }
   }
 
 
   State new_state = down ? S_down : S_up;
   State new_state = down ? S_down : S_up;
-  if (_buttons[index]._state == new_state) {
+  if (_buttons[index].state == new_state) {
     return;
     return;
   }
   }
-  _buttons[index]._state = new_state;
+  _buttons[index].state = new_state;
 
 
-  ButtonHandle handle = _buttons[index]._handle;
+  ButtonHandle handle = _buttons[index].handle;
 
 
   if (device_cat.is_spam()) {
   if (device_cat.is_spam()) {
     device_cat.spam()
     device_cat.spam()
@@ -208,19 +208,19 @@ set_control_state(int index, double state) {
     _controls.resize(index + 1, AnalogState());
     _controls.resize(index + 1, AnalogState());
   }
   }
 
 
-  if (device_cat.is_spam() && _controls[index]._state != state) {
+  if (device_cat.is_spam() && _controls[index].state != state) {
     device_cat.spam()
     device_cat.spam()
       << "Changed control " << index;
       << "Changed control " << index;
 
 
-    if (_controls[index]._axis != C_none) {
-      device_cat.spam(false) << " (" << _controls[index]._axis << ")";
+    if (_controls[index].known != C_none) {
+      device_cat.spam(false) << " (" << _controls[index].known << ")";
     }
     }
 
 
     device_cat.spam(false) << " to " << state << "\n";
     device_cat.spam(false) << " to " << state << "\n";
   }
   }
 
 
-  _controls[index]._state = state;
-  _controls[index]._known = true;
+  _controls[index].state = state;
+  _controls[index].known = true;
 }
 }
 
 
 /**
 /**
@@ -308,13 +308,13 @@ output_buttons(ostream &out) const {
   Buttons::const_iterator bi;
   Buttons::const_iterator bi;
   for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
   for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
     const ButtonState &state = (*bi);
     const ButtonState &state = (*bi);
-    if (state._state != S_unknown) {
+    if (state.state != S_unknown) {
       if (any_buttons) {
       if (any_buttons) {
         out << ", ";
         out << ", ";
       }
       }
       any_buttons = true;
       any_buttons = true;
       out << (int)(bi - _buttons.begin()) << "=";
       out << (int)(bi - _buttons.begin()) << "=";
-      if (state._state == S_up) {
+      if (state.state == S_up) {
         out << "up";
         out << "up";
       } else {
       } else {
         out << "down";
         out << "down";
@@ -336,17 +336,17 @@ write_buttons(ostream &out, int indent_level) const {
   Buttons::const_iterator bi;
   Buttons::const_iterator bi;
   for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
   for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
     const ButtonState &state = (*bi);
     const ButtonState &state = (*bi);
-    if (state._state != S_unknown) {
+    if (state.state != S_unknown) {
       any_buttons = true;
       any_buttons = true;
 
 
       indent(out, indent_level)
       indent(out, indent_level)
         << (int)(bi - _buttons.begin()) << ". ";
         << (int)(bi - _buttons.begin()) << ". ";
 
 
-      if (state._handle != ButtonHandle::none()) {
-        out << "(" << state._handle << ") ";
+      if (state.handle != ButtonHandle::none()) {
+        out << "(" << state.handle << ") ";
       }
       }
 
 
-      if (state._state == S_up) {
+      if (state.state == S_up) {
         out << "up";
         out << "up";
       } else {
       } else {
         out << "down";
         out << "down";
@@ -372,11 +372,11 @@ write_controls(ostream &out, int indent_level) const {
   Controls::const_iterator ai;
   Controls::const_iterator ai;
   for (ai = _controls.begin(); ai != _controls.end(); ++ai) {
   for (ai = _controls.begin(); ai != _controls.end(); ++ai) {
     const AnalogState &state = (*ai);
     const AnalogState &state = (*ai);
-    if (state._known) {
+    if (state.known) {
       any_controls = true;
       any_controls = true;
 
 
       indent(out, indent_level)
       indent(out, indent_level)
-        << (int)(ai - _controls.begin()) << ". " << state._state << "\n";
+        << (int)(ai - _controls.begin()) << ". " << state.state << "\n";
     }
     }
   }
   }
 
 

+ 24 - 7
panda/src/device/inputDevice.h

@@ -77,6 +77,9 @@ PUBLISHED:
 
 
     // Head-mounted display.
     // Head-mounted display.
     DC_hmd,
     DC_hmd,
+
+    // Count of this enum, used for loops
+    DC_COUNT,
   };
   };
 
 
 protected:
 protected:
@@ -247,13 +250,15 @@ public:
     S_down
     S_down
   };
   };
 
 
+PUBLISHED:
   class ButtonState {
   class ButtonState {
   public:
   public:
     INLINE ButtonState();
     INLINE ButtonState();
     INLINE ButtonState(ButtonHandle handle);
     INLINE ButtonState(ButtonHandle handle);
 
 
-    ButtonHandle _handle;
-    State _state;
+  PUBLISHED:
+    ButtonHandle handle;
+    State state;
   };
   };
   typedef pvector<ButtonState> Buttons;
   typedef pvector<ButtonState> Buttons;
   Buttons _buttons;
   Buttons _buttons;
@@ -262,9 +267,10 @@ public:
   public:
   public:
     INLINE AnalogState();
     INLINE AnalogState();
 
 
-    ControlAxis _axis;
-    double _state;
-    bool _known;
+  PUBLISHED:
+    ControlAxis axis;
+    double state;
+    bool known;
   };
   };
   typedef pvector<AnalogState> Controls;
   typedef pvector<AnalogState> Controls;
   Controls _controls;
   Controls _controls;
@@ -274,6 +280,17 @@ public:
 
 
   TrackerData _tracker_data;
   TrackerData _tracker_data;
 
 
+
+  INLINE ButtonState get_button(size_t index) const;
+  INLINE ButtonState find_button(ButtonHandle handle) const;
+
+  INLINE AnalogState get_control(size_t index) const;
+  INLINE AnalogState find_control(ControlAxis axis) const;
+
+  // Make device buttons and controls iterable
+  MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button);
+  MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -297,8 +314,8 @@ INLINE ostream &operator << (ostream &out, const InputDevice &device) {
   return out;
   return out;
 }
 }
 
 
-ostream &operator << (ostream &out, InputDevice::DeviceClass dc);
-ostream &operator << (ostream &out, InputDevice::ControlAxis axis);
+EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::DeviceClass dc);
+EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::ControlAxis axis);
 
 
 #include "inputDevice.I"
 #include "inputDevice.I"
 
 

+ 23 - 6
panda/src/device/inputDeviceManager.cxx

@@ -246,23 +246,40 @@ consider_add_js_device(int js_index) {
 #endif
 #endif
 
 
 /**
 /**
- * Description: Returns all currently connected gamepad devices.
+ * Description: Returns all currently connected devices.
  */
  */
 InputDeviceSet InputDeviceManager::
 InputDeviceSet InputDeviceManager::
-get_gamepads() const {
-  InputDeviceSet gamepads;
+get_devices() const {
+  InputDeviceSet devices;
   LightMutexHolder holder(_lock);
   LightMutexHolder holder(_lock);
 
 
   for (size_t i = 0; i < _connected_devices.size(); ++i) {
   for (size_t i = 0; i < _connected_devices.size(); ++i) {
     InputDevice *device = _connected_devices[i];
     InputDevice *device = _connected_devices[i];
-    if (device->get_device_class() == InputDevice::DC_gamepad) {
-      gamepads.add_device(device);
+    devices.add_device(device);
+  }
+
+  return devices;
+}
+
+/**
+ * Description: Returns all currently connected devices of the given device class.
+ */
+InputDeviceSet InputDeviceManager::
+get_devices(InputDevice::DeviceClass device_class) const {
+  InputDeviceSet devices;
+  LightMutexHolder holder(_lock);
+
+  for (size_t i = 0; i < _connected_devices.size(); ++i) {
+    InputDevice *device = _connected_devices[i];
+    if (device->get_device_class() == device_class) {
+      devices.add_device(device);
     }
     }
   }
   }
 
 
-  return gamepads;
+  return devices;
 }
 }
 
 
+
 /**
 /**
  * Called when a new device has been discovered.  This may also be used to
  * Called when a new device has been discovered.  This may also be used to
  * register virtual devices.
  * register virtual devices.

+ 5 - 5
panda/src/device/inputDeviceManager.h

@@ -37,10 +37,10 @@ private:
   InputDevice *consider_add_js_device(int index);
   InputDevice *consider_add_js_device(int index);
 #endif
 #endif
 
 
-public:
-  InputDeviceSet get_gamepads() const;
-
 PUBLISHED:
 PUBLISHED:
+  InputDeviceSet get_devices() const;
+  InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const;
+
   void add_device(InputDevice *device);
   void add_device(InputDevice *device);
   void remove_device(InputDevice *device);
   void remove_device(InputDevice *device);
 
 
@@ -48,8 +48,8 @@ PUBLISHED:
 
 
   INLINE static InputDeviceManager *get_global_ptr();
   INLINE static InputDeviceManager *get_global_ptr();
 
 
-  // The set of all currently connected gamepad devices.
-  MAKE_PROPERTY(gamepads, get_gamepads);
+  // The set of all currently connected devices.
+  MAKE_PROPERTY(devices, get_devices);
 
 
 private:
 private:
   LightMutex _lock;
   LightMutex _lock;

+ 71 - 0
panda/src/device/inputDeviceNode.cxx

@@ -0,0 +1,71 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file InputDeviceNode.cxx
+ * @author fireclaw
+ * @date 2016-07-14
+ */
+
+#include "config_device.h"
+#include "inputDeviceNode.h"
+#include "dataNodeTransmit.h"
+#include "inputDeviceManager.h"
+
+TypeHandle InputDeviceNode::_type_handle;
+
+InputDeviceNode::
+InputDeviceNode(InputDevice *device, const string &name) :
+  DataNode(name),
+  _device(device)
+{
+  _button_events_output = define_output("button_events", ButtonEventList::get_class_type());
+  _low_battery_event_output = define_output("low_battery_level_event", EventStoreInt::get_class_type());
+}
+
+/**
+ * Redirects the class to get the data from a different device.
+ */
+void InputDeviceNode::
+set_device(InputDevice *device) {
+  _device = device;
+}
+
+/**
+ * Returns the associated device.
+ */
+PT(InputDevice) InputDeviceNode::
+get_device() const {
+  return _device;
+}
+
+/**
+ * The virtual implementation of transmit_data().  This function receives an
+ * array of input parameters and should generate an array of output
+ * parameters.  The input parameters may be accessed with the index numbers
+ * returned by the define_input() calls that were made earlier (presumably in
+ * the constructor); likewise, the output parameters should be set with the
+ * index numbers returned by the define_output() calls.
+ */
+void InputDeviceNode::
+do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
+                 DataNodeTransmit &output) {
+  // get all button events of the device and forward them to the data graph
+  if (_device->has_button_event()) {
+    PT(ButtonEventList) bel = _device->get_button_events();
+    output.set_data(_button_events_output, EventParameter(bel));
+  }
+
+  // calculate the battery level in percent and set a warning if the level is to low
+  if (_device->has_battery()) {
+    short bl = _device->get_battery_level();
+    short bl_percent = bl / (_device->get_max_battery_level() / (short)100);
+    if (bl_percent <= low_battery_level) {
+      output.set_data(_low_battery_event_output, EventParameter(1));
+    }
+  }
+}

+ 66 - 0
panda/src/device/inputDeviceNode.h

@@ -0,0 +1,66 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file InputDeviceNode.h
+ * @author fireclaw
+ * @date 2016-07-14
+ */
+
+#ifndef INPUTDEVICENODE_H
+#define INPUTDEVICENODE_H
+
+#include "pandabase.h"
+
+#include "dataNode.h"
+#include "inputDeviceManager.h"
+#include "linmath_events.h"
+
+/**
+ * Reads the controler data sent from the InputDeviceManager, and
+ * transmits it down the data graph.
+ *
+ *
+ */
+class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode {
+PUBLISHED:
+  InputDeviceNode(InputDevice *device, const string &name);
+  void set_device(InputDevice *device);
+  PT(InputDevice) get_device() const;
+
+protected:
+  // Inherited from DataNode
+  virtual void do_transmit_data(DataGraphTraverser *trav,
+                                const DataNodeTransmit &input,
+                                DataNodeTransmit &output);
+
+private:
+  // outputs
+  int _button_events_output;
+  int _low_battery_event_output;
+
+  PT(InputDevice) _device;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    DataNode::init_type();
+    register_type(_type_handle, "InputDeviceNode",
+                  DataNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif // INPUTDEVICENODE_H

+ 14 - 4
panda/src/device/linuxJoystickDevice.cxx

@@ -163,11 +163,21 @@ open_device() {
         handle = GamepadButton::rtrigger();
         handle = GamepadButton::rtrigger();
         break;
         break;
 
 
+      case BTN_1:
+        handle = GamepadButton::action_1();
+        break;
+
+      case BTN_2:
+        handle = GamepadButton::action_2();
+        break;
+
       case BTN_SELECT:
       case BTN_SELECT:
+      case KEY_PREVIOUS:
         handle = GamepadButton::back();
         handle = GamepadButton::back();
         break;
         break;
 
 
       case BTN_START:
       case BTN_START:
+      case KEY_NEXT:
         handle = GamepadButton::start();
         handle = GamepadButton::start();
         break;
         break;
 
 
@@ -209,7 +219,7 @@ open_device() {
         handle = ButtonHandle::none();
         handle = ButtonHandle::none();
         break;
         break;
       }
       }
-      _buttons[i]._handle = handle;
+      _buttons[i].handle = handle;
     }
     }
   }
   }
 
 
@@ -311,7 +321,7 @@ open_device() {
         axis = C_none;
         axis = C_none;
         break;
         break;
       }
       }
-      _controls[i]._axis = axis;
+      _controls[i].axis = axis;
     }
     }
   }
   }
 
 
@@ -373,7 +383,7 @@ open_device() {
   // this gamepad yet (which means it hasn't been plugged in for this session)
   // this gamepad yet (which means it hasn't been plugged in for this session)
   if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
   if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
     for (int i = 0; i < _controls.size(); ++i) {
     for (int i = 0; i < _controls.size(); ++i) {
-      if (_controls[i]._state != 0.0) {
+      if (_controls[i].state != 0.0) {
         _is_connected = true;
         _is_connected = true;
         return true;
         return true;
       }
       }
@@ -437,7 +447,7 @@ process_events() {
         set_button_state(_dpad_up_button+1, events[i].value > 1000);
         set_button_state(_dpad_up_button+1, events[i].value > 1000);
       }
       }
 
 
-      ControlAxis axis = _controls[index]._axis;
+      ControlAxis axis = _controls[index].axis;
 
 
       if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) {
       if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) {
         // We'd like to use 0.0 to indicate the resting position.
         // We'd like to use 0.0 to indicate the resting position.

+ 1 - 0
panda/src/device/p3device_composite1.cxx

@@ -10,3 +10,4 @@
 #include "inputDeviceManager.cxx"
 #include "inputDeviceManager.cxx"
 #include "inputDeviceSet.cxx"
 #include "inputDeviceSet.cxx"
 #include "linuxJoystickDevice.cxx"
 #include "linuxJoystickDevice.cxx"
+#include "inputDeviceNode.cxx"

+ 12 - 0
panda/src/putil/gamepadButton.cxx

@@ -34,6 +34,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(back)
 DEFINE_GAMEPAD_BUTTON_HANDLE(guide)
 DEFINE_GAMEPAD_BUTTON_HANDLE(guide)
 DEFINE_GAMEPAD_BUTTON_HANDLE(start)
 DEFINE_GAMEPAD_BUTTON_HANDLE(start)
 
 
+DEFINE_GAMEPAD_BUTTON_HANDLE(next)
+DEFINE_GAMEPAD_BUTTON_HANDLE(previous)
+
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_a)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_a)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_b)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_b)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_c)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_c)
@@ -41,6 +44,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_x)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_y)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_y)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_z)
 DEFINE_GAMEPAD_BUTTON_HANDLE(action_z)
 
 
+DEFINE_GAMEPAD_BUTTON_HANDLE(action_1)
+DEFINE_GAMEPAD_BUTTON_HANDLE(action_2)
+
 /**
 /**
  * This is intended to be called only once, by the static initialization
  * This is intended to be called only once, by the static initialization
  * performed in config_util.cxx.
  * performed in config_util.cxx.
@@ -63,10 +69,16 @@ init_gamepad_buttons() {
   ButtonRegistry::ptr()->register_button(_guide, "guide");
   ButtonRegistry::ptr()->register_button(_guide, "guide");
   ButtonRegistry::ptr()->register_button(_start, "start");
   ButtonRegistry::ptr()->register_button(_start, "start");
 
 
+  ButtonRegistry::ptr()->register_button(_next, "next");
+  ButtonRegistry::ptr()->register_button(_previous, "previous");
+
   ButtonRegistry::ptr()->register_button(_action_a, "action_a");
   ButtonRegistry::ptr()->register_button(_action_a, "action_a");
   ButtonRegistry::ptr()->register_button(_action_b, "action_b");
   ButtonRegistry::ptr()->register_button(_action_b, "action_b");
   ButtonRegistry::ptr()->register_button(_action_c, "action_c");
   ButtonRegistry::ptr()->register_button(_action_c, "action_c");
   ButtonRegistry::ptr()->register_button(_action_x, "action_x");
   ButtonRegistry::ptr()->register_button(_action_x, "action_x");
   ButtonRegistry::ptr()->register_button(_action_y, "action_y");
   ButtonRegistry::ptr()->register_button(_action_y, "action_y");
   ButtonRegistry::ptr()->register_button(_action_z, "action_z");
   ButtonRegistry::ptr()->register_button(_action_z, "action_z");
+
+  ButtonRegistry::ptr()->register_button(_action_1, "action_1");
+  ButtonRegistry::ptr()->register_button(_action_2, "action_2");
 }
 }

+ 6 - 0
panda/src/putil/gamepadButton.h

@@ -40,6 +40,9 @@ PUBLISHED:
   static ButtonHandle guide();
   static ButtonHandle guide();
   static ButtonHandle start();
   static ButtonHandle start();
 
 
+  static ButtonHandle next();
+  static ButtonHandle previous();
+
   static ButtonHandle action_a();
   static ButtonHandle action_a();
   static ButtonHandle action_b();
   static ButtonHandle action_b();
   static ButtonHandle action_c();
   static ButtonHandle action_c();
@@ -47,6 +50,9 @@ PUBLISHED:
   static ButtonHandle action_y();
   static ButtonHandle action_y();
   static ButtonHandle action_z();
   static ButtonHandle action_z();
 
 
+  static ButtonHandle action_1();
+  static ButtonHandle action_2();
+
 public:
 public:
   static void init_gamepad_buttons();
   static void init_gamepad_buttons();
 };
 };

+ 131 - 0
samples/gamepad/gamepad.py

@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+'''
+Demonstrate usage of gamepads and other input devices
+
+In this sample you can use a gamepad type device to control the camera and
+show some messages on screen.  Using the left stick on the controler will
+move the camera where the right stick will rotate the camera.
+'''
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3
+from direct.gui.OnscreenText import OnscreenText
+
+loadPrcFileData("", "notify-level-device debug")
+
+class App(ShowBase):
+    def __init__(self):
+        ShowBase.__init__(self)
+        # print all events sent through the messenger
+        self.messenger.toggleVerbose()
+
+        self.lblWarning = OnscreenText(
+            text = "No devices found",
+            fg=(1,0,0,1),
+            scale = .25)
+        self.lblWarning.hide()
+
+        self.lblAction = OnscreenText(
+            text = "Action",
+            fg=(1,1,1,1),
+            scale = .15)
+        self.lblAction.hide()
+
+        self.checkDevices()
+
+        # Accept device dis-/connection events
+        # NOTE: catching the events here will overwrite the accept in showbase, hence
+        #       we need to forward the event in the functions we set here!
+        self.accept("connect-device", self.connect)
+        self.accept("disconnect-device", self.disconnect)
+
+        self.accept("escape", exit)
+        self.accept("gamepad0-start", exit)
+        self.accept("flight_stick0-start", exit)
+
+        # Accept button events of the first connected gamepad
+        self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"])
+        self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"])
+        self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"])
+        self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"])
+
+        self.environment = loader.loadModel("environment")
+        self.environment.reparentTo(render)
+
+        # disable pandas default mouse-camera controls so we can handle the camera
+        # movements by ourself
+        self.disableMouse()
+
+        # list of connected gamepad devices
+        gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
+
+        # set the center position of the control sticks
+        # NOTE: here we assume, that the wheel is centered when the application get started.
+        #       In real world applications, you should notice the user and give him enough time
+        #       to center the wheel until you store the center position of the controler!
+        self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state
+        self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state
+        self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state
+        self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state
+
+
+        self.taskMgr.add(self.moveTask, "movement update task")
+
+    def connect(self, device):
+        # we need to forward the event to the connectDevice function of showbase
+        self.connectDevice(device)
+        # Now we can check for ourself
+        self.checkDevices()
+
+    def disconnect(self, device):
+        # we need to forward the event to the disconnectDevice function of showbase
+        self.disconnectDevice(device)
+        # Now we can check for ourself
+        self.checkDevices()
+
+    def checkDevices(self):
+        # check if we have gamepad devices connected
+        if self.devices.get_devices(InputDevice.DC_gamepad):
+            # we have at least one gamepad device
+            self.lblWarning.hide()
+        else:
+            # no devices connected
+            self.lblWarning.show()
+
+    def doAction(self, showText, text):
+        if showText and self.lblAction.isHidden():
+            self.lblAction.show()
+        else:
+            self.lblAction.hide()
+
+    def moveTask(self, task):
+        dt = globalClock.getDt()
+        movementVec = Vec3()
+
+        gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
+        if len(gamepads) == 0:
+            # savety check
+            return task.cont
+
+        # we will use the first found gamepad
+        # Move the camera left/right
+        left_x = gamepads[0].findControl(InputDevice.C_left_x)
+        movementVec.setX(left_x.state - self.lxcenter)
+        # Move the camera forward/backward
+        left_y = gamepads[0].findControl(InputDevice.C_left_y)
+        movementVec.setY(left_y.state - self.lycenter)
+        # Control the cameras heading
+        right_x = gamepads[0].findControl(InputDevice.C_right_x)
+        base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter))
+        # Control the cameras pitch
+        right_y = gamepads[0].findControl(InputDevice.C_right_y)
+        base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter))
+
+        # calculate movement
+        base.camera.setX(base.camera, 100 * dt * movementVec.getX())
+        base.camera.setY(base.camera, 100 * dt * movementVec.getY())
+
+        return task.cont
+
+app = App()
+app.run()

+ 342 - 0
samples/gamepad/mappingGUI.py

@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+'''
+Demonstrate how a simple button mapping gui can be written
+'''
+
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.DirectGui import (
+    DGG,
+    DirectFrame,
+    DirectButton,
+    DirectLabel,
+    OkCancelDialog,
+    DirectScrolledFrame)
+from panda3d.core import (
+    VBase4,
+    TextNode,
+    Vec2,
+    InputDevice,
+    loadPrcFileData)
+
+# Make sure the textures look crisp on every device that supports
+# non-power-2 textures
+loadPrcFileData("", "textures-auto-power-2 #t")
+
+class App(ShowBase):
+    def __init__(self):
+        ShowBase.__init__(self)
+
+        self.setBackgroundColor(0, 0, 0)
+        # make the font look nice at a big scale
+        DGG.getDefaultFont().setPixelsPerUnit(100)
+
+        # a dict of actions and button/axis events
+        self.gamepadMapping = {
+            "Move forward":"Left Stick Y",
+            "Move backward":"Left Stick Y",
+            "Move left":"Left Stick X",
+            "Move right":"Left Stick X",
+            "Jump":"a",
+            "Action":"b",
+            "Sprint":"x",
+            "Map":"y",
+            "action-1":"c",
+            "action-2":"d",
+            "action-3":"e",
+            "action-4":"f",
+            "action-5":"g",
+            "action-6":"h",
+            "action-7":"i",
+            "action-8":"j",
+            "action-9":"k",
+            "action-10":"l",
+            "action-11":"m",
+        }
+        # this will store the action that we want to remap
+        self.actionToMap = ""
+        # this will store the key/axis that we want to asign to an action
+        self.newActionKey = ""
+        # this will store the label that needs to be actualized in the list
+        self.actualizeLabel = None
+
+        # The geometry for our basic buttons
+        maps = loader.loadModel("models/button_map")
+        self.buttonGeom = (
+            maps.find("**/ready"),
+            maps.find("**/click"),
+            maps.find("**/hover"),
+            maps.find("**/disabled"))
+
+        # Create the dialog that asks the user for input on a given
+        # action to map a key to.
+        DGG.setDefaultDialogGeom("models/dialog.png")
+        # setup a dialog to ask for device input
+        self.dlgInput = OkCancelDialog(
+            dialogName="dlg_device_input",
+            pos=(0, 0, 0.25),
+            text="Hit desired key:",
+            text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
+            text_shadow=VBase4(0, 0, 0, 0.75),
+            text_shadowOffset=Vec2(0.05, 0.05),
+            text_scale=0.05,
+            text_align=TextNode.ACenter,
+            fadeScreen=0.65,
+            frameColor=VBase4(0.3, 0.3, 0.3, 1),
+            button_geom=self.buttonGeom,
+            button_scale=0.15,
+            button_text_scale=0.35,
+            button_text_align=TextNode.ALeft,
+            button_text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
+            button_text_pos=Vec2(-0.9, -0.125),
+            button_relief=1,
+            button_pad=Vec2(0.01, 0.01),
+            button_frameColor=VBase4(0, 0, 0, 0),
+            button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25),
+            button_pressEffect=False,
+            command=self.closeDialog)
+        self.dlgInput.setTransparency(True)
+        self.dlgInput.configureDialog()
+        scale = self.dlgInput["image_scale"]
+        self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0)
+        self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06)
+        self.dlgInput.hide()
+
+        # create a sample title
+        self.textscale = 0.1
+        self.title = DirectLabel(
+            scale=self.textscale,
+            pos=(base.a2dLeft + 0.05, 0.0, base.a2dTop - (self.textscale + 0.05)),
+            frameColor=VBase4(0, 0, 0, 0),
+            text="Button Mapping",
+            text_align=TextNode.ALeft,
+            text_fg=VBase4(1, 1, 1, 1),
+            text_shadow=VBase4(0, 0, 0, 0.75),
+            text_shadowOffset=Vec2(0.05, 0.05))
+        self.title.setTransparency(1)
+
+        # Set up the list of actions that we can map keys to
+        # create a frame that will create the scrollbars for us
+        # Load the models for the scrollbar elements
+        thumbMaps = loader.loadModel("models/thumb_map")
+        thumbGeom = (
+            thumbMaps.find("**/thumb_ready"),
+            thumbMaps.find("**/thumb_click"),
+            thumbMaps.find("**/thumb_hover"),
+            thumbMaps.find("**/thumb_disabled"))
+        incMaps = loader.loadModel("models/inc_map")
+        incGeom = (
+            incMaps.find("**/inc_ready"),
+            incMaps.find("**/inc_click"),
+            incMaps.find("**/inc_hover"),
+            incMaps.find("**/inc_disabled"))
+        decMaps = loader.loadModel("models/dec_map")
+        decGeom = (
+            decMaps.find("**/dec_ready"),
+            decMaps.find("**/dec_click"),
+            decMaps.find("**/dec_hover"),
+            decMaps.find("**/dec_disabled"))
+        # create the scrolled frame that will hold our list
+        self.lstActionMap = DirectScrolledFrame(
+            # make the frame occupy the whole window
+            frameSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 1.55),
+            # make the canvas as big as the frame
+            canvasSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 0.0),
+            # set the frames color to white
+            frameColor=VBase4(0, 0, 0.25, 0.75),
+            pos=(0, 0, -0.8),
+
+            verticalScroll_scrollSize=0.2,
+            verticalScroll_frameColor=VBase4(0.02, 0.02, 0.02, 1),
+
+            verticalScroll_thumb_relief=1,
+            verticalScroll_thumb_geom=thumbGeom,
+            verticalScroll_thumb_pressEffect=False,
+            verticalScroll_thumb_frameColor=VBase4(0, 0, 0, 0),
+
+            verticalScroll_incButton_relief=1,
+            verticalScroll_incButton_geom=incGeom,
+            verticalScroll_incButton_pressEffect=False,
+            verticalScroll_incButton_frameColor=VBase4(0, 0, 0, 0),
+
+            verticalScroll_decButton_relief=1,
+            verticalScroll_decButton_geom=decGeom,
+            verticalScroll_decButton_pressEffect=False,
+            verticalScroll_decButton_frameColor=VBase4(0, 0, 0, 0),)
+
+        # creat the list items
+        idx = 0
+        self.listBGEven = base.loader.loadModel("models/list_item_even")
+        self.listBGOdd = base.loader.loadModel("models/list_item_odd")
+        for key, value in self.gamepadMapping.items():
+            item = self.__makeListItem(key, key, value, idx)
+            item.reparentTo(self.lstActionMap.getCanvas())
+            idx += 1
+
+        # recalculate the canvas size to set scrollbars if necesary
+        self.lstActionMap["canvasSize"] = (
+            base.a2dLeft+0.05, base.a2dRight-0.05,
+            -(len(self.gamepadMapping.keys())*0.1), 0.09)
+        self.lstActionMap.setCanvasSize()
+
+    def closeDialog(self, result):
+        self.dlgInput.hide()
+        if result == DGG.DIALOG_OK:
+            # map the event to the given action
+            self.gamepadMapping[self.actionToMap] = self.newActionKey
+            # actualize the label in the list that shows the current
+            # event for the action
+            self.actualizeLabel["text"] = self.newActionKey
+
+        # cleanup
+        self.dlgInput["text"] ="Hit desired key:"
+        self.actionToMap = ""
+        self.newActionKey = ""
+        self.actualizeLabel = None
+        for bt in base.buttonThrowers:
+            bt.node().setButtonDownEvent("")
+        for bt in base.deviceButtonThrowers:
+            bt.node().setButtonDownEvent("")
+        taskMgr.remove("checkControls")
+
+    def changeMapping(self, action, label):
+        # set the action that we want to map a new key to
+        self.actionToMap = action
+        # set the label that needs to be actualized
+        self.actualizeLabel = label
+        # show our dialog
+        self.dlgInput.show()
+
+        # catch all button events
+        for bt in base.buttonThrowers:
+            bt.node().setButtonDownEvent("keyListenEvent")
+        for bt in base.deviceButtonThrowers:
+            bt.node().setButtonDownEvent("deviceListenEvent")
+        self.setKeyCalled = False
+        self.accept("keyListenEvent", self.setKey)
+        self.accept("deviceListenEvent", self.setDeviceKey)
+
+        # As there are no events thrown for control changes, we set up
+        # a task to check if the controls got moved
+        # This list will help us for checking which controls got moved
+        self.controlStates = {None:{}}
+        # fill it with all available controls
+        for device in base.devices.get_devices():
+            for ctrl in device.controls:
+                if device not in self.controlStates.keys():
+                    self.controlStates.update({device: {ctrl.axis: ctrl.state}})
+                else:
+                    self.controlStates[device].update({ctrl.axis: ctrl.state})
+        # start the task
+        taskMgr.add(self.watchControls, "checkControls")
+
+    def watchControls(self, task):
+        # move through all devices and all it's controls
+        for device in base.devices.get_devices():
+            for ctrl in device.controls:
+                # if a control got changed more than the given puffer zone
+                if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \
+                   self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state:
+                    # set the current state in the dict
+                    self.controlStates[device][ctrl.axis] = ctrl.state
+                    # check which axis got moved
+                    if ctrl.axis == InputDevice.C_left_x:
+                        self.setKey("Left Stick X")
+                    elif ctrl.axis == InputDevice.C_left_y:
+                        self.setKey("Left Stick Y")
+                    elif ctrl.axis == InputDevice.C_left_trigger:
+                        self.setKey("Left Trigger")
+                    elif ctrl.axis == InputDevice.C_right_x:
+                        self.setKey("Right Stick X")
+                    elif ctrl.axis == InputDevice.C_right_y:
+                        self.setKey("Right Stick Y")
+                    elif ctrl.axis == InputDevice.C_right_trigger:
+                        self.setKey("Right Trigger")
+                    elif ctrl.axis == InputDevice.C_x:
+                        self.setKey("X")
+                    elif ctrl.axis == InputDevice.C_y:
+                        self.setKey("Y")
+                    elif ctrl.axis == InputDevice.C_trigger:
+                        self.setKey("Trigger")
+                    elif ctrl.axis == InputDevice.C_throttle:
+                        self.setKey("Throttle")
+                    elif ctrl.axis == InputDevice.C_rudder:
+                        self.setKey("Rudder")
+                    elif ctrl.axis == InputDevice.C_hat_x:
+                        self.setKey("Hat X")
+                    elif ctrl.axis == InputDevice.C_hat_y:
+                        self.setKey("Hat Y")
+                    elif ctrl.axis == InputDevice.C_wheel:
+                        self.setKey("Wheel")
+                    elif ctrl.axis == InputDevice.C_accelerator:
+                        self.setKey("Acclerator")
+                    elif ctrl.axis == InputDevice.C_brake:
+                        self.setKey("Break")
+        return task.cont
+
+    def setKey(self, args):
+        self.setKeyCalled = True
+        if self.dlgInput.buttonList[0].guiItem.getState() == 1:
+            # this occurs if the OK button was clicked. To prevent to
+            # always set the mouse1 event whenever the OK button was
+            # pressed, we instantly return from this function
+            return
+        self.dlgInput["text"] = "New event will be:\n\n" + args
+        self.newActionKey = args
+
+    def setDeviceKey(self, args):
+        if not self.setKeyCalled:
+            self.setKey(args)
+        self.setKeyCalled = False
+
+    def __makeListItem(self, itemName, action, event, index):
+        def dummy(): pass
+        if index % 2 == 0:
+            bg = self.listBGEven
+        else:
+            bg = self.listBGOdd
+        item = DirectFrame(
+            text=itemName,
+            geom=bg,
+            geom_scale=(base.a2dRight-0.05, 1, 0.1),
+            frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05),
+            frameColor=VBase4(1,0,0,0),
+            text_align=TextNode.ALeft,
+            text_scale=0.05,
+            text_fg=VBase4(1,1,1,1),
+            text_pos=(base.a2dLeft + 0.3, -0.015),
+            text_shadow=VBase4(0, 0, 0, 0.35),
+            text_shadowOffset=Vec2(-0.05, -0.05),
+            pos=(0.05, 0, -(0.10 * index)))
+        item.setTransparency(True)
+        lbl = DirectLabel(
+            text=event,
+            text_fg=VBase4(1, 1, 1, 1),
+            text_scale=0.05,
+            text_pos=Vec2(0, -0.015),
+            frameColor=VBase4(0, 0, 0, 0),
+            )
+        lbl.reparentTo(item)
+        lbl.setTransparency(True)
+        buttonScale = 0.15
+        btn = DirectButton(
+            text="Change",
+            geom=self.buttonGeom,
+            scale=buttonScale,
+            text_scale=0.25,
+            text_align=TextNode.ALeft,
+            text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
+            text_pos=Vec2(-0.9, -0.085),
+            relief=1,
+            pad=Vec2(0.01, 0.01),
+            frameColor=VBase4(0, 0, 0, 0),
+            frameSize=VBase4(-1.0, 1.0, -0.25, 0.25),
+            pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0),
+            pressEffect=False,
+            command=self.changeMapping,
+            extraArgs=[action, lbl])
+        btn.setTransparency(True)
+        btn.reparentTo(item)
+        return item
+
+app = App()
+app.run()

+ 117 - 0
samples/gamepad/models/button_map.egg

@@ -0,0 +1,117 @@
+<Comment> {
+  "egg-texture-cards -o button_map.egg -p 768,192 -g -1.0,1.0,-0.25,0.25 -wm clamp ready.png click.png hover.png disabled.png"
+}
+<Texture> ready {
+  ready.png
+  <Scalar> wrap { clamp }
+}
+<Texture> hover {
+  hover.png
+  <Scalar> wrap { clamp }
+}
+<Texture> disabled {
+  disabled.png
+  <Scalar> wrap { clamp }
+}
+<Texture> click {
+  click.png
+  <Scalar> wrap { clamp }
+}
+<Group> {
+  <Switch> { 1 }
+  <Scalar> fps { 2 }
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -1 0.25 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -1 -0.25 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      1 -0.25 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      1 0.25 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 4 {
+      -1 0.25 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 5 {
+      -1 -0.25 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 6 {
+      1 -0.25 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 7 {
+      1 0.25 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 8 {
+      -1 0.25 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 9 {
+      -1 -0.25 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 10 {
+      1 -0.25 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 11 {
+      1 0.25 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 12 {
+      -1 0.25 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 13 {
+      -1 -0.25 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 14 {
+      1 -0.25 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 15 {
+      1 0.25 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> ready {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { ready }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+  <Group> click {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { click }
+      <VertexRef> { 4 5 6 7 <Ref> { vpool } }
+    }
+  }
+  <Group> hover {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { hover }
+      <VertexRef> { 8 9 10 11 <Ref> { vpool } }
+    }
+  }
+  <Group> disabled {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { disabled }
+      <VertexRef> { 12 13 14 15 <Ref> { vpool } }
+    }
+  }
+}

BIN
samples/gamepad/models/click.png


BIN
samples/gamepad/models/dec_click.png


BIN
samples/gamepad/models/dec_disabled.png


BIN
samples/gamepad/models/dec_hover.png


+ 117 - 0
samples/gamepad/models/dec_map.egg

@@ -0,0 +1,117 @@
+<Comment> {
+  "egg-texture-cards -o dec_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp dec_ready.png dec_click.png dec_hover.png dec_disabled.png"
+}
+<Texture> dec_ready {
+  dec_ready.png
+  <Scalar> wrap { clamp }
+}
+<Texture> dec_hover {
+  dec_hover.png
+  <Scalar> wrap { clamp }
+}
+<Texture> dec_disabled {
+  dec_disabled.png
+  <Scalar> wrap { clamp }
+}
+<Texture> dec_click {
+  dec_click.png
+  <Scalar> wrap { clamp }
+}
+<Group> {
+  <Switch> { 1 }
+  <Scalar> fps { 2 }
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 4 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 5 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 6 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 7 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 8 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 9 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 10 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 11 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 12 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 13 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 14 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 15 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> dec_ready {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { dec_ready }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+  <Group> dec_click {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { dec_click }
+      <VertexRef> { 4 5 6 7 <Ref> { vpool } }
+    }
+  }
+  <Group> dec_hover {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { dec_hover }
+      <VertexRef> { 8 9 10 11 <Ref> { vpool } }
+    }
+  }
+  <Group> dec_disabled {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { dec_disabled }
+      <VertexRef> { 12 13 14 15 <Ref> { vpool } }
+    }
+  }
+}

BIN
samples/gamepad/models/dec_ready.png


BIN
samples/gamepad/models/dialog.png


BIN
samples/gamepad/models/disabled.png


BIN
samples/gamepad/models/hover.png


BIN
samples/gamepad/models/inc_click.png


BIN
samples/gamepad/models/inc_disabled.png


BIN
samples/gamepad/models/inc_hover.png


+ 117 - 0
samples/gamepad/models/inc_map.egg

@@ -0,0 +1,117 @@
+<Comment> {
+  "egg-texture-cards -o inc_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp inc_ready.png inc_click.png inc_hover.png inc_disabled.png"
+}
+<Texture> inc_ready {
+  inc_ready.png
+  <Scalar> wrap { clamp }
+}
+<Texture> inc_hover {
+  inc_hover.png
+  <Scalar> wrap { clamp }
+}
+<Texture> inc_disabled {
+  inc_disabled.png
+  <Scalar> wrap { clamp }
+}
+<Texture> inc_click {
+  inc_click.png
+  <Scalar> wrap { clamp }
+}
+<Group> {
+  <Switch> { 1 }
+  <Scalar> fps { 2 }
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 4 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 5 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 6 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 7 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 8 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 9 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 10 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 11 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 12 {
+      -0.04 0.04 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 13 {
+      -0.04 -0.04 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 14 {
+      0.04 -0.04 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 15 {
+      0.04 0.04 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> inc_ready {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { inc_ready }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+  <Group> inc_click {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { inc_click }
+      <VertexRef> { 4 5 6 7 <Ref> { vpool } }
+    }
+  }
+  <Group> inc_hover {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { inc_hover }
+      <VertexRef> { 8 9 10 11 <Ref> { vpool } }
+    }
+  }
+  <Group> inc_disabled {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { inc_disabled }
+      <VertexRef> { 12 13 14 15 <Ref> { vpool } }
+    }
+  }
+}

BIN
samples/gamepad/models/inc_ready.png


BIN
samples/gamepad/models/li_ready_even.png


BIN
samples/gamepad/models/li_ready_odd.png


+ 33 - 0
samples/gamepad/models/list_item_even.egg

@@ -0,0 +1,33 @@
+<Comment> {
+  "egg-texture-cards -o list_item_even.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_even.png"
+}
+<Texture> li_ready_even {
+  li_ready_even.png
+}
+<Group> {
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -1 0.5 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -1 -0.5 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      1 -0.5 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      1 0.5 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> li_ready_even {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { li_ready_even }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+}

+ 33 - 0
samples/gamepad/models/list_item_odd.egg

@@ -0,0 +1,33 @@
+<Comment> {
+  "egg-texture-cards -o list_item_odd.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_odd.png"
+}
+<Texture> li_ready_odd {
+  li_ready_odd.png
+}
+<Group> {
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -1 0.5 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -1 -0.5 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      1 -0.5 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      1 0.5 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> li_ready_odd {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { li_ready_odd }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+}

BIN
samples/gamepad/models/ready.png


BIN
samples/gamepad/models/thumb_click.png


BIN
samples/gamepad/models/thumb_disabled.png


BIN
samples/gamepad/models/thumb_hover.png


+ 117 - 0
samples/gamepad/models/thumb_map.egg

@@ -0,0 +1,117 @@
+<Comment> {
+  "egg-texture-cards -o thumb_map.egg -p 32,440 -g -0.04,0.04,-0.55,0.55 -wm clamp thumb_ready.png thumb_click.png thumb_hover.png thumb_disabled.png"
+}
+<Texture> thumb_ready {
+  thumb_ready.png
+  <Scalar> wrap { clamp }
+}
+<Texture> thumb_hover {
+  thumb_hover.png
+  <Scalar> wrap { clamp }
+}
+<Texture> thumb_disabled {
+  thumb_disabled.png
+  <Scalar> wrap { clamp }
+}
+<Texture> thumb_click {
+  thumb_click.png
+  <Scalar> wrap { clamp }
+}
+<Group> {
+  <Switch> { 1 }
+  <Scalar> fps { 2 }
+  <VertexPool> vpool {
+    <Vertex> 0 {
+      -0.04 0.55 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 1 {
+      -0.04 -0.55 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 2 {
+      0.04 -0.55 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 3 {
+      0.04 0.55 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 4 {
+      -0.04 0.55 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 5 {
+      -0.04 -0.55 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 6 {
+      0.04 -0.55 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 7 {
+      0.04 0.55 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 8 {
+      -0.04 0.55 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 9 {
+      -0.04 -0.55 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 10 {
+      0.04 -0.55 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 11 {
+      0.04 0.55 0
+      <UV> { 1 1 }
+    }
+    <Vertex> 12 {
+      -0.04 0.55 0
+      <UV> { 0 1 }
+    }
+    <Vertex> 13 {
+      -0.04 -0.55 0
+      <UV> { 0 0 }
+    }
+    <Vertex> 14 {
+      0.04 -0.55 0
+      <UV> { 1 0 }
+    }
+    <Vertex> 15 {
+      0.04 0.55 0
+      <UV> { 1 1 }
+    }
+  }
+  <Group> thumb_ready {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { thumb_ready }
+      <VertexRef> { 0 1 2 3 <Ref> { vpool } }
+    }
+  }
+  <Group> thumb_click {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { thumb_click }
+      <VertexRef> { 4 5 6 7 <Ref> { vpool } }
+    }
+  }
+  <Group> thumb_hover {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { thumb_hover }
+      <VertexRef> { 8 9 10 11 <Ref> { vpool } }
+    }
+  }
+  <Group> thumb_disabled {
+    <Polygon> {
+      <RGBA> { 1 1 1 1 }
+      <TRef> { thumb_disabled }
+      <VertexRef> { 12 13 14 15 <Ref> { vpool } }
+    }
+  }
+}

BIN
samples/gamepad/models/thumb_ready.png


+ 140 - 0
samples/gamepad/steeringWheel.py

@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+'''
+Demonstrate usage of steering wheels
+
+In this sample you can use a wheel type device to control the camera and
+show some messages on screen.  You can acclerate forward using the
+accleration pedal and slow down using the break pedal.
+'''
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3
+from direct.gui.OnscreenText import OnscreenText
+
+loadPrcFileData("", "notify-level-device debug")
+
+class App(ShowBase):
+    def __init__(self):
+        ShowBase.__init__(self)
+        # print all events sent through the messenger
+        self.messenger.toggleVerbose()
+
+        self.lblWarning = OnscreenText(
+            text = "No devices found",
+            fg=(1,0,0,1),
+            scale = .25)
+        self.lblWarning.hide()
+
+        self.lblAction = OnscreenText(
+            text = "Action",
+            fg=(1,1,1,1),
+            scale = .15)
+        self.lblAction.hide()
+
+        self.checkDevices()
+
+        self.currentMoveSpeed = 0.0
+        self.maxAccleration = 28.0
+        self.deaccleration = 10.0
+        self.deaclerationBreak = 37.0
+        self.maxSpeed = 80.0
+
+        # Accept device dis-/connection events
+        # NOTE: catching the events here will overwrite the accept in showbase, hence
+        #       we need to forward the event in the functions we set here!
+        self.accept("connect-device", self.connect)
+        self.accept("disconnect-device", self.disconnect)
+
+        self.accept("escape", exit)
+        self.accept("flight_stick0-start", exit)
+
+        # Accept button events of the first connected steering wheel
+        self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"])
+        self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"])
+
+        self.environment = loader.loadModel("environment")
+        self.environment.reparentTo(render)
+
+        # save the center position of the wheel
+        # NOTE: here we assume, that the wheel is centered when the application get started.
+        #       In real world applications, you should notice the user and give him enough time
+        #       to center the wheel until you store the center position of the controler!
+        self.wheelCenter = 0
+        wheels = base.devices.getDevices(InputDevice.DC_steering_wheel)
+        if len(wheels) > 0:
+            self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state
+
+        # disable pandas default mouse-camera controls so we can handle the camera
+        # movements by ourself
+        self.disableMouse()
+        base.camera.setZ(2)
+
+        self.taskMgr.add(self.moveTask, "movement update task")
+
+    def connect(self, device):
+        # we need to forward the event to the connectDevice function of showbase
+        self.connectDevice(device)
+        # Now we can check for ourself
+        self.checkDevices()
+
+    def disconnect(self, device):
+        # we need to forward the event to the disconnectDevice function of showbase
+        self.disconnectDevice(device)
+        # Now we can check for ourself
+        self.checkDevices()
+
+    def checkDevices(self):
+        # check if we have wheel devices connected
+        if self.devices.get_devices(InputDevice.DC_steering_wheel):
+            # we have at least one steering wheel device
+            self.lblWarning.hide()
+        else:
+            # no devices connected
+            self.lblWarning.show()
+
+    def doAction(self, showText, text):
+        if showText and self.lblAction.isHidden():
+            self.lblAction.show()
+        else:
+            self.lblAction.hide()
+
+    def moveTask(self, task):
+        dt = globalClock.getDt()
+        movementVec = Vec3()
+
+        wheels = base.devices.getDevices(InputDevice.DC_steering_wheel)
+        if len(wheels) == 0:
+            # savety check
+            return task.cont
+
+        if self.currentMoveSpeed > 0:
+            self.currentMoveSpeed -= dt * self.deaccleration
+            if self.currentMoveSpeed < 0:
+                self.currentMoveSpeed = 0
+
+        # we will use the first found wheel
+        # Acclerate
+        acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state
+        accleration = accleratorPedal * self.maxAccleration
+        if self.currentMoveSpeed > accleratorPedal * self.maxSpeed:
+            self.currentMoveSpeed -= dt * self.deaccleration
+        self.currentMoveSpeed += dt * accleration
+
+        # Break
+        breakPedal = wheels[0].findControl(InputDevice.C_brake).state
+        deacleration = breakPedal * self.deaclerationBreak
+        self.currentMoveSpeed -= dt * deacleration
+        if self.currentMoveSpeed < 0:
+            self.currentMoveSpeed = 0
+
+        # Steering
+        rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state
+        base.camera.setH(base.camera, 100 * dt * rotation)
+
+        # calculate movement
+        base.camera.setY(base.camera, dt * self.currentMoveSpeed)
+
+        return task.cont
+
+app = App()
+app.run()