diff 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
line wrap: on
line diff
--- a/third_party/raylib/include/raygui.h	Sat Jan 10 13:35:09 2026 -0800
+++ b/third_party/raylib/include/raygui.h	Mon Jan 19 18:59:10 2026 -0800
@@ -479,6 +479,13 @@
 } GuiTextStyle;
 */
 
+// Result from JUNE_GuiTextBoxEx - includes selection info
+typedef struct JUNE_TextBoxResult {
+    int result;           // Original return value (0 = no change, 1 = mode changed)
+    int selectionStart;   // Start index of selection (-1 if no selection)
+    int selectionEnd;     // End index of selection (-1 if no selection)
+} JUNE_TextBoxResult;
+
 // Gui control state
 typedef enum {
     STATE_NORMAL = 0,
@@ -736,6 +743,7 @@
 // Basic controls set
 RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text);                                            // Label control
 RAYGUIAPI int GuiButton(Rectangle bounds, const char *text);                                           // Button control, returns true when clicked
+RAYGUIAPI int GuiButtonRounded(Rectangle bounds, const char *text, float roundness, int segments);
 RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text);                                      // Label button control, returns true when clicked
 RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active);                             // Toggle Button control
 RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active);                         // Toggle Group control
@@ -744,6 +752,7 @@
 RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active);                            // Combo Box control
 
 RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode);          // Dropdown Box control
+RAYGUIAPI int GuiDropdownBoxRounded(Rectangle bounds, const char *text, int *active, bool editMode, float roundness, int segments);
 RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control
 RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers
 RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values
@@ -1410,6 +1419,9 @@
 static int textBoxCursorIndex = 0;              // Cursor index, shared by all GuiTextBox*()
 //static int blinkCursorFrameCounter = 0;       // Frame counter for cursor blinking
 static int autoCursorCounter = 0;               // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay)
+static int textBoxSelectionStart = -1;          // Selection start index (-1 if no selection)
+static int textBoxSelectionEnd = -1;            // Selection end index (-1 if no selection)
+static bool textBoxSelecting = false;           // Currently selecting with mouse
 
 //----------------------------------------------------------------------------------
 // Style data array for all gui style properties (allocated on data segment by default)
@@ -2029,6 +2041,40 @@
     return result;      // Button pressed: result = 1
 }
 
+// JUNE
+int GuiButtonRounded(Rectangle bounds, const char *text, float roundness, int segments)
+{
+    int result = 0;
+    GuiState state = guiState;
+
+    // Update control
+    //--------------------------------------------------------------------
+    if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode)
+    {
+        Vector2 mousePoint = GetMousePosition();
+
+        // Check button state
+        if (CheckCollisionPointRec(mousePoint, bounds))
+        {
+            if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED;
+            else state = STATE_FOCUSED;
+
+            if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1;
+        }
+    }
+    //--------------------------------------------------------------------
+
+    // Draw control
+    //--------------------------------------------------------------------
+    DrawRectangleRounded(bounds, roundness, segments, GetColor(GuiGetStyle(BUTTON, BASE + (state*3))));
+    GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))));
+
+    if (state == STATE_FOCUSED) GuiTooltip(bounds);
+    //------------------------------------------------------------------
+
+    return result;      // Button pressed: result = 1
+}
+
 // Label button control
 int GuiLabelButton(Rectangle bounds, const char *text)
 {
@@ -2541,6 +2587,178 @@
     return result;   // Mouse click: result = 1
 }
 
