Support double and triple click text selection #85
			
				
			
		
		
		
	|  | @ -15,7 +15,6 @@ ORCA_EXPORT void oc_on_init(void) | ||||||
|     surface = oc_surface_canvas(); |     surface = oc_surface_canvas(); | ||||||
|     canvas = oc_canvas_create(); |     canvas = oc_canvas_create(); | ||||||
|     oc_ui_init(&ui); |     oc_ui_init(&ui); | ||||||
|     oc_ui_set_theme(&OC_UI_LIGHT_THEME); |  | ||||||
| 
 | 
 | ||||||
|     //NOTE: load font
 |     //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 }; |     oc_event event = { 0 }; | ||||||
|     event.window = oc_window_handle_from_ptr(window); |     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.action = action; | ||||||
|     event.key.code = button; |     event.key.code = button; | ||||||
|     event.key.mods = oc_get_mod_keys(); |     event.key.mods = oc_get_mod_keys(); | ||||||
|  |     event.key.clickCount = oc_appData.win32.clickCount[button]; | ||||||
| 
 | 
 | ||||||
|     oc_queue_event(&event); |     oc_queue_event(&event); | ||||||
| } | } | ||||||
|  | @ -311,6 +336,15 @@ LRESULT oc_win32_win_proc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM | ||||||
| 
 | 
 | ||||||
|     switch(message) |     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: |         case WM_CLOSE: | ||||||
|         { |         { | ||||||
|             oc_event event = { 0 }; |             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.deltaX = event.mouse.x - oc_appData.win32.lastMousePos.x; | ||||||
|                 event.mouse.deltaY = event.mouse.y - oc_appData.win32.lastMousePos.y; |                 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 }; |             oc_appData.win32.lastMousePos = (oc_vec2){ event.mouse.x, event.mouse.y }; | ||||||
| 
 | 
 | ||||||
|             if(!oc_appData.win32.mouseTracked) |             if(!oc_appData.win32.mouseTracked) | ||||||
|  |  | ||||||
|  | @ -40,6 +40,8 @@ typedef struct oc_win32_app_data | ||||||
|     int mouseCaptureMask; |     int mouseCaptureMask; | ||||||
|     bool mouseTracked; |     bool mouseTracked; | ||||||
|     oc_vec2 lastMousePos; |     oc_vec2 lastMousePos; | ||||||
|  |     u32 lastClickTime[OC_MOUSE_BUTTON_COUNT]; | ||||||
|  |     u32 clickCount[OC_MOUSE_BUTTON_COUNT]; | ||||||
|     u32 wheelScrollLines; |     u32 wheelScrollLines; | ||||||
| 
 | 
 | ||||||
| } oc_win32_app_data; | } oc_win32_app_data; | ||||||
|  |  | ||||||
|  | @ -67,6 +67,7 @@ extern "C" | ||||||
|     float cosf(float); |     float cosf(float); | ||||||
| 
 | 
 | ||||||
|     double fabs(double); |     double fabs(double); | ||||||
|  |     float fabsf(float); | ||||||
| 
 | 
 | ||||||
