Support move/select/delete word with keyboard in textbox #63
|
@ -211,7 +211,7 @@ typedef enum
|
||||||
OC_KEYMOD_SHIFT = 0x02,
|
OC_KEYMOD_SHIFT = 0x02,
|
||||||
OC_KEYMOD_CTRL = 0x04,
|
OC_KEYMOD_CTRL = 0x04,
|
||||||
OC_KEYMOD_CMD = 0x08,
|
OC_KEYMOD_CMD = 0x08,
|
||||||
OC_KEYMOD_MAIN_MODIFIER = 0x16 /* CMD on Mac, CTRL on Win32 */
|
OC_KEYMOD_MAIN_MODIFIER = 0x10 /* CMD on Mac, CTRL on Win32 */
|
||||||
} oc_keymod_flags;
|
} oc_keymod_flags;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
|
|
|
@ -528,7 +528,7 @@ LRESULT oc_win32_win_proc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM
|
||||||
|
|
||||||
case WM_CHAR:
|
case WM_CHAR:
|
||||||
{
|
{
|
||||||
if((u32)wParam >= 32)
|
if((u32)wParam >= 0x20 && (u32)wParam <= 0x7e)
|
||||||
{
|
{
|
||||||
oc_event event = { 0 };
|
oc_event event = { 0 };
|
||||||
event.window = oc_window_handle_from_ptr(mpWindow);
|
event.window = oc_window_handle_from_ptr(mpWindow);
|
||||||
|
|
170
src/ui/ui.c
170
src/ui/ui.c
|
@ -2313,6 +2313,20 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = {
|
||||||
.operation = OC_UI_EDIT_MOVE,
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): move one word left
|
||||||
|
{
|
||||||
|
.key = OC_KEY_LEFT,
|
||||||
|
.mods = OC_KEYMOD_ALT,
|
||||||
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
|
//NOTE(martin): move one word right
|
||||||
|
{
|
||||||
|
.key = OC_KEY_RIGHT,
|
||||||
|
.mods = OC_KEYMOD_ALT,
|
||||||
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): move start
|
//NOTE(martin): move start
|
||||||
{
|
{
|
||||||
.key = OC_KEY_Q,
|
.key = OC_KEY_Q,
|
||||||
|
@ -2349,6 +2363,20 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = {
|
||||||
.operation = OC_UI_EDIT_SELECT,
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): select one word left
|
||||||
|
{
|
||||||
|
.key = OC_KEY_LEFT,
|
||||||
|
.mods = OC_KEYMOD_ALT | OC_KEYMOD_SHIFT,
|
||||||
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
|
//NOTE(martin): select one word right
|
||||||
|
{
|
||||||
|
.key = OC_KEY_RIGHT,
|
||||||
|
.mods = OC_KEYMOD_ALT | OC_KEYMOD_SHIFT,
|
||||||
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): extend select to start
|
//NOTE(martin): extend select to start
|
||||||
{
|
{
|
||||||
.key = OC_KEY_Q,
|
.key = OC_KEY_Q,
|
||||||
|
@ -2385,12 +2413,26 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = {
|
||||||
.operation = OC_UI_EDIT_DELETE,
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): delete word
|
||||||
|
{
|
||||||
|
.key = OC_KEY_DELETE,
|
||||||
|
.mods = OC_KEYMOD_ALT,
|
||||||
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): backspace
|
//NOTE(martin): backspace
|
||||||
{
|
{
|
||||||
.key = OC_KEY_BACKSPACE,
|
.key = OC_KEY_BACKSPACE,
|
||||||
.operation = OC_UI_EDIT_DELETE,
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = -1 },
|
.direction = -1 },
|
||||||
|
//NOTE(martin): backspace word
|
||||||
|
{
|
||||||
|
.key = OC_KEY_BACKSPACE,
|
||||||
|
.mods = OC_KEYMOD_ALT,
|
||||||
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
//NOTE(martin): cut
|
//NOTE(martin): cut
|
||||||
{
|
{
|
||||||
.key = OC_KEY_X,
|
.key = OC_KEY_X,
|
||||||
|
@ -2424,6 +2466,20 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = {
|
||||||
.operation = OC_UI_EDIT_MOVE,
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): move one word left
|
||||||
|
{
|
||||||
|
.key = OC_KEY_LEFT,
|
||||||
|
.mods = OC_KEYMOD_CTRL,
|
||||||
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
|
//NOTE(martin): move one word right
|
||||||
|
{
|
||||||
|
.key = OC_KEY_RIGHT,
|
||||||
|
.mods = OC_KEYMOD_CTRL,
|
||||||
|
.operation = OC_UI_EDIT_MOVE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): move start
|
//NOTE(martin): move start
|
||||||
{
|
{
|
||||||
.key = OC_KEY_HOME,
|
.key = OC_KEY_HOME,
|
||||||
|
@ -2458,6 +2514,20 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = {
|
||||||
.operation = OC_UI_EDIT_SELECT,
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): select one word left
|
||||||
|
{
|
||||||
|
.key = OC_KEY_LEFT,
|
||||||
|
.mods = OC_KEYMOD_CTRL | OC_KEYMOD_SHIFT,
|
||||||
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
|
//NOTE(martin): select one word right
|
||||||
|
{
|
||||||
|
.key = OC_KEY_RIGHT,
|
||||||
|
.mods = OC_KEYMOD_CTRL | OC_KEYMOD_SHIFT,
|
||||||
|
.operation = OC_UI_EDIT_SELECT,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): extend select to start
|
//NOTE(martin): extend select to start
|
||||||
{
|
{
|
||||||
.key = OC_KEY_HOME,
|
.key = OC_KEY_HOME,
|
||||||
|
@ -2494,12 +2564,26 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = {
|
||||||
.operation = OC_UI_EDIT_DELETE,
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = 1 },
|
.direction = 1 },
|
||||||
|
//NOTE(martin): delete word
|
||||||
|
{
|
||||||
|
.key = OC_KEY_DELETE,
|
||||||
|
.mods = OC_KEYMOD_CTRL,
|
||||||
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = 1 },
|
||||||
//NOTE(martin): backspace
|
//NOTE(martin): backspace
|
||||||
{
|
{
|
||||||
.key = OC_KEY_BACKSPACE,
|
.key = OC_KEY_BACKSPACE,
|
||||||
.operation = OC_UI_EDIT_DELETE,
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
.move = OC_UI_EDIT_MOVE_ONE,
|
.move = OC_UI_EDIT_MOVE_ONE,
|
||||||
.direction = -1 },
|
.direction = -1 },
|
||||||
|
//NOTE(martin): backspace word
|
||||||
|
{
|
||||||
|
.key = OC_KEY_BACKSPACE,
|
||||||
|
.mods = OC_KEYMOD_CTRL,
|
||||||
|
.operation = OC_UI_EDIT_DELETE,
|
||||||
|
.move = OC_UI_EDIT_MOVE_WORD,
|
||||||
|
.direction = -1 },
|
||||||
//NOTE(martin): cut
|
//NOTE(martin): cut
|
||||||
{
|
{
|
||||||
.key = OC_KEY_X,
|
.key = OC_KEY_X,
|
||||||
|
@ -2523,7 +2607,23 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = {
|
||||||
const u32 OC_UI_EDIT_COMMAND_MACOS_COUNT = sizeof(OC_UI_EDIT_COMMANDS_MACOS) / sizeof(oc_ui_edit_command);
|
const u32 OC_UI_EDIT_COMMAND_MACOS_COUNT = sizeof(OC_UI_EDIT_COMMANDS_MACOS) / sizeof(oc_ui_edit_command);
|
||||||
const u32 OC_UI_EDIT_COMMAND_WINDOWS_COUNT = sizeof(OC_UI_EDIT_COMMANDS_WINDOWS) / sizeof(oc_ui_edit_command);
|
const u32 OC_UI_EDIT_COMMAND_WINDOWS_COUNT = sizeof(OC_UI_EDIT_COMMANDS_WINDOWS) / sizeof(oc_ui_edit_command);
|
||||||
|
|
||||||
void oc_ui_edit_perform_move(oc_ui_context* ui, oc_ui_edit_move move, int direction, u32 textLen)
|
bool oc_ui_edit_is_word_separator(u32 codepoint)
|
||||||
|
{
|
||||||
|
//NOTE(ilia): Printable ascii character, except for alphanumeric and _
|
||||||
|
return ('!' <= codepoint && codepoint <= '~'
|
||||||
|
&& !('0' <= codepoint && codepoint <= '9')
|
||||||
|
&& !('A' <= codepoint && codepoint <= 'Z')
|
||||||
|
&& !('a' <= codepoint && codepoint <= 'z')
|
||||||
|
&& codepoint != '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool oc_ui_edit_is_whitespace(u32 codepoint)
|
||||||
|
{
|
||||||
|
return (codepoint == ' ' || (0x09 <= codepoint && codepoint <= 0x0d) || codepoint == 0x85 || codepoint == 0xa0
|
||||||
|
|| codepoint == 0x1680 || (0x2000 <= codepoint && codepoint <= 0x200a) || codepoint == 0x202f|| codepoint == 0x205f || codepoint == 0x3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void oc_ui_edit_perform_move(oc_ui_context* ui, oc_ui_edit_move move, int direction, oc_str32 codepoints)
|
||||||
{
|
{
|
||||||
switch(move)
|
switch(move)
|
||||||
{
|
{
|
||||||
|
@ -2536,7 +2636,7 @@ void oc_ui_edit_perform_move(oc_ui_context* ui, oc_ui_edit_move move, int direct
|
||||||
{
|
{
|
||||||
ui->editCursor--;
|
ui->editCursor--;
|
||||||
}
|
}
|
||||||
else if(direction > 0 && ui->editCursor < textLen)
|
else if(direction > 0 && ui->editCursor < codepoints.len)
|
||||||
{
|
{
|
||||||
ui->editCursor++;
|
ui->editCursor++;
|
||||||
}
|
}
|
||||||
|
@ -2551,16 +2651,68 @@ void oc_ui_edit_perform_move(oc_ui_context* ui, oc_ui_edit_move move, int direct
|
||||||
}
|
}
|
||||||
else if(direction > 0)
|
else if(direction > 0)
|
||||||
{
|
{
|
||||||
ui->editCursor = textLen;
|
ui->editCursor = codepoints.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OC_UI_EDIT_MOVE_WORD:
|
case OC_UI_EDIT_MOVE_WORD:
|
||||||
OC_DEBUG_ASSERT(0, "not implemented yet");
|
{
|
||||||
|
//NOTE(ilia): a simple word break algorithm borrowed from Qt
|
||||||
|
// https://github.com/qt/qtbase/blob/cbea2f5705c39e31600cb7fff552db92198afd34/src/gui/text/qtextlayout.cpp#L643-L714
|
||||||
|
// proper implementation would involve bringing in ICU or querying unicode ranges and parsing ICU rules
|
||||||
|
if(direction < 0)
|
||||||
|
{
|
||||||
|
while(ui->editCursor > 0 && oc_ui_edit_is_whitespace(codepoints.ptr[ui->editCursor - 1]))
|
||||||
|
{
|
||||||
|
ui->editCursor--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ui->editCursor > 0 && oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor - 1]))
|
||||||
|
{
|
||||||
|
ui->editCursor--;
|
||||||
|
while(ui->editCursor > 0 && oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor - 1]))
|
||||||
|
{
|
||||||
|
ui->editCursor--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while(ui->editCursor > 0
|
||||||
|
&& !oc_ui_edit_is_whitespace(codepoints.ptr[ui->editCursor - 1])
|
||||||
|
&& !oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor - 1]))
|
||||||
|
{
|
||||||
|
ui->editCursor--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(direction > 0)
|
||||||
|
{
|
||||||
|
if(ui->editCursor < codepoints.len && oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor]))
|
||||||
|
{
|
||||||
|
ui->editCursor++;
|
||||||
|
while(ui->editCursor < codepoints.len && oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor]))
|
||||||
|
{
|
||||||
|
ui->editCursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while(ui->editCursor < codepoints.len
|
||||||
|
&& !oc_ui_edit_is_whitespace(codepoints.ptr[ui->editCursor])
|
||||||
|
&& !oc_ui_edit_is_word_separator(codepoints.ptr[ui->editCursor]))
|
||||||
|
{
|
||||||
|
ui->editCursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(ui->editCursor < codepoints.len && oc_ui_edit_is_whitespace(codepoints.ptr[ui->editCursor]))
|
||||||
|
{
|
||||||
|
ui->editCursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation, oc_ui_edit_move move, int direction, oc_str32 codepoints)
|
oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation, oc_ui_edit_move move, int direction, oc_str32 codepoints)
|
||||||
{
|
{
|
||||||
|
@ -2577,7 +2729,7 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation
|
||||||
{
|
{
|
||||||
//NOTE: we special case move-one when there is a selection
|
//NOTE: we special case move-one when there is a selection
|
||||||
// (just place the cursor at begining/end of selection)
|
// (just place the cursor at begining/end of selection)
|
||||||
oc_ui_edit_perform_move(ui, move, direction, codepoints.len);
|
oc_ui_edit_perform_move(ui, move, direction, codepoints);
|
||||||
}
|
}
|
||||||
ui->editMark = ui->editCursor;
|
ui->editMark = ui->editCursor;
|
||||||
}
|
}
|
||||||
|
@ -2585,7 +2737,7 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation
|
||||||
|
|
||||||
case OC_UI_EDIT_SELECT:
|
case OC_UI_EDIT_SELECT:
|
||||||
{
|
{
|
||||||
oc_ui_edit_perform_move(ui, move, direction, codepoints.len);
|
oc_ui_edit_perform_move(ui, move, direction, codepoints);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2597,7 +2749,7 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation
|
||||||
ui->editCursor = ui->editMark;
|
ui->editCursor = ui->editMark;
|
||||||
ui->editMark = tmp;
|
ui->editMark = tmp;
|
||||||
}
|
}
|
||||||
oc_ui_edit_perform_move(ui, move, direction, codepoints.len);
|
oc_ui_edit_perform_move(ui, move, direction, codepoints);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2605,7 +2757,7 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation
|
||||||
{
|
{
|
||||||
if(ui->editCursor == ui->editMark)
|
if(ui->editCursor == ui->editMark)
|
||||||
{
|
{
|
||||||
oc_ui_edit_perform_move(ui, move, direction, codepoints.len);
|
oc_ui_edit_perform_move(ui, move, direction, codepoints);
|
||||||
}
|
}
|
||||||
codepoints = oc_ui_edit_delete_selection(ui, codepoints);
|
codepoints = oc_ui_edit_delete_selection(ui, codepoints);
|
||||||
ui->editMark = ui->editCursor;
|
ui->editMark = ui->editCursor;
|
||||||
|
@ -2865,7 +3017,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
|
||||||
const oc_ui_edit_command* command = &(editCommands[i]);
|
const oc_ui_edit_command* command = &(editCommands[i]);
|
||||||
|
|
||||||
if((oc_key_pressed(&ui->input, command->key) || oc_key_repeated(&ui->input, command->key))
|
if((oc_key_pressed(&ui->input, command->key) || oc_key_repeated(&ui->input, command->key))
|
||||||
&& mods == command->mods)
|
&& (mods & ~OC_KEYMOD_MAIN_MODIFIER) == command->mods)
|
||||||
{
|
{
|
||||||
codepoints = oc_ui_edit_perform_operation(ui, command->operation, command->move, command->direction, codepoints);
|
codepoints = oc_ui_edit_perform_operation(ui, command->operation, command->move, command->direction, codepoints);
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue