From 0e3de701d9e97412dbe6e7c8d80fdffc50af7a1f Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Fri, 3 Feb 2012 15:15:37 +0000 Subject: [PATCH] From Stephan Huber, "attached you'll find a first version of multi-touch-support for OS X (>= 10.6), which will forward all multi-touch events from a trackpad to the corresponding osgGA-event-structures. The support is switched off per default, but you can enable multi-touch support via a new flag for GraphicsWindowCocoa::WindowData or directly via the GraphicsWindowCocoa-class. After switching multi-touch-support on, all mouse-events from the trackpad get ignored, otherwise you'll have multiple events for the same pointer which is very confusing (as the trackpad reports absolute movement, and as a mouse relative movement). I think this is not a problem, as multi-touch-input is a completely different beast as a mouse, so you'll have to code your own event-handlers anyway. While coding this stuff, I asked myself if we should refactor GUIEventAdapter/EventQueue and assign a specific event-type for touch-input instead of using PUSH/DRAG/RELEASE. This will make it clearer how to use the code, but will break the mouse-emulation for the first touch-point and with that all existing manipulators. What do you think? I am happy to code the proposed changes. Additionally I created a small (and ugly) example osgmultitouch which makes use of the osgGA::MultiTouchTrackballManipulator, shows all touch-points on a HUD and demonstrates how to get the touchpoints from an osgGA::GUIEventAdapter. There's even a small example video here: http://vimeo.com/31611842" --- src/osgViewer/GraphicsWindowCocoa.mm | 246 +++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 10 deletions(-) diff --git a/src/osgViewer/GraphicsWindowCocoa.mm b/src/osgViewer/GraphicsWindowCocoa.mm index f8750e75c..58c718a9d 100644 --- a/src/osgViewer/GraphicsWindowCocoa.mm +++ b/src/osgViewer/GraphicsWindowCocoa.mm @@ -269,6 +269,9 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) unsigned int _cachedModifierFlags; BOOL _handleTabletEvents; + NSMutableDictionary* _touchPoints; + unsigned int _lastTouchPointId; + } - (void)setGraphicsWindowCocoa: (osgViewer::GraphicsWindowCocoa*) win; @@ -307,10 +310,23 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) - (void)tabletProximity:(NSEvent *)theEvent; - (void)handleTabletEvents:(NSEvent*)theEvent; +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) +- (osgGA::GUIEventAdapter::TouchPhase) convertTouchPhase: (NSTouchPhase) phase; +- (unsigned int)computeTouchId: (NSTouch*) touch; +- (void)touchesBeganWithEvent:(NSEvent *)event; +- (void)touchesMovedWithEvent:(NSEvent *)event; +- (void)touchesEndedWithEvent:(NSEvent *)event; +- (void)touchesCancelledWithEvent:(NSEvent *)event; +#endif + +- (BOOL)useMultiTouchOnly: (NSEvent*) event; + - (BOOL)acceptsFirstResponder; - (BOOL)becomeFirstResponder; - (BOOL)resignFirstResponder; +- (void)dealloc; + @end @implementation GraphicsWindowCocoaGLView @@ -319,6 +335,13 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) -(void) setGraphicsWindowCocoa: (osgViewer::GraphicsWindowCocoa*) win { _win = win; + _touchPoints = NULL; +} + +-(void) dealloc +{ + if (_touchPoints) [_touchPoints release]; + [super dealloc]; } - (BOOL)acceptsFirstResponder @@ -336,6 +359,15 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) return YES; } +- (BOOL) useMultiTouchOnly: (NSEvent*) event +{ +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) + return ([self acceptsTouchEvents] && ([event subtype] == NSTouchEventSubtype)); +#else + return false; +#endif +} + - (NSPoint) getLocalPoint: (NSEvent*)theEvent { @@ -405,6 +437,10 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) - (void) mouseMoved:(NSEvent*)theEvent { + // if multitouch is enabled, disable standard event handling + if ([self useMultiTouchOnly: theEvent]) + return; + NSPoint converted_point = [self getLocalPoint: theEvent]; DEBUG_OUT("Mouse moved" << converted_point.x << "/" << converted_point.y); _win->getEventQueue()->mouseMotion(converted_point.x, converted_point.y); @@ -414,6 +450,10 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) - (void) mouseDown:(NSEvent*)theEvent { + // if multitouch is enabled, disable standard event handling + if ([self useMultiTouchOnly: theEvent]) + return; + DEBUG_OUT("Mouse down"); // Because many Mac users have only a 1-button mouse, we should provide ways // to access the button 2 and 3 actions of osgViewer. @@ -443,6 +483,10 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) - (void) mouseDragged:(NSEvent*)theEvent { + // if multitouch is enabled, disable standard event handling + if ([self useMultiTouchOnly: theEvent]) + return; + if (!_win) return; NSPoint converted_point = [self getLocalPoint: theEvent]; @@ -455,6 +499,10 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) - (void) mouseUp:(NSEvent*)theEvent { + // if multitouch is enabled, disable standard event handling + if ([self useMultiTouchOnly: theEvent]) + return; + // Because many Mac users have only a 1-button mouse, we should provide ways // to access the button 2 and 3 actions of osgViewer. // I will use the Ctrl modifer to represent right-clicking @@ -737,6 +785,159 @@ static NSRect convertToQuartzCoordinates(const NSRect& rect) _win->getEventQueue()->penProximity(pt, [theEvent isEnteringProximity]); } +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) + +- (osgGA::GUIEventAdapter::TouchPhase) convertTouchPhase: (NSTouchPhase) phase +{ + switch(phase) { + + case NSTouchPhaseBegan: + return osgGA::GUIEventAdapter::TOUCH_BEGAN; + break; + case NSTouchPhaseMoved: + return osgGA::GUIEventAdapter::TOUCH_MOVED; + break; + + case NSTouchPhaseStationary: + return osgGA::GUIEventAdapter::TOUCH_STATIONERY; + break; + + case NSTouchPhaseEnded: + case NSTouchPhaseCancelled: + return osgGA::GUIEventAdapter::TOUCH_ENDED; + break; + } + + return osgGA::GUIEventAdapter::TOUCH_ENDED; + +} + + +- (unsigned int)computeTouchId: (NSTouch*) touch +{ + unsigned int result(0); + + if(!_touchPoints) { + _touchPoints = [[NSMutableDictionary alloc] init]; + _lastTouchPointId = 0; + } + + switch([touch phase]) + { + + case NSTouchPhaseBegan: + if ([_touchPoints objectForKey: [touch identity]] == nil) + { + [_touchPoints setObject: [NSNumber numberWithInt: _lastTouchPointId] forKey: [touch identity]]; + result = _lastTouchPointId++; + break; + } + // missing "break" by intention! + + case NSTouchPhaseMoved: + case NSTouchPhaseStationary: + { + NSNumber* n = [_touchPoints objectForKey: [touch identity]]; + result = [n intValue]; + } + break; + + case NSTouchPhaseEnded: + case NSTouchPhaseCancelled: + { + NSNumber* n = [_touchPoints objectForKey: [touch identity]]; + result = [n intValue]; + [_touchPoints removeObjectForKey: [touch identity]]; + if([_touchPoints count] == 0) { + _lastTouchPointId = 0; + } + } + break; + + default: + break; + } + + return result; +} + + + +- (void)touchesBeganWithEvent:(NSEvent *)event +{ + NSSet *allTouches = [event touchesMatchingPhase: NSTouchPhaseAny inView: self]; + + osg::ref_ptr osg_event(NULL); + + NSRect bounds = [self bounds]; + for(unsigned int i=0; i<[allTouches count]; i++) + { + + NSTouch *touch = [[allTouches allObjects] objectAtIndex:i]; + NSPoint pos = [touch normalizedPosition]; + osg::Vec2 pixelPos(pos.x * bounds.size.width, (1-pos.y) * bounds.size.height); + unsigned int touch_id = [self computeTouchId: touch]; + if (!osg_event) { + osg_event = _win->getEventQueue()->touchBegan(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y()); + } else { + osg_event->addTouchPoint(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y()); + } + } +} + +- (void)touchesMovedWithEvent:(NSEvent *)event +{ + NSSet *allTouches = [event touchesMatchingPhase: NSTouchPhaseAny inView: self]; + + osg::ref_ptr osg_event(NULL); + NSRect bounds = [self bounds]; + + for(unsigned int i=0; i<[allTouches count]; i++) + { + NSTouch *touch = [[allTouches allObjects] objectAtIndex:i]; + NSPoint pos = [touch normalizedPosition]; + osg::Vec2 pixelPos(pos.x * bounds.size.width, (1 - pos.y) * bounds.size.height); + unsigned int touch_id = [self computeTouchId: touch]; + if (!osg_event) { + osg_event = _win->getEventQueue()->touchMoved(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y()); + } else { + osg_event->addTouchPoint(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y()); + } + } +} + + +- (void)touchesEndedWithEvent:(NSEvent *)event +{ + NSSet *allTouches = [event touchesMatchingPhase: NSTouchPhaseAny inView: self]; + + osg::ref_ptr osg_event(NULL); + NSRect bounds = [self bounds]; + + for(unsigned int i=0; i<[allTouches count]; i++) + { + NSTouch *touch = [[allTouches allObjects] objectAtIndex:i]; + NSPoint pos = [touch normalizedPosition]; + osg::Vec2 pixelPos(pos.x * bounds.size.width, (1 - pos.y) * bounds.size.height); + unsigned int touch_id = [self computeTouchId: touch]; + if (!osg_event) { + osg_event = _win->getEventQueue()->touchEnded(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y(), 1); + } else { + osg_event->addTouchPoint(touch_id, [self convertTouchPhase: [touch phase]], pixelPos.x(), pixelPos.y(), 1); + } + + } +} + + + +- (void)touchesCancelledWithEvent:(NSEvent *)event +{ + [self touchesEndedWithEvent: event]; +} + + +#endif @end @@ -1002,23 +1203,30 @@ bool GraphicsWindowCocoa::realizeImplementation() // set graphics handle for shared usage setNSOpenGLContext(_context); - GraphicsWindowCocoaGLView* theView = [[ GraphicsWindowCocoaGLView alloc ] initWithFrame:[ _window frame ] ]; - [theView setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable) ]; - [theView setGraphicsWindowCocoa: this]; - [theView setOpenGLContext:_context]; - _view = theView; - OSG_DEBUG << "GraphicsWindowCocoa::realizeImplementation / view: " << theView << std::endl; + + _view = [[ GraphicsWindowCocoaGLView alloc ] initWithFrame:[ _window frame ] ]; + [_view setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable) ]; + [_view setGraphicsWindowCocoa: this]; + [_view setOpenGLContext:_context]; + + // enable multitouch + if (_multiTouchEnabled || (windowData && windowData->isMultiTouchEnabled())) + { + setMultiTouchEnabled(true); + } + + OSG_DEBUG << "GraphicsWindowCocoa::realizeImplementation / view: " << _view << std::endl; if (_ownsWindow) { - [_window setContentView: theView]; + [_window setContentView: _view]; setupNSWindow(_window); - [theView release]; - + [_view release]; + MenubarController::instance()->attachWindow( new CocoaWindowAdapter(this) ); } else { - windowData->setCreatedNSView(theView); + windowData->setCreatedNSView(_view); } [pool release]; @@ -1423,6 +1631,24 @@ void GraphicsWindowCocoa::setSyncToVBlank(bool f) } +bool GraphicsWindowCocoa::isMultiTouchEnabled() +{ + return _multiTouchEnabled; +} +void GraphicsWindowCocoa::setMultiTouchEnabled(bool b) +{ + _multiTouchEnabled = b; + +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) + if (_view) [_view setAcceptsTouchEvents: b]; +#else + if (b) { + OSG_WARN << "GraphicsWindowCocoa :: multi-touch only available for OS X >= 10.6, please check your compile settings" << std::endl; + } +#endif +} + + // ---------------------------------------------------------------------------------------------------------- // d'tor // ----------------------------------------------------------------------------------------------------------