Support double and triple click text selection #85
			
				
			
		
		
		
	|  | @ -15,7 +15,6 @@ ORCA_EXPORT void oc_on_init(void) | |||
|     surface = oc_surface_canvas(); | ||||
|     canvas = oc_canvas_create(); | ||||
|     oc_ui_init(&ui); | ||||
|     oc_ui_set_theme(&OC_UI_LIGHT_THEME); | ||||
| 
 | ||||
|     //NOTE: load font
 | ||||
|     { | ||||
|  |  | |||
|  | @ -233,7 +233,31 @@ static void oc_win32_process_mouse_event(oc_window_data* window, oc_key_action a | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //TODO click/double click
 | ||||
|     if(action == OC_KEY_PRESS) | ||||
|     { | ||||
|         u32 clickTime = GetMessageTime(); | ||||
|         if(clickTime - oc_appData.win32.lastClickTime[button] > GetDoubleClickTime()) | ||||
|         { | ||||
|             oc_appData.win32.clickCount[button] = 0; | ||||
|         } | ||||
|         for (int i = 0; i < OC_MOUSE_BUTTON_COUNT; i++) | ||||
|         { | ||||
|             if(i != button) | ||||
|             { | ||||
|                 oc_appData.win32.clickCount[i] = 0; | ||||
|             } | ||||
|         } | ||||
|         oc_appData.win32.lastClickTime[button] = clickTime; | ||||
|         oc_appData.win32.clickCount[button]++; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         u32 clickTime = GetMessageTime(); | ||||
|         if(clickTime - oc_appData.win32.lastClickTime[button] > GetDoubleClickTime()) | ||||
|         { | ||||
|             oc_appData.win32.clickCount[button] = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     oc_event event = { 0 }; | ||||
|     event.window = oc_window_handle_from_ptr(window); | ||||
|  | @ -241,6 +265,7 @@ static void oc_win32_process_mouse_event(oc_window_data* window, oc_key_action a | |||
|     event.key.action = action; | ||||
|     event.key.code = button; | ||||
|     event.key.mods = oc_get_mod_keys(); | ||||
|     event.key.clickCount = oc_appData.win32.clickCount[button]; | ||||
| 
 | ||||
|     oc_queue_event(&event); | ||||
| } | ||||
|  | @ -311,6 +336,15 @@ LRESULT oc_win32_win_proc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM | |||
| 
 | ||||
|     switch(message) | ||||
|     { | ||||
|         case WM_ACTIVATE: | ||||
|         { | ||||
|             for(int i = 0; i < OC_MOUSE_BUTTON_COUNT; i++) | ||||
|             { | ||||
|                 oc_appData.win32.clickCount[i] = 0; | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|         case WM_CLOSE: | ||||
|         { | ||||
|             oc_event event = { 0 }; | ||||
|  | @ -459,6 +493,14 @@ LRESULT oc_win32_win_proc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM | |||
|                 event.mouse.deltaX = event.mouse.x - oc_appData.win32.lastMousePos.x; | ||||
|                 event.mouse.deltaY = event.mouse.y - oc_appData.win32.lastMousePos.y; | ||||
|             } | ||||
|             if(abs(event.mouse.x - oc_appData.win32.lastMousePos.x) > GetSystemMetrics(SM_CXDOUBLECLK) / 2 | ||||
|                || abs(event.mouse.y - oc_appData.win32.lastMousePos.y) > GetSystemMetrics(SM_CYDOUBLECLK) / 2) | ||||
|             { | ||||
|                 for(int i = 0; i < OC_MOUSE_BUTTON_COUNT; i++) | ||||
|                 { | ||||
|                     oc_appData.win32.clickCount[i] = 0; | ||||
|                 } | ||||
|             } | ||||
|             oc_appData.win32.lastMousePos = (oc_vec2){ event.mouse.x, event.mouse.y }; | ||||
| 
 | ||||
|             if(!oc_appData.win32.mouseTracked) | ||||
|  |  | |||
|  | @ -40,6 +40,8 @@ typedef struct oc_win32_app_data | |||
|     int mouseCaptureMask; | ||||
|     bool mouseTracked; | ||||
|     oc_vec2 lastMousePos; | ||||
|     u32 lastClickTime[OC_MOUSE_BUTTON_COUNT]; | ||||
|     u32 clickCount[OC_MOUSE_BUTTON_COUNT]; | ||||
|     u32 wheelScrollLines; | ||||
| 
 | ||||
| } oc_win32_app_data; | ||||
|  |  | |||
|  | @ -67,6 +67,7 @@ extern "C" | |||
|     float cosf(float); | ||||
| 
 | ||||
|     double fabs(double); | ||||
|     float fabsf(float); | ||||
| 
 | ||||
|     double floor(double); | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| #include <math.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| float fabsf(float x) | ||||
| { | ||||
|     union | ||||
|     { | ||||
|         float f; | ||||
|         uint32_t i; | ||||
|     } u = { x }; | ||||
| 
 | ||||
|     u.i &= -1U / 2; | ||||
|     return u.f; | ||||
| } | ||||
|  | @ -20,6 +20,7 @@ static void oc_update_key_state(oc_input_state* state, oc_key_state* key, oc_key | |||
|         key->repeatCount = 0; | ||||
|         key->sysClicked = false; | ||||
|         key->sysDoubleClicked = false; | ||||
|         key->sysTripleClicked = false; | ||||
|         key->lastUpdate = frameCounter; | ||||
|     } | ||||
| 
 | ||||
|  | @ -93,7 +94,6 @@ static void oc_update_mouse_leave(oc_input_state* state) | |||
| 
 | ||||
| static void oc_update_mouse_wheel(oc_input_state* state, f32 deltaX, f32 deltaY) | ||||
| { | ||||
|     oc_log_info("wheel"); | ||||
|     u64 frameCounter = state->frameCounter; | ||||
|     oc_mouse_state* mouse = &state->mouse; | ||||
|     if(mouse->lastUpdate != frameCounter) | ||||
|  | @ -178,10 +178,15 @@ void oc_input_process_event(oc_input_state* state, oc_event* event) | |||
|                 { | ||||
|                     key->sysClicked = true; | ||||
|                 } | ||||
|                 if(event->key.clickCount >= 2) | ||||
| 
 | ||||
|                 if(event->key.clickCount % 2 == 0) | ||||
|                 { | ||||
|                     key->sysDoubleClicked = true; | ||||
|                 } | ||||
|                 else if(event->key.clickCount > 1) | ||||
|                 { | ||||
|                     key->sysTripleClicked = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             oc_update_key_mods(state, event->key.mods); | ||||
|  | @ -314,10 +319,17 @@ bool oc_mouse_clicked(oc_input_state* input, oc_mouse_button button) | |||
| bool oc_mouse_double_clicked(oc_input_state* input, oc_mouse_button button) | ||||
| { | ||||
|     oc_key_state state = oc_mouse_button_get_state(input, button); | ||||
|     bool doubleClicked = state.sysClicked && (state.lastUpdate == input->frameCounter); | ||||
|     bool doubleClicked = state.sysDoubleClicked && (state.lastUpdate == input->frameCounter); | ||||
|     return (doubleClicked); | ||||
| } | ||||
| 
 | ||||
| bool oc_mouse_triple_clicked(oc_input_state* input, oc_mouse_button button) | ||||
| { | ||||
|     oc_key_state state = oc_mouse_button_get_state(input, button); | ||||
|     bool tripleClicked = state.sysTripleClicked && (state.lastUpdate == input->frameCounter); | ||||
|     return (tripleClicked); | ||||
| } | ||||
| 
 | ||||
| oc_keymod_flags oc_key_mods(oc_input_state* input) | ||||
| { | ||||
|     return (input->keyboard.mods); | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ typedef struct oc_key_state | |||
|     bool down; | ||||
|     bool sysClicked; | ||||
|     bool sysDoubleClicked; | ||||
|     bool sysTripleClicked; | ||||
| 
 | ||||
| } oc_key_state; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										197
									
								
								src/ui/ui.c
								
								
								
								
							
							
						
						
									
										197
									
								
								src/ui/ui.c
								
								
								
								
							|  | @ -7,6 +7,7 @@ | |||
| * | ||||
| *****************************************************************/ | ||||
| #include "ui.h" | ||||
| #include "math.h" | ||||
| #include "platform/platform.h" | ||||
| #include "platform/platform_clock.h" | ||||
| #include "platform/platform_debug.h" | ||||
|  | @ -570,6 +571,7 @@ oc_ui_sig oc_ui_box_sig(oc_ui_box* box) | |||
|                     box->dragging = true; | ||||
|                 } | ||||
|                 sig.doubleClicked = oc_mouse_double_clicked(input, OC_MOUSE_LEFT); | ||||
|                 sig.tripleClicked = oc_mouse_triple_clicked(input, OC_MOUSE_LEFT); | ||||
|                 sig.rightPressed = oc_mouse_pressed(input, OC_MOUSE_RIGHT); | ||||
|             } | ||||
| 
 | ||||
|  | @ -1531,6 +1533,8 @@ void oc_ui_init(oc_ui_context* ui) | |||
| 
 | ||||
|     oc_ui_set_context(ui); | ||||
|     oc_ui_set_theme(&OC_UI_DARK_THEME); | ||||
| 
 | ||||
|     ui->editSelectionMode = OC_UI_EDIT_MOVE_CHAR; | ||||
| } | ||||
| 
 | ||||
| void oc_ui_cleanup(void) | ||||
|  | @ -2735,14 +2739,6 @@ typedef enum | |||
|     OC_UI_EDIT_SELECT_ALL | ||||
| } oc_ui_edit_op; | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|     OC_UI_EDIT_MOVE_NONE = 0, | ||||
|     OC_UI_EDIT_MOVE_ONE, | ||||
|     OC_UI_EDIT_MOVE_WORD, | ||||
|     OC_UI_EDIT_MOVE_LINE | ||||
| } oc_ui_edit_move; | ||||
| 
 | ||||
| typedef struct oc_ui_edit_command | ||||
| { | ||||
|     oc_key_code key; | ||||
|  | @ -2759,13 +2755,13 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | |||
|     { | ||||
|         .key = OC_KEY_LEFT, | ||||
|         .operation = OC_UI_EDIT_MOVE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): move one right
 | ||||
|     { | ||||
|         .key = OC_KEY_RIGHT, | ||||
|         .operation = OC_UI_EDIT_MOVE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): move one word left
 | ||||
|     { | ||||
|  | @ -2808,14 +2804,14 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | |||
|         .key = OC_KEY_LEFT, | ||||
|         .mods = OC_KEYMOD_SHIFT, | ||||
|         .operation = OC_UI_EDIT_SELECT, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): select one right
 | ||||
|     { | ||||
|         .key = OC_KEY_RIGHT, | ||||
|         .mods = OC_KEYMOD_SHIFT, | ||||
|         .operation = OC_UI_EDIT_SELECT, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): select one word left
 | ||||
|     { | ||||
|  | @ -2865,7 +2861,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | |||
|     { | ||||
|         .key = OC_KEY_DELETE, | ||||
|         .operation = OC_UI_EDIT_DELETE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): delete word
 | ||||
|     { | ||||
|  | @ -2878,7 +2874,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | |||
|     { | ||||
|         .key = OC_KEY_BACKSPACE, | ||||
|         .operation = OC_UI_EDIT_DELETE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): backspace word
 | ||||
|     { | ||||
|  | @ -2912,13 +2908,13 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | |||
|     { | ||||
|         .key = OC_KEY_LEFT, | ||||
|         .operation = OC_UI_EDIT_MOVE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): move one right
 | ||||
|     { | ||||
|         .key = OC_KEY_RIGHT, | ||||
|         .operation = OC_UI_EDIT_MOVE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): move one word left
 | ||||
|     { | ||||
|  | @ -2959,14 +2955,14 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | |||
|         .key = OC_KEY_LEFT, | ||||
|         .mods = OC_KEYMOD_SHIFT, | ||||
|         .operation = OC_UI_EDIT_SELECT, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): select one right
 | ||||
|     { | ||||
|         .key = OC_KEY_RIGHT, | ||||
|         .mods = OC_KEYMOD_SHIFT, | ||||
|         .operation = OC_UI_EDIT_SELECT, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): select one word left
 | ||||
|     { | ||||
|  | @ -3016,7 +3012,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | |||
|     { | ||||
|         .key = OC_KEY_DELETE, | ||||
|         .operation = OC_UI_EDIT_DELETE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = 1 }, | ||||
|     //NOTE(martin): delete word
 | ||||
|     { | ||||
|  | @ -3029,7 +3025,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | |||
|     { | ||||
|         .key = OC_KEY_BACKSPACE, | ||||
|         .operation = OC_UI_EDIT_DELETE, | ||||
|         .move = OC_UI_EDIT_MOVE_ONE, | ||||
|         .move = OC_UI_EDIT_MOVE_CHAR, | ||||
|         .direction = -1 }, | ||||
|     //NOTE(martin): backspace word
 | ||||
|     { | ||||
|  | @ -3073,8 +3069,14 @@ bool oc_ui_edit_is_word_separator(u32 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); | ||||
|     return (codepoint == ' ' | ||||
|             || (0x09 <= codepoint && codepoint <= 0x0d) // HT, LF, VT, FF, CR
 | ||||
|             || codepoint == 0x85                        // NEXT LINE (NEL)
 | ||||
|             || codepoint == 0xa0                        // ↓ Unicode Separator, Space (Zs)
 | ||||
|             || (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) | ||||
|  | @ -3084,7 +3086,7 @@ void oc_ui_edit_perform_move(oc_ui_context* ui, oc_ui_edit_move move, int direct | |||
|         case OC_UI_EDIT_MOVE_NONE: | ||||
|             break; | ||||
| 
 | ||||
|         case OC_UI_EDIT_MOVE_ONE: | ||||
|         case OC_UI_EDIT_MOVE_CHAR: | ||||
|         { | ||||
|             if(direction < 0 && ui->editCursor > 0) | ||||
|             { | ||||
|  | @ -3181,7 +3183,7 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation | |||
|             u32 cursor = direction < 0 ? oc_min(ui->editCursor, ui->editMark) : oc_max(ui->editCursor, ui->editMark); | ||||
|             ui->editCursor = cursor; | ||||
| 
 | ||||
|             if(ui->editCursor == ui->editMark || move != OC_UI_EDIT_MOVE_ONE) | ||||
|             if(ui->editCursor == ui->editMark || move != OC_UI_EDIT_MOVE_CHAR) | ||||
|             { | ||||
|                 //NOTE: we special case move-one when there is a selection
 | ||||
|                 //      (just place the cursor at begining/end of selection)
 | ||||
|  | @ -3251,6 +3253,46 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation | |||
|     return (codepoints); | ||||
| } | ||||
| 
 | ||||
| i32 oc_ui_edit_find_word_start(oc_ui_context* ui, oc_str32 codepoints, i32 startChar) | ||||
| { | ||||
|     i32 c = startChar; | ||||
|     if(oc_ui_edit_is_whitespace(codepoints.ptr[startChar])) | ||||
|     { | ||||
|         while(c > 0 && oc_ui_edit_is_whitespace(codepoints.ptr[c - 1])) | ||||
|         { | ||||
|             c--; | ||||
|         } | ||||
|     } | ||||
|     else if(!oc_ui_edit_is_word_separator(codepoints.ptr[startChar])) | ||||
|     { | ||||
|         while(c > 0 && !oc_ui_edit_is_word_separator(codepoints.ptr[c - 1]) && !oc_ui_edit_is_whitespace(codepoints.ptr[c - 1])) | ||||
|         { | ||||
|             c--; | ||||
|         } | ||||
|     } | ||||
|     return c; | ||||
| } | ||||
| 
 | ||||
| i32 oc_ui_edit_find_word_end(oc_ui_context* ui, oc_str32 codepoints, i32 startChar) | ||||
| { | ||||
|     i32 c = oc_min(startChar + 1, codepoints.len); | ||||
|     if(startChar < codepoints.len && oc_ui_edit_is_whitespace(codepoints.ptr[startChar])) | ||||
|     { | ||||
|         while(c < codepoints.len && oc_ui_edit_is_whitespace(codepoints.ptr[c])) | ||||
|         { | ||||
|             c++; | ||||
|         } | ||||
|     } | ||||
|     else if(startChar < codepoints.len && !oc_ui_edit_is_word_separator(codepoints.ptr[startChar])) | ||||
|     { | ||||
|         while(c < codepoints.len && !oc_ui_edit_is_word_separator(codepoints.ptr[c]) && !oc_ui_edit_is_whitespace(codepoints.ptr[c])) | ||||
|         { | ||||
|             c++; | ||||
|         } | ||||
|     } | ||||
|     return c; | ||||
| } | ||||
| 
 | ||||
| void oc_ui_text_box_render(oc_ui_box* box, void* data) | ||||
| { | ||||
|     oc_str32 codepoints = *(oc_str32*)data; | ||||
|  | @ -3417,32 +3459,117 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 | |||
|         f32 cursorX = pos.x - textBox->rect.x; | ||||
| 
 | ||||
|         oc_str32 codepoints = oc_utf8_push_to_codepoints(&ui->frameArena, text); | ||||
|         i32 newCursor = codepoints.len; | ||||
|         i32 newCursor = 0; | ||||
|         i32 hoveredChar = 0; | ||||
|         f32 x = 0; | ||||
|         for(int i = ui->editFirstDisplayedChar; i < codepoints.len; i++) | ||||
|         { | ||||
|             oc_rect bbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, i, i + 1)); | ||||
|             if(x < cursorX) | ||||
|             { | ||||
|                 hoveredChar = i; | ||||
|             } | ||||
|             if(x + 0.5 * bbox.w > cursorX) | ||||
|             { | ||||
|                 newCursor = i; | ||||
|                 break; | ||||
|             } | ||||
|             if(i == codepoints.len - 1) | ||||
|             { | ||||
|                 newCursor = codepoints.len; | ||||
|             } | ||||
|             x += bbox.w; | ||||
|         } | ||||
|         //NOTE: put cursor the closest to new cursor (this maximizes the resulting selection,
 | ||||
|         //      and seems to be the standard behaviour across a number of text editor)
 | ||||
|         if(sig.pressed && abs(newCursor - ui->editCursor) > abs(newCursor - ui->editMark)) | ||||
| 
 | ||||
|         if(sig.doubleClicked) | ||||
|         { | ||||
|             i32 tmp = ui->editCursor; | ||||
|             ui->editCursor = ui->editMark; | ||||
|             ui->editMark = tmp; | ||||
|             ui->editCursor = oc_ui_edit_find_word_end(ui, codepoints, hoveredChar); | ||||
|             ui->editMark = oc_ui_edit_find_word_start(ui, codepoints, hoveredChar); | ||||
|             ui->editSelectionMode = OC_UI_EDIT_MOVE_WORD; | ||||
|             ui->editWordSelectionInitialCursor = ui->editCursor; | ||||
|             ui->editWordSelectionInitialMark = ui->editMark; | ||||
|         } | ||||
|         //NOTE: set the new cursor, and set or leave the mark depending on mode
 | ||||
|         ui->editCursor = newCursor; | ||||
|         if(sig.pressed && !(oc_key_mods(&ui->input) & OC_KEYMOD_SHIFT)) | ||||
|         else if(sig.tripleClicked) | ||||
|         { | ||||
|             ui->editMark = ui->editCursor; | ||||
|             ui->editCursor = codepoints.len; | ||||
|             ui->editMark = 0; | ||||
|             ui->editSelectionMode = OC_UI_EDIT_MOVE_LINE; | ||||
|         } | ||||
|         else if(sig.pressed | ||||
|                 && (oc_key_mods(&ui->input) & OC_KEYMOD_SHIFT) | ||||
|                 && !(newCursor >= oc_min(ui->editCursor, ui->editMark) && newCursor <= oc_max(ui->editCursor, ui->editMark))) | ||||
|         { | ||||
|             //NOTE: put cursor the closest to new cursor (this maximizes the resulting selection,
 | ||||
|             //      and seems to be the standard behaviour across a number of text editor)
 | ||||
|             if(abs(newCursor - ui->editCursor) > abs(newCursor - ui->editMark)) | ||||
|             { | ||||
|                 ui->editMark = ui->editCursor; | ||||
|                 ui->editCursor = newCursor; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ui->editCursor = newCursor; | ||||
|             } | ||||
|             ui->editSelectionMode = OC_UI_EDIT_MOVE_CHAR; | ||||
|         } | ||||
|         else if(sig.pressed) | ||||
|         { | ||||
|             ui->editCursor = newCursor; | ||||
|             ui->editMark = newCursor; | ||||
|             ui->editSelectionMode = OC_UI_EDIT_MOVE_CHAR; | ||||
|         } | ||||
|         else if(ui->editSelectionMode == OC_UI_EDIT_MOVE_LINE) | ||||
|         { | ||||
|             oc_rect bbox = oc_text_bounding_box_utf32(font, fontSize, codepoints); | ||||
|             if(fabsf(bbox.w - cursorX) < fabsf(cursorX)) | ||||
|             { | ||||
|                 ui->editCursor = codepoints.len; | ||||
|                 ui->editMark = 0; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ui->editCursor = 0; | ||||
|                 ui->editMark = codepoints.len; | ||||
|             } | ||||
|         } | ||||
|         else if(ui->editSelectionMode == OC_UI_EDIT_MOVE_WORD) | ||||
|         { | ||||
|             if(oc_min(ui->editCursor, ui->editMark) == oc_min(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark) | ||||
|                && oc_max(ui->editCursor, ui->editMark) == oc_max(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark)) | ||||
|             { | ||||
|                 oc_rect editCursorPrefixBbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editCursor)); | ||||
|                 oc_rect editMarkPrefixBbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editMark)); | ||||
|                 f32 editCursorX = editCursorPrefixBbox.w; | ||||
|                 f32 editMarkX = editMarkPrefixBbox.w; | ||||
|                 if(fabsf(cursorX - editMarkX) < fabsf(cursorX - editCursorX)) | ||||
|                 { | ||||
|                     i32 tmp = ui->editMark; | ||||
|                     ui->editMark = ui->editCursor; | ||||
|                     ui->editCursor = tmp; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(ui->editCursor >= ui->editMark) | ||||
|             { | ||||
|                 ui->editCursor = oc_ui_edit_find_word_end(ui, codepoints, hoveredChar); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ui->editCursor = oc_ui_edit_find_word_start(ui, codepoints, hoveredChar); | ||||
|             } | ||||
|         } | ||||
|         else if(ui->editSelectionMode == OC_UI_EDIT_MOVE_CHAR) | ||||
|         { | ||||
|             ui->editCursor = newCursor; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OC_DEBUG_ASSERT("Unexpected textbox branch"); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         ui->editSelectionMode = OC_UI_EDIT_MOVE_CHAR; | ||||
|     } | ||||
| 
 | ||||
|     if(sig.hovering) | ||||
|  |  | |||
							
								
								
									
										14
									
								
								src/ui/ui.h
								
								
								
								
							
							
						
						
									
										14
									
								
								src/ui/ui.h
								
								
								
								
							|  | @ -365,7 +365,7 @@ typedef struct oc_ui_theme | |||
|     oc_color sliderThumbBorder; | ||||
|     oc_color elevatedBorder; | ||||
| 
 | ||||
|     oc_ui_palette *palette; | ||||
|     oc_ui_palette* palette; | ||||
| } oc_ui_theme; | ||||
| 
 | ||||
| extern oc_ui_theme OC_UI_DARK_THEME; | ||||
|  | @ -451,6 +451,7 @@ typedef struct oc_ui_sig | |||
|     bool released; | ||||
|     bool clicked; | ||||
|     bool doubleClicked; | ||||
|     bool tripleClicked; | ||||
|     bool rightPressed; | ||||
| 
 | ||||
|     bool dragging; | ||||
|  | @ -577,6 +578,14 @@ enum | |||
|     OC_UI_BOX_MAP_BUCKET_COUNT = 1024 | ||||
| }; | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|     OC_UI_EDIT_MOVE_NONE = 0, | ||||
|     OC_UI_EDIT_MOVE_CHAR, | ||||
|     OC_UI_EDIT_MOVE_WORD, | ||||
|     OC_UI_EDIT_MOVE_LINE | ||||
| } oc_ui_edit_move; | ||||
| 
 | ||||
| typedef struct oc_ui_context | ||||
| { | ||||
|     bool init; | ||||
|  | @ -609,6 +618,9 @@ typedef struct oc_ui_context | |||
|     i32 editMark; | ||||
|     i32 editFirstDisplayedChar; | ||||
|     f64 editCursorBlinkStart; | ||||
|     oc_ui_edit_move editSelectionMode; | ||||
|     i32 editWordSelectionInitialCursor; | ||||
|     i32 editWordSelectionInitialMark; | ||||
| 
 | ||||
|     oc_ui_theme* theme; | ||||
| } oc_ui_context; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue