[dasher: 11/21] iPhone MultiTouch input



commit 2b5a0a74369de72284f527aa20244244d0a3f60a
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Sat Jul 2 19:57:27 2011 +0100

    iPhone MultiTouch input
    
    EAGLView provides GetAllTouchCoordsInto:(vector<CGPoint> *),
     and passes on extra touches as keyid 101, 102, ...
    
    Added CIPhoneTwoFinger{Input,Filter}: 2 touches are min/max of target (dasherY)
      range, >=3 touches to reverse.

 Src/iPhone/Classes/CDasherInterfaceBridge.h  |    6 +--
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |   31 ++++++++---
 Src/iPhone/Classes/DasherAppDelegate.mm      |    8 ++-
 Src/iPhone/Classes/EAGLView.h                |   14 +++--
 Src/iPhone/Classes/EAGLView.mm               |   70 ++++++++++++++++++--------
 Src/iPhone/Classes/IPhoneFilters.h           |   10 ++++
 Src/iPhone/Classes/IPhoneFilters.mm          |   32 ++++++++++++
 Src/iPhone/Classes/IPhoneInputs.h            |   17 +++++-
 Src/iPhone/Classes/IPhoneInputs.mm           |   38 ++++++++++++--
 Src/iPhone/Classes/InputMethodSelector.mm    |    1 +
 10 files changed, 176 insertions(+), 51 deletions(-)
---
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.h b/Src/iPhone/Classes/CDasherInterfaceBridge.h
index 852fda5..b4e5f25 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.h
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.h
@@ -83,11 +83,9 @@ private:
   void HandleEvent(int iParameter);
   
   DasherAppDelegate *dasherApp;   // objc counterpart
-  
-  CIPhoneTiltFilter *m_pTiltFilter;
-  CIPhoneTouchFilter *m_pTouchFilter;
-	
+  	
   CIPhoneMouseInput *m_pMouseDevice;
   CIPhoneTiltInput *m_pTiltDevice;
 	UndoubledTouch *m_pUndoubledTouch;
+  CIPhoneTwoFingerInput *m_pTwoFingerDevice;
 };
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index 41588c2..f8ef86a 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -18,11 +18,18 @@
 #import "ButtonMode.h"
 #import "TwoButtonDynamicFilter.h"
 #import "TwoPushDynamicFilter.h"
+#import "EAGLView.h"
 #import <iostream>
 #import <fcntl.h>
 
 #import <sys/stat.h>
 
+//declare some "friend" methods
+ interface DasherAppDelegate ()
+-(EAGLView *)glView;
+-(UIWebView *)getWebView;
+ end;
+
 using namespace std;
 
 class IPhoneGameModule : public CGameModule {
@@ -69,13 +76,13 @@ CDasherInterfaceBridge::CDasherInterfaceBridge(DasherAppDelegate *aDasherApp) :
 }
 
 void CDasherInterfaceBridge::CreateModules() {
-	//create the default set...a good idea?!?!
-
-  RegisterModule(m_pUndoubledTouch = new UndoubledTouch());
+	//don't create the default set...just the ones accessible from the GUI.
+  RegisterModule(m_pUndoubledTouch = new UndoubledTouch([dasherApp glView]));
 	RegisterModule(m_pMouseDevice = 
-				new CIPhoneMouseInput(this));
+				new CIPhoneMouseInput(this,[dasherApp glView]));
 	RegisterModule(m_pTiltDevice = 
 				new CIPhoneTiltInput());
+  RegisterModule(m_pTwoFingerDevice=new CIPhoneTwoFingerInput([dasherApp glView]));
   SetDefaultInputDevice(m_pMouseDevice);
                  
   RegisterModule(new CButtonMode(this, this, true, 9, "Menu Mode"));
@@ -83,16 +90,20 @@ void CDasherInterfaceBridge::CreateModules() {
   RegisterModule(new CTwoButtonDynamicFilter(this, this));
   RegisterModule(new CTwoPushDynamicFilter(this, this));
   
-	RegisterModule(m_pTiltFilter =
-				   new CIPhoneTiltFilter(this, this, 16, m_pMouseDevice));
-	RegisterModule(m_pTouchFilter = 
-				   new CIPhoneTouchFilter(this, this, 17, m_pUndoubledTouch, m_pTiltDevice));
-	SetDefaultInputMethod(m_pTouchFilter);
+	RegisterModule(new CIPhoneTiltFilter(this, this, 16, m_pMouseDevice));
+  //Touch filter is stylus filter with optional Tilt X....
+  CIPhoneTouchFilter *pTouchFilter = 
+          new CIPhoneTouchFilter(this, this, 17, m_pUndoubledTouch, m_pTiltDevice);
+	RegisterModule(pTouchFilter);
+	SetDefaultInputMethod(pTouchFilter);
+  
+  RegisterModule(new CIPhoneTwoFingerFilter(this,this,18));
 }
 	
 CDasherInterfaceBridge::~CDasherInterfaceBridge() {
   delete m_pMouseDevice;
 	delete m_pTiltDevice;
+  delete m_pTwoFingerDevice;
   delete m_pUndoubledTouch;
   //(ACL)registered input filters should be automatically free'd by the module mgr?
 }
@@ -112,6 +123,8 @@ void CDasherInterfaceBridge::Realize() {
   [dasherApp setAlphabet:GetActiveAlphabet()];
   //don't call HandleEvent, would call superclass and reconstruct the NCManager!
   //TODO maybe better to make ChangeAlphabet virtual and override that?  
+
+  [[dasherApp glView] startAnimation];
 }
 
 void CDasherInterfaceBridge::SetupPaths() {
diff --git a/Src/iPhone/Classes/DasherAppDelegate.mm b/Src/iPhone/Classes/DasherAppDelegate.mm
index efcdcc0..ac13d69 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.mm
+++ b/Src/iPhone/Classes/DasherAppDelegate.mm
@@ -88,6 +88,11 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 @synthesize allowsRotation = m_bAllowsRotation;
 @synthesize window;
 
+//a private method called only by CDasherInterfaceBridge
+-(EAGLView *)glView {
+  return glView;
+}
+
 -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
   if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
     return NO;
@@ -163,7 +168,7 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
   messageLabel = [[[UITextView alloc] init] autorelease];
   tools = [[UIToolbar alloc] init]; //retain a reference (until dealloc) because of rotation
 	glView = [[[EAGLView alloc] initWithFrame:[self doLayout:UIInterfaceOrientationPortrait] Delegate:self] autorelease];
-  //that last, calls ChangeScreen on the interface, so now we can:
+  glView.multipleTouchEnabled = YES;
 		
   //start Realization i.e. training in a separate thread. (Has to be after first
   // call to doLayout, or get a black band across top of screen)
@@ -243,7 +248,6 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
   //training takes too long to perform in applicationDidFinishLaunching;
   // so we do it here instead (having let the main thread display a "training" message);
   _dasherInterface->Realize();
-  [glView startAnimation];
   //the rest has to be done on the main thread to avoid problems with OpenGL contexts
   // (which are local to one thread); however, we'll have the background thread wait...
   [self performSelectorOnMainThread:@selector(finishStartup) withObject:nil waitUntilDone:YES];
diff --git a/Src/iPhone/Classes/EAGLView.h b/Src/iPhone/Classes/EAGLView.h
index 2047a00..68570b5 100644
--- a/Src/iPhone/Classes/EAGLView.h
+++ b/Src/iPhone/Classes/EAGLView.h
@@ -23,7 +23,6 @@ public:
   CDasherScreenBridge(EAGLView *_view, Dasher::screenint iWidth, Dasher::screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y, GLuint *textures);
   ///Only for EAGLView to call...
   void resize(Dasher::screenint iWidth, Dasher::screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y);
-  bool GetTouchCoords(Dasher::screenint &iX, Dasher::screenint &iY);
   void Display();
   void SendMarker(int iMarker);
   
@@ -47,7 +46,7 @@ Note that setting the view non-opaque will only work if the EAGL surface has an
     /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
     GLuint viewRenderbuffer, viewFramebuffer;
     
-	BOOL animating, doneLayout, anyDown;
+	BOOL animating, doneLayout;
     NSTimeInterval animationInterval;
 	DasherAppDelegate *dasherApp;
 	
@@ -55,11 +54,16 @@ Note that setting the view non-opaque will only work if the EAGL surface has an
 	GLuint textures[2];
 	GLint backingWidth, backingHeight;
 	GLfloat texw,texh;
-  
-  CGPoint lastTouchCoords;
+    
+  std::map<UITouch*,int> allTouches;
+  std::map<int,CGPoint> fingerPosns;
 }
 
