Browse Source

multitouch: Refactor PointerData and its usage to better support multitouch.

The goal is to have InputDevice be authoratative over the *current* state of each pointer instead of different copies of PointerData flying around. Instead, each PointerData will be modified directly, and will only be copied when being packed into a PointerEvent (which will be changed to hold only PointerData and a timestamp).
Donny Lawrence 6 years ago
parent
commit
1d78b605fb

+ 14 - 0
panda/src/device/inputDevice.I

@@ -22,6 +22,20 @@ InputDevice() {
   _pointer_events = new PointerEventList;
 }
 
+INLINE bool InputDevice::
+has_primary_pointer() const {
+  LightMutexHolder holder(_lock);
+  return _has_primary_pointer;
+}
+
+INLINE PointerData& InputDevice::
+get_primary_pointer() {
+  LightMutexHolder holder(_lock);
+  assert(_pointers.size() > 0);
+  assert(_has_primary_pointer);
+  return _pointers.at(_primary_pointer_id);
+}
+
 /**
  * Returns a human-readable name for the device.  Not necessarily unique.
  */

+ 35 - 70
panda/src/device/inputDevice.cxx

@@ -149,26 +149,31 @@ add_axis(Axis axis, int minimum, int maximum) {
   return add_axis(axis, minimum, maximum, centered);
 }
 
+PointerData& InputDevice::
+get_pointer(int id) {
+  if (_pointers.count(id) != 0) {
+    return _pointers.at(id);
+  } else {
+    return add_pointer(PointerType::unknown, id);
+  }
+}
+
+
 /**
  * Records that a new pointer was found.
  */