+// JUNE
+int GuiDropdownBoxRounded(Rectangle bounds, const char *text, int *active, bool editMode, float roundness, int segments)
+{
+    int result = 0;
+    GuiState state = guiState;
+
+    int temp = 0;
+    if (active == NULL) active = &temp;
+
+    int itemSelected = *active;
+    int itemFocused = -1;
+
+    int direction = 0; // Dropdown box open direction: down (default)
+    if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up
+
+    // Get substrings items from text (items pointers, lengths and count)
+    int itemCount = 0;
+    const char **items = GuiTextSplit(text, ';', &itemCount, NULL);
+
+    Rectangle boundsOpen = bounds;
+    boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
+    if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING);
+
+    Rectangle itemBounds = bounds;
+
+    // Update control
+    //--------------------------------------------------------------------
+    if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode)
+    {
+        Vector2 mousePoint = GetMousePosition();
+
+        if (editMode)
+        {
+            state = STATE_PRESSED;
+
+            // Check if mouse has been pressed or released outside limits
+            if (!CheckCollisionPointRec(mousePoint, boundsOpen))
+            {
+                if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1;
+            }
+
+            // Check if already selected item has been pressed again
+            if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1;
+
+            // Check focused and selected item
+            for (int i = 0; i < itemCount; i++)
+            {
+                // Update item rectangle y position for next item
+                if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
+                else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
+
+                if (CheckCollisionPointRec(mousePoint, itemBounds))
+                {
+                    itemFocused = i;
+                    if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
+                    {
+                        itemSelected = i;
+                        result = 1;         // Item selected
+                    }
+                    break;
+                }
+            }
+
+            itemBounds = bounds;
+        }
+        else
+        {
+            if (CheckCollisionPointRec(mousePoint, bounds))
+            {
+                if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
+                {
+                    result = 1;
+                    state = STATE_PRESSED;
+                }
+                else state = STATE_FOCUSED;
+            }
+        }
+    }
+    //--------------------------------------------------------------------
+
+    // Draw control
+    //--------------------------------------------------------------------
+    if (editMode) GuiPanel(boundsOpen, NULL);
+
+    // Main (closed) control: draw rounded background + rounded border
+    {
+        int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
+        Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3));
+        Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3));
+
+        // Filled rounded rect
+        DrawRectangleRounded(bounds, roundness, segments, baseColor);
+    }
+
+    GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3)));
+
+    if (editMode)
+    {
+        // Draw visible items
+        for (int i = 0; i < itemCount; i++)
+        {
+            // Update item rectangle y position for next item
+            if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
+            else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING));
+
+            bool isLastVisible = (i == itemCount - 1);
+
+            if (i == itemSelected)
+            {
+                Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED));
+                Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED));
+                int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
+
+                if (isLastVisible)
+                {
+                    DrawRectangleRounded(itemBounds, roundness, segments, baseColor);
+                }
+                else
+                {
+                    DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, baseColor);
+                    if (borderWidth > 0) DrawRectangleLinesEx(itemBounds, borderWidth, borderColor);
+                }
+
+                GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED)));
+            }
+            else if (i == itemFocused)
+            {
+                Color borderColor = GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED));
+                Color baseColor = GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED));
+                int borderWidth = GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH);
+
+                if (isLastVisible)
+                {
+                    DrawRectangleRounded(itemBounds, roundness, segments, baseColor);
+                }
+                else
+                {
+                    DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, baseColor);
+                    if (borderWidth > 0) DrawRectangleLinesEx(itemBounds, borderWidth, borderColor);
+                }
+
+                GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED)));
+            }
+            else
+            {
+                // Normal item: draw only text (background left as panel background),
+                // but if you want a visible background for normal items, uncomment the following:
+                // DrawRectangle(itemBounds.x, itemBounds.y, itemBounds.width, itemBounds.height, GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_NORMAL)));
+                GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL)));
+            }
+        }
+    }
+
+    if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN))
+    {
+        // Draw arrows (using icon if available)
+#if defined(RAYGUI_NO_ICONS)
+        GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 },
+            TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))));
+#else
+        GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 },
+            TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))));   // ICON_ARROW_DOWN_FILL
+#endif
+    }
+    //--------------------------------------------------------------------
+
+    *active = itemSelected;
+
+    // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item...
+    return result;   // Mouse click: result = 1
+}
+
 // Text Box control
 // NOTE: Returns true on ENTER pressed (useful for data validation)
 int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
@@ -2944,14 +3162,10 @@
     // Draw control
     //--------------------------------------------------------------------
     if (state == STATE_PRESSED)
-    {
-        GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
-    }
+        GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
     else if (state == STATE_DISABLED)
-    {
-        GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
-    }
-    else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
+        GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
+    else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
 
     // Draw text considering index offset if required
     // NOTE: Text index offset depends on cursor position
@@ -6066,7 +6280,7 @@
     return result;      // Button pressed: result = 1
 }
 
