comparison third_party/raylib/include/raygui.h @ 173:827c6ac504cd hg-web

Merged in default here.
author MrJuneJune <me@mrjunejune.com>
date Mon, 19 Jan 2026 18:59:10 -0800
parents 058de208e640
children
comparison
equal deleted inserted replaced
151:c033667da5f9 173:827c6ac504cd
477 int alignmentV; 477 int alignmentV;
478 int padding; 478 int padding;
479 } GuiTextStyle; 479 } GuiTextStyle;
480 */ 480 */
481 481
482 // Result from JUNE_GuiTextBoxEx - includes selection info
483 typedef struct JUNE_TextBoxResult {
484 int result; // Original return value (0 = no change, 1 = mode changed)
485 int selectionStart; // Start index of selection (-1 if no selection)
486 int selectionEnd; // End index of selection (-1 if no selection)
487 } JUNE_TextBoxResult;
488
482 // Gui control state 489 // Gui control state
483 typedef enum { 490 typedef enum {
484 STATE_NORMAL = 0, 491 STATE_NORMAL = 0,
485 STATE_FOCUSED, 492 STATE_FOCUSED,
486 STATE_PRESSED, 493 STATE_PRESSED,
734 RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control 741 RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control
735 742
736 // Basic controls set 743 // Basic controls set
737 RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control 744 RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control
738 RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked 745 RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked
746 RAYGUIAPI int GuiButtonRounded(Rectangle bounds, const char *text, float roundness, int segments);
739 RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked 747 RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked
740 RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control 748 RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control
741 RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control 749 RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control
742 RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control 750 RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control
743 RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active 751 RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active
744 RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control 752 RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control
745 753
746 RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control 754 RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control
755 RAYGUIAPI int GuiDropdownBoxRounded(Rectangle bounds, const char *text, int *active, bool editMode, float roundness, int segments);
747 RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control 756 RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control
748 RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers 757 RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers
749 RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values 758 RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values
750 RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text 759 RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text
751 760
1408 static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier 1417 static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier
1409 1418
1410 static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() 1419 static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*()
1411 //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking 1420 //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking
1412 static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) 1421 static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay)
1422 static int textBoxSelectionStart = -1; // Selection start index (-1 if no selection)
1423 static int textBoxSelectionEnd = -1; // Selection end index (-1 if no selection)
1424 static bool textBoxSelecting = false; // Currently selecting with mouse
1413 1425
1414 //---------------------------------------------------------------------------------- 1426 //----------------------------------------------------------------------------------
1415 // Style data array for all gui style properties (allocated on data segment by default) 1427 // Style data array for all gui style properties (allocated on data segment by default)
1416 // 1428 //
1417 // NOTE 1: First set of BASE properties are generic to all controls but could be individually 1429 // NOTE 1: First set of BASE properties are generic to all controls but could be individually
2027 //------------------------------------------------------------------ 2039 //------------------------------------------------------------------
2028 2040
2029 return result; // Button pressed: result = 1 2041 return result; // Button pressed: result = 1
2030 } 2042 }
2031 2043
2044 // JUNE
2045 int GuiButtonRounded(Rectangle bounds, const char *text, float roundness, int segments)
2046 {
2047 int result = 0;
2048 GuiState state = guiState;
2049
2050 // Update control
2051 //--------------------------------------------------------------------
2052 if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode)
2053 {
2054 Vector2 mousePoint = GetMousePosition();
2055
2056 // Check button state
2057 if (CheckCollisionPointRec(mousePoint, bounds))
2058 {
2059 if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED;
2060 else state = STATE_FOCUSED;
2061
2062 if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1;
2063 }
2064 }
2065 //--------------------------------------------------------------------
2066
2067 // Draw control
2068 //--------------------------------------------------------------------
2069 DrawRectangleRounded(bounds, roundness, segments, GetColor(GuiGetStyle(BUTTON, BASE + (state*3))));
2070 GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))));
2071
2072 if (state == STATE_FOCUSED) GuiTooltip(bounds);
2073 //------------------------------------------------------------------
2074
2075 return result; // Button pressed: result = 1
2076 }
2077
2032 // Label button control 2078 // Label button control
2033 int GuiLabelButton(Rectangle bounds, const char *text) 2079 int GuiLabelButton(Rectangle bounds, const char *text)
2034 { 2080 {
2035 GuiState state = guiState; 2081 GuiState state = guiState;
2036 bool pressed = false; 2082 bool pressed = false;
2539 2585
2540 // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... 2586 // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item...
2541 return result; // Mouse click: result = 1 2587 return result; // Mouse click: result = 1
2542 } 2588 }
2543 2589
2590 // JUNE
2591 int GuiDropdownBoxRounded(Rectangle bounds, const char *text, int *active, bool editMode, float roundness, int segments)
2592 {
2593 int result = 0;
2594 GuiState state = guiState;
2595
2596 int temp = 0;
2597 if (active == NULL) active = &temp;
2598
2599 int itemSelected = *active;
2600 int itemFocused = -1;
2601
2602 int direction = 0; // Dropdown box open direction: down (default)
2603 if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up
2604
2605 // Get substrings items from text (items pointers, lengths and count)
2606 int itemCount = 0;
2607 const char **items = GuiTextSplit(text, ';', &itemCount, NULL);
2608
2609 Rectangle boundsOpen = bounds;
2610 boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
2611 if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING);
2612
2613 Rectangle itemBounds = bounds;
2614
2615 // Update control
2616 //--------------------------------------------------------------------
2617 if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode)
2618 {
2619 Vector2 mousePoint = GetMousePosition();
2620
2621 if (editMode)
2622 {
2623 state = STATE_PRESSED;
2624
2625 // Check if mouse has been pressed or released outside limits
2626 if (!CheckCollisionPointRec(mousePoint, boundsOpen))
2627 {
2628 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1;
2629 }
2630
2631 // Check if already selected item has been pressed again
2632 if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1;
2633
2634 // Check focused and selected item
2635 for (int i = 0; i < itemCount; i++)
2636 {
2637 // Update item rectangle y position for next item
2638 if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
2639 else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
2640
2641 if (CheckCollisionPointRec(mousePoint, itemBounds))
2642 {
2643 itemFocused = i;
2644 if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
2645 {
2646 itemSelected = i;
2647 result = 1; // Item selected
2648 }
2649 break;
2650 }
2651 }
2652
2653 itemBounds = bounds;
2654 }
2655 else
2656 {
2657 if (CheckCollisionPointRec(mousePoint, bounds))
2658 {
2659 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
2660 {
2661 result = 1;
2662 state = STATE_PRESSED;
2663 }
2664 else state = STATE_FOCUSED;
2665 }
2666 }
2667 }
2668 //--------------------------------------------------------------------
2669
2670 // Draw control
2671 //--------------------------------------------------------------------
2672 if (editMode) GuiPanel(boundsOpen, NULL);
2673
2674 // Main (closed) control: draw rounded background + rounded border
2675 {
2676 int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
2677 Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3));
2678 Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3));
2679
2680 // Filled rounded rect
2681 DrawRectangleRounded(bounds, roundness, segments, baseColor);
2682 }
2683
2684 GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3)));
2685
2686 if (editMode)
2687 {
2688 // Draw visible items
2689 for (int i = 0; i < itemCount; i++)
2690 {
2691 // Update item rectangle y position for next item
2692 if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
2693 else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
2694
2695 bool isLastVisible = (i == itemCount - 1);
2696
2697 if (i == itemSelected)
2698 {
2699 Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED));
2700 Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED));
2701 int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
2702
2703 if (isLastVisible)
2704 {
2705 DrawRectangleRounded(itemBounds, roundness, segments, baseColor);
2706 }
2707 else
2708 {
2709 DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, baseColor);
2710 if (borderWidth > 0) DrawRectangleLinesEx(itemBounds, borderWidth, borderColor);
2711 }
2712
2713 GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED)));
2714 }
2715 else if (i == itemFocused)
2716 {
2717 Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED));
2718 Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED));
2719 int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
2720
2721 if (isLastVisible)
2722 {
2723 DrawRectangleRounded(itemBounds, roundness, segments, baseColor);
2724 }
2725 else
2726 {
2727 DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, baseColor);
2728 if (borderWidth > 0) DrawRectangleLinesEx(itemBounds, borderWidth, borderColor);
2729 }
2730
2731 GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED)));
2732 }
2733 else
2734 {
2735 // Normal item: draw only text (background left as panel background),
2736 // but if you want a visible background for normal items, uncomment the following:
2737 // DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_NORMAL)));
2738 GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL)));
2739 }
2740 }
2741 }
2742
2743 if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN))
2744 {
2745 // Draw arrows (using icon if available)
2746 #if defined(RAYGUI_NO_ICONS)
2747 GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 },
2748 TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))));
2749 #else
2750 GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 },
2751 TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL
2752 #endif
2753 }
2754 //--------------------------------------------------------------------
2755
2756 *active = itemSelected;
2757
2758 // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item...
2759 return result; // Mouse click: result = 1
2760 }
2761
2544 // Text Box control 2762 // Text Box control
2545 // NOTE: Returns true on ENTER pressed (useful for data validation) 2763 // NOTE: Returns true on ENTER pressed (useful for data validation)
2546 int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) 2764 int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
2547 { 2765 {
2548 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) 2766 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
2942 //-------------------------------------------------------------------- 3160 //--------------------------------------------------------------------
2943 3161
2944 // Draw control 3162 // Draw control
2945 //-------------------------------------------------------------------- 3163 //--------------------------------------------------------------------
2946 if (state == STATE_PRESSED) 3164 if (state == STATE_PRESSED)
2947 { 3165 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
2948 GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
2949 }
2950 else if (state == STATE_DISABLED) 3166 else if (state == STATE_DISABLED)
2951 { 3167 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
2952 GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); 3168 else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
2953 }
2954 else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
2955 3169
2956 // Draw text considering index offset if required 3170 // Draw text considering index offset if required
2957 // NOTE: Text index offset depends on cursor position 3171 // NOTE: Text index offset depends on cursor position
2958 GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); 3172 GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))));
2959 3173
6064 //------------------------------------------------------------------ 6278 //------------------------------------------------------------------
6065 6279
6066 return result; // Button pressed: result = 1 6280 return result; // Button pressed: result = 1
6067 } 6281 }
6068 6282
6069 int JUNE_GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) 6283 JUNE_TextBoxResult JUNE_GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode)
6070 { 6284 {
6071 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) 6285 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
6072 #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement 6286 #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement
6073 #endif 6287 #endif
6074 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) 6288 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY)
6075 #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement 6289 #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement
6076 #endif 6290 #endif
6077 6291 #if !defined(JUNE_MAX_VISUAL_LINES)
6078 int result = 0; 6292 #define JUNE_MAX_VISUAL_LINES 256
6293 #endif
6294
6295 JUNE_TextBoxResult resultStruct = { 0, -1, -1 };
6079 GuiState state = guiState; 6296 GuiState state = guiState;
6080 6297
6081 bool multiline = true; // TODO: Consider multiline text input
6082 int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
6083
6084 Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); 6298 Rectangle textBounds = GetTextBounds(TEXTBOX, bounds);
6085 int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length 6299 int textLength = (text != NULL) ? (int)strlen(text) : 0;
6086 int thisCursorIndex = textBoxCursorIndex; 6300 int thisCursorIndex = textBoxCursorIndex;
6087 if (thisCursorIndex > textLength) thisCursorIndex = textLength; 6301 if (thisCursorIndex > textLength) thisCursorIndex = textLength;
6088 6302
6089 // Calculate cursor position for multiline 6303 // Line height for multiline
6090 int cursorLine = 0; // Current line number (0-based) 6304 float lineHeight = GuiGetStyle(DEFAULT, TEXT_LINE_SPACING);
6091 int lineStart = 0; // Start index of current line 6305 float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE) / (float)guiFont.baseSize;
6092 6306 float maxLineWidth = textBounds.width - 4; // Small margin
6093 if (multiline) 6307
6094 { 6308 // Visual line structure: stores start index of each visual line
6095 for (int i = 0; i < thisCursorIndex; i++) 6309 int visualLineStarts[JUNE_MAX_VISUAL_LINES];
6096 { 6310 int visualLineCount = 0;
6097 if (text[i] == '\n') 6311
6098 { 6312 // Calculate visual lines (accounting for word wrap)
6099 cursorLine++; 6313 if (textLength > 0 && maxLineWidth > 0)
6100 lineStart = i + 1; 6314 {
6101 } 6315 int lineStartIdx = 0;
6102 } 6316 visualLineStarts[visualLineCount++] = 0;
6103 } 6317
6104 6318 int idx = 0;
6105 // Calculate horizontal position within current line 6319 while (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
6106 char lineText[1024] = { 0 }; 6320 {
6107 int lineTextLen = 0; 6321 // Check for hard newline
6108 if (multiline) 6322 if (text[idx] == '\n')
6109 { 6323 {
6110 // Extract current line text up to cursor 6324 idx++;
6111 int i = lineStart; 6325 if (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
6112 while (i < thisCursorIndex && text[i] != '\n' && lineTextLen < 1023) 6326 {
6113 { 6327 visualLineStarts[visualLineCount++] = idx;
6114 lineText[lineTextLen++] = text[i++]; 6328 lineStartIdx = idx;
6115 } 6329 }
6116 lineText[lineTextLen] = '\0'; 6330 continue;
6117 } 6331 }
6118 6332
6119 int textWidth = multiline ? GuiGetTextWidth(lineText) : (GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex)); 6333 // Calculate width from lineStartIdx to current position
6120 int textIndexOffset = 0; // Text index offset to start drawing in the box 6334 float currentWidth = 0;
6121 6335 int lastSpaceIdx = -1;
6122 // Line height for multiline (matches GuiDrawText line spacing) 6336 int charIdx = lineStartIdx;
6123 int lineHeight = GuiGetStyle(DEFAULT, TEXT_SIZE); 6337
6338 while (charIdx < textLength && text[charIdx] != '\n')
6339 {
6340 int cpSize = 0;
6341 int cp = GetCodepointNext(&text[charIdx], &cpSize);
6342 int glyphIdx = GetGlyphIndex(guiFont, cp);
6343
6344 float glyphWidth;
6345 if (guiFont.glyphs[glyphIdx].advanceX == 0)
6346 glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
6347 else
6348 glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
6349
6350 if (text[charIdx] == ' ') lastSpaceIdx = charIdx;
6351
6352 if (currentWidth + glyphWidth > maxLineWidth && charIdx > lineStartIdx)
6353 {
6354 // Need to wrap
6355 int wrapIdx;
6356 if (lastSpaceIdx > lineStartIdx)
6357 {
6358 // Wrap at last space
6359 wrapIdx = lastSpaceIdx + 1;
6360 }
6361 else
6362 {
6363 // No space found, wrap at current char
6364 wrapIdx = charIdx;
6365 }
6366
6367 if (visualLineCount < JUNE_MAX_VISUAL_LINES)
6368 {
6369 visualLineStarts[visualLineCount++] = wrapIdx;
6370 lineStartIdx = wrapIdx;
6371 idx = wrapIdx;
6372 lastSpaceIdx = -1;
6373 }
6374 break;
6375 }
6376
6377 currentWidth += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6378 charIdx += cpSize;
6379 }
6380
6381 if (charIdx >= textLength || text[charIdx] == '\n')
6382 {
6383 idx = charIdx;
6384 }
6385 }
6386 }
6387 else
6388 {
6389 visualLineStarts[visualLineCount++] = 0;
6390 }
6391
6392 // Helper: Find visual line for a given text index
6393 int cursorVisualLine = 0;
6394 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6395 {
6396 if (thisCursorIndex >= visualLineStarts[vl])
6397 {
6398 cursorVisualLine = vl;
6399 break;
6400 }
6401 }
6402
6403 // Helper: Get end of visual line (exclusive)
6404 int cursorLineStart = visualLineStarts[cursorVisualLine];
6405 int cursorLineEnd = (cursorVisualLine + 1 < visualLineCount) ? visualLineStarts[cursorVisualLine + 1] : textLength;
6406 // Adjust for newline at end
6407 if (cursorLineEnd > 0 && cursorLineEnd <= textLength && cursorLineEnd > cursorLineStart)
6408 {
6409 if (text[cursorLineEnd - 1] == '\n') cursorLineEnd--;
6410 }
6411
6412 // Calculate cursor X position within visual line
6413 float cursorXOffset = 0;
6414 for (int k = cursorLineStart; k < thisCursorIndex && k < textLength; k++)
6415 {
6416 if (text[k] == '\n') break;
6417 int cpSize = 0;
6418 int cp = GetCodepointNext(&text[k], &cpSize);
6419 int glyphIdx = GetGlyphIndex(guiFont, cp);
6420 float glyphWidth;
6421 if (guiFont.glyphs[glyphIdx].advanceX == 0)
6422 glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
6423 else
6424 glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
6425 cursorXOffset += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6426 }
6124 6427
6125 // Cursor rectangle 6428 // Cursor rectangle
6126 // NOTE: Position X and Y values updated for multiline support
6127 Rectangle cursor = { 6429 Rectangle cursor = {
6128 textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), 6430 textBounds.x + cursorXOffset,
6129 multiline ? (textBounds.y + cursorLine * lineHeight) : (textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)), 6431 textBounds.y + cursorVisualLine * lineHeight,
6130 2, 6432 2,
6131 (float)GuiGetStyle(DEFAULT, TEXT_SIZE) 6433 (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
6132 }; 6434 };
6133 6435
6134 if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; 6436 if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH) * 2;
6135 if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); 6437 if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH);
6136 6438
6137 // Mouse cursor rectangle
6138 // NOTE: Initialized outside of screen
6139 Rectangle mouseCursor = cursor;
6140 mouseCursor.x = -1;
6141 mouseCursor.width = 1;
6142
6143 // Blink-cursor frame counter
6144 //if (!autoCursorMode) blinkCursorFrameCounter++;
6145 //else blinkCursorFrameCounter = 0;
6146
6147 // Update control 6439 // Update control
6148 //-------------------------------------------------------------------- 6440 if ((state != STATE_DISABLED) &&
6149 // WARNING: Text editing is only supported under certain conditions: 6441 !GuiGetStyle(TEXTBOX, TEXT_READONLY) &&
6150 if ((state != STATE_DISABLED) && // Control not disabled 6442 !guiLocked &&
6151 !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode 6443 !guiControlExclusiveMode)
6152 !guiLocked && // Gui not locked
6153 !guiControlExclusiveMode && // No gui slider on dragging
6154 (wrapMode == TEXT_WRAP_NONE)) // No wrap mode
6155 { 6444 {
6156 Vector2 mousePosition = GetMousePosition(); 6445 Vector2 mousePosition = GetMousePosition();
6157 6446
6158 if (editMode) 6447 if (editMode)
6159 { 6448 {
6160 // GLOBAL: Auto-cursor movement logic
6161 // NOTE: Keystrokes are handled repeatedly when button is held down for some time
6162 if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; 6449 if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++;
6163 else autoCursorCounter = 0; 6450 else autoCursorCounter = 0;
6164 6451
6165 bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); 6452 bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0);
6453 bool shiftDown = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
6166 6454
6167 state = STATE_PRESSED; 6455 state = STATE_PRESSED;
6168 6456
6169 if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; 6457 if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
6170 6458
6171 // If text does not fit in the textbox and current cursor position is out of bounds,
6172 // we add an index offset to text for drawing only what requires depending on cursor
6173 while (textWidth >= textBounds.width)
6174 {
6175 int nextCodepointSize = 0;
6176 GetCodepointNext(text + textIndexOffset, &nextCodepointSize);
6177
6178 textIndexOffset += nextCodepointSize;
6179
6180 textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex);
6181 }
6182
6183 int codepoint = GetCharPressed(); // Get Unicode codepoint 6459 int codepoint = GetCharPressed(); // Get Unicode codepoint
6184 if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; 6460 if (IsKeyPressed(KEY_ENTER))
6461 codepoint = (int)'\n';
6185 6462
6186 // Encode codepoint as UTF-8 6463 // Encode codepoint as UTF-8
6187 int codepointSize = 0; 6464 int codepointSize = 0;
6188 const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); 6465 const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize);
6189 6466
6190 // Handle text paste action 6467 // Helper macro to check if there's an active selection
6191 if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6468 #define HAS_SELECTION() (textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
6192 { 6469 #define SELECTION_MIN() ((textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
6470 #define SELECTION_MAX() ((textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
6471
6472 // Ctrl+A: Select all
6473 if (IsKeyPressed(KEY_A) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6474 {
6475 textBoxSelectionStart = 0;
6476 textBoxSelectionEnd = textLength;
6477 textBoxCursorIndex = textLength;
6478 }
6479 // Ctrl+C: Copy selection to clipboard
6480 else if (IsKeyPressed(KEY_C) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6481 {
6482 if (HAS_SELECTION())
6483 {
6484 int selMin = SELECTION_MIN();
6485 int selMax = SELECTION_MAX();
6486 int selLen = selMax - selMin;
6487 char *clipText = (char *)RL_MALLOC(selLen + 1);
6488 if (clipText)
6489 {
6490 memcpy(clipText, text + selMin, selLen);
6491 clipText[selLen] = '\0';
6492 SetClipboardText(clipText);
6493 RL_FREE(clipText);
6494 }
6495 }
6496 }
6497 // Ctrl+X: Cut selection to clipboard
6498 else if (IsKeyPressed(KEY_X) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6499 {
6500 if (HAS_SELECTION())
6501 {
6502 int selMin = SELECTION_MIN();
6503 int selMax = SELECTION_MAX();
6504 int selLen = selMax - selMin;
6505
6506 // Copy to clipboard
6507 char *clipText = (char *)RL_MALLOC(selLen + 1);
6508 if (clipText)
6509 {
6510 memcpy(clipText, text + selMin, selLen);
6511 clipText[selLen] = '\0';
6512 SetClipboardText(clipText);
6513 RL_FREE(clipText);
6514 }
6515
6516 // Delete selection
6517 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6518 textLength -= selLen;
6519 textBoxCursorIndex = selMin;
6520 textBoxSelectionStart = -1;
6521 textBoxSelectionEnd = -1;
6522 }
6523 }
6524 // Ctrl+V: Paste (delete selection first if any)
6525 else if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6526 {
6527 // Delete selection first if any
6528 if (HAS_SELECTION())
6529 {
6530 int selMin = SELECTION_MIN();
6531 int selMax = SELECTION_MAX();
6532 int selLen = selMax - selMin;
6533 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6534 textLength -= selLen;
6535 textBoxCursorIndex = selMin;
6536 textBoxSelectionStart = -1;
6537 textBoxSelectionEnd = -1;
6538 }
6539
6193 const char *pasteText = GetClipboardText(); 6540 const char *pasteText = GetClipboardText();
6194 if (pasteText != NULL) 6541 if (pasteText != NULL)
6195 { 6542 {
6196 int pasteLength = 0; 6543 int pasteLength = 0;
6197 int pasteCodepoint; 6544 int pasteCodepoint;
6198 int pasteCodepointSize; 6545 int pasteCodepointSize;
6199 6546
6200 // Count how many codepoints to copy, stopping at the first unwanted control character
6201 while (true) 6547 while (true)
6202 { 6548 {
6203 pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); 6549 pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize);
6204 if (textLength + pasteLength + pasteCodepointSize >= textSize) break; 6550 if (textLength + pasteLength + pasteCodepointSize >= textSize) break;
6205 if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; 6551 if (!((pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break;
6206 pasteLength += pasteCodepointSize; 6552 pasteLength += pasteCodepointSize;
6207 } 6553 }
6208 6554
6209 if (pasteLength > 0) 6555 if (pasteLength > 0)
6210 { 6556 {
6211 // Move forward data from cursor position 6557 for (int j = textLength + pasteLength; j > textBoxCursorIndex; j--) text[j] = text[j - pasteLength];
6212 for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; 6558 for (int j = 0; j < pasteLength; j++) text[textBoxCursorIndex + j] = pasteText[j];
6213
6214 // Paste data in at cursor
6215 for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i];
6216 6559
6217 textBoxCursorIndex += pasteLength; 6560 textBoxCursorIndex += pasteLength;
6218 textLength += pasteLength; 6561 textLength += pasteLength;
6219 text[textLength] = '\0'; 6562 text[textLength] = '\0';
6220 } 6563 }
6221 } 6564 }
6222 } 6565 }
6223 else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) 6566 else if ((((codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize))
6224 { 6567 {
6568 // Delete selection first if any
6569 if (HAS_SELECTION())
6570 {
6571 int selMin = SELECTION_MIN();
6572 int selMax = SELECTION_MAX();
6573 int selLen = selMax - selMin;
6574 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6575 textLength -= selLen;
6576 textBoxCursorIndex = selMin;
6577 textBoxSelectionStart = -1;
6578 textBoxSelectionEnd = -1;
6579 }
6580
6225 // Adding codepoint to text, at current cursor position 6581 // Adding codepoint to text, at current cursor position
6226 6582 if ((textLength + codepointSize) < textSize)
6227 // Move forward data from cursor position 6583 {
6228 for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; 6584 for (int j = (textLength + codepointSize); j > textBoxCursorIndex; j--) text[j] = text[j - codepointSize];
6229 6585 for (int j = 0; j < codepointSize; j++) text[textBoxCursorIndex + j] = charEncoded[j];
6230 // Add new codepoint in current cursor position 6586
6231 for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; 6587 textBoxCursorIndex += codepointSize;
6232 6588 textLength += codepointSize;
6233 textBoxCursorIndex += codepointSize; 6589 text[textLength] = '\0';
6234 textLength += codepointSize; 6590 }
6235 6591 }
6236 // Make sure text last character is EOL 6592
6237 text[textLength] = '\0'; 6593 #undef HAS_SELECTION
6238 } 6594 #undef SELECTION_MIN
6239 6595 #undef SELECTION_MAX
6240 // Move cursor to start 6596
6241 if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; 6597 // Move cursor to start (with Shift selection support)
6242 6598 if ((textLength > 0) && IsKeyPressed(KEY_HOME))
6243 // Move cursor to end 6599 {
6244 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; 6600 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6245 6601 textBoxCursorIndex = 0;
6246 // Delete related codepoints from text, after current cursor position 6602 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6247 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6603 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6604 }
6605
6606 // Move cursor to end (with Shift selection support)
6607 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END))
6608 {
6609 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6610 textBoxCursorIndex = textLength;
6611 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6612 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6613 }
6614
6615 // Delete selection if any (on Delete or Backspace)
6616 if ((textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd) &&
6617 (IsKeyPressed(KEY_DELETE) || IsKeyPressed(KEY_BACKSPACE)))
6618 {
6619 int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6620 int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6621 int selLen = selMax - selMin;
6622 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6623 textLength -= selLen;
6624 textBoxCursorIndex = selMin;
6625 textBoxSelectionStart = -1;
6626 textBoxSelectionEnd = -1;
6627 }
6628 // Ctrl+Delete: Delete word after cursor
6629 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6248 { 6630 {
6249 int offset = textBoxCursorIndex; 6631 int offset = textBoxCursorIndex;
6250 int accCodepointSize = 0; 6632 int accCodepointSize = 0;
6251 int nextCodepointSize; 6633 int nextCodepointSize;
6252 int nextCodepoint; 6634 int nextCodepoint;
6253 6635
6254 // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
6255 // Not using isalnum() since it only works on ASCII characters
6256 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6636 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6257 bool puctuation = ispunct(nextCodepoint & 0xff); 6637 bool puctuation = ispunct(nextCodepoint & 0xff);
6258 while (offset < textLength) 6638 while (offset < textLength)
6259 { 6639 {
6260 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) 6640 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff))))
6262 offset += nextCodepointSize; 6642 offset += nextCodepointSize;
6263 accCodepointSize += nextCodepointSize; 6643 accCodepointSize += nextCodepointSize;
6264 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6644 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6265 } 6645 }
6266 6646
6267 // Check whitespace to delete (ASCII only)
6268 while (offset < textLength) 6647 while (offset < textLength)
6269 { 6648 {
6270 if (!isspace(nextCodepoint & 0xff)) break; 6649 if (!isspace(nextCodepoint & 0xff)) break;
6271
6272 offset += nextCodepointSize; 6650 offset += nextCodepointSize;
6273 accCodepointSize += nextCodepointSize; 6651 accCodepointSize += nextCodepointSize;
6274 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6652 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6275 } 6653 }
6276 6654
6277 // Move text after cursor forward (including final null terminator)
6278 for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; 6655 for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i];
6279
6280 textLength -= accCodepointSize; 6656 textLength -= accCodepointSize;
6281 } 6657 }
6282 6658 // Delete single character after cursor
6283 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) 6659 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger)))
6284 { 6660 {
6285 // Delete single codepoint from text, after current cursor position
6286
6287 int nextCodepointSize = 0; 6661 int nextCodepointSize = 0;
6288 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); 6662 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
6289
6290 // Move text after cursor forward (including final null terminator)
6291 for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; 6663 for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i];
6292
6293 textLength -= nextCodepointSize; 6664 textLength -= nextCodepointSize;
6294 } 6665 }
6295 6666 // Ctrl+Backspace: Delete word before cursor
6296 // Delete related codepoints from text, before current cursor position 6667 else if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6297 if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6298 { 6668 {
6299 int offset = textBoxCursorIndex; 6669 int offset = textBoxCursorIndex;
6300 int accCodepointSize = 0; 6670 int accCodepointSize = 0;
6301 int prevCodepointSize = 0; 6671 int prevCodepointSize = 0;
6302 int prevCodepoint = 0; 6672 int prevCodepoint = 0;
6303 6673
6304 // Check whitespace to delete (ASCII only)
6305 while (offset > 0) 6674 while (offset > 0)
6306 { 6675 {
6307 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6676 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6308 if (!isspace(prevCodepoint & 0xff)) break; 6677 if (!isspace(prevCodepoint & 0xff)) break;
6309
6310 offset -= prevCodepointSize; 6678 offset -= prevCodepointSize;
6311 accCodepointSize += prevCodepointSize; 6679 accCodepointSize += prevCodepointSize;
6312 } 6680 }
6313 6681
6314 // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
6315 // Not using isalnum() since it only works on ASCII characters
6316 bool puctuation = ispunct(prevCodepoint & 0xff); 6682 bool puctuation = ispunct(prevCodepoint & 0xff);
6317 while (offset > 0) 6683 while (offset > 0)
6318 { 6684 {
6319 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6685 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6320 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; 6686 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
6321
6322 offset -= prevCodepointSize; 6687 offset -= prevCodepointSize;
6323 accCodepointSize += prevCodepointSize; 6688 accCodepointSize += prevCodepointSize;
6324 } 6689 }
6325 6690
6326 // Move text after cursor forward (including final null terminator)
6327 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; 6691 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i];
6328
6329 textLength -= accCodepointSize; 6692 textLength -= accCodepointSize;
6330 textBoxCursorIndex -= accCodepointSize; 6693 textBoxCursorIndex -= accCodepointSize;
6331 } 6694 }
6332 6695 // Backspace single character before cursor
6333 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) 6696 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger)))
6334 { 6697 {
6335 // Delete single codepoint from text, before current cursor position
6336
6337 int prevCodepointSize = 0; 6698 int prevCodepointSize = 0;
6338
6339 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); 6699 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
6340
6341 // Move text after cursor forward (including final null terminator)
6342 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; 6700 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i];
6343
6344 textLength -= prevCodepointSize; 6701 textLength -= prevCodepointSize;
6345 textBoxCursorIndex -= prevCodepointSize; 6702 textBoxCursorIndex -= prevCodepointSize;
6346 } 6703 }
6347 6704
6348 // Move cursor position with keys 6705 // Move cursor position with keys (with Shift selection support)
6349 if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6706 if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6350 { 6707 {
6708 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6709
6351 int offset = textBoxCursorIndex; 6710 int offset = textBoxCursorIndex;
6352 //int accCodepointSize = 0;
6353 int prevCodepointSize = 0; 6711 int prevCodepointSize = 0;
6354 int prevCodepoint = 0; 6712 int prevCodepoint = 0;
6355 6713
6356 // Check whitespace to skip (ASCII only)
6357 while (offset > 0) 6714 while (offset > 0)
6358 { 6715 {
6359 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6716 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6360 if (!isspace(prevCodepoint & 0xff)) break; 6717 if (!isspace(prevCodepoint & 0xff)) break;
6361
6362 offset -= prevCodepointSize; 6718 offset -= prevCodepointSize;
6363 //accCodepointSize += prevCodepointSize; 6719 }
6364 } 6720
6365
6366 // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
6367 // Not using isalnum() since it only works on ASCII characters
6368 bool puctuation = ispunct(prevCodepoint & 0xff); 6721 bool puctuation = ispunct(prevCodepoint & 0xff);
6369 while (offset > 0) 6722 while (offset > 0)
6370 { 6723 {
6371 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6724 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6372 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; 6725 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
6373
6374 offset -= prevCodepointSize; 6726 offset -= prevCodepointSize;
6375 //accCodepointSize += prevCodepointSize;
6376 } 6727 }
6377 6728
6378 textBoxCursorIndex = offset; 6729 textBoxCursorIndex = offset;
6730
6731 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6732 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6379 } 6733 }
6380 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) 6734 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger)))
6381 { 6735 {
6736 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6737
6382 int prevCodepointSize = 0; 6738 int prevCodepointSize = 0;
6383 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); 6739 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
6384
6385 textBoxCursorIndex -= prevCodepointSize; 6740 textBoxCursorIndex -= prevCodepointSize;
6741
6742 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6743 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6386 } 6744 }
6387 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6745 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6388 { 6746 {
6747 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6748
6389 int offset = textBoxCursorIndex; 6749 int offset = textBoxCursorIndex;
6390 //int accCodepointSize = 0;
6391 int nextCodepointSize; 6750 int nextCodepointSize;
6392 int nextCodepoint; 6751 int nextCodepoint;
6393 6752
6394 // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
6395 // Not using isalnum() since it only works on ASCII characters
6396 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6753 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6397 bool puctuation = ispunct(nextCodepoint & 0xff); 6754 bool puctuation = ispunct(nextCodepoint & 0xff);
6398 while (offset < textLength) 6755 while (offset < textLength)
6399 { 6756 {
6400 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; 6757 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break;
6401
6402 offset += nextCodepointSize; 6758 offset += nextCodepointSize;
6403 //accCodepointSize += nextCodepointSize;
6404 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6759 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6405 } 6760 }
6406 6761
6407 // Check whitespace to skip (ASCII only)
6408 while (offset < textLength) 6762 while (offset < textLength)
6409 { 6763 {
6410 if (!isspace(nextCodepoint & 0xff)) break; 6764 if (!isspace(nextCodepoint & 0xff)) break;
6411
6412 offset += nextCodepointSize; 6765 offset += nextCodepointSize;
6413 //accCodepointSize += nextCodepointSize;
6414 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6766 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6415 } 6767 }
6416 6768
6417 textBoxCursorIndex = offset; 6769 textBoxCursorIndex = offset;
6770
6771 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6772 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6418 } 6773 }
6419 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) 6774 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger)))
6420 { 6775 {
6776 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6777
6421 int nextCodepointSize = 0; 6778 int nextCodepointSize = 0;
6422 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); 6779 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
6423
6424 textBoxCursorIndex += nextCodepointSize; 6780 textBoxCursorIndex += nextCodepointSize;
6425 } 6781
6426 6782 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6427 // Vertical cursor movement for multiline 6783 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6428 if (multiline && (IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger))) 6784 }
6429 { 6785
6430 // Find start of current line 6786 // Vertical cursor movement using visual lines (with Shift selection support)
6431 int currentLineStart = textBoxCursorIndex; 6787 if ((IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger)))
6432 while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--; 6788 {
6433 6789 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6434 // Calculate horizontal position in current line 6790
6435 int horizontalPos = textBoxCursorIndex - currentLineStart; 6791 // Find current visual line
6436 6792 int currVisLine = 0;
6437 // Find start of previous line 6793 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6438 if (currentLineStart > 0) 6794 {
6439 { 6795 if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
6440 int prevLineEnd = currentLineStart - 1; // Skip the newline 6796 }
6441 int prevLineStart = prevLineEnd; 6797
6442 while (prevLineStart > 0 && text[prevLineStart - 1] != '\n') prevLineStart--; 6798 // Calculate X offset in current line
6443 6799 int currLineStart = visualLineStarts[currVisLine];
6444 // Move to same horizontal position on previous line (or end of line if shorter) 6800 float xOffset = 0;
6445 int prevLineLength = prevLineEnd - prevLineStart; 6801 for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
6446 int targetPos = (horizontalPos < prevLineLength) ? horizontalPos : prevLineLength; 6802 {
6447 textBoxCursorIndex = prevLineStart + targetPos; 6803 if (text[k] == '\n') break;
6804 int cpSize = 0;
6805 int cp = GetCodepointNext(&text[k], &cpSize);
6806 int gi = GetGlyphIndex(guiFont, cp);
6807 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6808 xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6809 }
6810
6811 if (currVisLine > 0)
6812 {
6813 // Move to previous visual line at same X position
6814 int prevLineStart = visualLineStarts[currVisLine - 1];
6815 int prevLineEnd = visualLineStarts[currVisLine];
6816 if (prevLineEnd > 0 && text[prevLineEnd - 1] == '\n') prevLineEnd--;
6817
6818 float accum = 0;
6819 textBoxCursorIndex = prevLineStart;
6820 for (int k = prevLineStart; k < prevLineEnd; )
6821 {
6822 int cpSize = 0;
6823 int cp = GetCodepointNext(&text[k], &cpSize);
6824 int gi = GetGlyphIndex(guiFont, cp);
6825 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6826 if (accum + gw / 2 >= xOffset) break;
6827 accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6828 textBoxCursorIndex = k + cpSize;
6829 k += cpSize;
6830 }
6448 } 6831 }
6449 else 6832 else
6450 { 6833 {
6451 // Already on first line, move to start
6452 textBoxCursorIndex = 0; 6834 textBoxCursorIndex = 0;
6453 } 6835 }
6454 } 6836
6455 else if (multiline && (IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger))) 6837 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6456 { 6838 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6457 // Find start of current line 6839 }
6458 int currentLineStart = textBoxCursorIndex; 6840 else if ((IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger)))
6459 while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--; 6841 {
6460 6842 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6461 // Calculate horizontal position in current line 6843
6462 int horizontalPos = textBoxCursorIndex - currentLineStart; 6844 // Find current visual line
6463 6845 int currVisLine = 0;
6464 // Find end of current line 6846 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6465 int currentLineEnd = textBoxCursorIndex; 6847 {
6466 while (currentLineEnd < textLength && text[currentLineEnd] != '\n') currentLineEnd++; 6848 if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
6467 6849 }
6468 // Find next line 6850
6469 if (currentLineEnd < textLength) 6851 // Calculate X offset in current line
6470 { 6852 int currLineStart = visualLineStarts[currVisLine];
6471 int nextLineStart = currentLineEnd + 1; // Skip the newline 6853 float xOffset = 0;
6472 int nextLineEnd = nextLineStart; 6854 for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
6473 while (nextLineEnd < textLength && text[nextLineEnd] != '\n') nextLineEnd++; 6855 {
6474 6856 if (text[k] == '\n') break;
6475 // Move to same horizontal position on next line (or end of line if shorter) 6857 int cpSize = 0;
6476 int nextLineLength = nextLineEnd - nextLineStart; 6858 int cp = GetCodepointNext(&text[k], &cpSize);
6477 int targetPos = (horizontalPos < nextLineLength) ? horizontalPos : nextLineLength; 6859 int gi = GetGlyphIndex(guiFont, cp);
6478 textBoxCursorIndex = nextLineStart + targetPos; 6860 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6861 xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6862 }
6863
6864 if (currVisLine < visualLineCount - 1)
6865 {
6866 // Move to next visual line at same X position
6867 int nextLineStart = visualLineStarts[currVisLine + 1];
6868 int nextLineEnd = (currVisLine + 2 < visualLineCount) ? visualLineStarts[currVisLine + 2] : textLength;
6869 if (nextLineEnd > nextLineStart && text[nextLineEnd - 1] == '\n') nextLineEnd--;
6870
6871 float accum = 0;
6872 textBoxCursorIndex = nextLineStart;
6873 for (int k = nextLineStart; k < nextLineEnd; )
6874 {
6875 int cpSize = 0;
6876 int cp = GetCodepointNext(&text[k], &cpSize);
6877 int gi = GetGlyphIndex(guiFont, cp);
6878 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6879 if (accum + gw / 2 >= xOffset) break;
6880 accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6881 textBoxCursorIndex = k + cpSize;
6882 k += cpSize;
6883 }
6479 } 6884 }
6480 else 6885 else
6481 { 6886 {
6482 // Already on last line, move to end
6483 textBoxCursorIndex = textLength; 6887 textBoxCursorIndex = textLength;
6484 } 6888 }
6485 } 6889
6486 6890 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6487 // Move cursor position with mouse 6891 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6488 if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text 6892 }
6489 { 6893
6490 float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; 6894 // Move cursor position with mouse using visual lines
6491 int codepointIndex = 0; 6895 {
6492 float glyphWidth = 0.0f;
6493 float widthToMouseX = 0;
6494 int mouseCursorIndex = 0; 6896 int mouseCursorIndex = 0;
6495 6897 bool mouseInBounds = CheckCollisionPointRec(mousePosition, textBounds);
6496 for (int i = textIndexOffset; i < textLength; i += codepointSize) 6898 bool shouldCalculateMousePos = mouseInBounds || textBoxSelecting;
6497 { 6899
6498 codepoint = GetCodepointNext(&text[i], &codepointSize); 6900 if (shouldCalculateMousePos && textLength > 0)
6499 codepointIndex = GetGlyphIndex(guiFont, codepoint); 6901 {
6500 6902 // Determine which visual line the mouse is on
6501 if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); 6903 int mouseLine = (int)((mousePosition.y - textBounds.y) / lineHeight);
6502 else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); 6904 if (mouseLine < 0) mouseLine = 0;
6503 6905 if (mouseLine >= visualLineCount) mouseLine = visualLineCount - 1;
6504 if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) 6906
6907 // Get the start and end of the target visual line
6908 int targetLineStart = visualLineStarts[mouseLine];
6909 int targetLineEnd = (mouseLine + 1 < visualLineCount) ? visualLineStarts[mouseLine + 1] : textLength;
6910 if (targetLineEnd > targetLineStart && text[targetLineEnd - 1] == '\n') targetLineEnd--;
6911
6912 // Find character position within the line based on mouse X
6913 float relativeMouseX = mousePosition.x - textBounds.x;
6914 if (relativeMouseX < 0) relativeMouseX = 0;
6915
6916 float widthAccum = 0;
6917 mouseCursorIndex = targetLineStart;
6918
6919 for (int k = targetLineStart; k < targetLineEnd; )
6505 { 6920 {
6506 mouseCursor.x = textBounds.x + widthToMouseX; 6921 if (text[k] == '\n') break;
6507 mouseCursorIndex = i; 6922 int cpSize = 0;
6508 printf("before: %i\n", mouseCursorIndex); 6923 int cp = GetCodepointNext(&text[k], &cpSize);
6509 break; 6924 int gi = GetGlyphIndex(guiFont, cp);
6925
6926 float gw;
6927 if (guiFont.glyphs[gi].advanceX == 0)
6928 gw = (float)guiFont.recs[gi].width * scaleFactor;
6929 else
6930 gw = (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6931
6932 float charMidpoint = widthAccum + gw / 2.0f;
6933
6934 if (relativeMouseX <= charMidpoint)
6935 {
6936 mouseCursorIndex = k;
6937 break;
6938 }
6939
6940 widthAccum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6941 mouseCursorIndex = k + cpSize;
6942 k += cpSize;
6510 } 6943 }
6511 6944
6512 if (mousePosition.y >= textBounds.y && mousePosition.y <= textBounds.height) 6945 // Clamp to line end
6513 { 6946 if (mouseCursorIndex > targetLineEnd) mouseCursorIndex = targetLineEnd;
6514 mouseCursor.y = mousePosition.y; 6947 }
6515 mouseCursorIndex = i; 6948 else if (shouldCalculateMousePos)
6516 int number_of_n = (int)(mousePosition.y / lineHeight); 6949 {
6517 for (int i = 0; i < textSize; i++) 6950 mouseCursorIndex = 0;
6518 { 6951 }
6519 if (text[i] == '\n') 6952
6520 { 6953 // Mouse selection: start selection on mouse press (only when in bounds)
6521 number_of_n--; 6954 if (mouseInBounds && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
6522 if (number_of_n == 0) 6955 {
6523 mouseCursorIndex += i;
6524 }
6525 }
6526 break;
6527 }
6528
6529 widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING));
6530 }
6531
6532 // Check if mouse cursor is at the last position
6533 int textEndWidth = GuiGetTextWidth(text + textIndexOffset);
6534 if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2))
6535 {
6536 mouseCursor.x = textBounds.x + textEndWidth;
6537 mouseCursorIndex = textLength;
6538 }
6539
6540 // Place cursor at required index on mouse click
6541 if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
6542 {
6543 cursor.x = mouseCursor.x;
6544 textBoxCursorIndex = mouseCursorIndex; 6956 textBoxCursorIndex = mouseCursorIndex;
6545 } 6957 textBoxSelectionStart = mouseCursorIndex;
6546 } 6958 textBoxSelectionEnd = mouseCursorIndex;
6547 else mouseCursor.x = -1; 6959 textBoxSelecting = true;
6548 6960 }
6549 // Recalculate cursor position for multiline 6961 // Mouse selection: update selection while dragging (even outside bounds)
6550 if (multiline) 6962 else if (textBoxSelecting && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
6551 { 6963 {
6552 // Recalculate cursor line and position 6964 textBoxSelectionEnd = mouseCursorIndex;
6553 int newCursorLine = 0; 6965 textBoxCursorIndex = mouseCursorIndex;
6554 int newLineStart = 0; 6966 }
6555 6967 }
6556 for (int i = 0; i < textBoxCursorIndex; i++) 6968
6557 { 6969 // End mouse selection when button released
6558 if (text[i] == '\n') 6970 if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
6559 { 6971 {
6560 newCursorLine++; 6972 textBoxSelecting = false;
6561 newLineStart = i + 1; 6973 }
6562 } 6974
6563 } 6975 // Recalculate cursor position using visual lines
6564 6976 // (Visual lines already calculated at the start - need to recalculate after any changes)
6565 // Extract current line text up to cursor 6977 int newCursorVisLine = 0;
6566 char currentLineText[1024] = { 0 }; 6978 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6567 int currentLineLen = 0; 6979 {
6568 int i = newLineStart; 6980 if (textBoxCursorIndex >= visualLineStarts[vl]) { newCursorVisLine = vl; break; }
6569 while (i < textBoxCursorIndex && text[i] != '\n' && currentLineLen < 1023) 6981 }
6570 { 6982
6571 currentLineText[currentLineLen++] = text[i++]; 6983 int newCursorLineStart = visualLineStarts[newCursorVisLine];
6572 } 6984 float newCursorXOffset = 0;
6573 currentLineText[currentLineLen] = '\0'; 6985 for (int k = newCursorLineStart; k < textBoxCursorIndex && k < textLength; k++)
6574 6986 {
6575 cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(currentLineText) + GuiGetStyle(DEFAULT, TEXT_SPACING); 6987 if (text[k] == '\n') break;
6576 cursor.y = textBounds.y + (newCursorLine * lineHeight * 1.5); 6988 int cpSize = 0;
6577 } 6989 int cp = GetCodepointNext(&text[k], &cpSize);
6578 else 6990 int gi = GetGlyphIndex(guiFont, cp);
6579 { 6991 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6580 cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); 6992 newCursorXOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6581 } 6993 }
6994
6995 cursor.x = textBounds.x + newCursorXOffset;
6996 cursor.y = textBounds.y + newCursorVisLine * lineHeight;
6582 6997
6583 // Finish text editing on ENTER or mouse click outside bounds 6998 // Finish text editing on ENTER or mouse click outside bounds
6584 if ((!multiline && IsKeyPressed(KEY_ENTER)) || 6999 if ((!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
6585 (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
6586 { 7000 {
6587 textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index 7001 textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index
6588 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes 7002 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
6589 result = 1; 7003 textBoxSelectionStart = -1;
7004 textBoxSelectionEnd = -1;
7005 textBoxSelecting = false;
7006 resultStruct.result = 1;
6590 } 7007 }
6591 } 7008 }
6592 else 7009 else
6593 { 7010 {
6594 if (CheckCollisionPointRec(mousePosition, bounds)) 7011 if (CheckCollisionPointRec(mousePosition, bounds))
6597 7014
6598 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 7015 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
6599 { 7016 {
6600 textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text 7017 textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text
6601 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes 7018 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
6602 result = 1; 7019 textBoxSelectionStart = -1;
7020 textBoxSelectionEnd = -1;
7021 resultStruct.result = 1;
6603 } 7022 }
6604 } 7023 }
6605 } 7024 }
6606 } 7025 }
6607 //-------------------------------------------------------------------- 7026 //--------------------------------------------------------------------
6616 { 7035 {
6617 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); 7036 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
6618 } 7037 }
6619 else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); 7038 else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
6620 7039
6621 // Draw text considering index offset if required 7040 // Draw selection highlight using visual lines
6622 // NOTE: Text index offset depends on cursor position 7041 if (editMode && textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
6623 // Set vertical alignment to top for multiline 7042 {
7043 int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
7044 int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
7045
7046 // Find visual line for selection start
7047 int selStartVisLine = 0;
7048 for (int vl = visualLineCount - 1; vl >= 0; vl--)
7049 {
7050 if (selMin >= visualLineStarts[vl]) { selStartVisLine = vl; break; }
7051 }
7052
7053 // Find visual line for selection end
7054 int selEndVisLine = 0;
7055 for (int vl = visualLineCount - 1; vl >= 0; vl--)
7056 {
7057 if (selMax >= visualLineStarts[vl]) { selEndVisLine = vl; break; }
7058 }
7059
7060 Color selectionColor = (Color){ 100, 150, 255, 100 };
7061
7062 // Draw selection for each visual line in range
7063 for (int vl = selStartVisLine; vl <= selEndVisLine; vl++)
7064 {
7065 int vlStart = visualLineStarts[vl];
7066 int vlEnd = (vl + 1 < visualLineCount) ? visualLineStarts[vl + 1] : textLength;
7067 if (vlEnd > vlStart && vlEnd <= textLength && text[vlEnd - 1] == '\n') vlEnd--;
7068
7069 // Determine selection bounds within this visual line
7070 int drawStart = (vl == selStartVisLine) ? selMin : vlStart;
7071 int drawEnd = (vl == selEndVisLine) ? selMax : vlEnd;
7072
7073 // Calculate X positions
7074 float xStart = 0;
7075 for (int k = vlStart; k < drawStart && k < textLength; k++)
7076 {
7077 if (text[k] == '\n') break;
7078 int cpSize = 0;
7079 int cp = GetCodepointNext(&text[k], &cpSize);
7080 int gi = GetGlyphIndex(guiFont, cp);
7081 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
7082 xStart += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
7083 }
7084
7085 float xEnd = 0;
7086 for (int k = vlStart; k < drawEnd && k < textLength; k++)
7087 {
7088 if (text[k] == '\n') break;
7089 int cpSize = 0;
7090 int cp = GetCodepointNext(&text[k], &cpSize);
7091 int gi = GetGlyphIndex(guiFont, cp);
7092 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
7093 xEnd += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
7094 }
7095
7096 Rectangle selRect = {
7097 textBounds.x + xStart,
7098 textBounds.y + vl * lineHeight,
7099 xEnd - xStart,
7100 (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
7101 };
7102 GuiDrawRectangle(selRect, 0, BLANK, selectionColor);
7103 }
7104 }
7105
7106 // Draw text with word wrap enabled
6624 int prevVerticalAlignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); 7107 int prevVerticalAlignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL);
6625 if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); 7108 int prevWrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
6626 7109 GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP);
6627 GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); 7110 GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_CHAR);
6628 7111
6629 // Restore previous vertical alignment 7112 GuiDrawText(text, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))));
6630 if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment); 7113
7114 // Restore previous settings
7115 GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment);
7116 GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, prevWrapMode);
6631 7117
6632 // Draw cursor 7118 // Draw cursor
6633 if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) 7119 if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY))
6634 { 7120 {
6635 //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) 7121 //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0))
6639 // if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); 7125 // if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)));
6640 } 7126 }
6641 else if (state == STATE_FOCUSED) GuiTooltip(bounds); 7127 else if (state == STATE_FOCUSED) GuiTooltip(bounds);
6642 //-------------------------------------------------------------------- 7128 //--------------------------------------------------------------------
6643 7129
6644 return result; // Mouse button pressed: result = 1 7130 // Return selection info
7131 resultStruct.selectionStart = textBoxSelectionStart;
7132 resultStruct.selectionEnd = textBoxSelectionEnd;
7133
7134 return resultStruct;
6645 } 7135 }
6646 7136
6647 void JUNE_DrawRectangleLinesNoBottom(Rectangle rect, float thickness, Color color) { 7137 void JUNE_DrawRectangleLinesNoBottom(Rectangle rect, float thickness, Color color) {
6648 Vector2 topLeft = { rect.x, rect.y }; 7138 Vector2 topLeft = { rect.x, rect.y };
6649 Vector2 topRight = { rect.x + rect.width, rect.y }; 7139 Vector2 topRight = { rect.x + rect.width, rect.y };