-int InputDevice::
+PointerData& InputDevice::
 add_pointer(PointerType type, int id, bool primary) {
   //nassertr(_lock.debug_is_locked(), -1);
 
-  PointerData data;
-  data._id = id;
-  data._type = type;
-
-  int index = (int)_pointers.size();
-  if (_num_pointers == _pointers.size()) {
-    _pointers.push_back(data);
-  } else {
-    _pointers[index] = data;
+  PointerData data(id, primary, type);
+  // _pointers[id] = data;
+  _pointers.insert(std::pair<int, PointerData>(id, data));
+  if (primary) {
+    _primary_pointer_id = id;
   }
-  ++_num_pointers;
 
-  return index;
+  return _pointers.at(id);
 }
 
 /**
@@ -177,58 +182,29 @@ add_pointer(PointerType type, int id, bool primary) {
  */
 void InputDevice::
 remove_pointer(int id) {
-  nassertv(_lock.debug_is_locked());
-
-  size_t i;
-  for (i = 0; i < _pointers.size(); ++i) {
-    if (_pointers[i]._id == id) {
-      break;
-    }
-  }
-
-  if (i < _pointers.size()) {
-    if (_pointers[i]._pressure != 0.0) {
-      _pointers[i]._pressure = 0.0;
-
-      if (_enable_pointer_events) {
-        int seq = _event_sequence++;
-        double time = ClockObject::get_global_clock()->get_frame_time();
-        _pointer_events->add_event(_pointers[i], seq, time);
-      }
-    }
-
-    // Replace it with the last one.
-    if (i != _pointers.size() - 1) {
-      _pointers[i] = _pointers.back();
+  assert(_lock.debug_is_locked());
+
+  PointerData &pointer = _pointers.at(id);
+  if (pointer.get_pressure() != 0.0) {
+    pointer.set_pressure(0.0);
+    if (_enable_pointer_events) {
+      int seq = _event_sequence++;
+      double time = ClockObject::get_global_clock()->get_frame_time();
+      _pointer_events->add_event(pointer, seq, time);
     }
-    --_num_pointers;
   }
 }
 
-/**
- * Records that pointer data for a pointer has changed.  This can also be used
- * to add a new pointer.
- */
 void InputDevice::
-update_pointer(PointerData data, double time) {
-  nassertv(_lock.debug_is_locked());
+pointer_moved_absolute(int id, double x, double y, double pressure, double time) {
+  assert(_lock.debug_is_locked());
 
-  PointerData *ptr = nullptr;
-  for (size_t i = 0; i < _pointers.size(); ++i) {
-    if (_pointers[i]._id == data._id) {
-      ptr = &_pointers[i];
-      *ptr = data;
-      break;
-    }
-  }
-  if (ptr == nullptr) {
-    _pointers.push_back(data);
-    ptr = &_pointers.back();
-  }
+  PointerData &pointer = _pointers.at(id);
+  pointer.update(x, y, pressure);
 
   if (_enable_pointer_events) {
     int seq = _event_sequence++;
-    _pointer_events->add_event(*ptr, seq, time);
+    _pointer_events->add_event(pointer, seq, time);
   }
 }
 
@@ -239,28 +215,17 @@ void InputDevice::
 pointer_moved(int id, double x, double y, double time) {
   nassertv(_lock.debug_is_locked());
 
-  PointerData *ptr = nullptr;
-  for (size_t i = 0; i < _pointers.size(); ++i) {
-    if (_pointers[i]._id == id) {
-      ptr = &_pointers[i];
-      _pointers[i]._xpos = x;
-      _pointers[i]._ypos = y;
-      break;
-    }
-  }
-  nassertv_always(ptr != nullptr);
-
   if (device_cat.is_spam() && (x != 0 || y != 0)) {
     device_cat.spam()
       << "Pointer " << id << " moved by " << x << " x " << y << "\n";
   }
 
+  PointerData &pointer = _pointers.at(id);
+  pointer.rel_move(x, y);
+
   if (_enable_pointer_events) {
     int seq = _event_sequence++;
-    _pointer_events->add_event(ptr->_in_window,
-                               ptr->_xpos,
-                               ptr->_ypos,
-                               x, y, seq, time);
+    _pointer_events->add_event(pointer, seq, time);
   }
 }
 

+ 13 - 6
panda/src/device/inputDevice.h

@@ -286,10 +286,12 @@ protected:
   int add_axis(Axis axis, int minimum, int maximum, bool centered);
   int add_axis(Axis axis, int minimum, int maximum);
 
-  int add_pointer(PointerType type, int id, bool primary = false);
+  PointerData& add_pointer(PointerType type, int id, bool primary = false);
   void remove_pointer(int id);
-  void update_pointer(PointerData data, double time);
+
   void pointer_moved(int id, double x, double y, double time);
+  void pointer_moved_absolute(int id, double x, double y, double pressure, double time);
+
   void button_changed(int index, bool down);
   void axis_changed(int index, int value);
   void set_axis_value(int index, double state);
@@ -323,21 +325,26 @@ protected:
   bool _enable_pointer_events = false;
   PT(ButtonEventList) _button_events;
   PT(PointerEventList) _pointer_events;
-
-  size_t _num_pointers = 0;
-  typedef pvector<PointerData> Pointers;
+  typedef pmap<int, PointerData> Pointers;
   Pointers _pointers;
 
+  int _primary_pointer_id;
+  bool _has_primary_pointer;
+
 PUBLISHED:
   typedef pvector<ButtonState> Buttons;
   typedef pvector<AxisState> Axes;
   Buttons _buttons;
   Axes _axes;
 
-  PointerData _pointer_data;
   BatteryData _battery_data;
   TrackerData _tracker_data;
 
+  PointerData& get_pointer(int id);
+
+  INLINE bool has_primary_pointer() const;
+  INLINE PointerData& get_primary_pointer();
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 26 - 9
panda/src/display/graphicsWindowInputDevice.I

@@ -13,26 +13,31 @@
 
 /**
  * Returns the PointerData associated with the input device's pointer.  This
- * only makes sense if has_pointer() also returns true.
+ * only makes sense if has_pointer() also returns true. If you're using
+ * multitouch, you should use get_primary_pointer() instead.
+ *
+ * @deprecated 1.11
  */
 INLINE PointerData GraphicsWindowInputDevice::
 get_pointer() const {
   LightMutexHolder holder(_lock);
   if (!_pointers.empty()) {
-    return _pointers[0];
+    return _pointers.begin()->second;
   } else {
     return PointerData();
   }
 }
 
-/**
- * To be called by a particular kind of GraphicsWindow to indicate that the
- * pointer data has changed.
- */
-INLINE void GraphicsWindowInputDevice::
-update_pointer(PointerData data, double time) {
+INLINE PointerData& GraphicsWindowInputDevice::
+get_pointer(int id) {
+  LightMutexHolder holder(_lock);
+  return InputDevice::get_pointer(id);
+}
+
+INLINE PointerData& GraphicsWindowInputDevice::
+add_pointer(PointerType type, int id, bool primary) {
   LightMutexHolder holder(_lock);
-  InputDevice::update_pointer(std::move(data), time);
+  return InputDevice::add_pointer(type, id, primary);
 }
 
 /**
@@ -45,6 +50,18 @@ pointer_moved(double x, double y, double time) {
   InputDevice::pointer_moved(0, x, y, time);
 }
 
+INLINE void GraphicsWindowInputDevice::
+pointer_moved(int id, double x, double y, double time) {
+  LightMutexHolder holder(_lock);
+  InputDevice::pointer_moved(id, x, y, time);
+}
+
+INLINE void GraphicsWindowInputDevice::
+pointer_moved_absolute(int id, double x, double y, double pressure, double time) {
+  LightMutexHolder holder(_lock);
+  InputDevice::pointer_moved_absolute(id, x, y, pressure, time);
+}
+
 /**
  * To be called by a particular kind of GraphicsWindow to indicate that the
  * pointer no longer exists.

+ 27 - 13
panda/src/display/graphicsWindowInputDevice.cxx

@@ -34,7 +34,7 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer
 {
   if (pointer) {
     enable_feature(Feature::pointer);
-    add_pointer(PointerType::mouse, 0);
+    add_pointer(PointerType::mouse, 0, true);
   }
   if (keyboard) {
     enable_feature(Feature::keyboard);
@@ -165,13 +165,22 @@ raw_button_up(ButtonHandle button, double time) {
 void GraphicsWindowInputDevice::
 set_pointer_in_window(double x, double y, double time) {
   LightMutexHolder holder(_lock);
-  PointerData data = _pointers[0];
-  data._id = 0;
-  data._type = PointerType::mouse;
-  data._xpos = x;
-  data._ypos = y;
-  data._in_window = true;
-  InputDevice::update_pointer(data, time);
+
+  PointerData &pointer = _pointers.size() == 0
+                         ? InputDevice::add_pointer(PointerType::mouse, 0, true)
+                         : InputDevice::get_pointer(0);
+
+  pointer.update(x, y, 1.0);
+  pointer.set_in_window(true);
+
+
+  // PointerData data = _pointers[0];
+  // data._id = 0;
+  // data._type = PointerType::mouse;
+  // data._xpos = x;
+  // data._ypos = y;
+  // data._in_window = true;
+  // InputDevice::update_pointer(data, time);
 }
 
 /**
@@ -185,17 +194,22 @@ set_pointer_out_of_window(double time) {
     return;
   }
 
-  _pointers[0]._in_window = false;
-  _pointers[0]._pressure = 0.0;
+  PointerData &pointer = InputDevice::get_pointer(0);
+
+  pointer.set_pressure(0.0);
+  pointer.set_in_window(false);
+
+  // _pointers[0]._in_window = false;
+  // _pointers[0]._pressure = 0.0;
 
   if (_enable_pointer_events) {
     int seq = _event_sequence++;
     if (_pointer_events.is_null()) {
       _pointer_events = new PointerEventList();
     }
-    _pointer_events->add_event(_pointers[0]._in_window,
-                               _pointers[0]._xpos,
-                               _pointers[0]._ypos,
+    _pointer_events->add_event(pointer.get_in_window(),
+                               pointer.get_x(),
+                               pointer.get_y(),
                                seq, time);
   }
 }

+ 6 - 2
panda/src/display/graphicsWindowInputDevice.h

@@ -53,11 +53,15 @@ PUBLISHED:
   void raw_button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
   void raw_button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
 
-  INLINE PointerData get_pointer() const;
   void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time());
   void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time());
-  INLINE void update_pointer(PointerData data, double time = ClockObject::get_global_clock()->get_frame_time());
+
+  INLINE PointerData get_pointer() const;
+  INLINE PointerData& get_pointer(int id);
+  INLINE PointerData& add_pointer(PointerType type, int id, bool primary = false);
   INLINE void pointer_moved(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time());
+  INLINE void pointer_moved(int id, double x, double y, double time = ClockObject::get_global_clock()->get_frame_time());
+  INLINE void pointer_moved_absolute(int id, double x, double y, double pressure, double time = ClockObject::get_global_clock()->get_frame_time());
   INLINE void remove_pointer(int id);
 
 private:

+ 4 - 4
panda/src/display/mouseAndKeyboard.cxx

@@ -89,14 +89,14 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
     if (device->has_pointer()) {
       PointerData mdata = device->get_pointer();
 
-      if (mdata._in_window) {
+      if (mdata.get_in_window()) {
         // Get mouse motion in pixels.
-        _pixel_xy->set_value(LPoint2(mdata._xpos, mdata._ypos));
+        _pixel_xy->set_value(LPoint2(mdata.get_x(), mdata.get_y()));
         output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
 
         // Normalize pixel motion to range [-1,1].
-        PN_stdfloat xf = (PN_stdfloat)(2 * mdata._xpos) / (PN_stdfloat)w - 1.0f;
-        PN_stdfloat yf = 1.0f - (PN_stdfloat)(2 * mdata._ypos) / (PN_stdfloat)h;
+        PN_stdfloat xf = (PN_stdfloat)(2 * mdata.get_x()) / (PN_stdfloat)w - 1.0f;
+        PN_stdfloat yf = 1.0f - (PN_stdfloat)(2 * mdata.get_y()) / (PN_stdfloat)h;
 
         _xy->set_value(LPoint2(xf, yf));
         output.set_data(_xy_output, EventParameter(_xy));

+ 8 - 4
panda/src/eagldisplay/eaglGraphicsWindow.h

@@ -51,9 +51,10 @@ public:
   void app_activated();
   void app_deactivated();
 
-  void emulated_mouse_move(UITouch *touch);
-  void emulated_mouse_down(UITouch *touch);
-  void emulated_mouse_up(UITouch *touch);
+  virtual void touches_began(NSSet<UITouch *> *touch_set);
+  virtual void touches_moved(NSSet<UITouch *> *touch_set);
+  virtual void touches_ended(NSSet<UITouch *> *touch_set);
+  virtual void touches_cancelled(NSSet<UITouch *> *touch_set);
 
 private:
   PT(EAGLGraphicsBuffer) _backing_buffer;
@@ -69,7 +70,10 @@ protected:
 
   PandaEAGLView *_view;
 
-  PT(GraphicsWindowInputDevice) _emulated_mouse_input;
+  NSMutableSet<NSNumber *> *_touchIDPool;
+  UITouch *_primary_touch;
+
+  PT(GraphicsWindowInputDevice) _input;
 
 public:
   static TypeHandle get_class_type() {

+ 90 - 17
panda/src/eagldisplay/eaglGraphicsWindow.mm

@@ -12,6 +12,9 @@
  */
 
 #include "eaglGraphicsWindow.h"
+
+#import <objc/runtime.h>
+
 #include "eaglGraphicsStateGuardian.h"
 #include "mouseButton.h"
 // #import "iOSNSNotificationHandler.h"
@@ -22,6 +25,12 @@ PandaViewController *EAGLGraphicsWindow::next_view_controller = nil;
 TrueMutexImpl EAGLGraphicsWindow::vc_lock;
 TrueConditionVarImpl EAGLGraphicsWindow::vc_condition = TrueConditionVarImpl(EAGLGraphicsWindow::vc_lock);
 
+// See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocAssociativeReferences.html
+// for what this is doing. Since UITouches don't come with an ID (or any member
+// that can meaningfully be converted to one), we can attach our own using the
+// Objective-C runtime.
+static char touch_id_key;
+
 /**
  *
  */
@@ -37,11 +46,20 @@ GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
 {
   _view = nil;
 
-  _emulated_mouse_input =
-    GraphicsWindowInputDevice::pointer_only(this, "touch_mouse");
-  _input_devices.push_back(_emulated_mouse_input);
+  _input =
+    GraphicsWindowInputDevice::pointer_only(this, "device");
+  _input_devices.push_back(_input);
 
   _backing_buffer = new EAGLGraphicsBuffer(engine, pipe, "buffer", fb_prop, win_prop, flags, gsg, host);
+
+  // Generate a set of identifiers that Panda will use to keep track of touches,
+  // since UITouch does not provide an identifier. 5 is the maximum number of touches
+  // an iPhone or iPad display can handle.
+  int max_touches = 5;
+  _touchIDPool = [NSMutableSet setWithCapacity:max_touches];
+  for (int i = 0; i < max_touches; i++) {
+    [_touchIDPool addObject:[NSNumber numberWithInt:i]];
+  }
 }
 
 /**
@@ -237,24 +255,79 @@ screen_size_changed() {
 }
 
 void EAGLGraphicsWindow::
-emulated_mouse_move(UITouch *touch) {
-  CGPoint location = [touch locationInView:_view];
-  _emulated_mouse_input->set_pointer_in_window(location.x * _view.layer.contentsScale,
-                                               location.y * _view.layer.contentsScale);
+touches_began(NSSet<UITouch *> *touch_set) {
+  [touch_set enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
+    NSNumber *next_id = [_touchIDPool anyObject];
+    [_touchIDPool removeObject:next_id];
+
+    objc_setAssociatedObject(touch,
+                             &touch_id_key,
+                             next_id,
+                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+    CGPoint point = [touch locationInView:_view];
+    NSLog(@"%d, %d", point.x, point.y);
+
+    PointerData &data = _input->add_pointer(PointerType::finger, [next_id intValue], _primary_touch == nil);
+    data.update(point.x, point.y, 1.0);
+
+    // PointerData data;
+    // data._id = [next_id intValue];
+    // data._type = PointerType::finger;
+    // data._xpos = point.x;
+    // data._ypos = point.y;
+    // data._pressure = 1.0;
+    // _input->update_pointer(data);
+    if (_primary_touch != nil) {
+      _primary_touch = touch;
+    }
+
+    printf("Touch %d began at %d, %d\n", [next_id intValue], point.x, point.y);
+  }];
+}
+
+void EAGLGraphicsWindow::
+touches_moved(NSSet<UITouch *> *touch_set) {
+  [touch_set enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
+    NSNumber *touch_id = (NSNumber *)objc_getAssociatedObject(touch, &touch_id_key);
+
+    CGPoint point = [touch locationInView:_view];
+
+    // PointerData data;
+    // data._id = [touch_id intValue];
+    // data._xpos = point.x;
+    // data._ypos = point.y;
+    // data._pressure = 1.0;
+    _input->get_pointer([touch_id intValue]).update(point.x, point.y, 1.0);
+    //printf("Touch %d moved\n", [touch_id intValue]);
+  }];
 }
 
 void EAGLGraphicsWindow::
-emulated_mouse_down(UITouch *touch) {
-  CGPoint location = [touch locationInView:_view];
-  _emulated_mouse_input->set_pointer_in_window(location.x * _view.layer.contentsScale,
-                                               location.y * _view.layer.contentsScale);
-  _emulated_mouse_input->button_down(MouseButton::button(0));
+touches_ended(NSSet<UITouch *> *touch_set) {
+  [touch_set enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
+    NSNumber *touch_id = (NSNumber *)objc_getAssociatedObject(touch, &touch_id_key);
+    [_touchIDPool addObject:touch_id];
+    
+    objc_setAssociatedObject(touch,
+                             &touch_id_key,
+                             nil,
+                             OBJC_ASSOCIATION_ASSIGN);
+
+    PointerData &ptr = _input->get_pointer([touch_id intValue]);
+    int x = ptr.get_x();
+    int y = ptr.get_y();
+
+    _input->remove_pointer([touch_id intValue]);
+    if (_primary_touch == touch) {
+      _primary_touch = nil;
+    }
+
+    printf("Touch %d ended at %d, %d\n", [touch_id intValue], x, y);
+  }];
 }
 
 void EAGLGraphicsWindow::
-emulated_mouse_up(UITouch *touch) {
-  CGPoint location = [touch locationInView:_view];
-  _emulated_mouse_input->set_pointer_in_window(location.x * _view.layer.contentsScale,
-                                               location.y * _view.layer.contentsScale);
-  _emulated_mouse_input->button_up(MouseButton::button(0));
+touches_cancelled(NSSet<UITouch *> *touch_set) {
+  
 }

+ 7 - 4
panda/src/eagldisplay/pandaEAGLView.mm

@@ -32,6 +32,9 @@
     eaglLayer.opaque = YES;
     eaglLayer.contentsScale = 3.0;
     
+    self.multipleTouchEnabled = YES;
+    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+
     _graphics_window = window;
   }
   return self;
@@ -47,15 +50,15 @@
 // TODO: Handle multi-touch.
 
 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
-  _graphics_window->emulated_mouse_move([touches anyObject]);
+  _graphics_window->touches_moved(touches);
 }
 
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
-  _graphics_window->emulated_mouse_down([touches anyObject]);
+  _graphics_window->touches_began(touches);
 }
 
 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
-  _graphics_window->emulated_mouse_up([touches anyObject]);
+  _graphics_window->touches_ended(touches);
 }
 
 /**
@@ -64,7 +67,7 @@
  */
 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   // FIXME: Should there be separate handling for ended vs. cancelled touches?
-  _graphics_window->emulated_mouse_up([touches anyObject]);
+  _graphics_window->touches_cancelled(touches);
 }
 
 

+ 27 - 0
panda/src/putil/pointerData.I

@@ -55,6 +55,11 @@ get_type() const {
   return _type;
 }
 
+INLINE bool PointerData::
+get_primary() const {
+  return _primary;
+}
+
 /**
  * Returns the pressure of the pointer.  For mice, this will be 1.0 if any
  * button is pressed, 0.0 otherwise.
@@ -64,6 +69,28 @@ get_pressure() const {
   return _pressure;
 }
 
+INLINE void PointerData::
+set_pressure(double pressure) {
+  _pressure = pressure;
+}
+
+INLINE void PointerData::
+set_in_window(bool in_window) {
+  _in_window = in_window;
+}
+
+INLINE void PointerData::
+update(double x, double y, double pressure) {
+  _xpos = x;
+  _ypos = y;
+  _pressure = pressure;
+}
+
+INLINE void PointerData::
+rel_move(double rx, double ry) {
+  _xpos += rx;
+  _ypos += ry;
+}
 
 INLINE std::ostream &operator << (std::ostream &out, const PointerData &md) {
   md.output(out);

+ 9 - 0
panda/src/putil/pointerData.cxx

@@ -13,6 +13,15 @@
 
 #include "pointerData.h"
 
+PointerData::
+PointerData(int id, bool primary, PointerType type) :
+  _id(id),
+  _primary(primary),
+  _type(type)
+{
+
+}
+
 /**
  *
  */

+ 18 - 6
panda/src/putil/pointerData.h

@@ -36,27 +36,39 @@ END_PUBLISH
  * as the mouse in the GraphicsWindow.
  */
 class EXPCL_PANDA_PUTIL PointerData {
-PUBLISHED:
+  friend class PointerEvent;
+
+public:
+  PointerData() = default;
+  PointerData(int id, bool primary, PointerType type);
+
   INLINE double get_x() const;
   INLINE double get_y() const;
   INLINE bool get_in_window() const;
-
-public:
+  INLINE bool get_primary() const;
   INLINE int get_id() const;
   INLINE PointerType get_type() const;
   INLINE double get_pressure() const;
 
+  INLINE void set_in_window(bool in_window);
+  INLINE void set_pressure(double pressure);
+
   void output(std::ostream &out) const;
 
 PUBLISHED:
   MAKE_PROPERTY(x, get_x);
   MAKE_PROPERTY(y, get_y);
   MAKE_PROPERTY(type, get_type);
+  MAKE_PROPERTY(primary, get_primary);
   MAKE_PROPERTY(id, get_id);
-  MAKE_PROPERTY(in_window, get_in_window);
-  MAKE_PROPERTY(pressure, get_pressure);
+  MAKE_PROPERTY(in_window, get_in_window, set_in_window);
+  MAKE_PROPERTY(pressure, get_pressure, set_pressure);
 
-public:
+  INLINE void update(double x, double y, double pressure);
+  INLINE void rel_move(double rx, double ry);
+
+protected:
+  bool _primary = false;
   bool _in_window = false;
   double _xpos = 0.0;
   double _ypos = 0.0;