diff --git a/src/raygui.h b/src/raygui.h index 5a0e9b5..a16b099 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -191,6 +191,14 @@ typedef struct Font { } Font; #endif +// State object used by GuiTextBox() +typedef struct { + int cursor; // cursor position in text + int start; // text start position (from where we begin drawing the text) + int index; // text start index (index inside the text of `start` always in sync) + int select; // marks position of cursor when selection has started +} GuiTextBoxState; + // Gui global state enum typedef enum { GUI_STATE_NORMAL = 0, @@ -281,6 +289,8 @@ typedef enum { // TextBox / TextBoxMulti / ValueBox / Spinner typedef enum { MULTILINE_PADDING = 16, + COLOR_SELECTED_FG, + COLOR_SELECTED_BG } GuiTextBoxProperty; typedef enum { @@ -355,6 +365,23 @@ RAYGUIDEF void GuiState(int state); // Set g RAYGUIDEF void GuiFont(Font font); // Set gui custom font (global state) RAYGUIDEF void GuiFade(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +// Global functions for interacting with the active textbox control +RAYGUIDEF void GuiTextBoxSetActive(Rectangle bounds); // Sets the active textbox +RAYGUIDEF Rectangle GuiTextBoxGetActive(void); // Get bounds of active textbox +RAYGUIDEF void GuiTextBoxSetCursor(int cursor); // Set cursor position of active textbox +RAYGUIDEF int GuiTextBoxGetCursor(void); // Get cursor position of active textbox +RAYGUIDEF void GuiTextBoxSetSelection(int start, int length); // Set selection of active textbox +RAYGUIDEF Vector2 GuiTextBoxGetSelection(void); // Get selection of active textbox (x - selection start y - selection length) +RAYGUIDEF bool GuiTextBoxIsActive(Rectangle bounds); // Returns true if a textbox control with specified `bounds` is the active textbox +RAYGUIDEF GuiTextBoxState GuiTextBoxGetState(); // Get state for the active textbox +RAYGUIDEF void GuiTextBoxSetState(GuiTextBoxState state); // Set state for the active textbox (state must be valid else things will break) +RAYGUIDEF void GuiTextBoxSelectAll(const char* text); // Select all characters in the active textbox (same as pressing `CTRL` + `A`) +RAYGUIDEF void GuiTextBoxCopy(const char* text); // Copy selected text to clipboard from the active textbox (same as pressing `CTRL` + `C`) +RAYGUIDEF void GuiTextBoxPaste(char* text, int textSize); // Paste text from clipboard into the textbox (same as pressing `CTRL` + `V`) +RAYGUIDEF void GuiTextBoxCut(char* text); // Cut selected text in the active textbox and copy it to clipboard (same as pressing `CTRL` + `X`) +RAYGUIDEF int GuiTextBoxDelete(char* text, int length, bool before); // Deletes a character or selection before from the active textbox (depending on `before`). Returns bytes deleted. +RAYGUIDEF int GuiTextBoxGetByteIndex(const char* text, int start, int from, int to); // Get the byte index for a character starting at position `from` with index `start` until position `to`. + // Style set/get functions RAYGUIDEF void GuiSetStyle(int control, int property, int value); // Set one style property RAYGUIDEF int GuiGetStyle(int control, int property); // Get one style property @@ -456,6 +483,11 @@ static Font guiFont = { 0 }; // NOTE: Highly coupled to raylib static bool guiLocked = false; static float guiAlpha = 1.0f; +// Used by GuiTextBox() and GuiTextBoxMulti() +static Rectangle guiTextBoxActive = {0}; // Area of the currently active textbox +// Keeps state of the active textbox +static GuiTextBoxState guiTextBoxState = {.cursor = -1, .start = 0, .index = 0, .select = -1}; + // Global gui style array (allocated on heap by default) // NOTE: In raygui we manage a single int array with all the possible style properties. // When a new style is loaded, it loads over the global style... but default gui style @@ -724,6 +756,53 @@ RAYGUIDEF void GuiFade(float alpha) guiAlpha = alpha; } +// Sets the active textbox (reseting state of the previous active textbox) +RAYGUIDEF void GuiTextBoxSetActive(Rectangle bounds) +{ + guiTextBoxActive = bounds; + guiTextBoxState = (GuiTextBoxState){.cursor = -1, .start = 0, .index = 0, .select = -1}; +} +// Gets bounds of active textbox +RAYGUIDEF Rectangle GuiTextBoxGetActive(void) { return guiTextBoxActive; } +// Set cursor position of active textbox +RAYGUIDEF void GuiTextBoxSetCursor(int cursor) +{ + guiTextBoxState.cursor = (cursor < 0) ? -1 : cursor; + guiTextBoxState.start = -1; // Mark this to be recalculated +} +// Get cursor position of active textbox +RAYGUIDEF int GuiTextBoxGetCursor(void) { return guiTextBoxState.cursor; } +// Set selection of active textbox +RAYGUIDEF void GuiTextBoxSetSelection(int start, int length) +{ + if(start < 0) start = 0; + if(length < 0) length = 0; + GuiTextBoxSetCursor(start + length); + guiTextBoxState.select = start; +} +// Get selection of active textbox +RAYGUIDEF Vector2 GuiTextBoxGetSelection(void) +{ + if(guiTextBoxState.select == -1 || guiTextBoxState.select == guiTextBoxState.cursor) + return (Vector2){0}; + else if(guiTextBoxState.cursor > guiTextBoxState.select) + return (Vector2){guiTextBoxState.select, guiTextBoxState.cursor - guiTextBoxState.select}; + + return (Vector2){guiTextBoxState.cursor, guiTextBoxState.select - guiTextBoxState.cursor}; +} +// Returns true if a textbox control with specified `bounds` is the active textbox +RAYGUIDEF bool GuiTextBoxIsActive(Rectangle bounds) +{ + return (bounds.x == guiTextBoxActive.x && bounds.y == guiTextBoxActive.y && + bounds.width == guiTextBoxActive.width && bounds.height == guiTextBoxActive.height); +} +RAYGUIDEF GuiTextBoxState GuiTextBoxGetState(void) { return guiTextBoxState; } +RAYGUIDEF void GuiTextBoxSetState(GuiTextBoxState state) +{ + // NOTE: should we check if state values are valid ?!? + guiTextBoxState = state; +} + // Set control style property value RAYGUIDEF void GuiSetStyle(int control, int property, int value) { @@ -1397,11 +1476,15 @@ RAYGUIDEF bool GuiDropdownBox(Rectangle bounds, const char *text, int *active, b } // Spinner control, returns selected value -// NOTE: Requires static variables: framesCounter, valueSpeed - ERROR! +// NOTE: Requires static variables: timer, valueSpeed - ERROR! RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxValue, bool editMode) { - bool pressed = false; + #define GUI_SPINNER_HOLD_SPEED 0.2f // min 200ms delay + static float timer = 0.0f; + + bool pressed = false, active = GuiTextBoxIsActive(bounds); int tempValue = *value; + const float time = GetTime(); // Get current time Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SELECT_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SELECT_BUTTON_PADDING), bounds.y, bounds.width - 2*(GuiGetStyle(SPINNER, SELECT_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SELECT_BUTTON_PADDING)), bounds.height }; @@ -1410,17 +1493,33 @@ RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxVal // Update control //-------------------------------------------------------------------- - if (!editMode) + Vector2 mouse = GetMousePosition(); + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + + if(editMode) { - if (tempValue < minValue) tempValue = minValue; - if (tempValue > maxValue) tempValue = maxValue; + if(!active) + { + // This becomes the active textbox when mouse is pressed or held inside bounds + if((IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonDown(MOUSE_LEFT_BUTTON)) && CheckCollisionPointRec(mouse, bounds)) + { + GuiTextBoxSetActive(bounds); + active = true; + } + } } + + // Reset timer when one of the buttons is clicked (without this, holding the button down will not behave correctly) + if((CheckCollisionPointRec(mouse, leftButtonBound) || CheckCollisionPointRec(mouse, rightButtonBound)) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) ) + timer = time; //-------------------------------------------------------------------- // Draw control //-------------------------------------------------------------------- - // TODO: Set Spinner properties for ValueBox + if(GuiTextBoxIsActive(bounds)) guiTextBoxActive = spinner; // Set our spinner as the active textbox pressed = GuiValueBox(spinner, &tempValue, minValue, maxValue, editMode); + if(GuiTextBoxIsActive(spinner)) guiTextBoxActive = bounds; // Revert change // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values @@ -1429,229 +1528,883 @@ RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxVal int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); - + + char* icon = "<"; #if defined(RAYGUI_RICONS_SUPPORT) - if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) tempValue--; - if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) tempValue++; -#else - if (GuiButton(leftButtonBound, "<")) tempValue--; - if (GuiButton(rightButtonBound, ">")) tempValue++; + icon = (char*)GuiIconText(RICON_ARROW_LEFT_FILL, NULL); #endif + if (GuiButton(leftButtonBound, icon) || // NOTE: also decrease value when the button is held down + (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(mouse, leftButtonBound) && time - timer > GUI_SPINNER_HOLD_SPEED) ) + tempValue--; + + icon = ">"; +#if defined(RAYGUI_RICONS_SUPPORT) + icon = (char*)GuiIconText(RICON_ARROW_RIGHT_FILL, NULL); +#endif + if (GuiButton(rightButtonBound, icon) || // NOTE: also increase value when the button is held down + (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(mouse, rightButtonBound) && time - timer > GUI_SPINNER_HOLD_SPEED) ) + tempValue++; GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); //-------------------------------------------------------------------- - + + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + + // Reset timer + if(active && (time - timer > GUI_SPINNER_HOLD_SPEED || timer == 0.0f || timer > time)) + timer = time; + *value = tempValue; return pressed; } // Value Box control, updates input text with numbers -// NOTE: Requires static variables: framesCounter RAYGUIDEF bool GuiValueBox(Rectangle bounds, int *value, int minValue, int maxValue, bool editMode) { #define VALUEBOX_MAX_CHARS 32 - static int framesCounter = 0; // Required for blinking cursor - - GuiControlState state = guiState; - bool pressed = false; - - char text[VALUEBOX_MAX_CHARS + 1] = "\0"; + char text[VALUEBOX_MAX_CHARS + 1] = {0}; sprintf(text, "%i", *value); - - // Update control - //-------------------------------------------------------------------- - if ((state != GUI_STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = GUI_STATE_PRESSED; - - framesCounter++; - - int keyCount = strlen(text); - - // Only allow keys in range [48..57] - if (keyCount < VALUEBOX_MAX_CHARS) - { - int maxWidth = (bounds.width - (GuiGetStyle(VALUEBOX, INNER_PADDING)*2)); - if (GetTextWidth(text) < maxWidth) - { - int key = GetKeyPressed(); - if ((key >= 48) && (key <= 57)) - { - text[keyCount] = (char)key; - keyCount++; - valueHasChanged = true; - } - } - } - - // Delete text - if (keyCount > 0) - { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - text[keyCount] = '\0'; - framesCounter = 0; - if (keyCount < 0) keyCount = 0; - valueHasChanged = true; - } - else if (IsKeyDown(KEY_BACKSPACE)) - { - if ((framesCounter > TEXTEDIT_CURSOR_BLINK_FRAMES) && (framesCounter%2) == 0) keyCount--; - text[keyCount] = '\0'; - if (keyCount < 0) keyCount = 0; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = atoi(text); - } - else - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - } - - if (!editMode) - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = GUI_STATE_FOCUSED; - if (IsMouseButtonPressed(0)) pressed = true; - } - } - else - { - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(0))) pressed = true; - } - - if (pressed) framesCounter = 0; - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - DrawRectangleLinesEx(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), guiAlpha)); - - if (state == GUI_STATE_PRESSED) - { - DrawRectangle(bounds.x + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_FOCUSED)), guiAlpha)); - if (editMode && ((framesCounter/20)%2 == 0)) DrawRectangle(bounds.x + GetTextWidth(text)/2 + bounds.width/2 + 2, bounds.y + GuiGetStyle(VALUEBOX, INNER_PADDING), 1, bounds.height - GuiGetStyle(VALUEBOX, INNER_PADDING)*2, Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_FOCUSED)), guiAlpha)); - } - else if (state == GUI_STATE_DISABLED) - { - DrawRectangle(bounds.x + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)), guiAlpha)); - } - - GuiDrawText(text, GetTextBounds(VALUEBOX, bounds), GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3))), guiAlpha)); - //-------------------------------------------------------------------- - + + bool pressed = GuiTextBox(bounds, text, VALUEBOX_MAX_CHARS, editMode); + *value = atoi(text); + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + return pressed; } -// Text Box control, updates input text +enum { + GUI_MEASURE_MODE_CURSOR_END = 0xA, + GUI_MEASURE_MODE_CURSOR_POS, + GUI_MEASURE_MODE_CURSOR_COORDS, +}; + +// Required by GuiTextBox() +// Highly synchronized with calculations in DrawTextRecEx() +static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *pos, int mode) +{ + // Get gui font properties + if (guiFont.texture.id == 0) guiFont = GetFontDefault(); + const Font font = guiFont; + const float fontSize = GuiGetStyle(DEFAULT, TEXT_SIZE); + const float spacing = GuiGetStyle(DEFAULT, TEXT_SPACING); + + int textOffsetX = 0; // Offset between characters + float scaleFactor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + int i = 0, k = 0; + int glyphWidth = 0; + for (i = 0; i < length; i++, k++) + { + glyphWidth = 0; + int next = 1; + letter = GetNextCodepoint(&text[i], &next); + if(letter == 0x3f) next = 1; + index = GetGlyphIndex(font, letter); + i += next - 1; + + if (letter != '\n') + { + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.chars[index].rec.width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + + if ((textOffsetX + glyphWidth + 1) >= rec.width) break; + + if(mode == GUI_MEASURE_MODE_CURSOR_POS && *pos == k) + { + break; + } + else if(mode == GUI_MEASURE_MODE_CURSOR_COORDS) + { + // Check if the mouse pointer is inside the glyph rect + Rectangle grec = {rec.x + textOffsetX - 1, rec.y, glyphWidth, (font.baseSize + font.baseSize/2)*scaleFactor - 1}; + Vector2 mouse = GetMousePosition(); + if(CheckCollisionPointRec(mouse, grec)) + { + // Smooth selection by dividing the glyph rectangle into 2 equal parts and checking where the mouse resides + if( mouse.x > grec.x + glyphWidth/2) + { + textOffsetX += glyphWidth; + k++; + } + break; + } + } + } + else break; + + textOffsetX += glyphWidth; + } + + *pos = k; + return rec.x + textOffsetX - 1; +} + +static int GetPrevCodepoint(const char* text, const char* start, int* prev) +{ + int c = 0x3f; + char* p = (char*)text; + *prev = 1; + for(int i = 0; p >= start && i < 4; p--, i++) + { + if((((unsigned char)*p) >> 6) != 2) + { + c = GetNextCodepoint(p, prev); + break; + } + } + return c; +} + +// Required by GuiTextBoxEx() +// Highly synchronized with calculations in DrawTextRecEx() +static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int *pos) +{ + // Get gui font properties + if (guiFont.texture.id == 0) guiFont = GetFontDefault(); + const Font font = guiFont; + const float fontSize = GuiGetStyle(DEFAULT, TEXT_SIZE); + const float spacing = GuiGetStyle(DEFAULT, TEXT_SPACING); + + int textOffsetX = 0; // Offset between characters + float scaleFactor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + int i = 0, k = 0; + int glyphWidth = 0, prev = 1; + for (i = length; i >= 0; i--, k++) + { + glyphWidth = 0; + letter = GetPrevCodepoint(&text[i], &text[0], &prev); + if(letter == 0x3f) prev = 1; + index = GetGlyphIndex(font, letter); + i -= prev - 1; + + if (letter != '\n') + { + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.chars[index].rec.width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + + if ((textOffsetX + glyphWidth + 1) >= rec.width) break; + } + else break; + + textOffsetX += glyphWidth; + } + *pos = k; + return i+prev; +} + + +// Calculate cursor coordinates based on the cursor position `pos` inside the `text`. +static inline int GuiTextBoxGetCursorCoordinates(const char *text, int length, Rectangle rec, int pos) +{ + return GuiMeasureTextBox(text, length, rec, &pos, GUI_MEASURE_MODE_CURSOR_POS); +} + +// Calculate cursor position in textbox based on mouse coordinates. +static inline int GuiTextBoxGetCursorFromMouse(const char *text, int length, Rectangle rec, int* pos) +{ + return GuiMeasureTextBox(text, length, rec, pos, GUI_MEASURE_MODE_CURSOR_COORDS); +} + +// Calculates how many characters is the textbox able to draw inside rec +static inline int GuiTextBoxMaxCharacters(const char *text, int length, Rectangle rec) +{ + int pos = -1; + GuiMeasureTextBox(text, length, rec, &pos, GUI_MEASURE_MODE_CURSOR_END); + return pos; +} + +// Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` or a `\n` is found. +// NOTE: If a invalid UTF8 sequence is encountered a `?`(0x3f) codepoint is counted instead. +static inline unsigned int GuiCountCodepointsUntilNewline(const char *text) +{ + unsigned int len = 0; + char* ptr = (char*)&text[0]; + while(*ptr != '\0' && *ptr != '\n') + { + int next = 0; + int letter = GetNextCodepoint(ptr, &next); + if(letter == 0x3f) ptr += 1; + else ptr += next; + ++len; + } + return len; +} + +static inline void MoveTextBoxCursorRight(const char* text, int length, Rectangle textRec) +{ + // FIXME: Counting codepoints each time we press the key is expensive, find another way + int count = GuiCountCodepointsUntilNewline(text); + if(guiTextBoxState.cursor < count ) guiTextBoxState.cursor++; + const int max = GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec); + if(guiTextBoxState.cursor - guiTextBoxState.start > max) + { + const int cidx = GuiTextBoxGetByteIndex(text, guiTextBoxState.index, guiTextBoxState.start, guiTextBoxState.cursor); + int pos = 0; + guiTextBoxState.index = GuiMeasureTextBoxRev(text, cidx-1, textRec, &pos); + guiTextBoxState.start = guiTextBoxState.cursor - pos; + } +} + +static inline void MoveTextBoxCursorLeft(const char* text) +{ + if(guiTextBoxState.cursor > 0) guiTextBoxState.cursor--; + if(guiTextBoxState.cursor < guiTextBoxState.start) + { + int prev = 0; + int letter = GetPrevCodepoint(&text[guiTextBoxState.index-1], text, &prev); + if(letter == 0x3f) prev = 1; + guiTextBoxState.start--; + guiTextBoxState.index -= prev; + } +} + +RAYGUIDEF int GuiTextBoxGetByteIndex(const char* text, int start, int from, int to) +{ + int i = start, k = from; + while(text[i] != '\0' && k < to) + { + int j = 0; + int letter = GetNextCodepoint(&text[i], &j); + if(letter == 0x3f) j = 1; + i += j; + ++k; + } + return i; +} + +RAYGUIDEF int GuiTextBoxDelete(char* text, int length, bool before) +{ + if(guiTextBoxState.cursor != -1 && text != NULL) + { + int startIdx = 0, endIdx = 0; + if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + { + // Delete selection + int start = guiTextBoxState.cursor, end = guiTextBoxState.select; + if(guiTextBoxState.cursor > guiTextBoxState.select) + { + start = guiTextBoxState.select; + end = guiTextBoxState.cursor; + } + + // Convert to byte indexes + startIdx = GuiTextBoxGetByteIndex(text, 0, 0, start); + endIdx = GuiTextBoxGetByteIndex(text, 0, 0, end); + + // Adjust text box state + guiTextBoxState.cursor = start; // Always set cursor to start of selection + if(guiTextBoxState.select < guiTextBoxState.start) guiTextBoxState.start = -1; // Force to recalculate on the next frame + } + else + { + if(before) + { + // Delete character before cursor + if(guiTextBoxState.cursor != 0) + { + endIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + guiTextBoxState.cursor--; + startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + if(guiTextBoxState.cursor < guiTextBoxState.start) guiTextBoxState.start = -1; // Force to recalculate on the next frame + } + } + else + { + // Delete character after cursor + if(guiTextBoxState.cursor + 1 <= GuiCountCodepointsUntilNewline(text)) + { + startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + endIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor+1); + } + } + } + + memmove(&text[startIdx], &text[endIdx], length - endIdx); + text[length - (endIdx - startIdx)] = '\0'; + guiTextBoxState.select = -1; // Always deselect + return endIdx - startIdx; + } + return 0; +} + +RAYGUIDEF void GuiTextBoxSelectAll(const char* text) +{ + guiTextBoxState.cursor = GuiCountCodepointsUntilNewline(text); + if(guiTextBoxState.cursor > 0) + { + guiTextBoxState.select = 0; + guiTextBoxState.start = -1; // Force recalculate on the next frame + } + else + guiTextBoxState.select = -1; +} + +RAYGUIDEF void GuiTextBoxCopy(const char* text) +{ + if(guiTextBoxState.select != -1 && guiTextBoxState.cursor != -1 && guiTextBoxState.select != guiTextBoxState.cursor && text != NULL) + { + int start = guiTextBoxState.cursor, end = guiTextBoxState.select; + if(guiTextBoxState.cursor > guiTextBoxState.select) + { + start = guiTextBoxState.select; + end = guiTextBoxState.cursor; + } + + // Convert to byte indexes + start = GuiTextBoxGetByteIndex(text, 0, 0, start); + end = GuiTextBoxGetByteIndex(text, 0, 0, end); + + // FIXME: `TextSubtext()` only lets use copy MAX_TEXT_BUFFER_LENGTH (1024) bytes + // maybe modify `SetClipboardText()` so we can use it only on part of a string + const char* clipText = TextSubtext(text, start, end - start); + SetClipboardText(clipText); + } +} + +// Paste text from clipboard into the active textbox. +// `text` is the pointer to the buffer used by the textbox while `textSize` is the text buffer max size +RAYGUIDEF void GuiTextBoxPaste(char* text, int textSize) +{ + const char* clipText = GetClipboardText(); // GLFW guaratees this should be UTF8 encoded! + int length = strlen(text); + if(clipText != NULL && guiTextBoxState.cursor != -1 && text != NULL) + { + if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + { + // If there's a selection we'll have to delete it first + length -= GuiTextBoxDelete(text, length, true); + } + + int clipLen = strlen(clipText); // We want the length in bytes + // Calculate how many bytes can we copy from clipboard text before we run out of space + int size = (length + clipLen <= textSize) ? clipLen : textSize - length; + // Make room by shifting to right the bytes after cursor + int startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + int endIdx = startIdx + size; + memmove(&text[endIdx], &text[startIdx], length - startIdx); + text[length + size] = '\0'; // Set the NULL char + // At long last copy the clipboard text + memcpy(&text[startIdx], clipText, size); + // Set cursor position at the end of the pasted text + guiTextBoxState.cursor = 0; + for(int i=0; i < startIdx + size; guiTextBoxState.cursor++) + { + int next = 0; + int letter = GetNextCodepoint(&text[i], &next); + if(letter != 0x3f) i += next; + else i += 1; + } + guiTextBoxState.start = -1; // Force to recalculate on the next frame + } +} + +RAYGUIDEF void GuiTextBoxCut(char* text) +{ + if(guiTextBoxState.select != -1 && guiTextBoxState.cursor != -1 && guiTextBoxState.select != guiTextBoxState.cursor && text != NULL) + { + // First copy selection to clipboard; + int start = guiTextBoxState.cursor, end = guiTextBoxState.select; + if(guiTextBoxState.cursor > guiTextBoxState.select) + { + start = guiTextBoxState.select; + end = guiTextBoxState.cursor; + } + + // Convert to byte indexes + int startIdx = GuiTextBoxGetByteIndex(text, 0, 0, start); + int endIdx = GuiTextBoxGetByteIndex(text, 0, 0, end); + + // FIXME: `TextSubtext()` only lets use copy MAX_TEXT_BUFFER_LENGTH (1024) bytes + // maybe modify `SetClipboardText()` so we can use it only on parts of a string + const char* clipText = TextSubtext(text, startIdx, endIdx - startIdx); + SetClipboardText(clipText); + + // Now delete selection (copy data over it) + int len = strlen(text); + memmove(&text[startIdx], &text[endIdx], len - endIdx); + text[len - (endIdx - startIdx)] = '\0'; + + // Adjust text box state + guiTextBoxState.cursor = start; // Always set cursor to start of selection + if(guiTextBoxState.select < guiTextBoxState.start) guiTextBoxState.start = -1; // Force to recalculate + guiTextBoxState.select = -1; // Deselect + } +} + +static int EncodeCodepoint(unsigned int c, char out[5]) +{ + int len = 0; + if(c <= 0x7f) + { + out[0] = (char)c; + len = 1; + } + else if(c <= 0x7ff) + { + out[0] = (char)(((c >> 6) & 0x1f) | 0xc0); + out[1] = (char)((c & 0x3f) | 0x80); + len = 2; + } + else if(c <= 0xffff) + { + out[0] = (char)(((c >> 12) & 0x0f) | 0xe0); + out[1] = (char)(((c >> 6) & 0x3f) | 0x80); + out[2] = (char)((c & 0x3f) | 0x80); + len = 3; + } + else if(c <= 0x10ffff) + { + out[0] = (char)(((c >> 18) & 0x07) | 0xf0); + out[1] = (char)(((c >> 12) & 0x3f) | 0x80); + out[2] = (char)(((c >> 6) & 0x3f) | 0x80); + out[3] = (char)((c & 0x3f) | 0x80); + len = 4; + } + + out[len] = 0; + return len; +} + +// A text box control supporting text selection, cursor positioning and commonly used keyboard shortcuts. // NOTE 1: Requires static variables: framesCounter // NOTE 2: Returns if KEY_ENTER pressed (useful for data validation) RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { + #define GUI_TEXTBOX_CURSOR_SPEED_MODIFIER 5 // Controls the cursor movement/selection speed when movement keys are held/pressed static int framesCounter = 0; // Required for blinking cursor - + GuiControlState state = guiState; bool pressed = false; - + + // Make sure length doesn't exceed `textSize`. `textSize` is actually the max amount of characters the textbox can handle. + int length = strlen(text); + if(length > textSize) + { + text[textSize] = '\0'; + length = textSize; + } + + // Make sure we have enough room to draw at least 1 character + if(bounds.width - 2*GuiGetStyle(TEXTBOX, INNER_PADDING) < GuiGetStyle(DEFAULT, TEXT_SIZE)) + bounds.width = GuiGetStyle(DEFAULT, TEXT_SIZE) + 2*GuiGetStyle(TEXTBOX, INNER_PADDING); + + // Center the text vertically + int verticalPadding = (bounds.height - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH) - GuiGetStyle(DEFAULT, TEXT_SIZE))/2; + if(verticalPadding < 0) + { + // Make sure the height is sufficient + bounds.height = 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(DEFAULT, TEXT_SIZE); + verticalPadding = 0; + } + + // Calculate the drawing area for the text inside the control `bounds` + Rectangle textRec = {bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, INNER_PADDING), bounds.y + verticalPadding + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.width - 2*(GuiGetStyle(TEXTBOX, INNER_PADDING) + GuiGetStyle(TEXTBOX, BORDER_WIDTH)), GuiGetStyle(DEFAULT, TEXT_SIZE)}; + Vector2 cursorPos = {textRec.x, textRec.y}; // This holds the coordinates inside textRec of the cursor at current position and will be recalculated later + bool active = GuiTextBoxIsActive(bounds); // Check if this textbox is the global active textbox + + int selStart = 0, selLength = 0, textStartIndex = 0; + // Update control //-------------------------------------------------------------------- if ((state != GUI_STATE_DISABLED) && !guiLocked) { - Vector2 mousePoint = GetMousePosition(); - + const Vector2 mousePoint = GetMousePosition(); + if (editMode) { - state = GUI_STATE_PRESSED; - framesCounter++; - - int key = GetKeyPressed(); - int keyCount = strlen(text); - - // Only allow keys in range [32..125] - if (keyCount < (textSize - 1)) + // Check if we are the global active textbox + // A textbox becomes active when the user clicks it :) + if(!active) { - int maxWidth = (bounds.width - (GuiGetStyle(DEFAULT, INNER_PADDING)*2)); - - if (GetTextWidth(text) < (maxWidth - GuiGetStyle(DEFAULT, TEXT_SIZE))) + if(CheckCollisionPointRec(mousePoint, bounds) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))) { - if (((key >= 32) && (key <= 125)) || - ((key >= 128) && (key < 255))) + // Hurray!!! we just became the active textbox + active = true; + GuiTextBoxSetActive(bounds); + } + } + else if(!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) + { + // When active and the right mouse is clicked outside the textbox we should deactivate it + GuiTextBoxSetActive((Rectangle){0,0,-1,-1}); // Set a dummy rect as the active textbox bounds + active = false; + } + + if(active) + { + state = GUI_STATE_PRESSED; + framesCounter++; + + // Make sure state doesn't have invalid values + if(guiTextBoxState.cursor > length) guiTextBoxState.cursor = -1; + if(guiTextBoxState.select > length) guiTextBoxState.select = -1; + if(guiTextBoxState.start > length) guiTextBoxState.start = -1; + + + // Check textbox state for changes and recalculate if necesary + if(guiTextBoxState.cursor == -1) + { + // Set cursor to last visible character in textbox + guiTextBoxState.cursor = GuiTextBoxMaxCharacters(text, length, textRec); + } + + if(guiTextBoxState.start == -1) + { + // Force recalculate text start position and text start index + + // NOTE: start and index are always in sync + // start will hold the starting character position from where the text will be drawn + // while index will hold the byte index inside the text for that character + + if(guiTextBoxState.cursor == 0) { - text[keyCount] = (char)key; - keyCount++; + guiTextBoxState.start = guiTextBoxState.index = 0; // No need to recalculate + } + else + { + int pos = 0; + int len = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + guiTextBoxState.index = GuiMeasureTextBoxRev(text, len, textRec, &pos); + guiTextBoxState.start = guiTextBoxState.cursor - pos + 1; } } - } - - // Delete text - if (keyCount > 0) - { - if (IsKeyPressed(KEY_BACKSPACE)) + + // ----------------- + // HANDLE KEY INPUT + // ----------------- + // * -> | LSHIFT + -> move cursor to the right | increase selection by one + // * <- | LSHIFT + <- move cursor to the left | decrease selection by one + // * HOME | LSHIFT + HOME moves cursor to start of text | selects text from cursor to start of text + // * END | LSHIFT + END move cursor to end of text | selects text from cursor until end of text + // * CTRL + A select all characters in text + // * CTRL + C copy selected text + // * CTRL + X cut selected text + // * CTRL + V remove selected text, if any, then paste clipboard data + // * DEL delete character or selection after cursor + // * BACKSPACE delete character or selection before cursor + // TODO: add more shortcuts (insert mode, select word, moveto/select prev/next word ...) + if(IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER == 0))) { - keyCount--; - text[keyCount] = '\0'; + if(IsKeyDown(KEY_LEFT_SHIFT)) + { + // Selecting + if(guiTextBoxState.select == -1) + guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + MoveTextBoxCursorRight(text, length, textRec); + } + else + { + if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + { + // Deselect and move cursor to end of selection + if(guiTextBoxState.cursor < guiTextBoxState.select) + { + guiTextBoxState.cursor = guiTextBoxState.select - 1; + MoveTextBoxCursorRight(text, length, textRec); + } + } + else + { + // Move cursor to the right + MoveTextBoxCursorRight(text, length, textRec); + } + guiTextBoxState.select = -1; + } framesCounter = 0; - if (keyCount < 0) keyCount = 0; } - else if (IsKeyDown(KEY_BACKSPACE)) + else if(IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER == 0))) { - if ((framesCounter > TEXTEDIT_CURSOR_BLINK_FRAMES) && (framesCounter%2) == 0) keyCount--; - text[keyCount] = '\0'; - if (keyCount < 0) keyCount = 0; + if(IsKeyDown(KEY_LEFT_SHIFT)) + { + // Selecting + if(guiTextBoxState.select == -1) + guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + MoveTextBoxCursorLeft(text); + } + else + { + if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + { + // Deselect and move cursor to start of selection + if(guiTextBoxState.cursor > guiTextBoxState.select) + { + guiTextBoxState.cursor = guiTextBoxState.select; + if(guiTextBoxState.start > guiTextBoxState.cursor) + { + guiTextBoxState.start = guiTextBoxState.cursor; + guiTextBoxState.index = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.start); // Recalculate byte index + } + } + } + else + { + // Move cursor to the left + MoveTextBoxCursorLeft(text); + } + guiTextBoxState.select = -1; + } + framesCounter = 0; } - } - } + else if(IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER) == 0)) + { + GuiTextBoxDelete(text, length, true); + } + else if(IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER) == 0)) + { + GuiTextBoxDelete(text, length, false); + } + else if(IsKeyPressed(KEY_HOME)) + { + if(IsKeyDown(KEY_LEFT_SHIFT)) + { + // Select from start of text to cursor + if(guiTextBoxState.select > guiTextBoxState.cursor || (guiTextBoxState.select == -1 && guiTextBoxState.cursor != 0) ) + guiTextBoxState.select = guiTextBoxState.cursor; + } + else guiTextBoxState.select = -1; // Deselect everything + + // Move cursor to start of text + guiTextBoxState.cursor = guiTextBoxState.start = guiTextBoxState.index = 0; + framesCounter = 0; + } + else if(IsKeyPressed(KEY_END)) + { + int max = GuiCountCodepointsUntilNewline(text); + if(IsKeyDown(KEY_LEFT_SHIFT)) + { + if(guiTextBoxState.select == -1 && guiTextBoxState.cursor != max) + guiTextBoxState.select = guiTextBoxState.cursor; + } + else guiTextBoxState.select = -1; // Deselect everything + guiTextBoxState.cursor = max; + int pos = 0; + int len = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + guiTextBoxState.index = GuiMeasureTextBoxRev(text, len, textRec, &pos); + guiTextBoxState.start = guiTextBoxState.cursor - pos + 1; + } + else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_A)) + { + // `CTRL + A` Select all + GuiTextBoxSelectAll(text); + } + else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) + { + // `CTRL + C` Copy selected text to clipboard + GuiTextBoxCopy(text); + } + else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_X)) + { + // `CTRL + X` Cut selected text + GuiTextBoxCut(text); + } + else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) + { + // `CTRL + V` Paste clipboard text + GuiTextBoxPaste(text, textSize); + } + else if(IsKeyPressed(KEY_ENTER)) + { + pressed = true; + } + else + { + int key = GetKeyPressed(); + if(key >= 32 && guiTextBoxState.cursor+1 < textSize) + { + if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + { + // Delete selection + GuiTextBoxDelete(text, length, true); + } + + // Decode codepoint + char out[5] = {0}; + int sz = EncodeCodepoint(key, &out[0]); + if(sz != 0) + { + int startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); + int endIdx = startIdx + sz; + if(endIdx <= textSize) + { + guiTextBoxState.cursor++; + guiTextBoxState.select = -1; + memmove(&text[endIdx], &text[startIdx], length - startIdx); + memcpy(&text[startIdx], &out[0], sz); + length += sz; + text[length] = '\0'; + if(guiTextBoxState.start != -1) + { + const int max = GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length-guiTextBoxState.index, textRec); + if(guiTextBoxState.cursor - guiTextBoxState.start > max) + guiTextBoxState.start = -1; + } + } + } + } + + } + + // ------------- + // HANDLE MOUSE + // ------------- + if(CheckCollisionPointRec(mousePoint, bounds)) + { + if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if(CheckCollisionPointRec(mousePoint, textRec)) + { + GuiTextBoxGetCursorFromMouse(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, &guiTextBoxState.cursor); + guiTextBoxState.cursor += guiTextBoxState.start; + guiTextBoxState.select = -1; + } + else + { + // Clicked outside the `textRec` but still inside bounds + if(mousePoint.x <= bounds.x+bounds.width/2) guiTextBoxState.cursor = 0 + guiTextBoxState.start; + else guiTextBoxState.cursor = guiTextBoxState.start + GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec); + guiTextBoxState.select = -1; + } + } + else if(IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + int cursor = guiTextBoxState.cursor - guiTextBoxState.start; + bool move = false; + if(CheckCollisionPointRec(mousePoint, textRec)) + { + GuiTextBoxGetCursorFromMouse(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, &cursor); + } + else + { + // Clicked outside the `textRec` but still inside bounds, this means that we must move the text + move = true; + if(mousePoint.x > bounds.x+bounds.width/2) + cursor = GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec); + } + guiTextBoxState.cursor = cursor + guiTextBoxState.start; + + if(guiTextBoxState.select == -1) + { + // Mark start of selection + guiTextBoxState.select = guiTextBoxState.cursor; + } + + // Move the text when cursor is positioned before or after the text + if((framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER) == 0 && move) + { + if(cursor == 0) + { + MoveTextBoxCursorLeft(text); + } + else if(cursor == GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec)) + { + MoveTextBoxCursorRight(text, length, textRec); + } + } + } + } + // Calculate X coordinate of the blinking cursor + cursorPos.x = GuiTextBoxGetCursorCoordinates(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, + guiTextBoxState.cursor - guiTextBoxState.start); - if (!editMode) + // Update variables + textStartIndex = guiTextBoxState.index; + if(guiTextBoxState.select == -1) + { + selStart = guiTextBoxState.cursor; + selLength = 0; + } + else if(guiTextBoxState.cursor > guiTextBoxState.select) + { + selStart = guiTextBoxState.select; + selLength = guiTextBoxState.cursor - guiTextBoxState.select; + } + else + { + selStart = guiTextBoxState.cursor; + selLength = guiTextBoxState.select - guiTextBoxState.cursor; + } + + // We aren't drawing all of the text so make sure `DrawTextRecEx()` is selecting things correctly + if(guiTextBoxState.start > selStart) + { + selLength -= guiTextBoxState.start - selStart; + selStart = 0; + } + else selStart = selStart - guiTextBoxState.start; + } + else + state = GUI_STATE_FOCUSED; + } + else { if (CheckCollisionPointRec(mousePoint, bounds)) { state = GUI_STATE_FOCUSED; if (IsMouseButtonPressed(0)) pressed = true; } + + if(active && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) + { + // If active copy all text to clipboard even when disabled + + // Backup textbox state + int select = guiTextBoxState.select; + int cursor = guiTextBoxState.cursor; + int start = guiTextBoxState.start; + if(guiTextBoxState.select == -1 || guiTextBoxState.select == guiTextBoxState.cursor) + { + // If no selection then mark all text to be copied to clipboard + GuiTextBoxSelectAll(text); + } + GuiTextBoxCopy(text); + + // Restore textbox state + guiTextBoxState.select = select; + guiTextBoxState.cursor = cursor; + guiTextBoxState.start = start; + } } - else - { - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(0))) pressed = true; - } - - if (pressed) framesCounter = 0; } - //-------------------------------------------------------------------- - + // Draw control //-------------------------------------------------------------------- DrawRectangleLinesEx(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha)); - + if (state == GUI_STATE_PRESSED) { DrawRectangle(bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_FOCUSED)), guiAlpha)); - - // Draw blinking cursor - if (editMode && ((framesCounter/20)%2 == 0)) DrawRectangle(bounds.x + GuiGetStyle(TEXTBOX, INNER_PADDING) + GetTextWidth(text) + 2 + bounds.width/2*GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), 1, GuiGetStyle(DEFAULT, TEXT_SIZE)*2, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + if (editMode && active && ((framesCounter/TEXTEDIT_CURSOR_BLINK_FRAMES)%2 == 0) && selLength == 0) + { + // Draw the blinking cursor + DrawRectangle(cursorPos.x, cursorPos.y, 1, GuiGetStyle(DEFAULT, TEXT_SIZE), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + } } else if (state == GUI_STATE_DISABLED) { DrawRectangle(bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)), guiAlpha)); } - - GuiDrawText(text, GetTextBounds(TEXTBOX, bounds), GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); - //-------------------------------------------------------------------- - + + // Finally draw the text and selection + if (guiFont.texture.id == 0) guiFont = GetFontDefault(); + DrawTextRecEx(guiFont, &text[textStartIndex], textRec, GuiGetStyle(DEFAULT, TEXT_SIZE), GuiGetStyle(DEFAULT, TEXT_SPACING), false, Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha), selStart, selLength, GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_FG)), GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_BG))); return pressed; } @@ -3017,6 +3770,8 @@ RAYGUIDEF void GuiLoadStyleDefault(void) GuiSetStyle(TEXTBOX, INNER_PADDING, 4); GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); GuiSetStyle(TEXTBOX, MULTILINE_PADDING, 5); + GuiSetStyle(TEXTBOX, COLOR_SELECTED_FG, 0xf0fffeff); + GuiSetStyle(TEXTBOX, COLOR_SELECTED_BG, 0x839affe0); GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); GuiSetStyle(SPINNER, SELECT_BUTTON_WIDTH, 20); GuiSetStyle(SPINNER, SELECT_BUTTON_PADDING, 2);