Skip to content

Commit f56bf5f

Browse files
committed
fix: OSX backend improvements
* Fixed keyboard support using kVK_ codes * Added gamepad support via GameController framework * Updated example_apple_metal and example_apple_opengl2 projects NOTE: ImGui_ImplOSX_Init now requires the view that our keyboard responder will be attached to. In doing so, the macOS backend input behaves similarly to GLFW and SDL2
1 parent 66f0fb9 commit f56bf5f

File tree

6 files changed

+202
-98
lines changed

6 files changed

+202
-98
lines changed

backends/imgui_impl_osx.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
@class NSEvent;
1919
@class NSView;
2020

21-
IMGUI_IMPL_API bool ImGui_ImplOSX_Init();
21+
IMGUI_IMPL_API bool ImGui_ImplOSX_Init(NSView * _Nonnull view);
2222
IMGUI_IMPL_API void ImGui_ImplOSX_Shutdown();
2323
IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(NSView* _Nullable view);
2424
IMGUI_IMPL_API bool ImGui_ImplOSX_HandleEvent(NSEvent* _Nonnull event, NSView* _Nullable view);

backends/imgui_impl_osx.mm

Lines changed: 184 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@
55
// Implemented features:
66
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
77
// [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
8-
// Issues:
9-
// [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters]..
8+
// [X] Platform: Keyboard arrays indexed using kVK_* codes, e.g. ImGui::IsKeyPressed(kVK_Space).
9+
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
1010

1111
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
1212
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
1313
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
1414
// Read online: https://github.com/ocornut/imgui/tree/master/docs
1515

16-
#include "imgui.h"
17-
#include "imgui_impl_osx.h"
16+
#import "imgui.h"
17+
#import "imgui_impl_osx.h"
1818
#import <Cocoa/Cocoa.h>
19-
#include <mach/mach_time.h>
19+
#import <mach/mach_time.h>
20+
#import <Carbon/Carbon.h>
21+
#import <GameController/GameController.h>
2022

2123
// CHANGELOG
2224
// (minor and older changes stripped away, please see git history for details)
25+
// 2021-11-23: Fix keyboard support, add game controller support
2326
// 2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards.
2427
// 2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events.
2528
// 2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key.
@@ -37,6 +40,7 @@
3740
// 2018-07-07: Initial version.
3841

3942
@class ImFocusObserver;
43+
@class KeyEventResponder;
4044

4145
// Data
4246
static double g_HostClockPeriod = 0.0;
@@ -46,6 +50,7 @@
4650
static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {};
4751
static bool g_MouseDown[ImGuiMouseButton_COUNT] = {};
4852
static ImFocusObserver* g_FocusObserver = NULL;
53+
static KeyEventResponder* g_KeyEventResponder = nil;
4954

5055
// Undocumented methods for creating cursors.
5156
@interface NSCursor()
@@ -74,6 +79,84 @@ static void resetKeys()
7479
io.KeyCtrl = io.KeyShift = io.KeyAlt = io.KeySuper = false;
7580
}
7681

82+
@interface KeyEventResponder: NSView<NSTextInputClient>
83+
@end
84+
85+
@implementation KeyEventResponder
86+
87+
- (void)viewDidMoveToWindow
88+
{
89+
[self.window makeFirstResponder:self];
90+
}
91+
92+
- (void)keyDown:(NSEvent *)event {
93+
[self interpretKeyEvents:@[event]];
94+
}
95+
96+
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
97+
{
98+
ImGuiIO& io = ImGui::GetIO();
99+
100+
NSString *characters;
101+
if ([aString isKindOfClass:[NSAttributedString class]])
102+
characters = [aString string];
103+
else
104+
characters = (NSString *)aString;
105+
106+
io.AddInputCharactersUTF8(characters.UTF8String);
107+
}
108+
109+
- (BOOL)acceptsFirstResponder {
110+
return YES;
111+
}
112+
113+
- (void)doCommandBySelector:(SEL)myselector
114+
{
115+
}
116+
117+
- (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange {
118+
return nil;
119+
}
120+
121+
- (NSUInteger)characterIndexForPoint:(NSPoint)point {
122+
return 0;
123+
}
124+
125+
126+
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange {
127+
return NSZeroRect;
128+
}
129+
130+
131+
- (BOOL)hasMarkedText {
132+
return NO;
133+
}
134+
135+
136+
- (NSRange)markedRange {
137+
return NSMakeRange(NSNotFound, 0);
138+
}
139+
140+
141+
- (NSRange)selectedRange {
142+
return NSMakeRange(NSNotFound, 0);
143+
}
144+
145+
146+
- (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
147+
}
148+
149+
150+
- (void)unmarkText {
151+
}
152+
153+
154+
- (nonnull NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
155+
return @[];
156+
}
157+
158+
@end
159+
77160
@interface ImFocusObserver : NSObject
78161

79162
- (void)onApplicationBecomeActive:(NSNotification*)aNotification;
@@ -103,7 +186,7 @@ - (void)onApplicationBecomeInactive:(NSNotification*)aNotification
103186
@end
104187

105188
// Functions
106-
bool ImGui_ImplOSX_Init()
189+
bool ImGui_ImplOSX_Init(NSView *view)
107190
{
108191
ImGuiIO& io = ImGui::GetIO();
109192

@@ -115,29 +198,28 @@ bool ImGui_ImplOSX_Init()
115198
io.BackendPlatformName = "imgui_impl_osx";
116199

117200
// Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeyDown[] array.
118-
const int offset_for_function_keys = 256 - 0xF700;
119-
io.KeyMap[ImGuiKey_Tab] = '\t';
120-
io.KeyMap[ImGuiKey_LeftArrow] = NSLeftArrowFunctionKey + offset_for_function_keys;
121-
io.KeyMap[ImGuiKey_RightArrow] = NSRightArrowFunctionKey + offset_for_function_keys;
122-
io.KeyMap[ImGuiKey_UpArrow] = NSUpArrowFunctionKey + offset_for_function_keys;
123-
io.KeyMap[ImGuiKey_DownArrow] = NSDownArrowFunctionKey + offset_for_function_keys;
124-
io.KeyMap[ImGuiKey_PageUp] = NSPageUpFunctionKey + offset_for_function_keys;
125-
io.KeyMap[ImGuiKey_PageDown] = NSPageDownFunctionKey + offset_for_function_keys;
126-
io.KeyMap[ImGuiKey_Home] = NSHomeFunctionKey + offset_for_function_keys;
127-
io.KeyMap[ImGuiKey_End] = NSEndFunctionKey + offset_for_function_keys;
128-
io.KeyMap[ImGuiKey_Insert] = NSInsertFunctionKey + offset_for_function_keys;
129-
io.KeyMap[ImGuiKey_Delete] = NSDeleteFunctionKey + offset_for_function_keys;
130-
io.KeyMap[ImGuiKey_Backspace] = 127;
131-
io.KeyMap[ImGuiKey_Space] = 32;
132-
io.KeyMap[ImGuiKey_Enter] = 13;
133-
io.KeyMap[ImGuiKey_Escape] = 27;
134-
io.KeyMap[ImGuiKey_KeyPadEnter] = 3;
135-
io.KeyMap[ImGuiKey_A] = 'A';
136-
io.KeyMap[ImGuiKey_C] = 'C';
137-
io.KeyMap[ImGuiKey_V] = 'V';
138-
io.KeyMap[ImGuiKey_X] = 'X';
139-
io.KeyMap[ImGuiKey_Y] = 'Y';
140-
io.KeyMap[ImGuiKey_Z] = 'Z';
201+
io.KeyMap[ImGuiKey_Tab] = kVK_Tab;
202+
io.KeyMap[ImGuiKey_LeftArrow] = kVK_LeftArrow;
203+
io.KeyMap[ImGuiKey_RightArrow] = kVK_RightArrow;
204+
io.KeyMap[ImGuiKey_UpArrow] = kVK_UpArrow;
205+
io.KeyMap[ImGuiKey_DownArrow] = kVK_DownArrow;
206+
io.KeyMap[ImGuiKey_PageUp] = kVK_PageUp;
207+
io.KeyMap[ImGuiKey_PageDown] = kVK_PageDown;
208+
io.KeyMap[ImGuiKey_Home] = kVK_Home;
209+
io.KeyMap[ImGuiKey_End] = kVK_End;
210+
io.KeyMap[ImGuiKey_Insert] = kVK_F13;
211+
io.KeyMap[ImGuiKey_Delete] = kVK_ForwardDelete;
212+
io.KeyMap[ImGuiKey_Backspace] = kVK_Delete;
213+
io.KeyMap[ImGuiKey_Space] = kVK_Space;
214+
io.KeyMap[ImGuiKey_Enter] = kVK_Return;
215+
io.KeyMap[ImGuiKey_Escape] = kVK_Escape;
216+
io.KeyMap[ImGuiKey_KeyPadEnter] = kVK_ANSI_KeypadEnter;
217+
io.KeyMap[ImGuiKey_A] = kVK_ANSI_A;
218+
io.KeyMap[ImGuiKey_C] = kVK_ANSI_C;
219+
io.KeyMap[ImGuiKey_V] = kVK_ANSI_V;
220+
io.KeyMap[ImGuiKey_X] = kVK_ANSI_X;
221+
io.KeyMap[ImGuiKey_Y] = kVK_ANSI_Y;
222+
io.KeyMap[ImGuiKey_Z] = kVK_ANSI_Z;
141223

142224
// Load cursors. Some of them are undocumented.
143225
g_MouseCursorHidden = false;
@@ -189,6 +271,9 @@ bool ImGui_ImplOSX_Init()
189271
selector:@selector(onApplicationBecomeInactive:)
190272
name:NSApplicationDidResignActiveNotification
191273
object:nil];
274+
275+
g_KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
276+
[view addSubview:g_KeyEventResponder];
192277

193278
return true;
194279
}
@@ -234,6 +319,51 @@ static void ImGui_ImplOSX_UpdateMouseCursorAndButtons()
234319
}
235320
}
236321

322+
void ImGui_ImplOSX_UpdateGamepads()
323+
{
324+
ImGuiIO& io = ImGui::GetIO();
325+
memset(io.NavInputs, 0, sizeof(io.NavInputs));
326+
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
327+
return;
328+
329+
GCController *controller;
330+
if (@available(macOS 11.0, *)) {
331+
controller = GCController.current;
332+
} else {
333+
controller = GCController.controllers.firstObject;
334+
}
335+
336+
if (controller == nil || controller.extendedGamepad == nil)
337+
{
338+
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
339+
return;
340+
}
341+
342+
GCExtendedGamepad *gp = controller.extendedGamepad;
343+
344+
#define MAP_BUTTON(NAV_NO, NAME) { io.NavInputs[NAV_NO] = gp.NAME.isPressed ? 1.0 : 0.0; }
345+
MAP_BUTTON(ImGuiNavInput_Activate, buttonA);
346+
MAP_BUTTON(ImGuiNavInput_Cancel, buttonB);
347+
MAP_BUTTON(ImGuiNavInput_Menu, buttonX);
348+
MAP_BUTTON(ImGuiNavInput_Input, buttonY);
349+
MAP_BUTTON(ImGuiNavInput_DpadLeft, dpad.left);
350+
MAP_BUTTON(ImGuiNavInput_DpadRight, dpad.right);
351+
MAP_BUTTON(ImGuiNavInput_DpadUp, dpad.up);
352+
MAP_BUTTON(ImGuiNavInput_DpadDown, dpad.down);
353+
MAP_BUTTON(ImGuiNavInput_FocusPrev, leftShoulder);
354+
MAP_BUTTON(ImGuiNavInput_FocusNext, rightShoulder);
355+
MAP_BUTTON(ImGuiNavInput_TweakSlow, leftTrigger);
356+
MAP_BUTTON(ImGuiNavInput_TweakFast, rightTrigger);
357+
#undef MAP_BUTTON
358+
359+
io.NavInputs[ImGuiNavInput_LStickLeft] = gp.leftThumbstick.left.value;
360+
io.NavInputs[ImGuiNavInput_LStickRight] = gp.leftThumbstick.right.value;
361+
io.NavInputs[ImGuiNavInput_LStickUp] = gp.leftThumbstick.up.value;
362+
io.NavInputs[ImGuiNavInput_LStickDown] = gp.leftThumbstick.down.value;
363+
364+
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
365+
}
366+
237367
void ImGui_ImplOSX_NewFrame(NSView* view)
238368
{
239369
// Setup display size
@@ -256,19 +386,7 @@ void ImGui_ImplOSX_NewFrame(NSView* view)
256386
g_Time = current_time;
257387

258388
ImGui_ImplOSX_UpdateMouseCursorAndButtons();
259-
}
260-
261-
static int mapCharacterToKey(int c)
262-
{
263-
if (c >= 'a' && c <= 'z')
264-
return c - 'a' + 'A';
265-
if (c == 25) // SHIFT+TAB -> TAB
266-
return 9;
267-
if (c >= 0 && c < 256)
268-
return c;
269-
if (c >= 0xF700 && c < 0xF700 + 256)
270-
return c - 0xF700 + 256;
271-
return -1;
389+
ImGui_ImplOSX_UpdateGamepads();
272390
}
273391

274392
bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
@@ -329,59 +447,39 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
329447
return io.WantCaptureMouse;
330448
}
331449

332-
// FIXME: All the key handling is wrong and broken. Refer to GLFW's cocoa_init.mm and cocoa_window.mm.
333-
if (event.type == NSEventTypeKeyDown)
450+
if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp)
334451
{
335-
NSString* str = [event characters];
336-
NSUInteger len = [str length];
337-
for (NSUInteger i = 0; i < len; i++)
338-
{
339-
int c = [str characterAtIndex:i];
340-
if (!io.KeySuper && !(c >= 0xF700 && c <= 0xFFFF) && c != 127)
341-
io.AddInputCharacter((unsigned int)c);
342-
343-
// We must reset in case we're pressing a sequence of special keys while keeping the command pressed
344-
int key = mapCharacterToKey(c);
345-
if (key != -1 && key < 256 && !io.KeySuper)
346-
resetKeys();
347-
if (key != -1)
348-
io.KeysDown[key] = true;
349-
}
452+
unsigned short code = event.keyCode;
453+
IM_ASSERT(code >= 0 && code < IM_ARRAYSIZE(io.KeysDown));
454+
io.KeysDown[code] = event.type == NSEventTypeKeyDown;
455+
NSEventModifierFlags flags = event.modifierFlags;
456+
io.KeyCtrl = (flags & NSEventModifierFlagControl) != 0;
457+
io.KeyShift = (flags & NSEventModifierFlagShift) != 0;
458+
io.KeyAlt = (flags & NSEventModifierFlagOption) != 0;
459+
io.KeySuper = (flags & NSEventModifierFlagCommand) != 0;
350460
return io.WantCaptureKeyboard;
351461
}
352462

353-
if (event.type == NSEventTypeKeyUp)
463+
if (event.type == NSEventTypeFlagsChanged)
354464
{
355-
NSString* str = [event characters];
356-
NSUInteger len = [str length];
357-
for (NSUInteger i = 0; i < len; i++)
465+
NSEventModifierFlags flags = event.modifierFlags;
466+
switch (event.keyCode)
358467
{
359-
int c = [str characterAtIndex:i];
360-
int key = mapCharacterToKey(c);
361-
if (key != -1)
362-
io.KeysDown[key] = false;
468+
case kVK_Control:
469+
io.KeyCtrl = (flags & NSEventModifierFlagControl) != 0;
470+
break;
471+
case kVK_Shift:
472+
io.KeyShift = (flags & NSEventModifierFlagShift) != 0;
473+
break;
474+
case kVK_Option:
475+
io.KeyAlt = (flags & NSEventModifierFlagOption) != 0;
476+
break;
477+
case kVK_Command:
478+
io.KeySuper = (flags & NSEventModifierFlagCommand) != 0;
479+
break;
363480
}
364481
return io.WantCaptureKeyboard;
365482
}
366483

367-
if (event.type == NSEventTypeFlagsChanged)
368-
{
369-
unsigned int flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
370-
371-
bool oldKeyCtrl = io.KeyCtrl;
372-
bool oldKeyShift = io.KeyShift;
373-
bool oldKeyAlt = io.KeyAlt;
374-
bool oldKeySuper = io.KeySuper;
375-
io.KeyCtrl = flags & NSEventModifierFlagControl;
376-
io.KeyShift = flags & NSEventModifierFlagShift;
377-
io.KeyAlt = flags & NSEventModifierFlagOption;
378-
io.KeySuper = flags & NSEventModifierFlagCommand;
379-
380-
// We must reset them as we will not receive any keyUp event if they where pressed with a modifier
381-
if ((oldKeyShift && !io.KeyShift) || (oldKeyCtrl && !io.KeyCtrl) || (oldKeyAlt && !io.KeyAlt) || (oldKeySuper && !io.KeySuper))
382-
resetKeys();
383-
return io.WantCaptureKeyboard;
384-
}
385-
386484
return false;
387485
}

0 commit comments

Comments
 (0)