-int JUNE_GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
+JUNE_TextBoxResult JUNE_GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode)
 {
     #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
         #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN  20        // Frames to wait for autocursor movement
@@ -6074,122 +6288,255 @@
     #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY)
         #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY      1        // Frames delay for autocursor movement
     #endif
-
-    int result = 0;
+    #if !defined(JUNE_MAX_VISUAL_LINES)
+        #define JUNE_MAX_VISUAL_LINES 256
+    #endif
+
+    JUNE_TextBoxResult resultStruct = { 0, -1, -1 };
     GuiState state = guiState;
 
-    bool multiline = true; // TODO: Consider multiline text input
-    int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
-
     Rectangle textBounds = GetTextBounds(TEXTBOX, bounds);
-    int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length
+    int textLength = (text != NULL) ? (int)strlen(text) : 0;
     int thisCursorIndex = textBoxCursorIndex;
     if (thisCursorIndex > textLength) thisCursorIndex = textLength;
 
-    // Calculate cursor position for multiline
-    int cursorLine = 0;  // Current line number (0-based)
-    int lineStart = 0;   // Start index of current line
-
-    if (multiline)
+    // Line height for multiline
+    float lineHeight = GuiGetStyle(DEFAULT, TEXT_LINE_SPACING);
+    float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE) / (float)guiFont.baseSize;
+    float maxLineWidth = textBounds.width - 4; // Small margin
+
+    // Visual line structure: stores start index of each visual line
+    int visualLineStarts[JUNE_MAX_VISUAL_LINES];
+    int visualLineCount = 0;
+
+    // Calculate visual lines (accounting for word wrap)
+    if (textLength > 0 && maxLineWidth > 0)
     {
-        for (int i = 0; i < thisCursorIndex; i++)
+        int lineStartIdx = 0;
+        visualLineStarts[visualLineCount++] = 0;
+
+        int idx = 0;
+        while (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
         {
-            if (text[i] == '\n')
+            // Check for hard newline
+            if (text[idx] == '\n')
+            {
+                idx++;
+                if (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
+                {
+                    visualLineStarts[visualLineCount++] = idx;
+                    lineStartIdx = idx;
+                }
+                continue;
+            }
+
+            // Calculate width from lineStartIdx to current position
+            float currentWidth = 0;
+            int lastSpaceIdx = -1;
+            int charIdx = lineStartIdx;
+
+            while (charIdx < textLength && text[charIdx] != '\n')
             {
-                cursorLine++;
-                lineStart = i + 1;
+                int cpSize = 0;
+                int cp = GetCodepointNext(&text[charIdx], &cpSize);
+                int glyphIdx = GetGlyphIndex(guiFont, cp);
+
+                float glyphWidth;
+                if (guiFont.glyphs[glyphIdx].advanceX == 0)
+                    glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
+                else
+                    glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
+
+                if (text[charIdx] == ' ') lastSpaceIdx = charIdx;
+
+                if (currentWidth + glyphWidth > maxLineWidth && charIdx > lineStartIdx)
+                {
+                    // Need to wrap
+                    int wrapIdx;
+                    if (lastSpaceIdx > lineStartIdx)
+                    {
+                        // Wrap at last space
+                        wrapIdx = lastSpaceIdx + 1;
+                    }
+                    else
+                    {
+                        // No space found, wrap at current char
+                        wrapIdx = charIdx;
+                    }
+
+                    if (visualLineCount < JUNE_MAX_VISUAL_LINES)
+                    {
+                        visualLineStarts[visualLineCount++] = wrapIdx;
+                        lineStartIdx = wrapIdx;
+                        idx = wrapIdx;
+                        lastSpaceIdx = -1;
+                    }
+                    break;
+                }
+
+                currentWidth += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                charIdx += cpSize;
+            }
+
+            if (charIdx >= textLength || text[charIdx] == '\n')
+            {
+                idx = charIdx;
             }
         }
     }
-
-    // Calculate horizontal position within current line
-    char lineText[1024] = { 0 };
-    int lineTextLen = 0;
-    if (multiline)
+    else
     {
-        // Extract current line text up to cursor
-        int i = lineStart;
-        while (i < thisCursorIndex && text[i] != '\n' && lineTextLen < 1023)
+        visualLineStarts[visualLineCount++] = 0;
+    }
+
+    // Helper: Find visual line for a given text index
+    int cursorVisualLine = 0;
+    for (int vl = visualLineCount - 1; vl >= 0; vl--)
+    {
+        if (thisCursorIndex >= visualLineStarts[vl])
         {
-            lineText[lineTextLen++] = text[i++];
+            cursorVisualLine = vl;
+            break;
         }
-        lineText[lineTextLen] = '\0';
+    }
+
+    // Helper: Get end of visual line (exclusive)
+    int cursorLineStart = visualLineStarts[cursorVisualLine];
+    int cursorLineEnd = (cursorVisualLine + 1 < visualLineCount) ? visualLineStarts[cursorVisualLine + 1] : textLength;
+    // Adjust for newline at end
+    if (cursorLineEnd > 0 && cursorLineEnd <= textLength && cursorLineEnd > cursorLineStart)
+    {
+        if (text[cursorLineEnd - 1] == '\n') cursorLineEnd--;
     }
 
-    int textWidth = multiline ? GuiGetTextWidth(lineText) : (GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex));
-    int textIndexOffset = 0; // Text index offset to start drawing in the box
-
-    // Line height for multiline (matches GuiDrawText line spacing)
-    int lineHeight = GuiGetStyle(DEFAULT, TEXT_SIZE);
+    // Calculate cursor X position within visual line
+    float cursorXOffset = 0;
+    for (int k = cursorLineStart; k < thisCursorIndex && k < textLength; k++)
+    {
+        if (text[k] == '\n') break;
+        int cpSize = 0;
+        int cp = GetCodepointNext(&text[k], &cpSize);
+        int glyphIdx = GetGlyphIndex(guiFont, cp);
+        float glyphWidth;
+        if (guiFont.glyphs[glyphIdx].advanceX == 0)
+            glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
+        else
+            glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
+        cursorXOffset += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+    }
 
     // Cursor rectangle
-    // NOTE: Position X and Y values updated for multiline support
     Rectangle cursor = {
-        textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING),
-        multiline ? (textBounds.y + cursorLine * lineHeight) : (textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)),
+        textBounds.x + cursorXOffset,
+        textBounds.y + cursorVisualLine * lineHeight,
         2,
         (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
     };
 
-    if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2;
+    if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH) * 2;
     if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH);
 
-    // Mouse cursor rectangle
-    // NOTE: Initialized outside of screen
-    Rectangle mouseCursor = cursor;
-    mouseCursor.x = -1;
-    mouseCursor.width = 1;
-
-    // Blink-cursor frame counter
-    //if (!autoCursorMode) blinkCursorFrameCounter++;
-    //else blinkCursorFrameCounter = 0;
-
     // Update control
-    //--------------------------------------------------------------------
-    // WARNING: Text editing is only supported under certain conditions:
-    if ((state != STATE_DISABLED) &&                // Control not disabled
-        !GuiGetStyle(TEXTBOX, TEXT_READONLY) &&     // TextBox not on read-only mode
-        !guiLocked &&                               // Gui not locked
-        !guiControlExclusiveMode &&                       // No gui slider on dragging
-        (wrapMode == TEXT_WRAP_NONE))               // No wrap mode
+    if ((state != STATE_DISABLED) &&
+        !GuiGetStyle(TEXTBOX, TEXT_READONLY) &&
+        !guiLocked &&
+        !guiControlExclusiveMode)
     {
         Vector2 mousePosition = GetMousePosition();
 
         if (editMode)
         {
-            // GLOBAL: Auto-cursor movement logic
-            // NOTE: Keystrokes are handled repeatedly when button is held down for some time
             if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++;
             else autoCursorCounter = 0;
 
             bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0);
+            bool shiftDown = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
 
             state = STATE_PRESSED;
 
             if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
 
-            // If text does not fit in the textbox and current cursor position is out of bounds,
-            // we add an index offset to text for drawing only what requires depending on cursor
-            while (textWidth >= textBounds.width)
-            {
-                int nextCodepointSize = 0;
-                GetCodepointNext(text + textIndexOffset, &nextCodepointSize);
-
-                textIndexOffset += nextCodepointSize;
-
-                textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex);
-            }
-
             int codepoint = GetCharPressed();       // Get Unicode codepoint
