comparison third_party/raylib/include/raygui.h @ 157:2db6253f355d

[ThirdParty] Added highlight library for better readability on blog.
author June Park <parkjune1995@gmail.com>
date Tue, 13 Jan 2026 19:18:47 -0800
parents 249881ceff7b
children 87d8d3eb3491
comparison
equal deleted inserted replaced
156:cd35e600ae34 157:2db6253f355d
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,
1408 static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier 1415 static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier
1409 1416
1410 static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() 1417 static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*()
1411 //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking 1418 //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) 1419 static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay)
1420 static int textBoxSelectionStart = -1; // Selection start index (-1 if no selection)
1421 static int textBoxSelectionEnd = -1; // Selection end index (-1 if no selection)
1422 static bool textBoxSelecting = false; // Currently selecting with mouse
1413 1423
1414 //---------------------------------------------------------------------------------- 1424 //----------------------------------------------------------------------------------
1415 // Style data array for all gui style properties (allocated on data segment by default) 1425 // Style data array for all gui style properties (allocated on data segment by default)
1416 // 1426 //
1417 // NOTE 1: First set of BASE properties are generic to all controls but could be individually 1427 // NOTE 1: First set of BASE properties are generic to all controls but could be individually
6064 //------------------------------------------------------------------ 6074 //------------------------------------------------------------------
6065 6075
6066 return result; // Button pressed: result = 1 6076 return result; // Button pressed: result = 1
6067 } 6077 }
6068 6078
6069 int JUNE_GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) 6079 JUNE_TextBoxResult JUNE_GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode)
6070 { 6080 {
6071 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) 6081 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
6072 #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement 6082 #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement
6073 #endif 6083 #endif
6074 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) 6084 #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY)
6075 #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement 6085 #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement
6076 #endif 6086 #endif
6077 6087 #if !defined(JUNE_MAX_VISUAL_LINES)
6078 int result = 0; 6088 #define JUNE_MAX_VISUAL_LINES 256
6089 #endif
6090
6091 JUNE_TextBoxResult resultStruct = { 0, -1, -1 };
6079 GuiState state = guiState; 6092 GuiState state = guiState;
6080 6093
6081 bool multiline = true; // TODO: Consider multiline text input
6082 int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
6083
6084 Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); 6094 Rectangle textBounds = GetTextBounds(TEXTBOX, bounds);
6085 int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length 6095 int textLength = (text != NULL) ? (int)strlen(text) : 0;
6086 int thisCursorIndex = textBoxCursorIndex; 6096 int thisCursorIndex = textBoxCursorIndex;
6087 if (thisCursorIndex > textLength) thisCursorIndex = textLength; 6097 if (thisCursorIndex > textLength) thisCursorIndex = textLength;
6088 6098
6089 // Calculate cursor position for multiline 6099 // Line height for multiline
6090 int cursorLine = 0; // Current line number (0-based) 6100 float lineHeight = GuiGetStyle(DEFAULT, TEXT_LINE_SPACING);
6091 int lineStart = 0; // Start index of current line 6101 float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE) / (float)guiFont.baseSize;
6092 6102 float maxLineWidth = textBounds.width - 4; // Small margin
6093 if (multiline) 6103
6094 { 6104 // Visual line structure: stores start index of each visual line
6095 for (int i = 0; i < thisCursorIndex; i++) 6105 int visualLineStarts[JUNE_MAX_VISUAL_LINES];
6096 { 6106 int visualLineCount = 0;
6097 if (text[i] == '\n') 6107
6098 { 6108 // Calculate visual lines (accounting for word wrap)
6099 cursorLine++; 6109 if (textLength > 0 && maxLineWidth > 0)
6100 lineStart = i + 1; 6110 {
6101 } 6111 int lineStartIdx = 0;
6102 } 6112 visualLineStarts[visualLineCount++] = 0;
6103 } 6113
6104 6114 int idx = 0;
6105 // Calculate horizontal position within current line 6115 while (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
6106 char lineText[1024] = { 0 }; 6116 {
6107 int lineTextLen = 0; 6117 // Check for hard newline
6108 if (multiline) 6118 if (text[idx] == '\n')
6109 { 6119 {
6110 // Extract current line text up to cursor 6120 idx++;
6111 int i = lineStart; 6121 if (idx < textLength && visualLineCount < JUNE_MAX_VISUAL_LINES)
6112 while (i < thisCursorIndex && text[i] != '\n' && lineTextLen < 1023) 6122 {
6113 { 6123 visualLineStarts[visualLineCount++] = idx;
6114 lineText[lineTextLen++] = text[i++]; 6124 lineStartIdx = idx;
6115 } 6125 }
6116 lineText[lineTextLen] = '\0'; 6126 continue;
6117 } 6127 }
6118 6128
6119 int textWidth = multiline ? GuiGetTextWidth(lineText) : (GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex)); 6129 // Calculate width from lineStartIdx to current position
6120 int textIndexOffset = 0; // Text index offset to start drawing in the box 6130 float currentWidth = 0;
6121 6131 int lastSpaceIdx = -1;
6122 // Line height for multiline (matches GuiDrawText line spacing) 6132 int charIdx = lineStartIdx;
6123 int lineHeight = GuiGetStyle(DEFAULT, TEXT_SIZE); 6133
6134 while (charIdx < textLength && text[charIdx] != '\n')
6135 {
6136 int cpSize = 0;
6137 int cp = GetCodepointNext(&text[charIdx], &cpSize);
6138 int glyphIdx = GetGlyphIndex(guiFont, cp);
6139
6140 float glyphWidth;
6141 if (guiFont.glyphs[glyphIdx].advanceX == 0)
6142 glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
6143 else
6144 glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
6145
6146 if (text[charIdx] == ' ') lastSpaceIdx = charIdx;
6147
6148 if (currentWidth + glyphWidth > maxLineWidth && charIdx > lineStartIdx)
6149 {
6150 // Need to wrap
6151 int wrapIdx;
6152 if (lastSpaceIdx > lineStartIdx)
6153 {
6154 // Wrap at last space
6155 wrapIdx = lastSpaceIdx + 1;
6156 }
6157 else
6158 {
6159 // No space found, wrap at current char
6160 wrapIdx = charIdx;
6161 }
6162
6163 if (visualLineCount < JUNE_MAX_VISUAL_LINES)
6164 {
6165 visualLineStarts[visualLineCount++] = wrapIdx;
6166 lineStartIdx = wrapIdx;
6167 idx = wrapIdx;
6168 lastSpaceIdx = -1;
6169 }
6170 break;
6171 }
6172
6173 currentWidth += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6174 charIdx += cpSize;
6175 }
6176
6177 if (charIdx >= textLength || text[charIdx] == '\n')
6178 {
6179 idx = charIdx;
6180 }
6181 }
6182 }
6183 else
6184 {
6185 visualLineStarts[visualLineCount++] = 0;
6186 }
6187
6188 // Helper: Find visual line for a given text index
6189 int cursorVisualLine = 0;
6190 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6191 {
6192 if (thisCursorIndex >= visualLineStarts[vl])
6193 {
6194 cursorVisualLine = vl;
6195 break;
6196 }
6197 }
6198
6199 // Helper: Get end of visual line (exclusive)
6200 int cursorLineStart = visualLineStarts[cursorVisualLine];
6201 int cursorLineEnd = (cursorVisualLine + 1 < visualLineCount) ? visualLineStarts[cursorVisualLine + 1] : textLength;
6202 // Adjust for newline at end
6203 if (cursorLineEnd > 0 && cursorLineEnd <= textLength && cursorLineEnd > cursorLineStart)
6204 {
6205 if (text[cursorLineEnd - 1] == '\n') cursorLineEnd--;
6206 }
6207
6208 // Calculate cursor X position within visual line
6209 float cursorXOffset = 0;
6210 for (int k = cursorLineStart; k < thisCursorIndex && k < textLength; k++)
6211 {
6212 if (text[k] == '\n') break;
6213 int cpSize = 0;
6214 int cp = GetCodepointNext(&text[k], &cpSize);
6215 int glyphIdx = GetGlyphIndex(guiFont, cp);
6216 float glyphWidth;
6217 if (guiFont.glyphs[glyphIdx].advanceX == 0)
6218 glyphWidth = (float)guiFont.recs[glyphIdx].width * scaleFactor;
6219 else
6220 glyphWidth = (float)guiFont.glyphs[glyphIdx].advanceX * scaleFactor;
6221 cursorXOffset += glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6222 }
6124 6223
6125 // Cursor rectangle 6224 // Cursor rectangle
6126 // NOTE: Position X and Y values updated for multiline support
6127 Rectangle cursor = { 6225 Rectangle cursor = {
6128 textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), 6226 textBounds.x + cursorXOffset,
6129 multiline ? (textBounds.y + cursorLine * lineHeight) : (textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)), 6227 textBounds.y + cursorVisualLine * lineHeight,
6130 2, 6228 2,
6131 (float)GuiGetStyle(DEFAULT, TEXT_SIZE) 6229 (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
6132 }; 6230 };
6133 6231
6134 if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; 6232 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); 6233 if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH);
6136 6234
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 6235 // Update control
6148 //-------------------------------------------------------------------- 6236 if ((state != STATE_DISABLED) &&
6149 // WARNING: Text editing is only supported under certain conditions: 6237 !GuiGetStyle(TEXTBOX, TEXT_READONLY) &&
6150 if ((state != STATE_DISABLED) && // Control not disabled 6238 !guiLocked &&
6151 !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode 6239 !guiControlExclusiveMode)
6152 !guiLocked && // Gui not locked
6153 !guiControlExclusiveMode && // No gui slider on dragging
6154 (wrapMode == TEXT_WRAP_NONE)) // No wrap mode
6155 { 6240 {
6156 Vector2 mousePosition = GetMousePosition(); 6241 Vector2 mousePosition = GetMousePosition();
6157 6242
6158 if (editMode) 6243 if (editMode)
6159 { 6244 {
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++; 6245 if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++;
6163 else autoCursorCounter = 0; 6246 else autoCursorCounter = 0;
6164 6247
6165 bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); 6248 bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0);
6249 bool shiftDown = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
6166 6250
6167 state = STATE_PRESSED; 6251 state = STATE_PRESSED;
6168 6252
6169 if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; 6253 if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
6170 6254
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 6255 int codepoint = GetCharPressed(); // Get Unicode codepoint
6184 if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; 6256 if (IsKeyPressed(KEY_ENTER))
6257 codepoint = (int)'\n';
6185 6258
6186 // Encode codepoint as UTF-8 6259 // Encode codepoint as UTF-8
6187 int codepointSize = 0; 6260 int codepointSize = 0;
6188 const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); 6261 const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize);
6189 6262
6190 // Handle text paste action 6263 // Helper macro to check if there's an active selection
6191 if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6264 #define HAS_SELECTION() (textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
6192 { 6265 #define SELECTION_MIN() ((textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
6266 #define SELECTION_MAX() ((textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd)
6267
6268 // Ctrl+A: Select all
6269 if (IsKeyPressed(KEY_A) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6270 {
6271 textBoxSelectionStart = 0;
6272 textBoxSelectionEnd = textLength;
6273 textBoxCursorIndex = textLength;
6274 }
6275 // Ctrl+C: Copy selection to clipboard
6276 else if (IsKeyPressed(KEY_C) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6277 {
6278 if (HAS_SELECTION())
6279 {
6280 int selMin = SELECTION_MIN();
6281 int selMax = SELECTION_MAX();
6282 int selLen = selMax - selMin;
6283 char *clipText = (char *)RL_MALLOC(selLen + 1);
6284 if (clipText)
6285 {
6286 memcpy(clipText, text + selMin, selLen);
6287 clipText[selLen] = '\0';
6288 SetClipboardText(clipText);
6289 RL_FREE(clipText);
6290 }
6291 }
6292 }
6293 // Ctrl+X: Cut selection to clipboard
6294 else if (IsKeyPressed(KEY_X) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6295 {
6296 if (HAS_SELECTION())
6297 {
6298 int selMin = SELECTION_MIN();
6299 int selMax = SELECTION_MAX();
6300 int selLen = selMax - selMin;
6301
6302 // Copy to clipboard
6303 char *clipText = (char *)RL_MALLOC(selLen + 1);
6304 if (clipText)
6305 {
6306 memcpy(clipText, text + selMin, selLen);
6307 clipText[selLen] = '\0';
6308 SetClipboardText(clipText);
6309 RL_FREE(clipText);
6310 }
6311
6312 // Delete selection
6313 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6314 textLength -= selLen;
6315 textBoxCursorIndex = selMin;
6316 textBoxSelectionStart = -1;
6317 textBoxSelectionEnd = -1;
6318 }
6319 }
6320 // Ctrl+V: Paste (delete selection first if any)
6321 else if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER)))
6322 {
6323 // Delete selection first if any
6324 if (HAS_SELECTION())
6325 {
6326 int selMin = SELECTION_MIN();
6327 int selMax = SELECTION_MAX();
6328 int selLen = selMax - selMin;
6329 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6330 textLength -= selLen;
6331 textBoxCursorIndex = selMin;
6332 textBoxSelectionStart = -1;
6333 textBoxSelectionEnd = -1;
6334 }
6335
6193 const char *pasteText = GetClipboardText(); 6336 const char *pasteText = GetClipboardText();
6194 if (pasteText != NULL) 6337 if (pasteText != NULL)
6195 { 6338 {
6196 int pasteLength = 0; 6339 int pasteLength = 0;
6197 int pasteCodepoint; 6340 int pasteCodepoint;
6198 int pasteCodepointSize; 6341 int pasteCodepointSize;
6199 6342
6200 // Count how many codepoints to copy, stopping at the first unwanted control character
6201 while (true) 6343 while (true)
6202 { 6344 {
6203 pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); 6345 pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize);
6204 if (textLength + pasteLength + pasteCodepointSize >= textSize) break; 6346 if (textLength + pasteLength + pasteCodepointSize >= textSize) break;
6205 if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; 6347 if (!((pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break;
6206 pasteLength += pasteCodepointSize; 6348 pasteLength += pasteCodepointSize;
6207 } 6349 }
6208 6350
6209 if (pasteLength > 0) 6351 if (pasteLength > 0)
6210 { 6352 {
6211 // Move forward data from cursor position 6353 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]; 6354 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 6355
6217 textBoxCursorIndex += pasteLength; 6356 textBoxCursorIndex += pasteLength;
6218 textLength += pasteLength; 6357 textLength += pasteLength;
6219 text[textLength] = '\0'; 6358 text[textLength] = '\0';
6220 } 6359 }
6221 } 6360 }
6222 } 6361 }
6223 else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) 6362 else if ((((codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize))
6224 { 6363 {
6364 // Delete selection first if any
6365 if (HAS_SELECTION())
6366 {
6367 int selMin = SELECTION_MIN();
6368 int selMax = SELECTION_MAX();
6369 int selLen = selMax - selMin;
6370 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6371 textLength -= selLen;
6372 textBoxCursorIndex = selMin;
6373 textBoxSelectionStart = -1;
6374 textBoxSelectionEnd = -1;
6375 }
6376
6225 // Adding codepoint to text, at current cursor position 6377 // Adding codepoint to text, at current cursor position
6226 6378 if ((textLength + codepointSize) < textSize)
6227 // Move forward data from cursor position 6379 {
6228 for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; 6380 for (int j = (textLength + codepointSize); j > textBoxCursorIndex; j--) text[j] = text[j - codepointSize];
6229 6381 for (int j = 0; j < codepointSize; j++) text[textBoxCursorIndex + j] = charEncoded[j];
6230 // Add new codepoint in current cursor position 6382
6231 for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; 6383 textBoxCursorIndex += codepointSize;
6232 6384 textLength += codepointSize;
6233 textBoxCursorIndex += codepointSize; 6385 text[textLength] = '\0';
6234 textLength += codepointSize; 6386 }
6235 6387 }
6236 // Make sure text last character is EOL 6388
6237 text[textLength] = '\0'; 6389 #undef HAS_SELECTION
6238 } 6390 #undef SELECTION_MIN
6239 6391 #undef SELECTION_MAX
6240 // Move cursor to start 6392
6241 if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; 6393 // Move cursor to start (with Shift selection support)
6242 6394 if ((textLength > 0) && IsKeyPressed(KEY_HOME))
6243 // Move cursor to end 6395 {
6244 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; 6396 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6245 6397 textBoxCursorIndex = 0;
6246 // Delete related codepoints from text, after current cursor position 6398 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6247 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6399 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6400 }
6401
6402 // Move cursor to end (with Shift selection support)
6403 if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END))
6404 {
6405 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6406 textBoxCursorIndex = textLength;
6407 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6408 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6409 }
6410
6411 // Delete selection if any (on Delete or Backspace)
6412 if ((textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd) &&
6413 (IsKeyPressed(KEY_DELETE) || IsKeyPressed(KEY_BACKSPACE)))
6414 {
6415 int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6416 int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6417 int selLen = selMax - selMin;
6418 for (int j = selMax; j <= textLength; j++) text[j - selLen] = text[j];
6419 textLength -= selLen;
6420 textBoxCursorIndex = selMin;
6421 textBoxSelectionStart = -1;
6422 textBoxSelectionEnd = -1;
6423 }
6424 // Ctrl+Delete: Delete word after cursor
6425 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6248 { 6426 {
6249 int offset = textBoxCursorIndex; 6427 int offset = textBoxCursorIndex;
6250 int accCodepointSize = 0; 6428 int accCodepointSize = 0;
6251 int nextCodepointSize; 6429 int nextCodepointSize;
6252 int nextCodepoint; 6430 int nextCodepoint;
6253 6431
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); 6432 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6257 bool puctuation = ispunct(nextCodepoint & 0xff); 6433 bool puctuation = ispunct(nextCodepoint & 0xff);
6258 while (offset < textLength) 6434 while (offset < textLength)
6259 { 6435 {
6260 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) 6436 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff))))
6262 offset += nextCodepointSize; 6438 offset += nextCodepointSize;
6263 accCodepointSize += nextCodepointSize; 6439 accCodepointSize += nextCodepointSize;
6264 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6440 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6265 } 6441 }
6266 6442
6267 // Check whitespace to delete (ASCII only)
6268 while (offset < textLength) 6443 while (offset < textLength)
6269 { 6444 {
6270 if (!isspace(nextCodepoint & 0xff)) break; 6445 if (!isspace(nextCodepoint & 0xff)) break;
6271
6272 offset += nextCodepointSize; 6446 offset += nextCodepointSize;
6273 accCodepointSize += nextCodepointSize; 6447 accCodepointSize += nextCodepointSize;
6274 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6448 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6275 } 6449 }
6276 6450
6277 // Move text after cursor forward (including final null terminator)
6278 for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; 6451 for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i];
6279
6280 textLength -= accCodepointSize; 6452 textLength -= accCodepointSize;
6281 } 6453 }
6282 6454 // Delete single character after cursor
6283 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) 6455 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger)))
6284 { 6456 {
6285 // Delete single codepoint from text, after current cursor position
6286
6287 int nextCodepointSize = 0; 6457 int nextCodepointSize = 0;
6288 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); 6458 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]; 6459 for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i];
6292
6293 textLength -= nextCodepointSize; 6460 textLength -= nextCodepointSize;
6294 } 6461 }
6295 6462 // Ctrl+Backspace: Delete word before cursor
6296 // Delete related codepoints from text, before current cursor position 6463 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 { 6464 {
6299 int offset = textBoxCursorIndex; 6465 int offset = textBoxCursorIndex;
6300 int accCodepointSize = 0; 6466 int accCodepointSize = 0;
6301 int prevCodepointSize = 0; 6467 int prevCodepointSize = 0;
6302 int prevCodepoint = 0; 6468 int prevCodepoint = 0;
6303 6469
6304 // Check whitespace to delete (ASCII only)
6305 while (offset > 0) 6470 while (offset > 0)
6306 { 6471 {
6307 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6472 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6308 if (!isspace(prevCodepoint & 0xff)) break; 6473 if (!isspace(prevCodepoint & 0xff)) break;
6309
6310 offset -= prevCodepointSize; 6474 offset -= prevCodepointSize;
6311 accCodepointSize += prevCodepointSize; 6475 accCodepointSize += prevCodepointSize;
6312 } 6476 }
6313 6477
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); 6478 bool puctuation = ispunct(prevCodepoint & 0xff);
6317 while (offset > 0) 6479 while (offset > 0)
6318 { 6480 {
6319 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6481 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6320 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; 6482 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
6321
6322 offset -= prevCodepointSize; 6483 offset -= prevCodepointSize;
6323 accCodepointSize += prevCodepointSize; 6484 accCodepointSize += prevCodepointSize;
6324 } 6485 }
6325 6486
6326 // Move text after cursor forward (including final null terminator)
6327 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; 6487 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i];
6328
6329 textLength -= accCodepointSize; 6488 textLength -= accCodepointSize;
6330 textBoxCursorIndex -= accCodepointSize; 6489 textBoxCursorIndex -= accCodepointSize;
6331 } 6490 }
6332 6491 // Backspace single character before cursor
6333 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) 6492 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger)))
6334 { 6493 {
6335 // Delete single codepoint from text, before current cursor position
6336
6337 int prevCodepointSize = 0; 6494 int prevCodepointSize = 0;
6338
6339 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); 6495 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]; 6496 for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i];
6343
6344 textLength -= prevCodepointSize; 6497 textLength -= prevCodepointSize;
6345 textBoxCursorIndex -= prevCodepointSize; 6498 textBoxCursorIndex -= prevCodepointSize;
6346 } 6499 }
6347 6500
6348 // Move cursor position with keys 6501 // Move cursor position with keys (with Shift selection support)
6349 if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6502 if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6350 { 6503 {
6504 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6505
6351 int offset = textBoxCursorIndex; 6506 int offset = textBoxCursorIndex;
6352 //int accCodepointSize = 0;
6353 int prevCodepointSize = 0; 6507 int prevCodepointSize = 0;
6354 int prevCodepoint = 0; 6508 int prevCodepoint = 0;
6355 6509
6356 // Check whitespace to skip (ASCII only)
6357 while (offset > 0) 6510 while (offset > 0)
6358 { 6511 {
6359 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6512 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6360 if (!isspace(prevCodepoint & 0xff)) break; 6513 if (!isspace(prevCodepoint & 0xff)) break;
6361
6362 offset -= prevCodepointSize; 6514 offset -= prevCodepointSize;
6363 //accCodepointSize += prevCodepointSize; 6515 }
6364 } 6516
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); 6517 bool puctuation = ispunct(prevCodepoint & 0xff);
6369 while (offset > 0) 6518 while (offset > 0)
6370 { 6519 {
6371 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); 6520 prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
6372 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; 6521 if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break;
6373
6374 offset -= prevCodepointSize; 6522 offset -= prevCodepointSize;
6375 //accCodepointSize += prevCodepointSize;
6376 } 6523 }
6377 6524
6378 textBoxCursorIndex = offset; 6525 textBoxCursorIndex = offset;
6526
6527 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6528 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6379 } 6529 }
6380 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) 6530 else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger)))
6381 { 6531 {
6532 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6533
6382 int prevCodepointSize = 0; 6534 int prevCodepointSize = 0;
6383 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); 6535 GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
6384
6385 textBoxCursorIndex -= prevCodepointSize; 6536 textBoxCursorIndex -= prevCodepointSize;
6537
6538 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6539 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6386 } 6540 }
6387 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) 6541 else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
6388 { 6542 {
6543 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6544
6389 int offset = textBoxCursorIndex; 6545 int offset = textBoxCursorIndex;
6390 //int accCodepointSize = 0;
6391 int nextCodepointSize; 6546 int nextCodepointSize;
6392 int nextCodepoint; 6547 int nextCodepoint;
6393 6548
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); 6549 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6397 bool puctuation = ispunct(nextCodepoint & 0xff); 6550 bool puctuation = ispunct(nextCodepoint & 0xff);
6398 while (offset < textLength) 6551 while (offset < textLength)
6399 { 6552 {
6400 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; 6553 if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break;
6401
6402 offset += nextCodepointSize; 6554 offset += nextCodepointSize;
6403 //accCodepointSize += nextCodepointSize;
6404 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6555 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6405 } 6556 }
6406 6557
6407 // Check whitespace to skip (ASCII only)
6408 while (offset < textLength) 6558 while (offset < textLength)
6409 { 6559 {
6410 if (!isspace(nextCodepoint & 0xff)) break; 6560 if (!isspace(nextCodepoint & 0xff)) break;
6411
6412 offset += nextCodepointSize; 6561 offset += nextCodepointSize;
6413 //accCodepointSize += nextCodepointSize;
6414 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); 6562 nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
6415 } 6563 }
6416 6564
6417 textBoxCursorIndex = offset; 6565 textBoxCursorIndex = offset;
6566
6567 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6568 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6418 } 6569 }
6419 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) 6570 else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger)))
6420 { 6571 {
6572 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6573
6421 int nextCodepointSize = 0; 6574 int nextCodepointSize = 0;
6422 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); 6575 GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
6423
6424 textBoxCursorIndex += nextCodepointSize; 6576 textBoxCursorIndex += nextCodepointSize;
6425 } 6577
6426 6578 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6427 // Vertical cursor movement for multiline 6579 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6428 if (multiline && (IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger))) 6580 }
6429 { 6581
6430 // Find start of current line 6582 // Vertical cursor movement using visual lines (with Shift selection support)
6431 int currentLineStart = textBoxCursorIndex; 6583 if ((IsKeyPressed(KEY_UP) || (IsKeyDown(KEY_UP) && autoCursorShouldTrigger)))
6432 while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--; 6584 {
6433 6585 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6434 // Calculate horizontal position in current line 6586
6435 int horizontalPos = textBoxCursorIndex - currentLineStart; 6587 // Find current visual line
6436 6588 int currVisLine = 0;
6437 // Find start of previous line 6589 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6438 if (currentLineStart > 0) 6590 {
6439 { 6591 if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
6440 int prevLineEnd = currentLineStart - 1; // Skip the newline 6592 }
6441 int prevLineStart = prevLineEnd; 6593
6442 while (prevLineStart > 0 && text[prevLineStart - 1] != '\n') prevLineStart--; 6594 // Calculate X offset in current line
6443 6595 int currLineStart = visualLineStarts[currVisLine];
6444 // Move to same horizontal position on previous line (or end of line if shorter) 6596 float xOffset = 0;
6445 int prevLineLength = prevLineEnd - prevLineStart; 6597 for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
6446 int targetPos = (horizontalPos < prevLineLength) ? horizontalPos : prevLineLength; 6598 {
6447 textBoxCursorIndex = prevLineStart + targetPos; 6599 if (text[k] == '\n') break;
6600 int cpSize = 0;
6601 int cp = GetCodepointNext(&text[k], &cpSize);
6602 int gi = GetGlyphIndex(guiFont, cp);
6603 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6604 xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6605 }
6606
6607 if (currVisLine > 0)
6608 {
6609 // Move to previous visual line at same X position
6610 int prevLineStart = visualLineStarts[currVisLine - 1];
6611 int prevLineEnd = visualLineStarts[currVisLine];
6612 if (prevLineEnd > 0 && text[prevLineEnd - 1] == '\n') prevLineEnd--;
6613
6614 float accum = 0;
6615 textBoxCursorIndex = prevLineStart;
6616 for (int k = prevLineStart; k < prevLineEnd; )
6617 {
6618 int cpSize = 0;
6619 int cp = GetCodepointNext(&text[k], &cpSize);
6620 int gi = GetGlyphIndex(guiFont, cp);
6621 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6622 if (accum + gw / 2 >= xOffset) break;
6623 accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6624 textBoxCursorIndex = k + cpSize;
6625 k += cpSize;
6626 }
6448 } 6627 }
6449 else 6628 else
6450 { 6629 {
6451 // Already on first line, move to start
6452 textBoxCursorIndex = 0; 6630 textBoxCursorIndex = 0;
6453 } 6631 }
6454 } 6632
6455 else if (multiline && (IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger))) 6633 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6456 { 6634 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6457 // Find start of current line 6635 }
6458 int currentLineStart = textBoxCursorIndex; 6636 else if ((IsKeyPressed(KEY_DOWN) || (IsKeyDown(KEY_DOWN) && autoCursorShouldTrigger)))
6459 while (currentLineStart > 0 && text[currentLineStart - 1] != '\n') currentLineStart--; 6637 {
6460 6638 if (shiftDown && textBoxSelectionStart < 0) textBoxSelectionStart = textBoxCursorIndex;
6461 // Calculate horizontal position in current line 6639
6462 int horizontalPos = textBoxCursorIndex - currentLineStart; 6640 // Find current visual line
6463 6641 int currVisLine = 0;
6464 // Find end of current line 6642 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6465 int currentLineEnd = textBoxCursorIndex; 6643 {
6466 while (currentLineEnd < textLength && text[currentLineEnd] != '\n') currentLineEnd++; 6644 if (textBoxCursorIndex >= visualLineStarts[vl]) { currVisLine = vl; break; }
6467 6645 }
6468 // Find next line 6646
6469 if (currentLineEnd < textLength) 6647 // Calculate X offset in current line
6470 { 6648 int currLineStart = visualLineStarts[currVisLine];
6471 int nextLineStart = currentLineEnd + 1; // Skip the newline 6649 float xOffset = 0;
6472 int nextLineEnd = nextLineStart; 6650 for (int k = currLineStart; k < textBoxCursorIndex && k < textLength; k++)
6473 while (nextLineEnd < textLength && text[nextLineEnd] != '\n') nextLineEnd++; 6651 {
6474 6652 if (text[k] == '\n') break;
6475 // Move to same horizontal position on next line (or end of line if shorter) 6653 int cpSize = 0;
6476 int nextLineLength = nextLineEnd - nextLineStart; 6654 int cp = GetCodepointNext(&text[k], &cpSize);
6477 int targetPos = (horizontalPos < nextLineLength) ? horizontalPos : nextLineLength; 6655 int gi = GetGlyphIndex(guiFont, cp);
6478 textBoxCursorIndex = nextLineStart + targetPos; 6656 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6657 xOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6658 }
6659
6660 if (currVisLine < visualLineCount - 1)
6661 {
6662 // Move to next visual line at same X position
6663 int nextLineStart = visualLineStarts[currVisLine + 1];
6664 int nextLineEnd = (currVisLine + 2 < visualLineCount) ? visualLineStarts[currVisLine + 2] : textLength;
6665 if (nextLineEnd > nextLineStart && text[nextLineEnd - 1] == '\n') nextLineEnd--;
6666
6667 float accum = 0;
6668 textBoxCursorIndex = nextLineStart;
6669 for (int k = nextLineStart; k < nextLineEnd; )
6670 {
6671 int cpSize = 0;
6672 int cp = GetCodepointNext(&text[k], &cpSize);
6673 int gi = GetGlyphIndex(guiFont, cp);
6674 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6675 if (accum + gw / 2 >= xOffset) break;
6676 accum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6677 textBoxCursorIndex = k + cpSize;
6678 k += cpSize;
6679 }
6479 } 6680 }
6480 else 6681 else
6481 { 6682 {
6482 // Already on last line, move to end
6483 textBoxCursorIndex = textLength; 6683 textBoxCursorIndex = textLength;
6484 } 6684 }
6485 } 6685
6486 6686 if (shiftDown) textBoxSelectionEnd = textBoxCursorIndex;
6487 // Move cursor position with mouse 6687 else { textBoxSelectionStart = -1; textBoxSelectionEnd = -1; }
6488 if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text 6688 }
6489 { 6689
6490 float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; 6690 // Move cursor position with mouse using visual lines
6491 int codepointIndex = 0; 6691 {
6492 float glyphWidth = 0.0f;
6493 float widthToMouseX = 0;
6494 int mouseCursorIndex = 0; 6692 int mouseCursorIndex = 0;
6495 6693 bool mouseInBounds = CheckCollisionPointRec(mousePosition, textBounds);
6496 for (int i = textIndexOffset; i < textLength; i += codepointSize) 6694 bool shouldCalculateMousePos = mouseInBounds || textBoxSelecting;
6497 { 6695
6498 codepoint = GetCodepointNext(&text[i], &codepointSize); 6696 if (shouldCalculateMousePos && textLength > 0)
6499 codepointIndex = GetGlyphIndex(guiFont, codepoint); 6697 {
6500 6698 // Determine which visual line the mouse is on
6501 if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); 6699 int mouseLine = (int)((mousePosition.y - textBounds.y) / lineHeight);
6502 else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); 6700 if (mouseLine < 0) mouseLine = 0;
6503 6701 if (mouseLine >= visualLineCount) mouseLine = visualLineCount - 1;
6504 if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) 6702
6703 // Get the start and end of the target visual line
6704 int targetLineStart = visualLineStarts[mouseLine];
6705 int targetLineEnd = (mouseLine + 1 < visualLineCount) ? visualLineStarts[mouseLine + 1] : textLength;
6706 if (targetLineEnd > targetLineStart && text[targetLineEnd - 1] == '\n') targetLineEnd--;
6707
6708 // Find character position within the line based on mouse X
6709 float relativeMouseX = mousePosition.x - textBounds.x;
6710 if (relativeMouseX < 0) relativeMouseX = 0;
6711
6712 float widthAccum = 0;
6713 mouseCursorIndex = targetLineStart;
6714
6715 for (int k = targetLineStart; k < targetLineEnd; )
6505 { 6716 {
6506 mouseCursor.x = textBounds.x + widthToMouseX; 6717 if (text[k] == '\n') break;
6507 mouseCursorIndex = i; 6718 int cpSize = 0;
6508 printf("before: %i\n", mouseCursorIndex); 6719 int cp = GetCodepointNext(&text[k], &cpSize);
6509 break; 6720 int gi = GetGlyphIndex(guiFont, cp);
6721
6722 float gw;
6723 if (guiFont.glyphs[gi].advanceX == 0)
6724 gw = (float)guiFont.recs[gi].width * scaleFactor;
6725 else
6726 gw = (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6727
6728 float charMidpoint = widthAccum + gw / 2.0f;
6729
6730 if (relativeMouseX <= charMidpoint)
6731 {
6732 mouseCursorIndex = k;
6733 break;
6734 }
6735
6736 widthAccum += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6737 mouseCursorIndex = k + cpSize;
6738 k += cpSize;
6510 } 6739 }
6511 6740
6512 if (mousePosition.y >= textBounds.y && mousePosition.y <= textBounds.height) 6741 // Clamp to line end
6513 { 6742 if (mouseCursorIndex > targetLineEnd) mouseCursorIndex = targetLineEnd;
6514 mouseCursor.y = mousePosition.y; 6743 }
6515 mouseCursorIndex = i; 6744 else if (shouldCalculateMousePos)
6516 int number_of_n = (int)(mousePosition.y / lineHeight); 6745 {
6517 for (int i = 0; i < textSize; i++) 6746 mouseCursorIndex = 0;
6518 { 6747 }
6519 if (text[i] == '\n') 6748
6520 { 6749 // Mouse selection: start selection on mouse press (only when in bounds)
6521 number_of_n--; 6750 if (mouseInBounds && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
6522 if (number_of_n == 0) 6751 {
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; 6752 textBoxCursorIndex = mouseCursorIndex;
6545 } 6753 textBoxSelectionStart = mouseCursorIndex;
6546 } 6754 textBoxSelectionEnd = mouseCursorIndex;
6547 else mouseCursor.x = -1; 6755 textBoxSelecting = true;
6548 6756 }
6549 // Recalculate cursor position for multiline 6757 // Mouse selection: update selection while dragging (even outside bounds)
6550 if (multiline) 6758 else if (textBoxSelecting && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
6551 { 6759 {
6552 // Recalculate cursor line and position 6760 textBoxSelectionEnd = mouseCursorIndex;
6553 int newCursorLine = 0; 6761 textBoxCursorIndex = mouseCursorIndex;
6554 int newLineStart = 0; 6762 }
6555 6763 }
6556 for (int i = 0; i < textBoxCursorIndex; i++) 6764
6557 { 6765 // End mouse selection when button released
6558 if (text[i] == '\n') 6766 if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
6559 { 6767 {
6560 newCursorLine++; 6768 textBoxSelecting = false;
6561 newLineStart = i + 1; 6769 }
6562 } 6770
6563 } 6771 // Recalculate cursor position using visual lines
6564 6772 // (Visual lines already calculated at the start - need to recalculate after any changes)
6565 // Extract current line text up to cursor 6773 int newCursorVisLine = 0;
6566 char currentLineText[1024] = { 0 }; 6774 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6567 int currentLineLen = 0; 6775 {
6568 int i = newLineStart; 6776 if (textBoxCursorIndex >= visualLineStarts[vl]) { newCursorVisLine = vl; break; }
6569 while (i < textBoxCursorIndex && text[i] != '\n' && currentLineLen < 1023) 6777 }
6570 { 6778
6571 currentLineText[currentLineLen++] = text[i++]; 6779 int newCursorLineStart = visualLineStarts[newCursorVisLine];
6572 } 6780 float newCursorXOffset = 0;
6573 currentLineText[currentLineLen] = '\0'; 6781 for (int k = newCursorLineStart; k < textBoxCursorIndex && k < textLength; k++)
6574 6782 {
6575 cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(currentLineText) + GuiGetStyle(DEFAULT, TEXT_SPACING); 6783 if (text[k] == '\n') break;
6576 cursor.y = textBounds.y + (newCursorLine * lineHeight * 1.5); 6784 int cpSize = 0;
6577 } 6785 int cp = GetCodepointNext(&text[k], &cpSize);
6578 else 6786 int gi = GetGlyphIndex(guiFont, cp);
6579 { 6787 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); 6788 newCursorXOffset += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6581 } 6789 }
6790
6791 cursor.x = textBounds.x + newCursorXOffset;
6792 cursor.y = textBounds.y + newCursorVisLine * lineHeight;
6582 6793
6583 // Finish text editing on ENTER or mouse click outside bounds 6794 // Finish text editing on ENTER or mouse click outside bounds
6584 if ((!multiline && IsKeyPressed(KEY_ENTER)) || 6795 if ((!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
6585 (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
6586 { 6796 {
6587 textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index 6797 textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index
6588 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes 6798 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
6589 result = 1; 6799 textBoxSelectionStart = -1;
6800 textBoxSelectionEnd = -1;
6801 textBoxSelecting = false;
6802 resultStruct.result = 1;
6590 } 6803 }
6591 } 6804 }
6592 else 6805 else
6593 { 6806 {
6594 if (CheckCollisionPointRec(mousePosition, bounds)) 6807 if (CheckCollisionPointRec(mousePosition, bounds))
6597 6810
6598 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 6811 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
6599 { 6812 {
6600 textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text 6813 textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text
6601 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes 6814 autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
6602 result = 1; 6815 textBoxSelectionStart = -1;
6816 textBoxSelectionEnd = -1;
6817 resultStruct.result = 1;
6603 } 6818 }
6604 } 6819 }
6605 } 6820 }
6606 } 6821 }
6607 //-------------------------------------------------------------------- 6822 //--------------------------------------------------------------------
6616 { 6831 {
6617 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); 6832 GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
6618 } 6833 }
6619 else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); 6834 else GuiDrawRectangle(bounds, 0, GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
6620 6835
6621 // Draw text considering index offset if required 6836 // Draw selection highlight using visual lines
6622 // NOTE: Text index offset depends on cursor position 6837 if (editMode && textBoxSelectionStart >= 0 && textBoxSelectionEnd >= 0 && textBoxSelectionStart != textBoxSelectionEnd)
6623 // Set vertical alignment to top for multiline 6838 {
6839 int selMin = (textBoxSelectionStart < textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6840 int selMax = (textBoxSelectionStart > textBoxSelectionEnd) ? textBoxSelectionStart : textBoxSelectionEnd;
6841
6842 // Find visual line for selection start
6843 int selStartVisLine = 0;
6844 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6845 {
6846 if (selMin >= visualLineStarts[vl]) { selStartVisLine = vl; break; }
6847 }
6848
6849 // Find visual line for selection end
6850 int selEndVisLine = 0;
6851 for (int vl = visualLineCount - 1; vl >= 0; vl--)
6852 {
6853 if (selMax >= visualLineStarts[vl]) { selEndVisLine = vl; break; }
6854 }
6855
6856 Color selectionColor = (Color){ 100, 150, 255, 100 };
6857
6858 // Draw selection for each visual line in range
6859 for (int vl = selStartVisLine; vl <= selEndVisLine; vl++)
6860 {
6861 int vlStart = visualLineStarts[vl];
6862 int vlEnd = (vl + 1 < visualLineCount) ? visualLineStarts[vl + 1] : textLength;
6863 if (vlEnd > vlStart && vlEnd <= textLength && text[vlEnd - 1] == '\n') vlEnd--;
6864
6865 // Determine selection bounds within this visual line
6866 int drawStart = (vl == selStartVisLine) ? selMin : vlStart;
6867 int drawEnd = (vl == selEndVisLine) ? selMax : vlEnd;
6868
6869 // Calculate X positions
6870 float xStart = 0;
6871 for (int k = vlStart; k < drawStart && k < textLength; k++)
6872 {
6873 if (text[k] == '\n') break;
6874 int cpSize = 0;
6875 int cp = GetCodepointNext(&text[k], &cpSize);
6876 int gi = GetGlyphIndex(guiFont, cp);
6877 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6878 xStart += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6879 }
6880
6881 float xEnd = 0;
6882 for (int k = vlStart; k < drawEnd && k < textLength; k++)
6883 {
6884 if (text[k] == '\n') break;
6885 int cpSize = 0;
6886 int cp = GetCodepointNext(&text[k], &cpSize);
6887 int gi = GetGlyphIndex(guiFont, cp);
6888 float gw = (guiFont.glyphs[gi].advanceX == 0) ? (float)guiFont.recs[gi].width * scaleFactor : (float)guiFont.glyphs[gi].advanceX * scaleFactor;
6889 xEnd += gw + (float)GuiGetStyle(DEFAULT, TEXT_SPACING);
6890 }
6891
6892 Rectangle selRect = {
6893 textBounds.x + xStart,
6894 textBounds.y + vl * lineHeight,
6895 xEnd - xStart,
6896 (float)GuiGetStyle(DEFAULT, TEXT_SIZE)
6897 };
6898 GuiDrawRectangle(selRect, 0, BLANK, selectionColor);
6899 }
6900 }
6901
6902 // Draw text with word wrap enabled
6624 int prevVerticalAlignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); 6903 int prevVerticalAlignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL);
6625 if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); 6904 int prevWrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE);
6626 6905 GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP);
6627 GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); 6906 GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_CHAR);
6628 6907
6629 // Restore previous vertical alignment 6908 GuiDrawText(text, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))));
6630 if (multiline) GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment); 6909
6910 // Restore previous settings
6911 GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, prevVerticalAlignment);
6912 GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, prevWrapMode);
6631 6913
6632 // Draw cursor 6914 // Draw cursor
6633 if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) 6915 if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY))
6634 { 6916 {
6635 //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) 6917 //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0))
6639 // if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); 6921 // if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)));
6640 } 6922 }
6641 else if (state == STATE_FOCUSED) GuiTooltip(bounds); 6923 else if (state == STATE_FOCUSED) GuiTooltip(bounds);
6642 //-------------------------------------------------------------------- 6924 //--------------------------------------------------------------------
6643 6925
6644 return result; // Mouse button pressed: result = 1 6926 // Return selection info
6927 resultStruct.selectionStart = textBoxSelectionStart;
6928 resultStruct.selectionEnd = textBoxSelectionEnd;
6929
6930 return resultStruct;
6645 } 6931 }
6646 6932
6647 void JUNE_DrawRectangleLinesNoBottom(Rectangle rect, float thickness, Color color) { 6933 void JUNE_DrawRectangleLinesNoBottom(Rectangle rect, float thickness, Color color) {
6648 Vector2 topLeft = { rect.x, rect.y }; 6934 Vector2 topLeft = { rect.x, rect.y };
6649 Vector2 topRight = { rect.x + rect.width, rect.y }; 6935 Vector2 topRight = { rect.x + rect.width, rect.y };