|     double floor(double); |     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->repeatCount = 0; | ||||||
|         key->sysClicked = false; |         key->sysClicked = false; | ||||||
|         key->sysDoubleClicked = false; |         key->sysDoubleClicked = false; | ||||||
|  |         key->sysTripleClicked = false; | ||||||
|         key->lastUpdate = frameCounter; |         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) | static void oc_update_mouse_wheel(oc_input_state* state, f32 deltaX, f32 deltaY) | ||||||
| { | { | ||||||
|     oc_log_info("wheel"); |  | ||||||
|     u64 frameCounter = state->frameCounter; |     u64 frameCounter = state->frameCounter; | ||||||
|     oc_mouse_state* mouse = &state->mouse; |     oc_mouse_state* mouse = &state->mouse; | ||||||
|     if(mouse->lastUpdate != frameCounter) |     if(mouse->lastUpdate != frameCounter) | ||||||
|  | @ -178,10 +178,15 @@ void oc_input_process_event(oc_input_state* state, oc_event* event) | ||||||
|                 { |                 { | ||||||
|                     key->sysClicked = true; |                     key->sysClicked = true; | ||||||
|                 } |                 } | ||||||
|                 if(event->key.clickCount >= 2) | 
 | ||||||
|  |                 if(event->key.clickCount % 2 == 0) | ||||||
|                 { |                 { | ||||||
|                     key->sysDoubleClicked = true; |                     key->sysDoubleClicked = true; | ||||||
|                 } |                 } | ||||||
|  |                 else if(event->key.clickCount > 1) | ||||||
|  |                 { | ||||||
|  |                     key->sysTripleClicked = true; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             oc_update_key_mods(state, event->key.mods); |             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) | bool oc_mouse_double_clicked(oc_input_state* input, oc_mouse_button button) | ||||||
| { | { | ||||||
|     oc_key_state state = oc_mouse_button_get_state(input, 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); |     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) | oc_keymod_flags oc_key_mods(oc_input_state* input) | ||||||
| { | { | ||||||
|     return (input->keyboard.mods); |     return (input->keyboard.mods); | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ typedef struct oc_key_state | ||||||
|     bool down; |     bool down; | ||||||
|     bool sysClicked; |     bool sysClicked; | ||||||
|     bool sysDoubleClicked; |     bool sysDoubleClicked; | ||||||
|  |     bool sysTripleClicked; | ||||||
| 
 | 
 | ||||||
| } oc_key_state; | } oc_key_state; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										197
									
								
								src/ui/ui.c
								
								
								
								
							
							
						
						
									
										197
									
								
								src/ui/ui.c
								
								
								
								
							|  | @ -7,6 +7,7 @@ | ||||||
| * | * | ||||||
| *****************************************************************/ | *****************************************************************/ | ||||||
| #include "ui.h" | #include "ui.h" | ||||||
|  | #include "math.h" | ||||||
| #include "platform/platform.h" | #include "platform/platform.h" | ||||||
| #include "platform/platform_clock.h" | #include "platform/platform_clock.h" | ||||||
| #include "platform/platform_debug.h" | #include "platform/platform_debug.h" | ||||||
|  | @ -570,6 +571,7 @@ oc_ui_sig oc_ui_box_sig(oc_ui_box* box) | ||||||
|                     box->dragging = true; |                     box->dragging = true; | ||||||
|                 } |                 } | ||||||
|                 sig.doubleClicked = oc_mouse_double_clicked(input, OC_MOUSE_LEFT); |                 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); |                 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_context(ui); | ||||||
|     oc_ui_set_theme(&OC_UI_DARK_THEME); |     oc_ui_set_theme(&OC_UI_DARK_THEME); | ||||||
|  | 
 | ||||||
|  |     ui->editSelectionMode = OC_UI_EDIT_MOVE_CHAR; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void oc_ui_cleanup(void) | void oc_ui_cleanup(void) | ||||||
|  | @ -2735,14 +2739,6 @@ typedef enum | ||||||
|     OC_UI_EDIT_SELECT_ALL |     OC_UI_EDIT_SELECT_ALL | ||||||
| } oc_ui_edit_op; | } 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 | typedef struct oc_ui_edit_command | ||||||
| { | { | ||||||
|     oc_key_code key; |     oc_key_code key; | ||||||
|  | @ -2759,13 +2755,13 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_LEFT, |         .key = OC_KEY_LEFT, | ||||||
|         .operation = OC_UI_EDIT_MOVE, |         .operation = OC_UI_EDIT_MOVE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): move one right
 |     //NOTE(martin): move one right
 | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_RIGHT, |         .key = OC_KEY_RIGHT, | ||||||
|         .operation = OC_UI_EDIT_MOVE, |         .operation = OC_UI_EDIT_MOVE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): move one word left
 |     //NOTE(martin): move one word left
 | ||||||
|     { |     { | ||||||
|  | @ -2808,14 +2804,14 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | ||||||
|         .key = OC_KEY_LEFT, |         .key = OC_KEY_LEFT, | ||||||
|         .mods = OC_KEYMOD_SHIFT, |         .mods = OC_KEYMOD_SHIFT, | ||||||
|         .operation = OC_UI_EDIT_SELECT, |         .operation = OC_UI_EDIT_SELECT, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): select one right
 |     //NOTE(martin): select one right
 | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_RIGHT, |         .key = OC_KEY_RIGHT, | ||||||
|         .mods = OC_KEYMOD_SHIFT, |         .mods = OC_KEYMOD_SHIFT, | ||||||
|         .operation = OC_UI_EDIT_SELECT, |         .operation = OC_UI_EDIT_SELECT, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): select one word left
 |     //NOTE(martin): select one word left
 | ||||||
