imgui_impl_osx.mm 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. // dear imgui: Platform Backend for OSX / Cocoa
  2. // This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..)
  3. // - Not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac.
  4. // - Requires linking with the GameController framework ("-framework GameController").
  5. // Implemented features:
  6. // [X] Platform: Clipboard support is part of core Dear ImGui (no specific code in this backend).
  7. // [X] Platform: Mouse support. Can discriminate Mouse/Pen.
  8. // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values are obsolete since 1.87 and not supported since 1.91.5]
  9. // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
  10. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
  11. // [X] Platform: IME support.
  12. // [x] Platform: Multi-viewport / platform windows.
  13. // Issues:
  14. // [ ] Platform: Multi-viewport: Window size not correctly reported when enabling io.ConfigViewportsNoDecoration
  15. // [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor).
  16. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
  17. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
  18. // Learn about Dear ImGui:
  19. // - FAQ https://dearimgui.com/faq
  20. // - Getting Started https://dearimgui.com/getting-started
  21. // - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
  22. // - Introduction, links and more at the top of imgui.cpp
  23. #import "imgui.h"
  24. #ifndef IMGUI_DISABLE
  25. #import "imgui_impl_osx.h"
  26. #import <Cocoa/Cocoa.h>
  27. #import <Carbon/Carbon.h>
  28. #import <GameController/GameController.h>
  29. #import <time.h>
  30. // CHANGELOG
  31. // (minor and older changes stripped away, please see git history for details)
  32. // 2025-XX-XX: Added support for multiple windows via the ImGuiPlatformIO interface.
  33. // 2025-01-20: Removed notification observer when shutting down. (#8331)
  34. // 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
  35. // - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn
  36. // - io.SetClipboardTextFn -> platform_io.Platform_SetClipboardTextFn
  37. // - io.PlatformSetImeDataFn -> platform_io.Platform_SetImeDataFn
  38. // 2024-07-02: Update for io.SetPlatformImeDataFn() -> io.PlatformSetImeDataFn() renaming in main library.
  39. // 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F20 function keys. Stopped mapping F13 into PrintScreen.
  40. // 2023-04-09: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_Pen.
  41. // 2023-02-01: Fixed scroll wheel scaling for devices emitting events with hasPreciseScrollingDeltas==false (e.g. non-Apple mices).
  42. // 2022-11-02: Fixed mouse coordinates before clicking the host window.
  43. // 2022-10-06: Fixed mouse inputs on flipped views.
  44. // 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
  45. // 2022-05-03: Inputs: Removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture.
  46. // 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
  47. // 2022-03-22: Inputs: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key
  48. // 2022-02-07: Inputs: Forward keyDown/keyUp events to OS when unused by dear imgui.
  49. // 2022-01-31: Fixed building with old Xcode versions that are missing gamepad features.
  50. // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
  51. // 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
  52. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
  53. // 2022-01-12: Inputs: Added basic Platform IME support, hooking the io.SetPlatformImeDataFn() function.
  54. // 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
  55. // 2021-12-13: *BREAKING CHANGE* Add NSView parameter to ImGui_ImplOSX_Init(). Generally fix keyboard support. Using kVK_* codes for keyboard keys.
  56. // 2021-12-13: Add game controller support.
  57. // 2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards.
  58. // 2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events.
  59. // 2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key.
  60. // 2021-04-19: Inputs: Added a fix for keys remaining stuck in pressed state when CMD-tabbing into different application.
  61. // 2021-01-27: Inputs: Added a fix for mouse position not being reported when mouse buttons other than left one are down.
  62. // 2020-10-28: Inputs: Added a fix for handling keypad-enter key.
  63. // 2020-05-25: Inputs: Added a fix for missing trackpad clicks when done with "soft tap".
  64. // 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
  65. // 2019-10-11: Inputs: Fix using Backspace key.
  66. // 2019-07-21: Re-added clipboard handlers as they are not enabled by default in core imgui.cpp (reverted 2019-05-18 change).
  67. // 2019-05-28: Inputs: Added mouse cursor shape and visibility support.
  68. // 2019-05-18: Misc: Removed clipboard handlers as they are now supported by core imgui.cpp.
  69. // 2019-05-11: Inputs: Don't filter character values before calling AddInputCharacter() apart from 0xF700..0xFFFF range.
  70. // 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
  71. // 2018-07-07: Initial version.
  72. #define APPLE_HAS_BUTTON_OPTIONS (__IPHONE_OS_VERSION_MIN_REQUIRED >= 130000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 || __TV_OS_VERSION_MIN_REQUIRED >= 130000)
  73. #define APPLE_HAS_CONTROLLER (__IPHONE_OS_VERSION_MIN_REQUIRED >= 140000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 110000 || __TV_OS_VERSION_MIN_REQUIRED >= 140000)
  74. #define APPLE_HAS_THUMBSTICKS (__IPHONE_OS_VERSION_MIN_REQUIRED >= 120100 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101401 || __TV_OS_VERSION_MIN_REQUIRED >= 120100)
  75. @class ImGuiObserver;
  76. @class KeyEventResponder;
  77. // Data
  78. struct ImGui_ImplOSX_Data
  79. {
  80. CFTimeInterval Time;
  81. NSCursor* MouseCursors[ImGuiMouseCursor_COUNT];
  82. bool MouseCursorHidden;
  83. ImGuiObserver* Observer;
  84. KeyEventResponder* KeyEventResponder;
  85. NSTextInputContext* InputContext;
  86. id Monitor;
  87. NSWindow* Window;
  88. ImGui_ImplOSX_Data() { memset((void*)this, 0, sizeof(*this)); }
  89. };
  90. static ImGui_ImplOSX_Data* ImGui_ImplOSX_GetBackendData() { return (ImGui_ImplOSX_Data*)ImGui::GetIO().BackendPlatformUserData; }
  91. static void ImGui_ImplOSX_DestroyBackendData() { IM_DELETE(ImGui_ImplOSX_GetBackendData()); }
  92. static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
  93. // Forward Declarations
  94. static void ImGui_ImplOSX_InitMultiViewportSupport();
  95. static void ImGui_ImplOSX_ShutdownMultiViewportSupport();
  96. static void ImGui_ImplOSX_UpdateMonitors();
  97. static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view);
  98. static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view);
  99. // Undocumented methods for creating cursors.
  100. @interface NSCursor()
  101. + (id)_windowResizeNorthWestSouthEastCursor;
  102. + (id)_windowResizeNorthEastSouthWestCursor;
  103. + (id)_windowResizeNorthSouthCursor;
  104. + (id)_windowResizeEastWestCursor;
  105. @end
  106. /**
  107. KeyEventResponder implements the NSTextInputClient protocol as is required by the macOS text input manager.
  108. The macOS text input manager is invoked by calling the interpretKeyEvents method from the keyDown method.
  109. Keyboard events are then evaluated by the macOS input manager and valid text input is passed back via the
  110. insertText:replacementRange method.
  111. This is the same approach employed by other cross-platform libraries such as SDL2:
  112. https://github.com/spurious/SDL-mirror/blob/e17aacbd09e65a4fd1e166621e011e581fb017a8/src/video/cocoa/SDL_cocoakeyboard.m#L53
  113. and GLFW:
  114. https://github.com/glfw/glfw/blob/b55a517ae0c7b5127dffa79a64f5406021bf9076/src/cocoa_window.m#L722-L723
  115. */
  116. @interface KeyEventResponder: NSView<NSTextInputClient>
  117. @end
  118. @implementation KeyEventResponder
  119. {
  120. float _posX;
  121. float _posY;
  122. NSRect _imeRect;
  123. }
  124. #pragma mark - Public
  125. - (void)setImePosX:(float)posX imePosY:(float)posY
  126. {
  127. _posX = posX;
  128. _posY = posY;
  129. }
  130. - (void)updateImePosWithView:(NSView *)view
  131. {
  132. NSWindow* window = view.window;
  133. if (!window)
  134. return;
  135. ImGuiIO& io = ImGui::GetIO();
  136. if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
  137. {
  138. NSRect frame = window.frame;
  139. NSRect contentRect = window.contentLayoutRect;
  140. if (window.styleMask & NSWindowStyleMaskFullSizeContentView) // No title bar windows should be considered.
  141. contentRect = frame;
  142. NSRect firstScreenFrame = NSScreen.screens[0].frame;
  143. _imeRect = NSMakeRect(_posX, _posY, 0, 0);
  144. _imeRect.origin.y = firstScreenFrame.size.height - _imeRect.size.height - _imeRect.origin.y; // Opposite of ConvertNSRect()
  145. }
  146. else
  147. {
  148. NSRect contentRect = [window contentRectForFrameRect:window.frame];
  149. NSRect rect = NSMakeRect(_posX, contentRect.size.height - _posY, 0, 0);
  150. _imeRect = [window convertRectToScreen:rect];
  151. }
  152. }
  153. - (void)viewDidMoveToWindow
  154. {
  155. // Ensure self is a first responder to receive the input events.
  156. [self.window makeFirstResponder:self];
  157. }
  158. - (void)keyDown:(NSEvent*)event
  159. {
  160. if (!ImGui_ImplOSX_HandleEvent(event, self))
  161. [super keyDown:event];
  162. // Call to the macOS input manager system.
  163. [self interpretKeyEvents:@[event]];
  164. }
  165. - (void)keyUp:(NSEvent*)event
  166. {
  167. if (!ImGui_ImplOSX_HandleEvent(event, self))
  168. [super keyUp:event];
  169. }
  170. - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
  171. {
  172. ImGuiIO& io = ImGui::GetIO();
  173. NSString* characters;
  174. if ([aString isKindOfClass:[NSAttributedString class]])
  175. characters = [aString string];
  176. else
  177. characters = (NSString*)aString;
  178. io.AddInputCharactersUTF8(characters.UTF8String);
  179. }
  180. - (BOOL)acceptsFirstResponder
  181. {
  182. return YES;
  183. }
  184. - (void)doCommandBySelector:(SEL)myselector
  185. {
  186. }
  187. - (nullable NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
  188. {
  189. return nil;
  190. }
  191. - (NSUInteger)characterIndexForPoint:(NSPoint)point
  192. {
  193. return 0;
  194. }
  195. - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
  196. {
  197. return _imeRect;
  198. }
  199. - (BOOL)hasMarkedText
  200. {
  201. return NO;
  202. }
  203. - (NSRange)markedRange
  204. {
  205. return NSMakeRange(NSNotFound, 0);
  206. }
  207. - (NSRange)selectedRange
  208. {
  209. return NSMakeRange(NSNotFound, 0);
  210. }
  211. - (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
  212. {
  213. }
  214. - (void)unmarkText
  215. {
  216. }
  217. - (nonnull NSArray<NSAttributedStringKey>*)validAttributesForMarkedText
  218. {
  219. return @[];
  220. }
  221. @end
  222. @interface ImGuiObserver : NSObject
  223. - (void)onApplicationBecomeActive:(NSNotification*)aNotification;
  224. - (void)onApplicationBecomeInactive:(NSNotification*)aNotification;
  225. - (void)displaysDidChange:(NSNotification*)aNotification;
  226. @end
  227. @implementation ImGuiObserver
  228. - (void)onApplicationBecomeActive:(NSNotification*)aNotification
  229. {
  230. ImGuiIO& io = ImGui::GetIO();
  231. io.AddFocusEvent(true);
  232. }
  233. - (void)onApplicationBecomeInactive:(NSNotification*)aNotification
  234. {
  235. ImGuiIO& io = ImGui::GetIO();
  236. io.AddFocusEvent(false);
  237. }
  238. - (void)displaysDidChange:(NSNotification*)aNotification
  239. {
  240. ImGui_ImplOSX_UpdateMonitors();
  241. }
  242. @end
  243. // Functions
  244. // Not static to allow third-party code to use that if they want to (but undocumented)
  245. ImGuiKey ImGui_ImplOSX_KeyCodeToImGuiKey(int key_code);
  246. ImGuiKey ImGui_ImplOSX_KeyCodeToImGuiKey(int key_code)
  247. {
  248. switch (key_code)
  249. {
  250. case kVK_ANSI_A: return ImGuiKey_A;
  251. case kVK_ANSI_S: return ImGuiKey_S;
  252. case kVK_ANSI_D: return ImGuiKey_D;
  253. case kVK_ANSI_F: return ImGuiKey_F;
  254. case kVK_ANSI_H: return ImGuiKey_H;
  255. case kVK_ANSI_G: return ImGuiKey_G;
  256. case kVK_ANSI_Z: return ImGuiKey_Z;
  257. case kVK_ANSI_X: return ImGuiKey_X;
  258. case kVK_ANSI_C: return ImGuiKey_C;
  259. case kVK_ANSI_V: return ImGuiKey_V;
  260. case kVK_ANSI_B: return ImGuiKey_B;
  261. case kVK_ANSI_Q: return ImGuiKey_Q;
  262. case kVK_ANSI_W: return ImGuiKey_W;
  263. case kVK_ANSI_E: return ImGuiKey_E;
  264. case kVK_ANSI_R: return ImGuiKey_R;
  265. case kVK_ANSI_Y: return ImGuiKey_Y;
  266. case kVK_ANSI_T: return ImGuiKey_T;
  267. case kVK_ANSI_1: return ImGuiKey_1;
  268. case kVK_ANSI_2: return ImGuiKey_2;
  269. case kVK_ANSI_3: return ImGuiKey_3;
  270. case kVK_ANSI_4: return ImGuiKey_4;
  271. case kVK_ANSI_6: return ImGuiKey_6;
  272. case kVK_ANSI_5: return ImGuiKey_5;
  273. case kVK_ANSI_Equal: return ImGuiKey_Equal;
  274. case kVK_ANSI_9: return ImGuiKey_9;
  275. case kVK_ANSI_7: return ImGuiKey_7;
  276. case kVK_ANSI_Minus: return ImGuiKey_Minus;
  277. case kVK_ANSI_8: return ImGuiKey_8;
  278. case kVK_ANSI_0: return ImGuiKey_0;
  279. case kVK_ANSI_RightBracket: return ImGuiKey_RightBracket;
  280. case kVK_ANSI_O: return ImGuiKey_O;
  281. case kVK_ANSI_U: return ImGuiKey_U;
  282. case kVK_ANSI_LeftBracket: return ImGuiKey_LeftBracket;
  283. case kVK_ANSI_I: return ImGuiKey_I;
  284. case kVK_ANSI_P: return ImGuiKey_P;
  285. case kVK_ANSI_L: return ImGuiKey_L;
  286. case kVK_ANSI_J: return ImGuiKey_J;
  287. case kVK_ANSI_Quote: return ImGuiKey_Apostrophe;
  288. case kVK_ANSI_K: return ImGuiKey_K;
  289. case kVK_ANSI_Semicolon: return ImGuiKey_Semicolon;
  290. case kVK_ANSI_Backslash: return ImGuiKey_Backslash;
  291. case kVK_ANSI_Comma: return ImGuiKey_Comma;
  292. case kVK_ANSI_Slash: return ImGuiKey_Slash;
  293. case kVK_ANSI_N: return ImGuiKey_N;
  294. case kVK_ANSI_M: return ImGuiKey_M;
  295. case kVK_ANSI_Period: return ImGuiKey_Period;
  296. case kVK_ANSI_Grave: return ImGuiKey_GraveAccent;
  297. case kVK_ANSI_KeypadDecimal: return ImGuiKey_KeypadDecimal;
  298. case kVK_ANSI_KeypadMultiply: return ImGuiKey_KeypadMultiply;
  299. case kVK_ANSI_KeypadPlus: return ImGuiKey_KeypadAdd;
  300. case kVK_ANSI_KeypadClear: return ImGuiKey_NumLock;
  301. case kVK_ANSI_KeypadDivide: return ImGuiKey_KeypadDivide;
  302. case kVK_ANSI_KeypadEnter: return ImGuiKey_KeypadEnter;
  303. case kVK_ANSI_KeypadMinus: return ImGuiKey_KeypadSubtract;
  304. case kVK_ANSI_KeypadEquals: return ImGuiKey_KeypadEqual;
  305. case kVK_ANSI_Keypad0: return ImGuiKey_Keypad0;
  306. case kVK_ANSI_Keypad1: return ImGuiKey_Keypad1;
  307. case kVK_ANSI_Keypad2: return ImGuiKey_Keypad2;
  308. case kVK_ANSI_Keypad3: return ImGuiKey_Keypad3;
  309. case kVK_ANSI_Keypad4: return ImGuiKey_Keypad4;
  310. case kVK_ANSI_Keypad5: return ImGuiKey_Keypad5;
  311. case kVK_ANSI_Keypad6: return ImGuiKey_Keypad6;
  312. case kVK_ANSI_Keypad7: return ImGuiKey_Keypad7;
  313. case kVK_ANSI_Keypad8: return ImGuiKey_Keypad8;
  314. case kVK_ANSI_Keypad9: return ImGuiKey_Keypad9;
  315. case kVK_Return: return ImGuiKey_Enter;
  316. case kVK_Tab: return ImGuiKey_Tab;
  317. case kVK_Space: return ImGuiKey_Space;
  318. case kVK_Delete: return ImGuiKey_Backspace;
  319. case kVK_Escape: return ImGuiKey_Escape;
  320. case kVK_CapsLock: return ImGuiKey_CapsLock;
  321. case kVK_Control: return ImGuiKey_LeftCtrl;
  322. case kVK_Shift: return ImGuiKey_LeftShift;
  323. case kVK_Option: return ImGuiKey_LeftAlt;
  324. case kVK_Command: return ImGuiKey_LeftSuper;
  325. case kVK_RightControl: return ImGuiKey_RightCtrl;
  326. case kVK_RightShift: return ImGuiKey_RightShift;
  327. case kVK_RightOption: return ImGuiKey_RightAlt;
  328. case kVK_RightCommand: return ImGuiKey_RightSuper;
  329. // case kVK_Function: return ImGuiKey_;
  330. // case kVK_VolumeUp: return ImGuiKey_;
  331. // case kVK_VolumeDown: return ImGuiKey_;
  332. // case kVK_Mute: return ImGuiKey_;
  333. case kVK_F1: return ImGuiKey_F1;
  334. case kVK_F2: return ImGuiKey_F2;
  335. case kVK_F3: return ImGuiKey_F3;
  336. case kVK_F4: return ImGuiKey_F4;
  337. case kVK_F5: return ImGuiKey_F5;
  338. case kVK_F6: return ImGuiKey_F6;
  339. case kVK_F7: return ImGuiKey_F7;
  340. case kVK_F8: return ImGuiKey_F8;
  341. case kVK_F9: return ImGuiKey_F9;
  342. case kVK_F10: return ImGuiKey_F10;
  343. case kVK_F11: return ImGuiKey_F11;
  344. case kVK_F12: return ImGuiKey_F12;
  345. case kVK_F13: return ImGuiKey_F13;
  346. case kVK_F14: return ImGuiKey_F14;
  347. case kVK_F15: return ImGuiKey_F15;
  348. case kVK_F16: return ImGuiKey_F16;
  349. case kVK_F17: return ImGuiKey_F17;
  350. case kVK_F18: return ImGuiKey_F18;
  351. case kVK_F19: return ImGuiKey_F19;
  352. case kVK_F20: return ImGuiKey_F20;
  353. case 0x6E: return ImGuiKey_Menu;
  354. case kVK_Help: return ImGuiKey_Insert;
  355. case kVK_Home: return ImGuiKey_Home;
  356. case kVK_PageUp: return ImGuiKey_PageUp;
  357. case kVK_ForwardDelete: return ImGuiKey_Delete;
  358. case kVK_End: return ImGuiKey_End;
  359. case kVK_PageDown: return ImGuiKey_PageDown;
  360. case kVK_LeftArrow: return ImGuiKey_LeftArrow;
  361. case kVK_RightArrow: return ImGuiKey_RightArrow;
  362. case kVK_DownArrow: return ImGuiKey_DownArrow;
  363. case kVK_UpArrow: return ImGuiKey_UpArrow;
  364. default: return ImGuiKey_None;
  365. }
  366. }
  367. #ifdef IMGUI_IMPL_METAL_CPP_EXTENSIONS
  368. IMGUI_IMPL_API bool ImGui_ImplOSX_Init(void* _Nonnull view) {
  369. return ImGui_ImplOSX_Init((__bridge NSView*)(view));
  370. }
  371. IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(void* _Nullable view) {
  372. return ImGui_ImplOSX_NewFrame((__bridge NSView*)(view));
  373. }
  374. #endif
  375. bool ImGui_ImplOSX_Init(NSView* view)
  376. {
  377. ImGuiIO& io = ImGui::GetIO();
  378. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
  379. IMGUI_CHECKVERSION();
  380. IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
  381. // Setup backend capabilities flags
  382. ImGui_ImplOSX_Data* bd = IM_NEW(ImGui_ImplOSX_Data)();
  383. io.BackendPlatformUserData = (void*)bd;
  384. io.BackendPlatformName = "imgui_impl_osx";
  385. io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
  386. //io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
  387. io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
  388. //io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can call io.AddMouseViewportEvent() with correct data (optional)
  389. bd->Observer = [ImGuiObserver new];
  390. bd->Window = view.window ?: NSApp.orderedWindows.firstObject;
  391. ImGuiViewport* main_viewport = ImGui::GetMainViewport();
  392. main_viewport->PlatformHandle = main_viewport->PlatformHandleRaw = (__bridge_retained void*)bd->Window;
  393. ImGui_ImplOSX_UpdateMonitors();
  394. ImGui_ImplOSX_InitMultiViewportSupport();
  395. // Load cursors. Some of them are undocumented.
  396. bd->MouseCursorHidden = false;
  397. bd->MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor];
  398. bd->MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor];
  399. bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor];
  400. bd->MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor];
  401. bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor];
  402. bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
  403. bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
  404. bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
  405. bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
  406. // Note that imgui.cpp also include default OSX clipboard handlers which can be enabled
  407. // by adding '#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS' in imconfig.h and adding '-framework ApplicationServices' to your linker command-line.
  408. // Since we are already in ObjC land here, it is easy for us to add a clipboard handler using the NSPasteboard api.
  409. platform_io.Platform_SetClipboardTextFn = [](ImGuiContext*, const char* str) -> void
  410. {
  411. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  412. [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
  413. [pasteboard setString:[NSString stringWithUTF8String:str] forType:NSPasteboardTypeString];
  414. };
  415. platform_io.Platform_GetClipboardTextFn = [](ImGuiContext*) -> const char*
  416. {
  417. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  418. NSString* available = [pasteboard availableTypeFromArray: [NSArray arrayWithObject:NSPasteboardTypeString]];
  419. if (![available isEqualToString:NSPasteboardTypeString])
  420. return nullptr;
  421. NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
  422. if (string == nil)
  423. return nullptr;
  424. const char* string_c = (const char*)[string UTF8String];
  425. size_t string_len = strlen(string_c);
  426. static ImVector<char> s_clipboard;
  427. s_clipboard.resize((int)string_len + 1);
  428. strcpy(s_clipboard.Data, string_c);
  429. return s_clipboard.Data;
  430. };
  431. [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
  432. selector:@selector(onApplicationBecomeActive:)
  433. name:NSApplicationDidBecomeActiveNotification
  434. object:nil];
  435. [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
  436. selector:@selector(onApplicationBecomeInactive:)
  437. name:NSApplicationDidResignActiveNotification
  438. object:nil];
  439. // Add the NSTextInputClient to the view hierarchy,
  440. // to receive keyboard events and translate them to input text.
  441. bd->KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
  442. bd->InputContext = [[NSTextInputContext alloc] initWithClient:bd->KeyEventResponder];
  443. [view addSubview:bd->KeyEventResponder];
  444. ImGui_ImplOSX_AddTrackingArea(view);
  445. platform_io.Platform_SetImeDataFn = [](ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) -> void
  446. {
  447. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  448. if (data->WantVisible)
  449. {
  450. [bd->InputContext activate];
  451. }
  452. else
  453. {
  454. [bd->InputContext discardMarkedText];
  455. [bd->InputContext invalidateCharacterCoordinates];
  456. [bd->InputContext deactivate];
  457. }
  458. [bd->KeyEventResponder setImePosX:data->InputPos.x imePosY:data->InputPos.y + data->InputLineHeight];
  459. };
  460. return true;
  461. }
  462. void ImGui_ImplOSX_Shutdown()
  463. {
  464. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  465. IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
  466. [[NSNotificationCenter defaultCenter] removeObserver:bd->Observer];
  467. bd->Observer = nullptr;
  468. if (bd->Monitor != nullptr)
  469. {
  470. [NSEvent removeMonitor:bd->Monitor];
  471. bd->Monitor = nullptr;
  472. }
  473. ImGui_ImplOSX_ShutdownMultiViewportSupport();
  474. ImGui_ImplOSX_DestroyBackendData();
  475. ImGuiIO& io = ImGui::GetIO();
  476. io.BackendPlatformName = nullptr;
  477. io.BackendPlatformUserData = nullptr;
  478. io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_PlatformHasViewports);
  479. }
  480. static void ImGui_ImplOSX_UpdateMouseCursor()
  481. {
  482. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  483. ImGuiIO& io = ImGui::GetIO();
  484. if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
  485. return;
  486. ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
  487. if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
  488. {
  489. // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
  490. if (!bd->MouseCursorHidden)
  491. {
  492. bd->MouseCursorHidden = true;
  493. [NSCursor hide];
  494. }
  495. }
  496. else
  497. {
  498. NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow];
  499. // -[NSCursor set] generates measureable overhead if called unconditionally.
  500. if (desired != NSCursor.currentCursor)
  501. {
  502. [desired set];
  503. }
  504. if (bd->MouseCursorHidden)
  505. {
  506. bd->MouseCursorHidden = false;
  507. [NSCursor unhide];
  508. }
  509. }
  510. }
  511. static void ImGui_ImplOSX_UpdateGamepads()
  512. {
  513. ImGuiIO& io = ImGui::GetIO();
  514. if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
  515. return;
  516. #if APPLE_HAS_CONTROLLER
  517. GCController* controller = GCController.current;
  518. #else
  519. GCController* controller = GCController.controllers.firstObject;
  520. #endif
  521. if (controller == nil || controller.extendedGamepad == nil)
  522. {
  523. io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
  524. return;
  525. }
  526. GCExtendedGamepad* gp = controller.extendedGamepad;
  527. // Update gamepad inputs
  528. #define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
  529. #define MAP_BUTTON(KEY_NO, BUTTON_NAME) { io.AddKeyEvent(KEY_NO, gp.BUTTON_NAME.isPressed); }
  530. #define MAP_ANALOG(KEY_NO, AXIS_NAME, V0, V1) { float vn = (float)(gp.AXIS_NAME.value - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
  531. const float thumb_dead_zone = 0.0f;
  532. #if APPLE_HAS_BUTTON_OPTIONS
  533. MAP_BUTTON(ImGuiKey_GamepadBack, buttonOptions);
  534. #endif
  535. MAP_BUTTON(ImGuiKey_GamepadFaceLeft, buttonX); // Xbox X, PS Square
  536. MAP_BUTTON(ImGuiKey_GamepadFaceRight, buttonB); // Xbox B, PS Circle
  537. MAP_BUTTON(ImGuiKey_GamepadFaceUp, buttonY); // Xbox Y, PS Triangle
  538. MAP_BUTTON(ImGuiKey_GamepadFaceDown, buttonA); // Xbox A, PS Cross
  539. MAP_BUTTON(ImGuiKey_GamepadDpadLeft, dpad.left);
  540. MAP_BUTTON(ImGuiKey_GamepadDpadRight, dpad.right);
  541. MAP_BUTTON(ImGuiKey_GamepadDpadUp, dpad.up);
  542. MAP_BUTTON(ImGuiKey_GamepadDpadDown, dpad.down);
  543. MAP_ANALOG(ImGuiKey_GamepadL1, leftShoulder, 0.0f, 1.0f);
  544. MAP_ANALOG(ImGuiKey_GamepadR1, rightShoulder, 0.0f, 1.0f);
  545. MAP_ANALOG(ImGuiKey_GamepadL2, leftTrigger, 0.0f, 1.0f);
  546. MAP_ANALOG(ImGuiKey_GamepadR2, rightTrigger, 0.0f, 1.0f);
  547. #if APPLE_HAS_THUMBSTICKS
  548. MAP_BUTTON(ImGuiKey_GamepadL3, leftThumbstickButton);
  549. MAP_BUTTON(ImGuiKey_GamepadR3, rightThumbstickButton);
  550. #endif
  551. MAP_ANALOG(ImGuiKey_GamepadLStickLeft, leftThumbstick.xAxis, -thumb_dead_zone, -1.0f);
  552. MAP_ANALOG(ImGuiKey_GamepadLStickRight, leftThumbstick.xAxis, +thumb_dead_zone, +1.0f);
  553. MAP_ANALOG(ImGuiKey_GamepadLStickUp, leftThumbstick.yAxis, +thumb_dead_zone, +1.0f);
  554. MAP_ANALOG(ImGuiKey_GamepadLStickDown, leftThumbstick.yAxis, -thumb_dead_zone, -1.0f);
  555. MAP_ANALOG(ImGuiKey_GamepadRStickLeft, rightThumbstick.xAxis, -thumb_dead_zone, -1.0f);
  556. MAP_ANALOG(ImGuiKey_GamepadRStickRight, rightThumbstick.xAxis, +thumb_dead_zone, +1.0f);
  557. MAP_ANALOG(ImGuiKey_GamepadRStickUp, rightThumbstick.yAxis, +thumb_dead_zone, +1.0f);
  558. MAP_ANALOG(ImGuiKey_GamepadRStickDown, rightThumbstick.yAxis, -thumb_dead_zone, -1.0f);
  559. #undef MAP_BUTTON
  560. #undef MAP_ANALOG
  561. io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
  562. }
  563. static void ImGui_ImplOSX_UpdateImePosWithView(NSView* view)
  564. {
  565. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  566. ImGuiIO& io = ImGui::GetIO();
  567. if (io.WantTextInput)
  568. [bd->KeyEventResponder updateImePosWithView:view];
  569. }
  570. void ImGui_ImplOSX_NewFrame(NSView* view)
  571. {
  572. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  573. IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOSX_Init()?");
  574. ImGuiIO& io = ImGui::GetIO();
  575. // Setup display size
  576. if (view)
  577. {
  578. const float dpi = (float)[view.window backingScaleFactor];
  579. io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height);
  580. io.DisplayFramebufferScale = ImVec2(dpi, dpi);
  581. }
  582. // Setup time step
  583. if (bd->Time == 0.0)
  584. bd->Time = GetMachAbsoluteTimeInSeconds();
  585. double current_time = GetMachAbsoluteTimeInSeconds();
  586. io.DeltaTime = (float)(current_time - bd->Time);
  587. bd->Time = current_time;
  588. ImGui_ImplOSX_UpdateMouseCursor();
  589. ImGui_ImplOSX_UpdateGamepads();
  590. ImGui_ImplOSX_UpdateImePosWithView(view);
  591. }
  592. // Must only be called for a mouse event, otherwise an exception occurs
  593. // (Note that NSEventTypeScrollWheel is considered "other input". Oddly enough an exception does not occur with it, but the value will sometimes be wrong!)
  594. static ImGuiMouseSource GetMouseSource(NSEvent* event)
  595. {
  596. switch (event.subtype)
  597. {
  598. case NSEventSubtypeTabletPoint:
  599. return ImGuiMouseSource_Pen;
  600. // macOS considers input from relative touch devices (like the trackpad or Apple Magic Mouse) to be touch input.
  601. // This doesn't really make sense for Dear ImGui, which expects absolute touch devices only.
  602. // There does not seem to be a simple way to disambiguate things here so we consider NSEventSubtypeTouch events to always come from mice.
  603. // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingTouchEvents/HandlingTouchEvents.html#//apple_ref/doc/uid/10000060i-CH13-SW24
  604. //case NSEventSubtypeTouch:
  605. // return ImGuiMouseSource_TouchScreen;
  606. case NSEventSubtypeMouseEvent:
  607. default:
  608. return ImGuiMouseSource_Mouse;
  609. }
  610. }
  611. static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
  612. {
  613. ImGuiIO& io = ImGui::GetIO();
  614. if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown)
  615. {
  616. int button = (int)[event buttonNumber];
  617. if (button >= 0 && button < ImGuiMouseButton_COUNT)
  618. {
  619. io.AddMouseSourceEvent(GetMouseSource(event));
  620. io.AddMouseButtonEvent(button, true);
  621. }
  622. return io.WantCaptureMouse;
  623. }
  624. if (event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp)
  625. {
  626. int button = (int)[event buttonNumber];
  627. if (button >= 0 && button < ImGuiMouseButton_COUNT)
  628. {
  629. io.AddMouseSourceEvent(GetMouseSource(event));
  630. io.AddMouseButtonEvent(button, false);
  631. }
  632. return io.WantCaptureMouse;
  633. }
  634. if (event.type == NSEventTypeMouseMoved || event.type == NSEventTypeLeftMouseDragged || event.type == NSEventTypeRightMouseDragged || event.type == NSEventTypeOtherMouseDragged)
  635. {
  636. NSPoint mousePoint;
  637. if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
  638. {
  639. mousePoint = NSEvent.mouseLocation;
  640. mousePoint.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - mousePoint.y; // Normalize y coordinate to top-left of main display.
  641. }
  642. else
  643. {
  644. mousePoint = event.locationInWindow;
  645. if (event.window == nil)
  646. mousePoint = [[view window] convertPointFromScreen:mousePoint];
  647. mousePoint = [view convertPoint:mousePoint fromView:nil]; // Convert to local coordinates of view
  648. if ([view isFlipped])
  649. mousePoint = NSMakePoint(mousePoint.x, mousePoint.y);
  650. else
  651. mousePoint = NSMakePoint(mousePoint.x, view.bounds.size.height - mousePoint.y);
  652. }
  653. io.AddMouseSourceEvent(GetMouseSource(event));
  654. io.AddMousePosEvent((float)mousePoint.x, (float)mousePoint.y);
  655. return io.WantCaptureMouse;
  656. }
  657. if (event.type == NSEventTypeScrollWheel)
  658. {
  659. // Ignore canceled events.
  660. //
  661. // From macOS 12.1, scrolling with two fingers and then decelerating
  662. // by tapping two fingers results in two events appearing:
  663. //
  664. // 1. A scroll wheel NSEvent, with a phase == NSEventPhaseMayBegin, when the user taps
  665. // two fingers to decelerate or stop the scroll events.
  666. //
  667. // 2. A scroll wheel NSEvent, with a phase == NSEventPhaseCancelled, when the user releases the
  668. // two-finger tap. It is this event that sometimes contains large values for scrollingDeltaX and
  669. // scrollingDeltaY. When these are added to the current x and y positions of the scrolling view,
  670. // it appears to jump up or down. It can be observed in Preview, various JetBrains IDEs and here.
  671. if (event.phase == NSEventPhaseCancelled)
  672. return false;
  673. double wheel_dx = 0.0;
  674. double wheel_dy = 0.0;
  675. #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
  676. if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6)
  677. {
  678. wheel_dx = [event scrollingDeltaX];
  679. wheel_dy = [event scrollingDeltaY];
  680. if ([event hasPreciseScrollingDeltas])
  681. {
  682. wheel_dx *= 0.01;
  683. wheel_dy *= 0.01;
  684. }
  685. }
  686. else
  687. #endif // MAC_OS_X_VERSION_MAX_ALLOWED
  688. {
  689. wheel_dx = [event deltaX] * 0.1;
  690. wheel_dy = [event deltaY] * 0.1;
  691. }
  692. if (wheel_dx != 0.0 || wheel_dy != 0.0)
  693. io.AddMouseWheelEvent((float)wheel_dx, (float)wheel_dy);
  694. return io.WantCaptureMouse;
  695. }
  696. if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp)
  697. {
  698. if ([event isARepeat])
  699. return io.WantCaptureKeyboard;
  700. int key_code = (int)[event keyCode];
  701. ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
  702. io.AddKeyEvent(key, event.type == NSEventTypeKeyDown);
  703. io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
  704. return io.WantCaptureKeyboard;
  705. }
  706. if (event.type == NSEventTypeFlagsChanged)
  707. {
  708. unsigned short key_code = [event keyCode];
  709. NSEventModifierFlags modifier_flags = [event modifierFlags];
  710. io.AddKeyEvent(ImGuiMod_Shift, (modifier_flags & NSEventModifierFlagShift) != 0);
  711. io.AddKeyEvent(ImGuiMod_Ctrl, (modifier_flags & NSEventModifierFlagControl) != 0);
  712. io.AddKeyEvent(ImGuiMod_Alt, (modifier_flags & NSEventModifierFlagOption) != 0);
  713. io.AddKeyEvent(ImGuiMod_Super, (modifier_flags & NSEventModifierFlagCommand) != 0);
  714. ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
  715. if (key != ImGuiKey_None)
  716. {
  717. // macOS does not generate down/up event for modifiers. We're trying
  718. // to use hardware dependent masks to extract that information.
  719. // 'imgui_mask' is left as a fallback.
  720. NSEventModifierFlags mask = 0;
  721. switch (key)
  722. {
  723. case ImGuiKey_LeftCtrl: mask = 0x0001; break;
  724. case ImGuiKey_RightCtrl: mask = 0x2000; break;
  725. case ImGuiKey_LeftShift: mask = 0x0002; break;
  726. case ImGuiKey_RightShift: mask = 0x0004; break;
  727. case ImGuiKey_LeftSuper: mask = 0x0008; break;
  728. case ImGuiKey_RightSuper: mask = 0x0010; break;
  729. case ImGuiKey_LeftAlt: mask = 0x0020; break;
  730. case ImGuiKey_RightAlt: mask = 0x0040; break;
  731. default:
  732. return io.WantCaptureKeyboard;
  733. }
  734. NSEventModifierFlags modifier_flags = [event modifierFlags];
  735. io.AddKeyEvent(key, (modifier_flags & mask) != 0);
  736. io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
  737. }
  738. return io.WantCaptureKeyboard;
  739. }
  740. return false;
  741. }
  742. static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view)
  743. {
  744. // If we want to receive key events, we either need to be in the responder chain of the key view,
  745. // or else we can install a local monitor. The consequence of this heavy-handed approach is that
  746. // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our
  747. // window, we'd want to be much more careful than just ingesting the complete event stream.
  748. // To match the behavior of other backends, we pass every event down to the OS.
  749. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  750. if (bd->Monitor)
  751. return;
  752. NSEventMask eventMask = 0;
  753. eventMask |= NSEventMaskMouseMoved | NSEventMaskScrollWheel;
  754. eventMask |= NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
  755. eventMask |= NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | NSEventMaskRightMouseDragged;
  756. eventMask |= NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp | NSEventMaskOtherMouseDragged;
  757. eventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
  758. bd->Monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask
  759. handler:^NSEvent* _Nullable(NSEvent* event)
  760. {
  761. ImGui_ImplOSX_HandleEvent(event, view);
  762. return event;
  763. }];
  764. }
  765. //--------------------------------------------------------------------------------------------------------
  766. // MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
  767. // This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports simultaneously.
  768. // If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
  769. //--------------------------------------------------------------------------------------------------------
  770. struct ImGuiViewportDataOSX
  771. {
  772. NSWindow* Window;
  773. bool WindowOwned;
  774. ImGuiViewportDataOSX() { WindowOwned = false; }
  775. ~ImGuiViewportDataOSX() { IM_ASSERT(Window == nil); }
  776. };
  777. @interface ImGui_ImplOSX_Window: NSWindow
  778. @end
  779. @implementation ImGui_ImplOSX_Window
  780. - (BOOL)canBecomeKeyWindow
  781. {
  782. return YES;
  783. }
  784. @end
  785. static void ConvertNSRect(NSRect* r)
  786. {
  787. NSRect firstScreenFrame = NSScreen.screens[0].frame;
  788. IM_ASSERT(firstScreenFrame.origin.x == 0 && firstScreenFrame.origin.y == 0);
  789. r->origin.y = firstScreenFrame.size.height - r->origin.y - r->size.height;
  790. }
  791. static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport)
  792. {
  793. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  794. ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)();
  795. viewport->PlatformUserData = data;
  796. NSScreen* screen = bd->Window.screen;
  797. NSRect rect = NSMakeRect(viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y);
  798. ConvertNSRect(&rect);
  799. NSWindowStyleMask styleMask = 0;
  800. if (viewport->Flags & ImGuiViewportFlags_NoDecoration)
  801. styleMask |= NSWindowStyleMaskBorderless;
  802. else
  803. styleMask |= NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
  804. NSWindow* window = [[ImGui_ImplOSX_Window alloc] initWithContentRect:rect
  805. styleMask:styleMask
  806. backing:NSBackingStoreBuffered
  807. defer:YES
  808. screen:screen];
  809. if (viewport->Flags & ImGuiViewportFlags_TopMost)
  810. [window setLevel:NSFloatingWindowLevel];
  811. window.title = @"Untitled";
  812. window.opaque = YES;
  813. KeyEventResponder* view = [[KeyEventResponder alloc] initWithFrame:rect];
  814. if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6 && ceil(NSAppKitVersionNumber) < NSAppKitVersionNumber10_15)
  815. [view setWantsBestResolutionOpenGLSurface:YES];
  816. window.contentView = view;
  817. data->Window = window;
  818. data->WindowOwned = true;
  819. viewport->PlatformRequestResize = false;
  820. viewport->PlatformHandle = viewport->PlatformHandleRaw = (__bridge_retained void*)window;
  821. }
  822. static void ImGui_ImplOSX_DestroyWindow(ImGuiViewport* viewport)
  823. {
  824. NSWindow* window = (__bridge_transfer NSWindow*)viewport->PlatformHandleRaw;
  825. window = nil;
  826. if (ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData)
  827. {
  828. NSWindow* window = data->Window;
  829. if (window != nil && data->WindowOwned)
  830. {
  831. window.contentView = nil;
  832. window.contentViewController = nil;
  833. [window orderOut:nil];
  834. }
  835. data->Window = nil;
  836. IM_DELETE(data);
  837. }
  838. viewport->PlatformUserData = viewport->PlatformHandle = viewport->PlatformHandleRaw = nullptr;
  839. }
  840. static void ImGui_ImplOSX_ShowWindow(ImGuiViewport* viewport)
  841. {
  842. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  843. IM_ASSERT(data->Window != 0);
  844. if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing)
  845. [data->Window orderFront:nil];
  846. else
  847. [data->Window makeKeyAndOrderFront:nil];
  848. [data->Window setIsVisible:YES];
  849. }
  850. static ImVec2 ImGui_ImplOSX_GetWindowPos(ImGuiViewport* viewport)
  851. {
  852. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  853. IM_ASSERT(data->Window != 0);
  854. NSWindow* window = data->Window;
  855. NSRect frame = window.frame;
  856. NSRect contentRect = window.contentLayoutRect;
  857. if (window.styleMask & NSWindowStyleMaskFullSizeContentView) // No title bar windows should be considered.
  858. contentRect = frame;
  859. NSRect firstScreenFrame = NSScreen.screens[0].frame;
  860. return ImVec2(frame.origin.x, firstScreenFrame.size.height - frame.origin.y - contentRect.size.height);
  861. }
  862. static void ImGui_ImplOSX_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
  863. {
  864. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  865. IM_ASSERT(data->Window != 0);
  866. NSWindow* window = data->Window;
  867. NSSize size = window.frame.size;
  868. NSRect r = NSMakeRect(pos.x, pos.y, size.width, size.height);
  869. ConvertNSRect(&r);
  870. [window setFrameOrigin:r.origin];
  871. }
  872. static ImVec2 ImGui_ImplOSX_GetWindowSize(ImGuiViewport* viewport)
  873. {
  874. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  875. IM_ASSERT(data->Window != 0);
  876. NSWindow* window = data->Window;
  877. NSSize size = window.contentLayoutRect.size;
  878. return ImVec2(size.width, size.height);
  879. }
  880. static void ImGui_ImplOSX_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
  881. {
  882. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  883. IM_ASSERT(data->Window != 0);
  884. NSWindow* window = data->Window;
  885. NSRect rect = window.frame;
  886. rect.origin.y -= (size.y - rect.size.height);
  887. rect.size.width = size.x;
  888. rect.size.height = size.y;
  889. [window setFrame:rect display:YES];
  890. }
  891. static void ImGui_ImplOSX_SetWindowFocus(ImGuiViewport* viewport)
  892. {
  893. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  894. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  895. IM_ASSERT(data->Window != 0);
  896. [data->Window makeKeyAndOrderFront:bd->Window];
  897. }
  898. static bool ImGui_ImplOSX_GetWindowFocus(ImGuiViewport* viewport)
  899. {
  900. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  901. IM_ASSERT(data->Window != 0);
  902. return data->Window.isKeyWindow;
  903. }
  904. static bool ImGui_ImplOSX_GetWindowMinimized(ImGuiViewport* viewport)
  905. {
  906. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  907. IM_ASSERT(data->Window != 0);
  908. return data->Window.isMiniaturized;
  909. }
  910. static void ImGui_ImplOSX_SetWindowTitle(ImGuiViewport* viewport, const char* title)
  911. {
  912. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  913. IM_ASSERT(data->Window != 0);
  914. data->Window.title = [NSString stringWithUTF8String:title];
  915. }
  916. static void ImGui_ImplOSX_SetWindowAlpha(ImGuiViewport* viewport, float alpha)
  917. {
  918. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  919. IM_ASSERT(data->Window != 0);
  920. IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f);
  921. data->Window.alphaValue = alpha;
  922. }
  923. static float ImGui_ImplOSX_GetWindowDpiScale(ImGuiViewport* viewport)
  924. {
  925. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData;
  926. IM_ASSERT(data->Window != 0);
  927. return data->Window.backingScaleFactor;
  928. }
  929. static void ImGui_ImplOSX_UpdateMonitors()
  930. {
  931. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
  932. platform_io.Monitors.resize(0);
  933. NSRect firstScreenFrame = NSScreen.screens[0].frame;
  934. IM_ASSERT(firstScreenFrame.origin.x == 0 && firstScreenFrame.origin.y == 0);
  935. for (NSScreen* screen in NSScreen.screens)
  936. {
  937. NSRect frame = screen.frame;
  938. NSRect visibleFrame = screen.visibleFrame;
  939. ConvertNSRect(&frame);
  940. ConvertNSRect(&visibleFrame);
  941. ImGuiPlatformMonitor imgui_monitor;
  942. imgui_monitor.MainPos = ImVec2(frame.origin.x, frame.origin.y);
  943. imgui_monitor.MainSize = ImVec2(frame.size.width, frame.size.height);
  944. imgui_monitor.WorkPos = ImVec2(visibleFrame.origin.x, visibleFrame.origin.y);
  945. imgui_monitor.WorkSize = ImVec2(visibleFrame.size.width, visibleFrame.size.height);
  946. imgui_monitor.DpiScale = screen.backingScaleFactor;
  947. imgui_monitor.PlatformHandle = (__bridge_retained void*)screen;
  948. platform_io.Monitors.push_back(imgui_monitor);
  949. }
  950. }
  951. static void ImGui_ImplOSX_InitMultiViewportSupport()
  952. {
  953. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  954. // Register platform interface (will be coupled with a renderer interface)
  955. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
  956. platform_io.Platform_CreateWindow = ImGui_ImplOSX_CreateWindow;
  957. platform_io.Platform_DestroyWindow = ImGui_ImplOSX_DestroyWindow;
  958. platform_io.Platform_ShowWindow = ImGui_ImplOSX_ShowWindow;
  959. platform_io.Platform_SetWindowPos = ImGui_ImplOSX_SetWindowPos;
  960. platform_io.Platform_GetWindowPos = ImGui_ImplOSX_GetWindowPos;
  961. platform_io.Platform_SetWindowSize = ImGui_ImplOSX_SetWindowSize;
  962. platform_io.Platform_GetWindowSize = ImGui_ImplOSX_GetWindowSize;
  963. platform_io.Platform_SetWindowFocus = ImGui_ImplOSX_SetWindowFocus;
  964. platform_io.Platform_GetWindowFocus = ImGui_ImplOSX_GetWindowFocus;
  965. platform_io.Platform_GetWindowMinimized = ImGui_ImplOSX_GetWindowMinimized;
  966. platform_io.Platform_SetWindowTitle = ImGui_ImplOSX_SetWindowTitle;
  967. platform_io.Platform_SetWindowAlpha = ImGui_ImplOSX_SetWindowAlpha;
  968. platform_io.Platform_GetWindowDpiScale = ImGui_ImplOSX_GetWindowDpiScale; // FIXME-DPI
  969. // Register main window handle (which is owned by the main application, not by us)
  970. ImGuiViewport* main_viewport = ImGui::GetMainViewport();
  971. ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)();
  972. data->Window = bd->Window;
  973. data->WindowOwned = false;
  974. main_viewport->PlatformUserData = data;
  975. main_viewport->PlatformHandle = (__bridge void*)bd->Window;
  976. [NSNotificationCenter.defaultCenter addObserver:bd->Observer
  977. selector:@selector(displaysDidChange:)
  978. name:NSApplicationDidChangeScreenParametersNotification
  979. object:nil];
  980. }
  981. static void ImGui_ImplOSX_ShutdownMultiViewportSupport()
  982. {
  983. ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
  984. [NSNotificationCenter.defaultCenter removeObserver:bd->Observer
  985. name:NSApplicationDidChangeScreenParametersNotification
  986. object:nil];
  987. bd->Observer = nullptr;
  988. bd->Window = nullptr;
  989. if (bd->Monitor != nullptr)
  990. {
  991. [NSEvent removeMonitor:bd->Monitor];
  992. bd->Monitor = nullptr;
  993. }
  994. ImGuiViewport* main_viewport = ImGui::GetMainViewport();
  995. ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)main_viewport->PlatformUserData;
  996. IM_DELETE(data);
  997. main_viewport->PlatformUserData = nullptr;
  998. ImGui::DestroyPlatformWindows();
  999. }
  1000. //-----------------------------------------------------------------------------
  1001. #endif // #ifndef IMGUI_DISABLE