-            if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n';
+            if (IsKeyPressed(KEY_ENTER))
+              codepoint = (int)'\n';
 
             // Encode codepoint as UTF-8
             int codepointSize = 0;
             const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize);
 
-            // Handle text paste action
-            if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
+            // Helper macro to check if there's an active selection
+            #define HAS_SELECTION() (textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
+            #define SELECTION_MIN() ((textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
+            #define SELECTION_MAX() ((textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
+
+            // Ctrl+A: Select all
+            if (IsKeyPressed(KEY_A) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
+            {
+                textBoxSelectionStart = 0;
+                textBoxSelectionEnd = textLength;
+                textBoxCursorIndex = textLength;
+            }
+            // Ctrl+C: Copy selection to clipboard
+            else if (IsKeyPressed(KEY_C) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
+            {
+                if (HAS_SELECTION())
+                {
+                    int selMin = SELECTION_MIN();
+                    int selMax = SELECTION_MAX();
+                    int selLen = selMax - selMin;
+                    char *clipText = (char *)RL_MALLOC(selLen + 1);
+                    if (clipText)
+                    {
+                        memcpy(clipText, text + selMin, selLen);
+                        clipText[selLen] = '\0';
+                        SetClipboardText(clipText);
+                        RL_FREE(clipText);
+                    }
+                }
+            }
+            // Ctrl+X: Cut selection to clipboard
+            else if (IsKeyPressed(KEY_X) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
             {
+                if (HAS_SELECTION())
+                {
+                    int selMin = SELECTION_MIN();
+                    int selMax = SELECTION_MAX();
+                    int selLen = selMax - selMin;
+
+                    // Copy to clipboard
+                    char *clipText = (char *)RL_MALLOC(selLen + 1);
+                    if (clipText)
+                    {
+                        memcpy(clipText, text + selMin, selLen);
+                        clipText[selLen] = '\0';
+                        SetClipboardText(clipText);
+                        RL_FREE(clipText);
+                    }
+
+                    // Delete selection
+                    for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
+                    textLength -= selLen;
+                    textBoxCursorIndex = selMin;
+                    textBoxSelectionStart = -1;
+                    textBoxSelectionEnd = -1;
+                }
+            }
+            // Ctrl+V: Paste (delete selection first if any)
+            else if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
+            {
+                // Delete selection first if any
+                if (HAS_SELECTION())
+                {
+                    int selMin = SELECTION_MIN();
+                    int selMax = SELECTION_MAX();
+                    int selLen = selMax - selMin;
+                    for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
+                    textLength -= selLen;
+                    textBoxCursorIndex = selMin;
+                    textBoxSelectionStart = -1;
+                    textBoxSelectionEnd = -1;
+                }
+
                 const char *pasteText = GetClipboardText();
                 if (pasteText != NULL)
                 {
@@ -6197,22 +6544,18 @@
                     int pasteCodepoint;
                     int pasteCodepointSize;
 
-                    // Count how many codepoints to copy, stopping at the first unwanted control character
                     while (true)
                     {
                         pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize);
                         if (textLength + pasteLength + pasteCodepointSize >= textSize) break;
-                        if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break;
+                        if (!((pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break;
                         pasteLength += pasteCodepointSize;
                     }
 
                     if (pasteLength > 0)
                     {
-                        // Move forward data from cursor position
-                        for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength];
-
-                        // Paste data in at cursor
-                        for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i];
+                        for (int j = textLength + pasteLength; j > textBoxCursorIndex; j--) text[j] = text[j - pasteLength];
+                        for (int j = 0; j < pasteLength; j++) text[textBoxCursorIndex + j] = pasteText[j];
 
                         textBoxCursorIndex += pasteLength;
                         textLength += pasteLength;
@@ -6220,39 +6563,76 @@
                     }
                 }
             }
-            else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize))
+            else if ((((codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize))
             {
+                // Delete selection first if any
+                if (HAS_SELECTION())
+                {
+                    int selMin = SELECTION_MIN();
+                    int selMax = SELECTION_MAX();
+                    int selLen = selMax - selMin;
+                    for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
+                    textLength -= selLen;
+                    textBoxCursorIndex = selMin;
+                    textBoxSelectionStart = -1;
+                    textBoxSelectionEnd = -1;
+                }
+
                 // Adding codepoint to text, at current cursor position
-
-                // Move forward data from cursor position
-                for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize];
-
-                // Add new codepoint in current cursor position
-                for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i];
-
-                textBoxCursorIndex += codepointSize;
-                textLength += codepointSize;
-
-                // Make sure text last character is EOL
-                text[textLength] = '\0';
+                if ((textLength + codepointSize) < textSize)
+                {
+                    for (int j = (textLength + codepointSize); j > textBoxCursorIndex; j--) text[j] = text[j - codepointSize];
+                    for (int j = 0; j < codepointSize; j++) text[textBoxCursorIndex + j] = charEncoded[j];
+
+                    textBoxCursorIndex += codepointSize;
+                    textLength += codepointSize;
+                    text[textLength] = '\0';
+                }
+            }
+
+            #undef HAS_SELECTION
+            #undef SELECTION_MIN
+            #undef SELECTION_MAX
+
+            // Move cursor to start (with Shift selection support)
+            if ((textLength > 0) && IsKeyPressed(KEY_HOME))
+            {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+                textBoxCursorIndex = 0;
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
 
-            // Move cursor to start
-            if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0;
-
-            // Move cursor to end
-            if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength;
-
-            // Delete related codepoints from text, after current cursor position
-            if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
+            // Move cursor to end (with Shift selection support)
+            if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END))
+            {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+                textBoxCursorIndex = textLength;
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
+            }
+
+            // Delete selection if any (on Delete or Backspace)
+            if ((textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd) &&
+                (IsKeyPressed(KEY_DELETE) || IsKeyPressed(KEY_BACKSPACE)))
+            {
+                int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
+                int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
+                int selLen = selMax - selMin;
+                for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
+                textLength -= selLen;
+                textBoxCursorIndex = selMin;
+                textBoxSelectionStart = -1;
+                textBoxSelectionEnd = -1;
+            }
+            // Ctrl+Delete: Delete word after cursor
+            else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
             {
                 int offset = textBoxCursorIndex;
                 int accCodepointSize = 0;
                 int nextCodepointSize;
                 int nextCodepoint;
 
-                // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
-                // Not using isalnum() since it only works on ASCII characters
                 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 bool puctuation = ispunct(nextCodepoint & 0xff);
                 while (offset < textLength)
@@ -6264,329 +6644,366 @@
                     nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 }
 
-                // Check whitespace to delete (ASCII only)
                 while (offset < textLength)
                 {
                     if (!isspace(nextCodepoint & 0xff)) break;
-
                     offset += nextCodepointSize;
                     accCodepointSize += nextCodepointSize;
                     nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 }
 
-                // Move text after cursor forward (including final null terminator)
                 for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i];
-
                 textLength -= accCodepointSize;
             }
-
+            // Delete single character after cursor
             else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger)))
             {
-                // Delete single codepoint from text, after current cursor position
-
                 int nextCodepointSize = 0;
                 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
-
-                // Move text after cursor forward (including final null terminator)
                 for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i];
-
                 textLength -= nextCodepointSize;
             }
-
-            // Delete related codepoints from text, before current cursor position
-            if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
+            // Ctrl+Backspace: Delete word before cursor
+            else if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
             {
                 int offset = textBoxCursorIndex;
                 int accCodepointSize = 0;
                 int prevCodepointSize = 0;
                 int prevCodepoint = 0;
 
-                // Check whitespace to delete (ASCII only)
                 while (offset > 0)
                 {
                     prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
                     if (!isspace(prevCodepoint & 0xff)) break;
-
                     offset -= prevCodepointSize;
                     accCodepointSize += prevCodepointSize;
                 }
 
-                // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
-                // Not using isalnum() since it only works on ASCII characters
                 bool puctuation = ispunct(prevCodepoint & 0xff);
                 while (offset > 0)
                 {
                     prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
                     if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
-
                     offset -= prevCodepointSize;
                     accCodepointSize += prevCodepointSize;
                 }
 
-                // Move text after cursor forward (including final null terminator)
                 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i];
-
                 textLength -= accCodepointSize;
                 textBoxCursorIndex -= accCodepointSize;
             }
-
+            // Backspace single character before cursor
             else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger)))
             {
-                // Delete single codepoint from text, before current cursor position
-
                 int prevCodepointSize = 0;
-
                 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
-
-                // Move text after cursor forward (including final null terminator)
                 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i];
-
                 textLength -= prevCodepointSize;
                 textBoxCursorIndex -= prevCodepointSize;
             }
 
-            // Move cursor position with keys
+            // Move cursor position with keys (with Shift selection support)
             if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
             {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
                 int offset = textBoxCursorIndex;
-                //int accCodepointSize = 0;
                 int prevCodepointSize = 0;
                 int prevCodepoint = 0;
 
-                // Check whitespace to skip (ASCII only)
                 while (offset > 0)
                 {
                     prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
                     if (!isspace(prevCodepoint & 0xff)) break;
-
                     offset -= prevCodepointSize;
-                    //accCodepointSize += prevCodepointSize;
                 }
 
-                // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
-                // Not using isalnum() since it only works on ASCII characters
                 bool puctuation = ispunct(prevCodepoint & 0xff);
                 while (offset > 0)
                 {
                     prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
                     if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
-
                     offset -= prevCodepointSize;
-                    //accCodepointSize += prevCodepointSize;
                 }
 
                 textBoxCursorIndex = offset;
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
             else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger)))
             {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
                 int prevCodepointSize = 0;
                 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
-
                 textBoxCursorIndex -= prevCodepointSize;
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
             else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
             {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
                 int offset = textBoxCursorIndex;
-                //int accCodepointSize = 0;
                 int nextCodepointSize;
                 int nextCodepoint;
 
-                // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
-                // Not using isalnum() since it only works on ASCII characters
                 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 bool puctuation = ispunct(nextCodepoint & 0xff);
                 while (offset < textLength)
                 {
                     if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break;
-
                     offset += nextCodepointSize;
-                    //accCodepointSize += nextCodepointSize;
                     nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 }
 
-                // Check whitespace to skip (ASCII only)
                 while (offset < textLength)
                 {
                     if (!isspace(nextCodepoint & 0xff)) break;
-
                     offset += nextCodepointSize;
-                    //accCodepointSize += nextCodepointSize;
                     nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
                 }
 
                 textBoxCursorIndex = offset;
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
             else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger)))
             {
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
                 int nextCodepointSize = 0;
                 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
-
                 textBoxCursorIndex += nextCodepointSize;
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
 
-            // Vertical cursor movement for multiline
-            if (multiline && (IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger)))
+            // Vertical cursor movement using visual lines (with Shift selection support)
+            if ((IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger)))
             {
-                // Find start of current line
-                int currentLineStart = textBoxCursorIndex;
-                while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--;
-
-                // Calculate horizontal position in current line
-                int horizontalPos = textBoxCursorIndex - currentLineStart;
-
-                // Find start of previous line
-                if (currentLineStart > 0)
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
+                // Find current visual line
+                int currVisLine = 0;
+                for (int vl = visualLineCount - 1; vl >= 0; vl--)
+                {
+                    if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
+                }
+
+                // Calculate X offset in current line
+                int currLineStart = visualLineStarts[currVisLine];
+                float xOffset = 0;
+                for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
                 {
-                    int prevLineEnd = currentLineStart - 1; // Skip the newline
-                    int prevLineStart = prevLineEnd;
-                    while (prevLineStart > 0 && text[prevLineStart - 1] != '\n') prevLineStart--;
-
-                    // Move to same horizontal position on previous line (or end of line if shorter)
-                    int prevLineLength = prevLineEnd - prevLineStart;
-                    int targetPos = (horizontalPos < prevLineLength) ? horizontalPos : prevLineLength;
-                    textBoxCursorIndex = prevLineStart + targetPos;
+                    if (text[k] == '\n') break;
+                    int cpSize = 0;
+                    int cp = GetCodepointNext(&text[k], &cpSize);
+                    int gi = GetGlyphIndex(guiFont, cp);
+                    float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                    xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                }
+
+                if (currVisLine > 0)
+                {
+                    // Move to previous visual line at same X position
+                    int prevLineStart = visualLineStarts[currVisLine - 1];
+                    int prevLineEnd = visualLineStarts[currVisLine];
+                    if (prevLineEnd > 0 && text[prevLineEnd - 1] == '\n') prevLineEnd--;
+
+                    float accum = 0;
+                    textBoxCursorIndex = prevLineStart;
+                    for (int k = prevLineStart; k < prevLineEnd; )
+                    {
+                        int cpSize = 0;
+                        int cp = GetCodepointNext(&text[k], &cpSize);
+                        int gi = GetGlyphIndex(guiFont, cp);
+                        float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                        if (accum + gw / 2 >= xOffset) break;
+                        accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                        textBoxCursorIndex = k + cpSize;
+                        k += cpSize;
+                    }
                 }
                 else
                 {
-                    // Already on first line, move to start
                     textBoxCursorIndex = 0;
                 }
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
-            else if (multiline && (IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger)))
+            else if ((IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger)))
             {
-                // Find start of current line
-                int currentLineStart = textBoxCursorIndex;
-                while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--;
-
-                // Calculate horizontal position in current line
-                int horizontalPos = textBoxCursorIndex - currentLineStart;
-
-                // Find end of current line
-                int currentLineEnd = textBoxCursorIndex;
-                while (currentLineEnd < textLength && text[currentLineEnd] != '\n') currentLineEnd++;
-
-                // Find next line
-                if (currentLineEnd < textLength)
+                if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
+
+                // Find current visual line
+                int currVisLine = 0;
+                for (int vl = visualLineCount - 1; vl >= 0; vl--)
+                {
+                    if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
+                }
+
+                // Calculate X offset in current line
+                int currLineStart = visualLineStarts[currVisLine];
+                float xOffset = 0;
+                for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
                 {
-                    int nextLineStart = currentLineEnd + 1; // Skip the newline
-                    int nextLineEnd = nextLineStart;
-                    while (nextLineEnd < textLength && text[nextLineEnd] != '\n') nextLineEnd++;
-
-                    // Move to same horizontal position on next line (or end of line if shorter)
-                    int nextLineLength = nextLineEnd - nextLineStart;
-                    int targetPos = (horizontalPos < nextLineLength) ? horizontalPos : nextLineLength;
-                    textBoxCursorIndex = nextLineStart + targetPos;
+                    if (text[k] == '\n') break;
+                    int cpSize = 0;
+                    int cp = GetCodepointNext(&text[k], &cpSize);
+                    int gi = GetGlyphIndex(guiFont, cp);
+                    float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                    xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                }
+
+                if (currVisLine < visualLineCount - 1)
+                {
+                    // Move to next visual line at same X position
+                    int nextLineStart = visualLineStarts[currVisLine + 1];
+                    int nextLineEnd = (currVisLine + 2 < visualLineCount) ? visualLineStarts[currVisLine + 2] : textLength;
+                    if (nextLineEnd > nextLineStart && text[nextLineEnd - 1] == '\n') nextLineEnd--;
+
+                    float accum = 0;
+                    textBoxCursorIndex = nextLineStart;
+                    for (int k = nextLineStart; k < nextLineEnd; )
+                    {
+                        int cpSize = 0;
+                        int cp = GetCodepointNext(&text[k], &cpSize);
+                        int gi = GetGlyphIndex(guiFont, cp);
+                        float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                        if (accum + gw / 2 >= xOffset) break;
+                        accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                        textBoxCursorIndex = k + cpSize;
+                        k += cpSize;
+                    }
                 }
                 else
                 {
-                    // Already on last line, move to end
                     textBoxCursorIndex = textLength;
                 }
+
+                if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
+                else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
             }
 
-            // Move cursor position with mouse
-            if (CheckCollisionPointRec(mousePosition, textBounds))     // Mouse hover text
+            // Move cursor position with mouse using visual lines
             {
-                float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize;
-                int codepointIndex = 0;
-                float glyphWidth = 0.0f;
-                float widthToMouseX = 0;
                 int mouseCursorIndex = 0;
-
-                for (int i = textIndexOffset; i < textLength; i += codepointSize)
+                bool mouseInBounds = CheckCollisionPointRec(mousePosition, textBounds);
+                bool shouldCalculateMousePos = mouseInBounds || textBoxSelecting;
+
+                if (shouldCalculateMousePos && textLength > 0)
                 {
-                    codepoint = GetCodepointNext(&text[i], &codepointSize);
-                    codepointIndex = GetGlyphIndex(guiFont, codepoint);
-
-                    if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor);
-                    else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor);
-
-                    if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2)))
-                    {
-                        mouseCursor.x = textBounds.x + widthToMouseX;
-                        mouseCursorIndex = i;
-                        printf("before: %i\n", mouseCursorIndex);
-                        break;
-                    }
-
-                    if (mousePosition.y >= textBounds.y  && mousePosition.y <= textBounds.height)
+                    // Determine which visual line the mouse is on
+                    int mouseLine = (int)((mousePosition.y - textBounds.y) / lineHeight);
+                    if (mouseLine < 0) mouseLine = 0;
+                    if (mouseLine >= visualLineCount) mouseLine = visualLineCount - 1;
+
+                    // Get the start and end of the target visual line
+                    int targetLineStart = visualLineStarts[mouseLine];
+                    int targetLineEnd = (mouseLine + 1 < visualLineCount) ? visualLineStarts[mouseLine + 1] : textLength;
+                    if (targetLineEnd > targetLineStart && text[targetLineEnd - 1] == '\n') targetLineEnd--;
+
+                    // Find character position within the line based on mouse X
+                    float relativeMouseX = mousePosition.x - textBounds.x;
+                    if (relativeMouseX < 0) relativeMouseX = 0;
+
+                    float widthAccum = 0;
+                    mouseCursorIndex = targetLineStart;
+
+                    for (int k = targetLineStart; k < targetLineEnd; )
                     {
-                        mouseCursor.y = mousePosition.y;
-                        mouseCursorIndex = i;
-                        int number_of_n = (int)(mousePosition.y / lineHeight);
-                        for (int i = 0; i < textSize; i++)
+                        if (text[k] == '\n') break;
+                        int cpSize = 0;
+                        int cp = GetCodepointNext(&text[k], &cpSize);
+                        int gi = GetGlyphIndex(guiFont, cp);
+
+                        float gw;
+                        if (guiFont.glyphs[gi].advanceX == 0)
+                            gw = (float)guiFont.recs[gi].width * scaleFactor;
+                        else
+                            gw = (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+
+                        float charMidpoint = widthAccum + gw / 2.0f;
+
+                        if (relativeMouseX <= charMidpoint)
                         {
-                            if (text[i] == '\n')
-                            {
-                                number_of_n--;
-                                if (number_of_n == 0)
-                                  mouseCursorIndex += i;
-                            }
+                            mouseCursorIndex = k;
+                            break;
                         }
-                        break;
+
+                        widthAccum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+                        mouseCursorIndex = k + cpSize;
+                        k += cpSize;
                     }
 
-                    widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING));
+                    // Clamp to line end
+                    if (mouseCursorIndex > targetLineEnd) mouseCursorIndex = targetLineEnd;
                 }
-
-                // Check if mouse cursor is at the last position
-                int textEndWidth = GuiGetTextWidth(text + textIndexOffset);
-                if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2))
+                else if (shouldCalculateMousePos)
                 {
-                    mouseCursor.x = textBounds.x + textEndWidth;
-                    mouseCursorIndex = textLength;
+                    mouseCursorIndex = 0;
                 }
 
-                // Place cursor at required index on mouse click
-                if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
+                // Mouse selection: start selection on mouse press (only when in bounds)
+                if (mouseInBounds && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
                 {
-                    cursor.x = mouseCursor.x;
+                    textBoxCursorIndex = mouseCursorIndex;
+                    textBoxSelectionStart = mouseCursorIndex;
+                    textBoxSelectionEnd = mouseCursorIndex;
+                    textBoxSelecting = true;
+                }
+                // Mouse selection: update selection while dragging (even outside bounds)
+                else if (textBoxSelecting && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
+                {
+                    textBoxSelectionEnd = mouseCursorIndex;
                     textBoxCursorIndex = mouseCursorIndex;
                 }
             }
-            else mouseCursor.x = -1;
-
-            // Recalculate cursor position for multiline
-            if (multiline)
+
+            // End mouse selection when button released
+            if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
+            {
+                textBoxSelecting = false;
+            }
+
+            // Recalculate cursor position using visual lines
+            // (Visual lines already calculated at the start - need to recalculate after any changes)
+            int newCursorVisLine = 0;
+            for (int vl = visualLineCount - 1; vl >= 0; vl--)
             {
-                // Recalculate cursor line and position
-                int newCursorLine = 0;
-                int newLineStart = 0;
-
-                for (int i = 0; i < textBoxCursorIndex; i++)
-                {
-                    if (text[i] == '\n')
-                    {
-                        newCursorLine++;
-                        newLineStart = i + 1;
-                    }
-                }
-
-                // Extract current line text up to cursor
-                char currentLineText[1024] = { 0 };
-                int currentLineLen = 0;
-                int i = newLineStart;
-                while (i < textBoxCursorIndex && text[i] != '\n' && currentLineLen < 1023)
-                {
-                    currentLineText[currentLineLen++] = text[i++];
-                }
-                currentLineText[currentLineLen] = '\0';
-
-                cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(currentLineText) + GuiGetStyle(DEFAULT, TEXT_SPACING);
-                cursor.y = textBounds.y + (newCursorLine * lineHeight * 1.5);
+                if (textBoxCursorIndex >= visualLineStarts[vl]) { newCursorVisLine = vl; break; }
             }
-            else
+
+            int newCursorLineStart = visualLineStarts[newCursorVisLine];
+            float newCursorXOffset = 0;
+            for (int k = newCursorLineStart; k < textBoxCursorIndex && k < textLength; k++)
             {
-                cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING);
+                if (text[k] == '\n') break;
+                int cpSize = 0;
+                int cp = GetCodepointNext(&text[k], &cpSize);
+                int gi = GetGlyphIndex(guiFont, cp);
+                float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                newCursorXOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
             }
 
+            cursor.x = textBounds.x + newCursorXOffset;
+            cursor.y = textBounds.y + newCursorVisLine * lineHeight;
+
             // Finish text editing on ENTER or mouse click outside bounds
-            if ((!multiline && IsKeyPressed(KEY_ENTER)) ||
-                (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
+            if ((!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
             {
                 textBoxCursorIndex = 0;     // GLOBAL: Reset the shared cursor index
                 autoCursorCounter = 0;      // GLOBAL: Reset counter for repeated keystrokes
-                result = 1;
+                textBoxSelectionStart = -1;
+                textBoxSelectionEnd = -1;
+                textBoxSelecting = false;
+                resultStruct.result = 1;
             }
         }
         else
@@ -6599,7 +7016,9 @@
                 {
                     textBoxCursorIndex = textLength;   // GLOBAL: Place cursor index to the end of current text
                     autoCursorCounter = 0;             // GLOBAL: Reset counter for repeated keystrokes
-                    result = 1;
+                    textBoxSelectionStart = -1;
+                    textBoxSelectionEnd = -1;
+                    resultStruct.result = 1;
                 }
             }
         }
@@ -6618,16 +7037,83 @@
     }
     else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))),  GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
 
-    // Draw text considering index offset if required
-    // NOTE: Text index offset depends on cursor position
-    // Set vertical alignment to top for multiline
+    // Draw selection highlight using visual lines
+    if (editMode && textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
+    {
+        int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
+        int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
+
+        // Find visual line for selection start
+        int selStartVisLine = 0;
+        for (int vl = visualLineCount - 1; vl >= 0; vl--)
+        {
+            if (selMin >= visualLineStarts[vl]) { selStartVisLine = vl; break; }
+        }
+
+        // Find visual line for selection end
+        int selEndVisLine = 0;
+        for (int vl = visualLineCount - 1; vl >= 0; vl--)
+        {
+            if (selMax >= visualLineStarts[vl]) { selEndVisLine = vl; break; }
+        }
+
+        Color selectionColor = (Color){ 100, 150, 255, 100 };
+
+        // Draw selection for each visual line in range
+        for (int vl = selStartVisLine; vl <= selEndVisLine; vl++)
+        {
+            int vlStart = visualLineStarts[vl];
+            int vlEnd = (vl + 1 < visualLineCount) ? visualLineStarts[vl + 1] : textLength;
+            if (vlEnd > vlStart && vlEnd <= textLength && text[vlEnd - 1] == '\n') vlEnd--;
+
+            // Determine selection bounds within this visual line
+            int drawStart = (vl == selStartVisLine) ? selMin : vlStart;
+            int drawEnd = (vl == selEndVisLine) ? selMax : vlEnd;
+
+            // Calculate X positions
+            float xStart = 0;
+            for (int k = vlStart; k < drawStart && k < textLength; k++)
+            {
+                if (text[k] == '\n') break;
+                int cpSize = 0;
+                int cp = GetCodepointNext(&text[k], &cpSize);
+                int gi = GetGlyphIndex(guiFont, cp);
+                float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                xStart += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+            }
+
+            float xEnd = 0;
+            for (int k = vlStart; k < drawEnd && k < textLength; k++)
+            {
+                if (text[k] == '\n') break;
+                int cpSize = 0;
+                int cp = GetCodepointNext(&text[k], &cpSize);
+                int gi = GetGlyphIndex(guiFont, cp);
+                float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
+                xEnd += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
+            }
+
+            Rectangle selRect = {
+                textBounds.x + xStart,
+                textBounds.y + vl * lineHeight,
+                xEnd - xStart,
+                (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
+            };
+            GuiDrawRectangle(selRect, 0, BLANK, selectionColor);
+        }
+    }
+
+    // Draw text with word wrap enabled
     int prevVerticalAlignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL);
-    if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP);
-
-    GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))));
-
-    // Restore previous vertical alignment
-    if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment);
+    int prevWrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
+    GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP);
+    GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_CHAR);
+
+    GuiDrawText(text, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))));
+
+    // Restore previous settings
+    GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment);
+    GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, prevWrapMode);
 
     // Draw cursor
     if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY))
@@ -6641,7 +7127,11 @@
     else if (state == STATE_FOCUSED) GuiTooltip(bounds);
     //--------------------------------------------------------------------
 
-    return result;      // Mouse button pressed: result = 1
+    // Return selection info
+    resultStruct.selectionStart = textBoxSelectionStart;
+    resultStruct.selectionEnd = textBoxSelectionEnd;
+
+    return resultStruct;
 }
 
 void JUNE_DrawRectangleLinesNoBottom(Rectangle rect, float thickness, Color color) {