- property (readonly,assign) CGPoint lastTouchCoords;
+//Co-ordinates of earliest-started touch still touching
+-(CGPoint)lastTouchCoords;
+
+-(void)getAllTouchCoordsInto:(std::vector<CGPoint> *)into;
+
 //OpenGL context (needed to do any OGL operation) is only current per-thread, so must call this
 // if doing anything on any thread other than the main thread.
 -(void)makeContextCurrent;
diff --git a/Src/iPhone/Classes/EAGLView.mm b/Src/iPhone/Classes/EAGLView.mm
index 33301dd..8c09bd9 100644
--- a/Src/iPhone/Classes/EAGLView.mm
+++ b/Src/iPhone/Classes/EAGLView.mm
@@ -31,14 +31,7 @@ CDasherScreenBridge::CDasherScreenBridge(EAGLView *_view, screenint iWidth, scre
 void CDasherScreenBridge::resize(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y) {
   OpenGLScreen::resize(iWidth, iHeight, backingWidth, backingHeight, tc_x, tc_y);
 }
-  
-bool CDasherScreenBridge::GetTouchCoords(screenint &iX, screenint &iY) {
-  CGPoint p = view.lastTouchCoords;
-  if (p.x==-1) return false;
-  iX=p.x; iY=p.y;
-  return true;
-}
-  
+
 void CDasherScreenBridge::Display() {
   if (!view.readyToDisplay) return; //can't display anything yet!
   OpenGLScreen::Display();
@@ -71,8 +64,27 @@ CGSize CDasherScreenBridge::TextSize(NSString *str, unsigned int iFontSize, bool
     : [str sizeWithFont:font];
 }
 
+bool operator<(CGPoint p,CGPoint q) {
+  if (p.x<q.x) return true;
+  if (p.x>q.x) return false;
+  //equal x's
+  return (p.y<q.y);
+}
+
+bool operator==(CGPoint p,CGPoint q) {
+  return CGPointEqualToPoint(p, q);
+}
+
 @implementation EAGLView
- synthesize lastTouchCoords;
+
+-(CGPoint)lastTouchCoords {
+  return fingerPosns.empty() ? CGPointMake(-1,-1) : fingerPosns.begin()->second;
+}
+
+-(void)getAllTouchCoordsInto:(vector<CGPoint> *)into {
+  for (map<int,CGPoint>::iterator it=fingerPosns.begin(); it!=fingerPosns.end(); it++)
+    into->push_back(it->second);
+}
 
 // You must implement this method
 + (Class)layerClass {
@@ -118,25 +130,39 @@ CGSize CDasherScreenBridge::TextSize(NSString *str, unsigned int iFontSize, bool
 }
 
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSAssert([touches count] == 1, @"Multitouch?!");
-	lastTouchCoords = [((UITouch *)[touches anyObject]) locationInView:self];
-	NSAssert(!anyDown,@"Touches began when already in progress - multitouch enabled?!?!\n");
-	anyDown = YES;
-	dasherApp.dasherInterface->KeyDown(get_time(), 100, true, lastTouchCoords.x, lastTouchCoords.y);
+  const unsigned long time(get_time());
+  for (UITouch *touch in touches) {
+    const int finger(fingerPosns.empty() ? 0 : fingerPosns.rbegin()->first+1);
+    NSAssert(allTouches.find(touch)==allTouches.end(),@"Touch beginning already in progress?");
+    CGPoint p=[touch locationInView:self];
+    allTouches[touch]=finger;
+    fingerPosns[finger]=p;
+    //first finger with button id 100 = left button, then 101 and so on (element already inserted)
+    dasherApp.dasherInterface->KeyDown(time, allTouches.size()+99, true , p.x, p.y);
+  }
 }
 
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
-	lastTouchCoords = [[touches anyObject] locationInView:self];
+  for (UITouch *touch in touches) {
+    const int finger(allTouches[touch]);
+    map<int,CGPoint>::iterator it = fingerPosns.find(finger);
+    NSAssert(it!=fingerPosns.end(),@"Moving finger not found?");
+    it->second=[touch locationInView:self];
+  }
 }
 
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSAssert([touches count] == 1, @"Multitouch?!");
-	NSAssert(anyDown,@"Touches ended when not in progress - multitouch enabled?!?!\n");
-	lastTouchCoords = [(UITouch *)[touches anyObject] locationInView:self];
-	dasherApp.dasherInterface->KeyUp(get_time(), 100, true, lastTouchCoords.x, lastTouchCoords.y);
-  //finished dealing with touch-up event. Finger is now officially off the screen...
-  lastTouchCoords.x = lastTouchCoords.y = -1;
-  anyDown = NO;
+  const unsigned long time(get_time());
+  for (UITouch *touch in touches) {
+    map<UITouch*,int>::iterator it = allTouches.find(touch);
+    NSAssert(it != allTouches.end(), @"Release touch not in progress?");
+    map<int,CGPoint>::iterator it2=fingerPosns.find(it->second);
+    NSAssert(it2 != fingerPosns.end(), @"No coordinates for touch?");
+    CGPoint p=it2->second;
+    fingerPosns.erase(it2);
+    allTouches.erase(it);
+    dasherApp.dasherInterface->KeyUp(time, allTouches.size()+100, true, p.x, p.y);
+  }
 }
 
 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
diff --git a/Src/iPhone/Classes/IPhoneFilters.h b/Src/iPhone/Classes/IPhoneFilters.h
index 36e2366..4c45bcf 100644
--- a/Src/iPhone/Classes/IPhoneFilters.h
+++ b/Src/iPhone/Classes/IPhoneFilters.h
@@ -33,6 +33,8 @@ extern NSString *TOUCH_USE_TILT_X;
 
 #define TILT_FILTER "IPhone Tilt Filter"
 #define TOUCH_FILTER "IPhone Touch Filter"
+#define TWO_FINGER_FILTER "Two-finger filter"
+
 class CIPhoneTiltFilter : public COneDimensionalFilter, private IPhonePrefsObserver {
 public:
 	CIPhoneTiltFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID, CDasherInput *pTouch);
@@ -73,4 +75,12 @@ private:
   bool bUseTiltX;
 };
 
+class CIPhoneTwoFingerFilter : public CDefaultFilter {
+public:
+  CIPhoneTwoFingerFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID);
+  virtual void KeyUp(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel);
+  virtual void KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog);
+  virtual bool DecorateView(CDasherView *pView, CDasherInput *pInput);
+};
+
 /// @}
\ No newline at end of file
diff --git a/Src/iPhone/Classes/IPhoneFilters.mm b/Src/iPhone/Classes/IPhoneFilters.mm
index 3f3a6dd..a453e76 100644
--- a/Src/iPhone/Classes/IPhoneFilters.mm
+++ b/Src/iPhone/Classes/IPhoneFilters.mm
@@ -171,4 +171,36 @@ void CIPhoneTouchFilter::ApplyTransform(myint &iDasherX, myint &iDasherY, CDashe
     m_pTilt->GetDasherCoords(iDasherX,temp,pView);
   }
   CStylusFilter::ApplyTransform(iDasherX, iDasherY, pView);
+}
+
+CIPhoneTwoFingerFilter::CIPhoneTwoFingerFilter(CSettingsUser *pCreator, CDasherInterfaceBase *pInterface, ModuleID_t iID)
+: CDefaultFilter(pCreator, pInterface, iID, TWO_FINGER_FILTER) {
+}
+
+void CIPhoneTwoFingerFilter::KeyDown(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel, CUserLogBase *pUserLog) {
+  if (iId==101) m_pInterface->Unpause(iTime);
+  else if (iId!=100) CDefaultFilter::KeyDown(iTime, iId, pView, pInput, pModel, pUserLog);
+}
+
+void CIPhoneTwoFingerFilter::KeyUp(int iTime, int iId, CDasherView *pView, CDasherInput *pInput, CDasherModel *pModel) {
+  if (iId==101) m_pInterface->Stop();
+  else if (iId!=100) CDefaultFilter::KeyUp(iTime, iId, pView, pInput, pModel);
+}
+
+bool CIPhoneTwoFingerFilter::DecorateView(CDasherView *pView, CDasherInput *pInput) {
+  if (GetBoolParameter(BP_DRAW_MOUSE_LINE)) {
+    myint x[2], y[2];
+    x[0] = m_iLastX; x[1] = 0;
+    y[0] = m_iLastY;
+    for (y[1] = m_iLastY - m_iLastX; ;) {
+      if (GetBoolParameter(BP_CURVE_MOUSE_LINE))
+        pView->DasherSpaceLine(x[0], y[0], x[1], y[1], GetLongParameter(LP_LINE_WIDTH), 1);
+      else
+        pView->DasherPolyline(x, y, 2, GetLongParameter(LP_LINE_WIDTH), 1);
+      if (y[1] == m_iLastY + m_iLastX) break;
+      y[1] = m_iLastY + m_iLastX;
+    }
+    return true;
+  }
+  return false;
 }
\ No newline at end of file
diff --git a/Src/iPhone/Classes/IPhoneInputs.h b/Src/iPhone/Classes/IPhoneInputs.h
index 178dd99..d1aacfd 100644
--- a/Src/iPhone/Classes/IPhoneInputs.h
+++ b/Src/iPhone/Classes/IPhoneInputs.h
@@ -17,7 +17,9 @@
 #define UNDOUBLED_TOUCH "Undoubled Touch"
 #define TOUCH_INPUT "Mouse Input"
 #define TILT_INPUT "Tilt Input"
+#define TWO_FINGER_INPUT "Two-finger (multitouch) input"
 
+ class EAGLView;
 namespace Dasher {
 
 class CIPhoneTiltInput : public CScreenCoordInput {
@@ -51,17 +53,26 @@ private:
 
 class UndoubledTouch : public CScreenCoordInput {
 public:
-  UndoubledTouch();
+  UndoubledTouch(EAGLView *pView);
   bool GetScreenCoords(screenint &iX, screenint &iY, CDasherView *pView);
 protected:
-  UndoubledTouch(ModuleID_t iId, const char *szName);
+  UndoubledTouch(ModuleID_t iId, const char *szName, EAGLView *pView);
+  EAGLView * const m_pView;
 };
 
 class CIPhoneMouseInput : public UndoubledTouch, protected CSettingsUser {
 public:
-	CIPhoneMouseInput(CSettingsUser *pCreator);
+	CIPhoneMouseInput(CSettingsUser *pCreator, EAGLView *pView);
   
   bool GetScreenCoords(screenint &iX, screenint &iY, CDasherView *pView);
 };
+  
+  class CIPhoneTwoFingerInput : public CDasherCoordInput {
+  public:
+    CIPhoneTwoFingerInput(EAGLView *pView);
+    bool GetDasherCoords(myint &iX, myint &iY, CDasherView *pView);
+  protected:
+    EAGLView * const m_pGlView;
+  };
 }
 
diff --git a/Src/iPhone/Classes/IPhoneInputs.mm b/Src/iPhone/Classes/IPhoneInputs.mm
index 8e1d183..a97166f 100644
--- a/Src/iPhone/Classes/IPhoneInputs.mm
+++ b/Src/iPhone/Classes/IPhoneInputs.mm
@@ -100,20 +100,22 @@ bool CIPhoneTiltInput::GetScreenCoords(screenint &iX, screenint &iY, CDasherView
   return true;
 }
 
-UndoubledTouch::UndoubledTouch() : CScreenCoordInput(7, UNDOUBLED_TOUCH) {
+UndoubledTouch::UndoubledTouch(EAGLView *pView) : CScreenCoordInput(7, UNDOUBLED_TOUCH), m_pView(pView) {
 }
 
-UndoubledTouch::UndoubledTouch(ModuleID_t iId, const char *szName) : CScreenCoordInput(iId, szName) {
+UndoubledTouch::UndoubledTouch(ModuleID_t iId, const char *szName, EAGLView *pView) : CScreenCoordInput(iId, szName), m_pView(pView) {
 }
 
 bool UndoubledTouch::GetScreenCoords(screenint &iX, screenint &iY, CDasherView *pView) {
-  CDasherScreenBridge *sb(static_cast<CDasherScreenBridge *>(pView->Screen()));
-  return sb->GetTouchCoords(iX, iY);
+  CGPoint p = [m_pView lastTouchCoords];
+  if (p.x==-1) return false;
+  iX = p.x; iY=p.y;
+  return true;
 }
 
 
-CIPhoneMouseInput::CIPhoneMouseInput(CSettingsUser *pCreator) 
-	: UndoubledTouch(9, TOUCH_INPUT), CSettingsUser(pCreator) {
+CIPhoneMouseInput::CIPhoneMouseInput(CSettingsUser *pCreator, EAGLView *pView) 
+	: UndoubledTouch(9, TOUCH_INPUT, pView), CSettingsUser(pCreator) {
 };
 
 bool CIPhoneMouseInput::GetScreenCoords(screenint &iX, screenint &iY, CDasherView *pView) {
@@ -138,3 +140,27 @@ bool CIPhoneMouseInput::GetScreenCoords(screenint &iX, screenint &iY, CDasherVie
   }
   return true;
 }
+
+CIPhoneTwoFingerInput::CIPhoneTwoFingerInput(EAGLView *pView) : CDasherCoordInput(10, TWO_FINGER_INPUT), m_pGlView(pView) {
+}
+
+bool CIPhoneTwoFingerInput::GetDasherCoords(myint &iX, myint &iY, CDasherView *pView) {
+  vector<CGPoint> pts;
+  [m_pGlView getAllTouchCoordsInto:&pts];
+  if (pts.size()<2) return false;
+  myint x1,y1,x2,y2;
+  
+  //target Y is midpoint of Y coordinates of first two touches
+  pView->Screen2Dasher(pts[0].x, pts[0].y, x1, y1);
+  pView->Screen2Dasher(pts[1].x, pts[1].y, x2, y2);
+  iY = (y1+y2)/2;
+  //target X is half the distance between them (-> top/bottom of target range = top/bottom of first two touches)
+  iX = abs(y1-y2)/2;
+
+  if (pts.size()>2) {
+    //three or more fingers, go backwards...
+    pView->VisibleRegion(x1, y1, x2, y2); //x2 is now maxX
+    iX = (x2*iX)/2048; //(first two) fingers wider apart, will go backwards faster.
+  } 
+  return true;
+}
diff --git a/Src/iPhone/Classes/InputMethodSelector.mm b/Src/iPhone/Classes/InputMethodSelector.mm
index b1c3663..302cb2f 100644
--- a/Src/iPhone/Classes/InputMethodSelector.mm
+++ b/Src/iPhone/Classes/InputMethodSelector.mm
@@ -31,6 +31,7 @@ NSString *calibBtn=@"Calibrate...";//pointer equality used to mark cell for spec
 
 SFilterDesc asNormalFilters[] = {
 	{@"Touch Control", @"Tap or Hold", TOUCH_INPUT, TOUCH_FILTER, touchSettings},
+  {@"Multitouch",@"Two fingers, further apart = slower", TWO_FINGER_INPUT, TWO_FINGER_FILTER, NULL},
   {@"Tilt Control", calibBtn, TILT_INPUT, TILT_FILTER, tiltSettings},
 };
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]