macView.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "windowManager/mac/macView.h"
  23. #include "platform/event.h"
  24. #include "platform/platformInput.h"
  25. #include "console/console.h"
  26. #include "sim/actionMap.h"
  27. #include "app/mainLoop.h"
  28. // For left/right side definitions.
  29. #include <IOKit/hidsystem/IOLLEvent.h>
  30. #define WHEEL_DELTA ( 120 * 0.1 )
  31. static bool smApplicationInactive = false;
  32. extern InputModifiers convertModifierBits( const U32 in );
  33. inline U32 NSModifiersToTorqueModifiers( NSUInteger mods )
  34. {
  35. U32 torqueMods = 0;
  36. if( mods & NX_DEVICELSHIFTKEYMASK )
  37. torqueMods |= IM_LSHIFT;
  38. if( mods & NX_DEVICERSHIFTKEYMASK )
  39. torqueMods |= IM_RSHIFT;
  40. if( mods & NX_DEVICELALTKEYMASK )
  41. torqueMods |= IM_LOPT;
  42. if( mods & NX_DEVICERALTKEYMASK )
  43. torqueMods |= IM_ROPT;
  44. if( mods & NX_DEVICELCTLKEYMASK )
  45. torqueMods |= IM_LCTRL;
  46. if( mods & NX_DEVICERCTLKEYMASK )
  47. torqueMods |= IM_RCTRL;
  48. if( mods & NX_DEVICELCMDKEYMASK )
  49. torqueMods |= IM_LALT;
  50. if( mods & NX_DEVICERCMDKEYMASK )
  51. torqueMods |= IM_RALT;
  52. Input::setModifierKeys( convertModifierBits( torqueMods ) );
  53. return torqueMods;
  54. }
  55. @implementation GGMacView
  56. - (void)setTorqueWindow:(MacWindow*)theWindow
  57. {
  58. mTorqueWindow = theWindow;
  59. mLastMods = 0;
  60. }
  61. - (MacWindow*)torqueWindow
  62. {
  63. return mTorqueWindow;
  64. }
  65. -(void)trackModState:(U32)torqueKey withMacMods:(U32)macMods event:(NSEvent*)theEvent
  66. {
  67. // track state:
  68. // translate the torque key code to the torque flag that changed
  69. // xor with existing mods for new mod state
  70. // clear anything that the mac says both siblings are not down ( to help stay in sync, a little bit )
  71. // ?set left sibling of anything that the mac says some sibling is down, but that we don't see as down?
  72. U32 torqueMod = 0;
  73. switch(torqueKey)
  74. {
  75. case KEY_LSHIFT: torqueMod = IM_LSHIFT; break;
  76. case KEY_RSHIFT: torqueMod = IM_RSHIFT; break;
  77. case KEY_LCONTROL: torqueMod = IM_LCTRL; break;
  78. case KEY_RCONTROL: torqueMod = IM_RCTRL; break;
  79. case KEY_MAC_LOPT: torqueMod = IM_LOPT; break;
  80. case KEY_MAC_ROPT: torqueMod = IM_ROPT; break;
  81. case KEY_LALT: torqueMod = IM_LALT; break;
  82. case KEY_RALT: torqueMod = IM_RALT; break;
  83. };
  84. // flip the mod that we got an event for
  85. U32 newMods = mLastMods ^ torqueMod;
  86. // clear left and right if mac thinks both are up.
  87. if( !(macMods & NSShiftKeyMask)) newMods &= ~IM_LSHIFT, newMods &= ~IM_RSHIFT;
  88. if( !(macMods & NSControlKeyMask)) newMods &= ~IM_LCTRL, newMods &= ~IM_RCTRL;
  89. if( !(macMods & NSAlternateKeyMask)) newMods &= ~IM_LOPT, newMods &= ~IM_ROPT;
  90. if( !(macMods & NSCommandKeyMask)) newMods &= ~IM_LALT, newMods &= ~IM_RALT;
  91. // Generate keyUp/Down event (allows us to use modifier keys for actions)
  92. mLastMods = 0;
  93. [self rawKeyUpDown:theEvent keyDown:(newMods & torqueMod)];
  94. mLastMods = newMods;
  95. Input::setModifierKeys( convertModifierBits( mLastMods ) );
  96. }
  97. - (Point2I)viewCoordsToTorqueCoords:(NSPoint)mousePoint
  98. {
  99. if(mTorqueWindow->isFullscreen())
  100. {
  101. CGRect bounds = mTorqueWindow->getDisplayBounds();
  102. CGRect mainbounds = mTorqueWindow->getMainDisplayBounds();
  103. F32 offsetY = mainbounds.size.height - (bounds.size.height + bounds.origin.y);
  104. Point2I pt(mousePoint.x - bounds.origin.x, bounds.size.height + offsetY - mousePoint.y);
  105. return pt;
  106. }
  107. return Point2I(mousePoint.x, mTorqueWindow->getClientExtent().y - mousePoint.y);
  108. }
  109. - (void)signalGainFocus
  110. {
  111. if(smApplicationInactive)
  112. smApplicationInactive = false;
  113. bool gainFocus = static_cast<MacWindowManager*>(WindowManager)->canWindowGainFocus(mTorqueWindow);
  114. if(gainFocus)
  115. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), GainFocus);
  116. }
  117. #pragma mark -
  118. #pragma mark Mouse Input
  119. // We're funnelling all the standard cocoa event handlers to -mouseUpDown and -mouseMotion.
  120. - (void)mouseDown:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:YES]; }
  121. - (void)rightMouseDown:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:YES]; }
  122. - (void)otherMouseDown:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:YES]; }
  123. - (void)mouseUp:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:NO]; }
  124. - (void)rightMouseUp:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:NO]; }
  125. - (void)otherMouseUp:(NSEvent *)theEvent { [self mouseUpDown:theEvent mouseDown:NO]; }
  126. - (void)mouseDragged:(NSEvent *)theEvent { [self mouseMotion:theEvent]; }
  127. - (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseMotion:theEvent]; }
  128. - (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseMotion:theEvent]; }
  129. - (void)mouseMoved:(NSEvent *)theEvent { [self mouseMotion:theEvent]; }
  130. - (void)mouseUpDown:(NSEvent *)theEvent mouseDown:(BOOL)isMouseDown
  131. {
  132. Point2I eventLocation = [self viewCoordsToTorqueCoords: [theEvent locationInWindow]];
  133. U16 buttonNumber = [theEvent buttonNumber];
  134. U32 action = isMouseDown ? SI_MAKE : SI_BREAK;
  135. // If the event location is negative then it occurred in the structure region (e.g. title bar, resize corner), and we don't want
  136. // to lock the mouse/drop into fullscreen for that.
  137. if(WindowManager->getFocusedWindow() != mTorqueWindow && eventLocation.x > 0 && eventLocation.y > 0)
  138. [self signalGainFocus];
  139. mLastMods = NSModifiersToTorqueModifiers( [ theEvent modifierFlags ] );
  140. mTorqueWindow->buttonEvent.trigger(mTorqueWindow->getWindowId(), mLastMods, action, buttonNumber);
  141. }
  142. - (void)mouseMotion:(NSEvent *)theEvent
  143. {
  144. mTorqueWindow->_doMouseLockNow();
  145. // when moving the mouse to the center of the window for mouse locking, we need
  146. // to skip the next mouse delta event
  147. if(mTorqueWindow->isMouseLocked() && mTorqueWindow->_skipNextMouseEvent())
  148. {
  149. mTorqueWindow->_skippedMouseEvent();
  150. return;
  151. }
  152. Point2I eventLocation;
  153. if(mTorqueWindow->isMouseLocked())
  154. {
  155. eventLocation.x = [theEvent deltaX];
  156. eventLocation.y = [theEvent deltaY];
  157. }
  158. else
  159. {
  160. eventLocation = [self viewCoordsToTorqueCoords:[theEvent locationInWindow]];
  161. }
  162. mLastMods = NSModifiersToTorqueModifiers( [ theEvent modifierFlags ] );
  163. mTorqueWindow->mouseEvent.trigger(mTorqueWindow->getWindowId(), mLastMods, eventLocation.x, eventLocation.y, mTorqueWindow->isMouseLocked());
  164. }
  165. - (void)scrollWheel:(NSEvent *)theEvent
  166. {
  167. float deltaX = [ theEvent deltaX ];
  168. float deltaY = [ theEvent deltaY ];
  169. if( mIsZero( deltaX ) && mIsZero( deltaY ) )
  170. return;
  171. mLastMods = NSModifiersToTorqueModifiers( [ theEvent modifierFlags ] );
  172. mTorqueWindow->wheelEvent.trigger( mTorqueWindow->getWindowId(), mLastMods, S32( deltaX * WHEEL_DELTA ), S32( deltaY * WHEEL_DELTA ) );
  173. }
  174. #pragma mark -
  175. #pragma mark Keyboard Input
  176. - (BOOL)performKeyEquivalent:(NSEvent *)theEvent
  177. {
  178. // Pass it off to the main menu. If the main menu handled it, we're done.
  179. if([[NSApp mainMenu] performKeyEquivalent:theEvent])
  180. return YES;
  181. // cmd-q will quit. End of story.
  182. if(([theEvent modifierFlags] & NSCommandKeyMask && !([theEvent modifierFlags] & NSAlternateKeyMask) && !([theEvent modifierFlags] & NSControlKeyMask)) && [theEvent keyCode] == 0x0C)
  183. {
  184. StandardMainLoop::shutdown();
  185. [NSApp terminate:self];
  186. return YES;
  187. }
  188. // In fullscreen we grab ALL keyboard events, including ones which would normally be handled by the system,
  189. // like cmd-tab. Thus, we need to specifically check for cmd-tab and bail out of fullscreen and hide the app
  190. // to switch to the next application.
  191. // 0x30 is tab, so this grabs command-tab and command-shift-tab
  192. if([theEvent keyCode] == 0x30 && mTorqueWindow->isFullscreen())
  193. {
  194. // Bail!
  195. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), LoseFocus);
  196. [NSApp hide:nil];
  197. return YES;
  198. }
  199. // All other events are uninteresting to us and can be handled by Torque.
  200. if([theEvent type] == NSKeyDown)
  201. [self keyDown:theEvent];
  202. else if([theEvent type] == NSKeyUp)
  203. [self keyUp:theEvent];
  204. // Don't let the default handler continue processing these events, it does things we don't like.
  205. return YES;
  206. }
  207. - (void)flagsChanged:(NSEvent *)theEvent
  208. {
  209. U32 torqueKeyCode = TranslateOSKeyCode([theEvent keyCode]);
  210. U32 macMods = [theEvent modifierFlags];
  211. [self trackModState:torqueKeyCode withMacMods:macMods event:theEvent];
  212. }
  213. - (void)keyDown:(NSEvent *)theEvent
  214. {
  215. // If keyboard translation is on and the key isn't bound in the
  216. // global action map, first try turning this into one or more
  217. // character events.
  218. if( mTorqueWindow->getKeyboardTranslation()
  219. && !mTorqueWindow->shouldNotTranslate(
  220. convertModifierBits( NSModifiersToTorqueModifiers( [ theEvent modifierFlags ] ) ),
  221. ( InputObjectInstances ) TranslateOSKeyCode( [ theEvent keyCode ] ) ) )
  222. {
  223. mHandledAsCharEvent = false;
  224. [ self interpretKeyEvents: [ NSArray arrayWithObject: theEvent ] ];
  225. if( mHandledAsCharEvent )
  226. return;
  227. }
  228. // Fire as raw keyboard event.
  229. [ self rawKeyUpDown: theEvent keyDown: YES ];
  230. }
  231. - (void)keyUp:(NSEvent *)theEvent
  232. {
  233. [self rawKeyUpDown:theEvent keyDown:NO];
  234. }
  235. - (void)rawKeyUpDown:(NSEvent *)theEvent keyDown:(BOOL)isKeyDown
  236. {
  237. U32 action;
  238. if([theEvent type] != NSFlagsChanged)
  239. action = isKeyDown ? ([theEvent isARepeat] ? SI_REPEAT : SI_MAKE) : SI_BREAK;
  240. else
  241. action = isKeyDown ? SI_MAKE : SI_BREAK;
  242. U32 torqueKeyCode = TranslateOSKeyCode([theEvent keyCode]);
  243. mLastMods = NSModifiersToTorqueModifiers( [ theEvent modifierFlags ] );
  244. mTorqueWindow->keyEvent.trigger(mTorqueWindow->getWindowId(), mLastMods, action, torqueKeyCode);
  245. }
  246. - (void)insertText:(id)_inString
  247. {
  248. // input string may be an NSString or an NSAttributedString
  249. NSString *text = [_inString isKindOfClass:[NSAttributedString class]] ? [_inString string] : _inString;
  250. for(int i = 0; i < [text length]; i++)
  251. {
  252. mTorqueWindow->charEvent.trigger(mTorqueWindow->getWindowId(), mLastMods, [text characterAtIndex:i]);
  253. mHandledAsCharEvent = true;
  254. }
  255. }
  256. #pragma mark -
  257. #pragma mark Application Delegate
  258. - (void)applicationDidResignActive:(NSNotification *)aNotification
  259. {
  260. smApplicationInactive = true;
  261. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), LoseFocus);
  262. [NSApp setDelegate:nil];
  263. }
  264. - (void)applicationDidHide:(NSNotification *)aNotification
  265. {
  266. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), LoseFocus);
  267. }
  268. - (void)applicationDidUnhide:(NSNotification *)aNotification
  269. {
  270. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), GainFocus);
  271. }
  272. #ifndef TORQUE_SHARED
  273. - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
  274. {
  275. Platform::postQuitMessage(0);
  276. return NSTerminateCancel;
  277. }
  278. #endif
  279. #pragma mark -
  280. #pragma mark Window Delegate
  281. - (BOOL)windowShouldClose:(NSWindow *)sender
  282. {
  283. // We close the window ourselves
  284. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), WindowDestroy);
  285. return NO;
  286. }
  287. - (void)windowWillClose:(NSNotification *) notification
  288. {
  289. mTorqueWindow->_disassociateCocoaWindow();
  290. }
  291. - (void)windowDidBecomeKey:(NSNotification *)notification
  292. {
  293. // when our window is the key window, we become the app delegate.
  294. PlatformWindow* focusWindow = WindowManager->getFocusedWindow();
  295. if(focusWindow && focusWindow != mTorqueWindow)
  296. focusWindow->appEvent.trigger(mTorqueWindow->getWindowId(), LoseFocus);
  297. [NSApp setDelegate:self];
  298. [self signalGainFocus];
  299. }
  300. - (void)windowDidResignKey:(NSNotification*)notification
  301. {
  302. mTorqueWindow->appEvent.trigger(mTorqueWindow->getWindowId(), LoseScreen);
  303. mTorqueWindow->_associateMouse();
  304. mTorqueWindow->setCursorVisible(true);
  305. [NSApp setDelegate:nil];
  306. }
  307. - (void)windowDidChangeScreen:(NSNotification*)notification
  308. {
  309. NSWindow* wnd = [notification object];
  310. // TODO: Add a category to NSScreen to deal with this
  311. CGDirectDisplayID disp = (CGDirectDisplayID)[[[[wnd screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
  312. mTorqueWindow->setDisplay(disp);
  313. }
  314. - (void)windowDidResize:(NSNotification*)notification
  315. {
  316. Point2I clientExtent = mTorqueWindow->getClientExtent();
  317. mTorqueWindow->resizeEvent.trigger(mTorqueWindow->getWindowId(), clientExtent.x, clientExtent.y);
  318. }
  319. #pragma mark -
  320. #pragma mark responder status
  321. - (BOOL)acceptsFirstResponder { return YES; }
  322. - (BOOL)becomeFirstResponder { return YES; }
  323. - (BOOL)resignFirstResponder { return YES; }
  324. // Basic implementation because NSResponder's default implementation can cause infinite loops when our keyDown: method calls interpretKeyEvents:
  325. - (void)doCommandBySelector:(SEL)aSelector
  326. {
  327. if([self respondsToSelector:aSelector])
  328. [self performSelector:aSelector withObject:nil];
  329. }
  330. @end