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