Browse Source

Implement beginnings of multitouch support

rdb 7 years ago
parent
commit
a47130ffeb

+ 14 - 1
direct/src/gui/DirectButton.py

@@ -34,7 +34,7 @@ class DirectButton(DirectFrame):
             ('command',        None,       None),
             ('extraArgs',      [],         None),
             # Which mouse buttons can be used to click the button
-            ('commandButtons', (DGG.LMB,),     self.setCommandButtons),
+            ('commandButtons', (DGG.LMB, DGG.TOUCH), self.setCommandButtons),
             # Sounds to be used for button events
             ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound),
             ('clickSound',    DGG.getDefaultClickSound(),    self.setClickSound),
@@ -82,6 +82,7 @@ class DirectButton(DirectFrame):
         else:
             self.unbind(DGG.B1CLICK)
             self.guiItem.removeClickButton(MouseButton.one())
+
         # Middle mouse button
         if DGG.MMB in self['commandButtons']:
             self.guiItem.addClickButton(MouseButton.two())
@@ -89,6 +90,7 @@ class DirectButton(DirectFrame):
         else:
             self.unbind(DGG.B2CLICK)
             self.guiItem.removeClickButton(MouseButton.two())
+
         # Right mouse button
         if DGG.RMB in self['commandButtons']:
             self.guiItem.addClickButton(MouseButton.three())
@@ -97,6 +99,14 @@ class DirectButton(DirectFrame):
             self.unbind(DGG.B3CLICK)
             self.guiItem.removeClickButton(MouseButton.three())
 
+        # Tapping with the finger
+        if DGG.TOUCH in self['commandButtons']:
+            self.guiItem.addClickButton(MouseButton.tap())
+            self.bind(DGG.TOUCHCLICK, self.commandFunc)
+        else:
+            self.unbind(DGG.TOUCHCLICK)
+            self.guiItem.removeClickButton(MouseButton.tap())
+
     def commandFunc(self, event):
         if self['command']:
             # Pass any extra args to command
@@ -108,6 +118,7 @@ class DirectButton(DirectFrame):
         self.guiItem.clearSound(DGG.B1PRESS + self.guiId)
         self.guiItem.clearSound(DGG.B2PRESS + self.guiId)
         self.guiItem.clearSound(DGG.B3PRESS + self.guiId)
+        self.guiItem.clearSound(DGG.TOUCHPRESS + self.guiId)
         if clickSound:
             if DGG.LMB in self['commandButtons']:
                 self.guiItem.setSound(DGG.B1PRESS + self.guiId, clickSound)
@@ -115,6 +126,8 @@ class DirectButton(DirectFrame):
                 self.guiItem.setSound(DGG.B2PRESS + self.guiId, clickSound)
             if DGG.RMB in self['commandButtons']:
                 self.guiItem.setSound(DGG.B3PRESS + self.guiId, clickSound)
+            if DGG.TOUCH in self['commandButtons']:
+                self.guiItem.setSound(DGG.TOUCHPRESS + self.guiId, clickSound)
 
     def setRolloverSound(self):
         rolloverSound = self['rolloverSound']

+ 1 - 1
direct/src/gui/DirectCheckBox.py

@@ -23,7 +23,7 @@ class DirectCheckBox(DirectButton):
             ('command',        None,       None),
             ('extraArgs',      [],         None),
             # Which mouse buttons can be used to click the button
-            ('commandButtons', (DGG.LMB,),     self.setCommandButtons),
+            ('commandButtons', (DGG.LMB, DGG.TOUCH), self.setCommandButtons),
             # Sounds to be used for button events
             ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound),
             ('clickSound',    DGG.getDefaultClickSound(),    self.setClickSound),

+ 4 - 0
direct/src/gui/DirectGuiGlobals.py

@@ -25,6 +25,7 @@ INITOPT = ['initopt']
 LMB = 0
 MMB = 1
 RMB = 2
+TOUCH = -1
 
 # Widget state
 NORMAL = 'normal'
@@ -63,12 +64,15 @@ WITHOUT = PGButton.getWithoutPrefix()
 B1CLICK = PGButton.getClickPrefix() + MouseButton.one().getName() + '-'
 B2CLICK = PGButton.getClickPrefix() + MouseButton.two().getName() + '-'
 B3CLICK = PGButton.getClickPrefix() + MouseButton.three().getName() + '-'
+TOUCHCLICK = PGButton.getClickPrefix() + MouseButton.touch().getName() + '-'
 B1PRESS = PGButton.getPressPrefix() + MouseButton.one().getName() + '-'
 B2PRESS = PGButton.getPressPrefix() + MouseButton.two().getName() + '-'
 B3PRESS = PGButton.getPressPrefix() + MouseButton.three().getName() + '-'
+TOUCHPRESS = PGButton.getPressPrefix() + MouseButton.touch().getName() + '-'
 B1RELEASE = PGButton.getReleasePrefix() + MouseButton.one().getName() + '-'
 B2RELEASE = PGButton.getReleasePrefix() + MouseButton.two().getName() + '-'
 B3RELEASE = PGButton.getReleasePrefix() + MouseButton.three().getName() + '-'
+TOUCHRELEASE = PGButton.getPressPrefix() + MouseButton.touch().getName() + '-'
 # For DirectEntry widgets
 OVERFLOW = PGEntry.getOverflowPrefix()
 ACCEPT = PGEntry.getAcceptPrefix() + KeyboardButton.enter().getName() + '-'

+ 1 - 0
panda/src/device/inputDevice.h

@@ -259,6 +259,7 @@ PUBLISHED:
   // Enable rumble force-feedback effects
   INLINE void set_vibration(double strong, double weak);
 
+public:
   INLINE void enable_pointer_events();
   INLINE void disable_pointer_events();
 

+ 1 - 0
panda/src/pgui/pgButton.cxx

@@ -30,6 +30,7 @@ PGButton(const std::string &name) : PGItem(name)
 {
   _button_down = false;
   _click_buttons.insert(MouseButton::one());
+  _click_buttons.insert(MouseButton::touch());
 
   set_active(true);
 }

+ 10 - 0
panda/src/putil/mouseButton.cxx

@@ -22,6 +22,7 @@ ButtonHandle MouseButton::_wheel_up;
 ButtonHandle MouseButton::_wheel_down;
 ButtonHandle MouseButton::_wheel_left;
 ButtonHandle MouseButton::_wheel_right;
+ButtonHandle MouseButton::_touch;
 
 /**
  * Returns the ButtonHandle associated with the particular numbered mouse
@@ -112,6 +113,14 @@ wheel_right() {
   return _wheel_right;
 }
 
+/**
+ * Returns the ButtonHandle generated when a finger touches the screen.
+ */
+ButtonHandle MouseButton::
+touch() {
+  return _touch;
+}
+
 /**
  * Returns true if the indicated ButtonHandle is a mouse button, false if it
  * is some other kind of button.
@@ -146,4 +155,5 @@ init_mouse_buttons() {
   ButtonRegistry::ptr()->register_button(_wheel_down, "wheel_down");
   ButtonRegistry::ptr()->register_button(_wheel_left, "wheel_left");
   ButtonRegistry::ptr()->register_button(_wheel_right, "wheel_right");
+  ButtonRegistry::ptr()->register_button(_touch, "touch");
 }

+ 2 - 0
panda/src/putil/mouseButton.h

@@ -34,6 +34,7 @@ PUBLISHED:
   static ButtonHandle wheel_down();
   static ButtonHandle wheel_left();
   static ButtonHandle wheel_right();
+  static ButtonHandle touch();
 
   static bool is_mouse_button(ButtonHandle button);
 
@@ -46,6 +47,7 @@ public:
   static ButtonHandle _wheel_down;
   static ButtonHandle _wheel_left;
   static ButtonHandle _wheel_right;
+  static ButtonHandle _touch;
 };
 
 #endif

+ 8 - 0
panda/src/tform/mouseWatcher.I

@@ -147,6 +147,14 @@ is_button_down(ButtonHandle button) const {
   return _inactivity_state != IS_inactive && _current_buttons_down.get_bit(button.get_index());
 }
 
+/**
+ * Returns true if the pointer with the given identifier is currently pressed.
+ */
+INLINE bool MouseWatcher::
+is_pointer_down(int id) const {
+  return _active_pointers.find(id) != _active_pointers.end();
+}
+
 /**
  * Sets the pattern string that indicates how the event names are generated
  * when a button is depressed.  This is a string that may contain any of the

+ 127 - 9
panda/src/tform/mouseWatcher.cxx

@@ -120,6 +120,12 @@ remove_region(MouseWatcherRegion *region) {
     _preferred_button_down_region = nullptr;
   }
 
+  for (auto it = _active_pointers.begin(); it != _active_pointers.end(); ++it) {
+    if (it->second._region == region) {
+      it->second._region = nullptr;
+    }
+  }
+
   return MouseWatcherBase::do_remove_region(region);
 }
 
@@ -902,6 +908,90 @@ throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
   }
 }
 
+/**
+ * Records the indicated pointer as having made contact.
+ */
+void MouseWatcher::
+pointer_down(PointerType type, int id, const LPoint2 &pos, double pressure) {
+  nassertv(_lock.debug_is_locked());
+
+  MouseWatcherParameter param;
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(pos);
+  param.set_outside(false);
+  param._pressure = pressure;
+  param._pointer_id = id;
+
+  Regions regions;
+  get_over_regions(regions, pos);
+  MouseWatcherRegion *region = get_preferred_region(regions);
+
+  double now = ClockObject::get_global_clock()->get_frame_time();
+  _active_pointers[id] = {region, type, pressure, now};
+
+  if (region != nullptr && type != PointerType::mouse) {
+    param.set_button(MouseButton::touch());
+    region->press(param);
+  }
+}
+
+/**
+ * Records the indicated pointer as having moved.
+ */
+void MouseWatcher::
+pointer_move(int id, const LPoint2 &pos, double pressure) {
+  nassertv(_lock.debug_is_locked());
+
+  ActivePointer &pointer = _active_pointers[id];
+
+  MouseWatcherParameter param;
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(pos);
+  param._pressure = pressure;
+  param._pointer_id = id;
+
+  pointer._max_pressure = std::max(pointer._max_pressure, pressure);
+
+  MouseWatcherRegion *region = pointer._region;
+  if (region != nullptr) {
+    region->move(param);
+  }
+}
+
+/**
+ * Records the indicated pointer as no longer making contact.
+ */
+void MouseWatcher::
+pointer_up(int id, const LPoint2 &pos) {
+  nassertv(_lock.debug_is_locked());
+
+  MouseWatcherRegion *region = _active_pointers[id]._region;
+  if (region != nullptr && _active_pointers[id]._type != PointerType::mouse) {
+    // Generate a release event.
+    MouseWatcherParameter param;
+    param.set_modifier_buttons(_mods);
+    param.set_mouse(pos);
+    param.set_button(MouseButton::touch());
+
+    // Are we still within the same region?
+    PN_stdfloat mx = (pos[0] + 1.0f) * 0.5f * (_frame[1] - _frame[0]) + _frame[0];
+    PN_stdfloat my = (pos[1] + 1.0f) * 0.5f * (_frame[3] - _frame[2]) + _frame[2];
+
+    const LVecBase4 &frame = region->get_frame();
+    if (region->get_active() &&
+        mx >= frame[0] && mx <= frame[1] &&
+        my >= frame[2] && my <= frame[3]) {
+
+      param.set_outside(false);
+    } else {
+      param.set_outside(true);
+    }
+
+    param._pointer_id = id;
+    region->release(param);
+  }
+}
+
 /**
  * Records the indicated mouse or keyboard button as being moved from last
  * position.
@@ -913,6 +1003,7 @@ move() {
   MouseWatcherParameter param;
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
+  param._pressure = 1.0;
 
   if (_preferred_button_down_region != nullptr) {
     _preferred_button_down_region->move(param);
@@ -931,6 +1022,7 @@ press(ButtonHandle button, bool keyrepeat) {
   param.set_keyrepeat(keyrepeat);
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
+  param._pressure = 1.0;
 
   if (MouseButton::is_mouse_button(button)) {
     // Mouse buttons are inextricably linked to the mouse position.
@@ -982,6 +1074,7 @@ release(ButtonHandle button) {
   param.set_button(button);
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
+  param._pressure = 0.0;
 
   if (MouseButton::is_mouse_button(button)) {
     // Button up.  Send the up event associated with the region(s) we were
@@ -1023,6 +1116,7 @@ keystroke(int keycode) {
   param.set_keycode(keycode);
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
+  param._pressure = 1.0;
 
   // Make sure there are no duplicates in the regions vector.
   if (!_sorted) {
@@ -1322,17 +1416,41 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
 
   // Code for recording the mouse trail.
   _num_trail_recent = 0;
-  if (input.has_data(_pointer_events_input) && (_trail_log_duration > 0.0)) {
+  if (input.has_data(_pointer_events_input)) {
     const PointerEventList *this_pointer_events;
     DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr());
-    _num_trail_recent = this_pointer_events->get_num_events();
-    for (size_t i = 0; i < _num_trail_recent; i++) {
-      bool in_win = this_pointer_events->get_in_window(i);
-      int xpos = this_pointer_events->get_xpos(i);
-      int ypos = this_pointer_events->get_ypos(i);
-      int sequence = this_pointer_events->get_sequence(i);
-      double time = this_pointer_events->get_time(i);
-      _trail_log->add_event(in_win, xpos, ypos, sequence, time);
+
+    for (size_t i = 0; i < this_pointer_events->get_num_events(); ++i) {
+      const PointerEvent &event = this_pointer_events->get_event(i);
+      auto it = _active_pointers.find(event._id);
+
+      // Determine the position in the -1..1 range.
+      LVecBase2 size = _pixel_size->get_value();
+      LPoint2 pos((PN_stdfloat)(2 * event._xpos) / size[0] - 1.0f,
+                  1.0f - (PN_stdfloat)(2 * event._ypos) / size[1]);
+
+      if (event._pressure > 0.0) {
+        if (it == _active_pointers.end()) {
+          pointer_down(event._type, event._id, pos, event._pressure);
+        } else {
+          pointer_move(event._id, pos, event._pressure);
+        }
+      } else if (it != _active_pointers.end()) {
+        pointer_up(event._id, pos);
+        _active_pointers.erase(it);
+      }
+    }
+
+    if (_trail_log_duration > 0.0) {
+      _num_trail_recent = this_pointer_events->get_num_events();
+      for (size_t i = 0; i < _num_trail_recent; i++) {
+        bool in_win = this_pointer_events->get_in_window(i);
+        int xpos = this_pointer_events->get_xpos(i);
+        int ypos = this_pointer_events->get_ypos(i);
+        int sequence = this_pointer_events->get_sequence(i);
+        double time = this_pointer_events->get_time(i);
+        _trail_log->add_event(in_win, xpos, ypos, sequence, time);
+      }
     }
   }
   if (_trail_log->get_num_events() > 0) {

+ 16 - 0
panda/src/tform/mouseWatcher.h

@@ -84,6 +84,7 @@ PUBLISHED:
   MouseWatcherRegion *get_over_region(const LPoint2 &pos) const;
 
   INLINE bool is_button_down(ButtonHandle button) const;
+  INLINE bool is_pointer_down(int id) const;
 
   INLINE void set_button_down_pattern(const std::string &pattern);
   INLINE const std::string &get_button_down_pattern() const;
@@ -177,6 +178,10 @@ protected:
                            const MouseWatcherRegion *region,
                            const ButtonHandle &button);
 
+  void pointer_down(PointerType type, int id, const LPoint2 &pos, double pressure);
+  void pointer_move(int id, const LPoint2 &pos, double pressure);
+  void pointer_up(int id, const LPoint2 &pos);
+
   void move();
   void press(ButtonHandle button, bool keyrepeat);
   void release(ButtonHandle button);
@@ -217,6 +222,17 @@ private:
   LPoint2 _mouse_pixel;
   BitArray _current_buttons_down;
 
+  // Keeps track of which pointers are down and which regions they went down
+  // in.
+  struct ActivePointer {
+    PT(MouseWatcherRegion) _region;
+    PointerType _type;
+    double _max_pressure;
+    double _time;
+  };
+  pmap<int, ActivePointer> _active_pointers;
+  int _primary_pointer = -1;
+
   LVecBase4 _frame;
 
   PT(PointerEventList) _trail_log;

+ 18 - 1
panda/src/tform/mouseWatcherParameter.I

@@ -131,6 +131,23 @@ set_outside(bool flag) {
   }
 }
 
+/**
+ * Returns the identifier of the pointer that caused this event.
+ */
+INLINE int MouseWatcherParameter::
+get_pointer_id() const {
+  return _pointer_id;
+}
+
+/**
+ * Returns the pressure with which this button or pointer was pressed down,
+ * where 0.0 is the minimum and 1.0 is the maximum amount of pressure.
+ */
+INLINE int MouseWatcherParameter::
+get_pressure() const {
+  return _pressure;
+}
+
 /**
  * Returns true if this parameter has an associated mouse or keyboard button,
  * false otherwise.
@@ -170,7 +187,7 @@ has_keycode() const {
  * Returns the keycode associated with this event.  If has_keycode(), above,
  * returns false, this returns 0.
  */
-INLINE int MouseWatcherParameter::
+INLINE char32_t MouseWatcherParameter::
 get_keycode() const {
   return _keycode;
 }

+ 15 - 3
panda/src/tform/mouseWatcherParameter.h

@@ -43,13 +43,16 @@ public:
   INLINE void set_mouse(const LPoint2 &mouse);
   INLINE void set_outside(bool flag);
 
+  INLINE int get_pointer_id() const;
+  INLINE int get_pressure() const;
+
 PUBLISHED:
   INLINE bool has_button() const;
   INLINE ButtonHandle get_button() const;
   INLINE bool is_keyrepeat() const;
 
   INLINE bool has_keycode() const;
-  INLINE int get_keycode() const;
+  INLINE char32_t get_keycode() const;
 
   INLINE bool has_candidate() const;
 
@@ -72,15 +75,24 @@ PUBLISHED:
 
   void output(std::ostream &out) const;
 
+PUBLISHED:
+  MAKE_PROPERTY2(button, has_button, get_button);
+  MAKE_PROPERTY(modifier_buttons, get_modifier_buttons);
+  MAKE_PROPERTY2(keycode, has_keycode, get_keycode);
+  MAKE_PROPERTY2(pointer_id, has_mouse, get_pointer_id);
+  MAKE_PROPERTY(pressure, get_pressure);
+
 public:
-  ButtonHandle _button;
-  int _keycode;
+  ButtonHandle _button = ButtonHandle::none();
+  char32_t _keycode;
   std::wstring _candidate_string;
   size_t _highlight_start;
   size_t _highlight_end;
   size_t _cursor_pos;
   ModifierButtons _mods;
   LPoint2 _mouse;
+  int _pointer_id = 0;
+  double _pressure = 0.0;
 
   enum Flags {
     F_has_button    = 0x001,

+ 26 - 0
panda/src/windisplay/winGraphicsWindow.cxx

@@ -1361,6 +1361,12 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
   }
   WindowProperties properties;
 
+  // This is the Microsoft-sanctioned method for checking whether a mouse
+  // event is a compatibility event generated by a touch. :-/
+  if (msg != WM_TOUCH && (GetMessageExtraInfo() & 0xFFFFFF00) == 0xFF515700) {
+    return DefWindowProcW(hwnd, msg, wparam, lparam);
+  }
+
   switch (msg) {
   case WM_MOUSEMOVE:
     if (!_tracking_mouse_leaving) {
@@ -2145,6 +2151,26 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
     if (pGetTouchInputInfo != 0) {
       pGetTouchInputInfo((HTOUCHINPUT)lparam, _num_touches, _touches, sizeof(TOUCHINPUT));
       pCloseTouchInputHandle((HTOUCHINPUT)lparam);
+
+      POINT offset = {0, 0};
+      ScreenToClient(_hWnd, &offset);
+
+      for (UINT i = 0; i < _num_touches; ++i) {
+        PointerData data;
+        data._id = (int)_touches[i].dwID;
+        data._type = PointerType::finger;
+        data._xpos = _touches[i].x * 0.01 + offset.x;
+        data._ypos = _touches[i].y * 0.01 + offset.y;
+
+        if (_touches[i].dwFlags & 0x4) {
+          data._pressure = 0.0;
+          _input->update_pointer(data);
+          _input->remove_pointer(data._id);
+        } else {
+          data._pressure = 1.0;
+          _input->update_pointer(data);
+        }
+      }
     }
     break;
   }