|     { |     { | ||||||
|  | @ -2865,7 +2861,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_DELETE, |         .key = OC_KEY_DELETE, | ||||||
|         .operation = OC_UI_EDIT_DELETE, |         .operation = OC_UI_EDIT_DELETE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): delete word
 |     //NOTE(martin): delete word
 | ||||||
|     { |     { | ||||||
|  | @ -2878,7 +2874,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_MACOS[] = { | ||||||
|     { |     { | ||||||
|         .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_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): backspace word
 |     //NOTE(martin): backspace word
 | ||||||
|     { |     { | ||||||
|  | @ -2912,13 +2908,13 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_LEFT, |         .key = OC_KEY_LEFT, | ||||||
|         .operation = OC_UI_EDIT_MOVE, |         .operation = OC_UI_EDIT_MOVE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): move one right
 |     //NOTE(martin): move one right
 | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_RIGHT, |         .key = OC_KEY_RIGHT, | ||||||
|         .operation = OC_UI_EDIT_MOVE, |         .operation = OC_UI_EDIT_MOVE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): move one word left
 |     //NOTE(martin): move one word left
 | ||||||
|     { |     { | ||||||
|  | @ -2959,14 +2955,14 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | ||||||
|         .key = OC_KEY_LEFT, |         .key = OC_KEY_LEFT, | ||||||
|         .mods = OC_KEYMOD_SHIFT, |         .mods = OC_KEYMOD_SHIFT, | ||||||
|         .operation = OC_UI_EDIT_SELECT, |         .operation = OC_UI_EDIT_SELECT, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): select one right
 |     //NOTE(martin): select one right
 | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_RIGHT, |         .key = OC_KEY_RIGHT, | ||||||
|         .mods = OC_KEYMOD_SHIFT, |         .mods = OC_KEYMOD_SHIFT, | ||||||
|         .operation = OC_UI_EDIT_SELECT, |         .operation = OC_UI_EDIT_SELECT, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): select one word left
 |     //NOTE(martin): select one word left
 | ||||||
|     { |     { | ||||||
|  | @ -3016,7 +3012,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | ||||||
|     { |     { | ||||||
|         .key = OC_KEY_DELETE, |         .key = OC_KEY_DELETE, | ||||||
|         .operation = OC_UI_EDIT_DELETE, |         .operation = OC_UI_EDIT_DELETE, | ||||||
|         .move = OC_UI_EDIT_MOVE_ONE, |         .move = OC_UI_EDIT_MOVE_CHAR, | ||||||
|         .direction = 1 }, |         .direction = 1 }, | ||||||
|     //NOTE(martin): delete word
 |     //NOTE(martin): delete word
 | ||||||
|     { |     { | ||||||
|  | @ -3029,7 +3025,7 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = { | ||||||
|     { |     { | ||||||
|         .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_CHAR, | ||||||
|         .direction = -1 }, |         .direction = -1 }, | ||||||
|     //NOTE(martin): backspace word
 |     //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) | bool oc_ui_edit_is_whitespace(u32 codepoint) | ||||||
| { | { | ||||||
|     return (codepoint == ' ' || (0x09 <= codepoint && codepoint <= 0x0d) || codepoint == 0x85 || codepoint == 0xa0 |     return (codepoint == ' ' | ||||||
|             || codepoint == 0x1680 || (0x2000 <= codepoint && codepoint <= 0x200a) || codepoint == 0x202f || codepoint == 0x205f || codepoint == 0x3000); |             || (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) | 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: |         case OC_UI_EDIT_MOVE_NONE: | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case OC_UI_EDIT_MOVE_ONE: |         case OC_UI_EDIT_MOVE_CHAR: | ||||||
|         { |         { | ||||||
|             if(direction < 0 && ui->editCursor > 0) |             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); |             u32 cursor = direction < 0 ? oc_min(ui->editCursor, ui->editMark) : oc_max(ui->editCursor, ui->editMark); | ||||||
|             ui->editCursor = cursor; |             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
 |                 //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)
 | ||||||
|  | @ -3251,6 +3253,46 @@ oc_str32 oc_ui_edit_perform_operation(oc_ui_context* ui, oc_ui_edit_op operation | ||||||
|     return (codepoints); |     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) | void oc_ui_text_box_render(oc_ui_box* box, void* data) | ||||||
| { | { | ||||||
|     oc_str32 codepoints = *(oc_str32*)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; |         f32 cursorX = pos.x - textBox->rect.x; | ||||||
| 
 | 
 | ||||||
|         oc_str32 codepoints = oc_utf8_push_to_codepoints(&ui->frameArena, text); |         oc_str32 codepoints = oc_utf8_push_to_codepoints(&ui->frameArena, text); | ||||||
|         i32 newCursor = codepoints.len; |         i32 newCursor = 0; | ||||||
|  |         i32 hoveredChar = 0; | ||||||
|         f32 x = 0; |         f32 x = 0; | ||||||
|         for(int i = ui->editFirstDisplayedChar; i < codepoints.len; i++) |         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)); |             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) |             if(x + 0.5 * bbox.w > cursorX) | ||||||
|             { |             { | ||||||
|                 newCursor = i; |                 newCursor = i; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  |             if(i == codepoints.len - 1) | ||||||
|  |             { | ||||||
|  |                 newCursor = codepoints.len; | ||||||
|  |             } | ||||||
|             x += bbox.w; |             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.doubleClicked) | ||||||
|         if(sig.pressed && abs(newCursor - ui->editCursor) > abs(newCursor - ui->editMark)) |  | ||||||
|         { |         { | ||||||
|             i32 tmp = ui->editCursor; |             ui->editCursor = oc_ui_edit_find_word_end(ui, codepoints, hoveredChar); | ||||||
|             ui->editCursor = ui->editMark; |             ui->editMark = oc_ui_edit_find_word_start(ui, codepoints, hoveredChar); | ||||||
|             ui->editMark = tmp; |             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
 |         else if(sig.tripleClicked) | ||||||
|         ui->editCursor = newCursor; |  | ||||||
|         if(sig.pressed && !(oc_key_mods(&ui->input) & OC_KEYMOD_SHIFT)) |  | ||||||
|         { |         { | ||||||
|             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) |     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 sliderThumbBorder; | ||||||
|     oc_color elevatedBorder; |     oc_color elevatedBorder; | ||||||
| 
 | 
 | ||||||
|     oc_ui_palette *palette; |     oc_ui_palette* palette; | ||||||
| } oc_ui_theme; | } oc_ui_theme; | ||||||
| 
 | 
 | ||||||
| extern oc_ui_theme OC_UI_DARK_THEME; | extern oc_ui_theme OC_UI_DARK_THEME; | ||||||
|  | @ -451,6 +451,7 @@ typedef struct oc_ui_sig | ||||||
|     bool released; |     bool released; | ||||||
|     bool clicked; |     bool clicked; | ||||||
|     bool doubleClicked; |     bool doubleClicked; | ||||||
|  |     bool tripleClicked; | ||||||
|     bool rightPressed; |     bool rightPressed; | ||||||
| 
 | 
 | ||||||
|     bool dragging; |     bool dragging; | ||||||
|  | @ -577,6 +578,14 @@ enum | ||||||
|     OC_UI_BOX_MAP_BUCKET_COUNT = 1024 |     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 | typedef struct oc_ui_context | ||||||
| { | { | ||||||
|     bool init; |     bool init; | ||||||
|  | @ -609,6 +618,9 @@ typedef struct oc_ui_context | ||||||
|     i32 editMark; |     i32 editMark; | ||||||
|     i32 editFirstDisplayedChar; |     i32 editFirstDisplayedChar; | ||||||
|     f64 editCursorBlinkStart; |     f64 editCursorBlinkStart; | ||||||
|  |     oc_ui_edit_move editSelectionMode; | ||||||
|  |     i32 editWordSelectionInitialCursor; | ||||||
|  |     i32 editWordSelectionInitialMark; | ||||||
| 
 | 
 | ||||||
|     oc_ui_theme* theme; |     oc_ui_theme* theme; | ||||||
| } oc_ui_context; | } oc_ui_context; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue