[wip, input system] Fixed bug on macOS, where keyUp events are not received when the command modifier key is down and makes a meaningful combination with the pressed key (note this is normal macOS event handling behaviour, but we still want all events to arrive to track key states).

Fixed by overloading the sendEvent method of our derived NSApplication class. There we check if modifier is down and send the event.
Note glfw solves that by adding a block handler for the keyUp event with addLocalMonitorForEventsMatchingMask.
This commit is contained in:
Martin Fouilleul 2023-03-04 19:24:30 +01:00
parent c04e8639ab
commit e95efe85e8
6 changed files with 198 additions and 190 deletions

View File

@ -129,23 +129,47 @@ bool mp_next_event(mp_event* event)
// Input state updating
//---------------------------------------------------------------
static void mp_update_key_state(mp_key_state* key, bool down)
static void mp_update_key_state(mp_key_state* key, mp_key_action action)
{
u64 frameCounter = __mpApp.inputState.frameCounter;
if(key->lastUpdate != frameCounter)
{
key->transitionCounter = 0;
key->clicked = false;
key->doubleClicked = false;
key->transitionCount = 0;
key->repeatCount = 0;
key->sysClicked = false;
key->sysDoubleClicked = false;
key->lastUpdate = frameCounter;
}
if(key->down != down)
switch(action)
{
key->transitionCounter++;
case MP_KEY_PRESS:
{
if(!key->down)
{
key->transitionCount++;
}
key->down = true;
} break;
case MP_KEY_REPEAT:
{
key->repeatCount++;
key->down = true;
} break;
case MP_KEY_RELEASE:
{
if(key->down)
{
key->transitionCount++;
}
key->down = false;
} break;
default:
break;
}
key->down = down;
}
static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY)
@ -204,107 +228,138 @@ static void mp_update_text(utf32 codepoint)
// Input state polling
//--------------------------------------------------------------------
mp_key_state mp_input_get_key_state(mp_key_code key)
mp_key_state mp_key_get_state(mp_key_code key)
{
mp_key_state state = {0};
if(key <= MP_KEY_COUNT)
{
return(__mpApp.inputState.keyboard.keys[key]);
}
else
{
return((mp_key_state){0});
state = __mpApp.inputState.keyboard.keys[key];
}
return(state);
}
mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button)
mp_key_state mp_mouse_button_get_state(mp_mouse_button button)
{
mp_key_state state = {0};
if(button <= MP_MOUSE_BUTTON_COUNT)
{
return(__mpApp.inputState.mouse.buttons[button]);
state = __mpApp.inputState.mouse.buttons[button];
}
else
return(state);
}
int mp_key_state_press_count(mp_key_state* key)
{
int count = 0;
if(key->lastUpdate == __mpApp.inputState.frameCounter)
{
return((mp_key_state){0});
count = key->transitionCount / 2;
if(key->down)
{
//NOTE: add one if state is down transition count is odd
count += (key->transitionCount & 0x01);
}
}
return(count);
}
bool mp_input_check_key_transition(mp_key_state* key, bool pressed)
int mp_key_state_release_count(mp_key_state* key)
{
bool res = ( (key->lastUpdate == __mpApp.inputState.frameCounter)
&& key->transitionCounter
&&(key->down == pressed || key->transitionCounter > 1));
return(res);
int count = 0;
if(key->lastUpdate == __mpApp.inputState.frameCounter)
{
count = key->transitionCount / 2;
if(!key->down)
{
//NOTE: add one if state is up and transition count is odd
count += (key->transitionCount & 0x01);
}
}
return(count);
}
bool mp_input_key_down(mp_key_code key)
int mp_key_state_repeat_count(mp_key_state* key)
{
mp_key_state state = mp_input_get_key_state(key);
return(state.down);
}
bool mp_input_key_pressed(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, true);
return(res);
int count = 0;
if(key->lastUpdate == __mpApp.inputState.frameCounter)
{
count = key->repeatCount;
}
return(count);
}
bool mp_input_key_released(mp_key_code key)
bool mp_key_down(mp_key_code key)
{
mp_key_state state = mp_input_get_key_state(key);
bool res = mp_input_check_key_transition(&state, false);
return(res);
}
bool mp_input_mouse_down(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
mp_key_state state = mp_key_get_state(key);
return(state.down);
}
bool mp_input_mouse_pressed(mp_mouse_button button)
int mp_key_pressed(mp_key_code key)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, true);
mp_key_state state = mp_key_get_state(key);
int res = mp_key_state_press_count(&state);
return(res);
}
bool mp_input_mouse_released(mp_mouse_button button)
int mp_key_released(mp_key_code key)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool res = mp_input_check_key_transition(&state, false);
mp_key_state state = mp_key_get_state(key);
int res = mp_key_state_release_count(&state);
return(res);
}
bool mp_input_mouse_clicked(mp_mouse_button button)
int mp_key_repeated(mp_key_code key)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
bool clicked = state.clicked && (state.lastUpdate == __mpApp.inputState.frameCounter);
mp_key_state state = mp_key_get_state(key);
int res = mp_key_state_repeat_count(&state);
return(res);
}
bool mp_mouse_down(mp_mouse_button button)
{
mp_key_state state = mp_mouse_button_get_state(button);
return(state.down);
}
int mp_mouse_pressed(mp_mouse_button button)
{
mp_key_state state = mp_mouse_button_get_state(button);
int res = mp_key_state_press_count(&state);
return(res);
}
int mp_mouse_released(mp_mouse_button button)
{
mp_key_state state = mp_mouse_button_get_state(button);
int res = mp_key_state_release_count(&state);
return(res);
}
bool mp_mouse_clicked(mp_mouse_button button)
{
mp_key_state state = mp_mouse_button_get_state(button);
bool clicked = state.sysClicked && (state.lastUpdate == __mpApp.inputState.frameCounter);
return(clicked);
}
bool mp_input_mouse_double_clicked(mp_mouse_button button)
bool mp_mouse_double_clicked(mp_mouse_button button)
{
mp_key_state state = mp_input_get_mouse_button_state(button);
if(state.lastUpdate == __mpApp.inputState.frameCounter)
{
return(state.doubleClicked);
}
else
{
return(false);
}
mp_key_state state = mp_mouse_button_get_state(button);
bool doubleClicked = state.sysClicked && (state.lastUpdate == __mpApp.inputState.frameCounter);
return(doubleClicked);
}
mp_key_mods mp_input_key_mods()
mp_keymod_flags mp_key_mods()
{
return(__mpApp.inputState.keyboard.mods);
}
vec2 mp_input_mouse_position()
vec2 mp_mouse_position()
{
return(__mpApp.inputState.mouse.pos);
}
vec2 mp_input_mouse_delta()
vec2 mp_mouse_delta()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{
@ -316,7 +371,7 @@ vec2 mp_input_mouse_delta()
}
}
vec2 mp_input_mouse_wheel()
vec2 mp_mouse_wheel()
{
if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter)
{

View File

@ -193,7 +193,7 @@ typedef enum {
MP_KEYMOD_ALT = 0x01,
MP_KEYMOD_SHIFT = 0x02,
MP_KEYMOD_CTRL = 0x04,
MP_KEYMOD_CMD = 0x08 } mp_key_mods;
MP_KEYMOD_CMD = 0x08 } mp_keymod_flags;
typedef enum {
MP_MOUSE_LEFT = 0x00,
@ -207,7 +207,7 @@ typedef struct mp_key_event // keyboard and mouse buttons input
{
mp_key_action action;
i32 code;
mp_key_mods mods;
mp_keymod_flags mods;
char label[8];
u8 labelLen;
int clickCount;
@ -226,7 +226,7 @@ typedef struct mp_move_event // mouse move/scroll
f32 y;
f32 deltaX;
f32 deltaY;
mp_key_mods mods;
mp_keymod_flags mods;
} mp_move_event;
typedef struct mp_frame_event // window resize / move
@ -322,27 +322,27 @@ MP_API mp_rect mp_window_frame_rect_for_content_rect(mp_rect contentRect, mp_win
//--------------------------------------------------------------------
// Input state polling
//--------------------------------------------------------------------
MP_API bool mp_input_key_down(mp_key_code key);
MP_API bool mp_input_key_pressed(mp_key_code key);
MP_API bool mp_input_key_released(mp_key_code key);
MP_API mp_key_mods mp_input_key_mods(void);
MP_API str8 mp_key_to_label(mp_key_code key);
MP_API mp_key_code mp_label_to_key(str8 label);
MP_API bool mp_key_down(mp_key_code key);
MP_API int mp_key_pressed(mp_key_code key);
MP_API int mp_key_released(mp_key_code key);
MP_API int mp_key_repeated(mp_key_code key);
MP_API bool mp_input_mouse_down(mp_mouse_button button);
MP_API bool mp_input_mouse_pressed(mp_mouse_button button);
MP_API bool mp_input_mouse_released(mp_mouse_button button);
MP_API bool mp_input_mouse_clicked(mp_mouse_button button);
MP_API bool mp_input_mouse_double_clicked(mp_mouse_button button);
MP_API bool mp_mouse_down(mp_mouse_button button);
MP_API int mp_mouse_pressed(mp_mouse_button button);
MP_API int mp_mouse_released(mp_mouse_button button);
MP_API bool mp_mouse_clicked(mp_mouse_button button);
MP_API bool mp_mouse_double_clicked(mp_mouse_button button);
MP_API vec2 mp_input_mouse_position(void);
MP_API vec2 mp_input_mouse_delta(void);
MP_API vec2 mp_input_mouse_wheel(void);
MP_API vec2 mp_mouse_position(void);
MP_API vec2 mp_mouse_delta(void);
MP_API vec2 mp_mouse_wheel(void);
MP_API str32 mp_input_text_utf32(mem_arena* arena);
MP_API str8 mp_input_text_utf8(mem_arena* arena);
MP_API mp_keymod_flags mp_key_mods();
//--------------------------------------------------------------------
// Clipboard
//--------------------------------------------------------------------

View File

@ -35,17 +35,18 @@ typedef struct mp_key_utf8
typedef struct mp_key_state
{
u64 lastUpdate;
u32 transitionCounter;
u32 transitionCount;
u32 repeatCount;
bool down;
bool clicked;
bool doubleClicked;
bool sysClicked;
bool sysDoubleClicked;
} mp_key_state;
typedef struct mp_keyboard_state
{
mp_key_state keys[MP_KEY_COUNT];
mp_key_mods mods;
mp_keymod_flags mods;
} mp_keyboard_state;
typedef struct mp_mouse_state

View File

@ -236,9 +236,9 @@ static int mp_convert_osx_key(unsigned short nsCode)
}
}
static mp_key_mods mp_convert_osx_mods(NSUInteger nsFlags)
static mp_keymod_flags mp_convert_osx_mods(NSUInteger nsFlags)
{
mp_key_mods mods = MP_KEYMOD_NONE;
mp_keymod_flags mods = MP_KEYMOD_NONE;
if(nsFlags & NSEventModifierFlagShift)
{
mods |= MP_KEYMOD_SHIFT;
@ -369,7 +369,7 @@ void mp_install_keyboard_layout_listener()
object:nil];
}
static void mp_update_key_mods(mp_key_mods mods)
static void mp_update_key_mods(mp_keymod_flags mods)
{
__mpApp.inputState.keyboard.mods = mods;
}
@ -384,6 +384,15 @@ static void mp_update_key_mods(mp_key_mods mods)
@implementation MPApplication
-(void)noOpThread:(id)object
{}
//This is necessary in order to receive keyUp events when we have a key combination with Cmd.
- (void)sendEvent:(NSEvent *)event {
if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand))
[[self keyWindow] sendEvent:event];
else
[super sendEvent:event];
}
@end
@interface MPAppDelegate : NSObject <NSApplicationDelegate>
@ -791,101 +800,61 @@ static void mp_update_key_mods(mp_key_mods mods)
}
}
- (void)mouseDown:(NSEvent *)nsEvent
static void mp_process_mouse_button(NSEvent* nsEvent, mp_window_data* window, mp_mouse_button button, mp_key_action action)
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_PRESS;
event.key.code = MP_MOUSE_LEFT;
event.key.action = action;
event.key.code = button;
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
event.key.clickCount = [nsEvent clickCount];
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], true);
if(event.key.clickCount >= 1)
mp_key_state* keyState = &__mpApp.inputState.mouse.buttons[event.key.code];
mp_update_key_state(keyState, action);
if(action == MP_KEY_PRESS)
{
__mpApp.inputState.mouse.buttons[event.key.code].clicked = true;
if(event.key.clickCount >= 1)
{
keyState->sysClicked = true;
}
if(event.key.clickCount >= 2)
{
keyState->sysDoubleClicked = true;
}
}
if(event.key.clickCount >= 2)
{
__mpApp.inputState.mouse.buttons[event.key.code].doubleClicked = true;
}
mp_queue_event(&event);
}
- (void)mouseDown:(NSEvent *)nsEvent
{
mp_process_mouse_button(nsEvent, window, MP_MOUSE_LEFT, MP_KEY_PRESS);
[window->osx.nsWindow makeFirstResponder:self];
}
- (void)mouseUp:(NSEvent*)nsEvent
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_RELEASE;
event.key.code = MP_MOUSE_LEFT;
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
event.key.clickCount = [nsEvent clickCount];
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], false);
mp_queue_event(&event);
mp_process_mouse_button(nsEvent, window, MP_MOUSE_LEFT, MP_KEY_RELEASE);
}
- (void)rightMouseDown:(NSEvent*)nsEvent
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_PRESS;
event.key.code = MP_MOUSE_RIGHT;
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], true);
mp_queue_event(&event);
mp_process_mouse_button(nsEvent, window, MP_MOUSE_RIGHT, MP_KEY_PRESS);
}
- (void)rightMouseUp:(NSEvent*)nsEvent
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_RELEASE;
event.key.code = MP_MOUSE_RIGHT;
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], false);
mp_queue_event(&event);
mp_process_mouse_button(nsEvent, window, MP_MOUSE_RIGHT, MP_KEY_RELEASE);
}
- (void)otherMouseDown:(NSEvent*)nsEvent
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_PRESS;
event.key.code = [nsEvent buttonNumber];
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], true);
mp_queue_event(&event);
mp_process_mouse_button(nsEvent, window, [nsEvent buttonNumber], MP_KEY_PRESS);
}
- (void)otherMouseUp:(NSEvent*)nsEvent
{
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_MOUSE_BUTTON;
event.key.action = MP_KEY_RELEASE;
event.key.code = [nsEvent buttonNumber];
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_update_key_state(&__mpApp.inputState.mouse.buttons[event.key.code], false);
mp_queue_event(&event);
mp_process_mouse_button(nsEvent, window, [nsEvent buttonNumber], MP_KEY_RELEASE);
}
- (void)mouseDragged:(NSEvent*)nsEvent
@ -946,10 +915,10 @@ static void mp_update_key_mods(mp_key_mods mods)
mp_queue_event(&event);
}
- (void)keyDown:(NSEvent*)nsEvent
{
mp_key_action action = [nsEvent isARepeat] ? MP_KEY_REPEAT : MP_KEY_PRESS;
mp_event event = {};
event.window = mp_window_handle_from_ptr(window);
event.type = MP_EVENT_KEYBOARD_KEY;
@ -961,7 +930,7 @@ static void mp_update_key_mods(mp_key_mods mods)
event.key.labelLen = label.len;
memcpy(event.key.label, label.ptr, label.len);
mp_update_key_state(&__mpApp.inputState.keyboard.keys[event.key.code], true);
mp_update_key_state(&__mpApp.inputState.keyboard.keys[event.key.code], MP_KEY_PRESS);
mp_queue_event(&event);
@ -977,7 +946,7 @@ static void mp_update_key_mods(mp_key_mods mods)
event.key.code = mp_convert_osx_key([nsEvent keyCode]);
event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_update_key_state(&__mpApp.inputState.keyboard.keys[event.key.code], false);
mp_update_key_state(&__mpApp.inputState.keyboard.keys[event.key.code], MP_KEY_RELEASE);
mp_queue_event(&event);
}
@ -1092,7 +1061,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
{
NSString* characters;
NSEvent* nsEvent = [NSApp currentEvent];
mp_key_mods mods = mp_convert_osx_mods([nsEvent modifierFlags]);
mp_keymod_flags mods = mp_convert_osx_mods([nsEvent modifierFlags]);
if([string isKindOfClass:[NSAttributedString class]])
{
@ -2265,7 +2234,6 @@ int mp_alert_popup(const char* title,
[alert setAlertStyle:NSAlertStyleWarning];
int result = count - ([alert runModal]-NSAlertFirstButtonReturn) - 1;
printf("result = %i, NSAlertFirstButtonReturn = %li\n", result, (long)NSAlertFirstButtonReturn);
[keyWindow makeKeyWindow];
return(result);
}

View File

@ -450,7 +450,7 @@ vec2 ui_mouse_position(void)
{
ui_context* ui = ui_get_context();
vec2 mousePos = mp_input_mouse_position();
vec2 mousePos = mp_mouse_position();
mousePos.y = ui->height - mousePos.y;
return(mousePos);
}
@ -458,7 +458,7 @@ vec2 ui_mouse_position(void)
vec2 ui_mouse_delta(void)
{
ui_context* ui = ui_get_context();
vec2 delta = mp_input_mouse_delta();
vec2 delta = mp_mouse_delta();
delta.y *= -1.;
return(delta);
}
@ -466,7 +466,7 @@ vec2 ui_mouse_delta(void)
vec2 ui_mouse_wheel(void)
{
ui_context* ui = ui_get_context();
vec2 delta = mp_input_mouse_wheel();
vec2 delta = mp_mouse_wheel();
delta.y *= -1.;
return(delta);
}
@ -642,30 +642,25 @@ ui_sig ui_box_sig(ui_box* box)
{
if(sig.hovering)
{
if(mp_input_mouse_clicked(MP_MOUSE_LEFT))
{
printf("clicked while hovering!\n");
}
sig.pressed = mp_input_mouse_pressed(MP_MOUSE_LEFT);
sig.pressed = mp_mouse_pressed(MP_MOUSE_LEFT);
if(sig.pressed)
{
box->dragging = true;
}
sig.clicked = mp_input_mouse_clicked(MP_MOUSE_LEFT);
sig.doubleClicked = mp_input_mouse_double_clicked(MP_MOUSE_LEFT);
sig.doubleClicked = mp_mouse_double_clicked(MP_MOUSE_LEFT);
sig.rightPressed = mp_mouse_pressed(MP_MOUSE_RIGHT);
}
sig.released = mp_input_mouse_released(MP_MOUSE_LEFT);
sig.released = mp_mouse_released(MP_MOUSE_LEFT);
if(sig.released)
{
if(box->dragging && sig.hovering)
{
sig.triggered = true;
sig.clicked = true;
}
}
if(!mp_input_mouse_down(MP_MOUSE_LEFT))
if(!mp_mouse_down(MP_MOUSE_LEFT))
{
box->dragging = false;
}
@ -1589,7 +1584,7 @@ void ui_menu_bar_begin(const char* name)
ui_push_size(UI_AXIS_Y, UI_SIZE_TEXT, 0, 0);
ui_sig sig = ui_box_sig(bar);
if(!sig.hovering && mp_input_mouse_released(MP_MOUSE_LEFT))
if(!sig.hovering && mp_mouse_released(MP_MOUSE_LEFT))
{
ui_box_deactivate(bar);
}
@ -1725,11 +1720,8 @@ void ui_edit_copy_selection_to_clipboard(ui_context* ui, str32 codepoints)
u32 start = minimum(ui->editCursor, ui->editMark);
u32 end = maximum(ui->editCursor, ui->editMark);
str32 selection = str32_slice(codepoints, start, end);
str8 string = utf8_push_from_codepoints(&ui->frameArena, selection);
printf("copying '%.*s' to clipboard\n", (int)string.len, string.ptr);
mp_clipboard_clear();
mp_clipboard_set_string(string);
}
@ -1737,9 +1729,6 @@ void ui_edit_copy_selection_to_clipboard(ui_context* ui, str32 codepoints)
str32 ui_edit_replace_selection_with_clipboard(ui_context* ui, str32 codepoints)
{
str8 string = mp_clipboard_get_string(&ui->frameArena);
printf("pasting '%.*s' from clipboard\n", (int)string.len, string.ptr);
str32 input = utf8_push_to_codepoints(&ui->frameArena, string);
str32 result = ui_edit_replace_selection_with_codepoints(ui, codepoints, input);
return(result);
@ -1764,7 +1753,7 @@ typedef enum {
typedef struct ui_edit_command
{
mp_key_code key;
mp_key_mods mods;
mp_keymod_flags mods;
ui_edit_op operation;
ui_edit_move move;
@ -2186,18 +2175,13 @@ ui_text_box_result ui_text_box(const char* name, mem_arena* arena, str8 text)
}
//NOTE handle shortcuts
mp_key_mods mods = mp_input_key_mods();
mp_keymod_flags mods = mp_key_mods();
for(int i=0; i<UI_EDIT_COMMAND_COUNT; i++)
{
const ui_edit_command* command = &(UI_EDIT_COMMANDS[i]);
if(mp_input_key_pressed(MP_KEY_C))
{
printf("press C!\n");
}
if(mp_input_key_pressed(command->key) && mods == command->mods)
if(mp_key_pressed(command->key) && mods == command->mods)
{
codepoints = ui_edit_perform_operation(ui, command->operation, command->move, command->direction, codepoints);
break;
@ -2205,7 +2189,7 @@ ui_text_box_result ui_text_box(const char* name, mem_arena* arena, str8 text)
}
//NOTE(martin): text box focus shortcuts
if(mp_input_key_pressed(MP_KEY_ENTER))
if(mp_key_pressed(MP_KEY_ENTER))
{
//TODO(martin): extract in gui_edit_complete() (and use below)
ui_box_deactivate(frame);

View File

@ -123,10 +123,10 @@ typedef struct ui_sig
bool pressed;
bool released;
bool triggered;
bool clicked;
bool doubleClicked;
bool rightClicked;
bool rightPressed;
bool dragging;
bool hovering;