diff --git a/src/raygui.h b/src/raygui.h index a16b099..3838bb4 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -61,6 +61,10 @@ * Includes ricons.h header defining a set of 128 icons (binary format) to be used on * multiple controls and following raygui styles * +* #define RAYGUI_TEXTBOX_EXTENDED +* Enables the advance GuiTextBox()/GuiValueBox()/GuiSpinner() implementation with +* text selection support and text copy/cut/paste support +* * VERSIONS HISTORY: * 2.0 (xx-Dec-2018) Complete review of new controls, redesigned style system * 1.9 (01-May-2018) Lot of rework and redesign! Lots of new controls! @@ -74,6 +78,7 @@ * * CONTRIBUTORS: * Ramon Santamaria: Supervision, review, redesign, update and maintenance... +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) * Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) * Adria Arranz: Testing and Implementation of additional controls (2018) * Jordi Jorba: Testing and Implementation of additional controls (2018) @@ -191,13 +196,15 @@ typedef struct Font { } Font; #endif -// State object used by GuiTextBox() +#if defined(RAYGUI_TEXTBOX_EXTENDED) +// Gui text box state data 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 + 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; +#endif // Gui global state enum typedef enum { @@ -365,6 +372,11 @@ 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 +// 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 + +#if defined(RAYGUI_TEXTBOX_EXTENDED) // 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 @@ -375,16 +387,13 @@ RAYGUIDEF Vector2 GuiTextBoxGetSelection(void); // Get s 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 +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`. +#endif // Container/separator controls, useful for controls organization RAYGUIDEF bool GuiWindowBox(Rectangle bounds, const char *text); // Window Box control, shows a window that can be closed @@ -483,11 +492,6 @@ 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 @@ -495,6 +499,11 @@ static GuiTextBoxState guiTextBoxState = {.cursor = -1, .start = 0, .index = 0, static unsigned int guiStyle[NUM_CONTROLS*(NUM_PROPS_DEFAULT + NUM_PROPS_EXTENDED)] = { 0 }; static bool guiStyleLoaded = false; +#if defined(RAYGUI_TEXTBOX_EXTENDED) +static Rectangle guiTextBoxActive = { 0 }; // Area of the currently active textbox +static GuiTextBoxState guiTextBoxState = { .cursor = -1, .start = 0, .index = 0, .select = -1 }; // Keeps state of the active textbox +#endif + //---------------------------------------------------------------------------------- // Standalone Mode Functions Declaration // @@ -756,53 +765,6 @@ 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) { @@ -817,6 +779,61 @@ RAYGUIDEF int GuiGetStyle(int control, int property) return guiStyle[control*(NUM_PROPS_DEFAULT + NUM_PROPS_EXTENDED) + property]; } +#if defined(RAYGUI_TEXTBOX_EXTENDED) +// 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; +} +#endif + // Window Box control RAYGUIDEF bool GuiWindowBox(Rectangle bounds, const char *text) { @@ -1475,16 +1492,18 @@ RAYGUIDEF bool GuiDropdownBox(Rectangle bounds, const char *text, int *active, b return pressed; } +#if defined(RAYGUI_TEXTBOX_EXTENDED) // Spinner control, returns selected value // NOTE: Requires static variables: timer, valueSpeed - ERROR! RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxValue, bool editMode) { - #define GUI_SPINNER_HOLD_SPEED 0.2f // min 200ms delay + #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 + const float time = GetTime(); // Get current time + bool pressed = false, active = GuiTextBoxIsActive(bounds); 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 }; @@ -1497,12 +1516,13 @@ RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxVal if (tempValue < minValue) tempValue = minValue; if (tempValue > maxValue) tempValue = maxValue; - if(editMode) + if (editMode) { - if(!active) + 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)) + if ((IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonDown(MOUSE_LEFT_BUTTON)) && + CheckCollisionPointRec(mouse, bounds)) { GuiTextBoxSetActive(bounds); active = true; @@ -1511,15 +1531,18 @@ RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxVal } // 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) ) + if ((CheckCollisionPointRec(mouse, leftButtonBound) || CheckCollisionPointRec(mouse, rightButtonBound)) && + IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { timer = time; + } //-------------------------------------------------------------------- // Draw control //-------------------------------------------------------------------- - if(GuiTextBoxIsActive(bounds)) guiTextBoxActive = spinner; // Set our spinner as the active textbox + 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 + if (GuiTextBoxIsActive(spinner)) guiTextBoxActive = bounds; // Revert change // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values @@ -1529,21 +1552,29 @@ 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 = "<"; + char *icon = "<"; #if defined(RAYGUI_RICONS_SUPPORT) - icon = (char*)GuiIconText(RICON_ARROW_LEFT_FILL, NULL); + 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) ) + (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); + 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) ) + (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + CheckCollisionPointRec(mouse, rightButtonBound) && + (time - timer) > GUI_SPINNER_HOLD_SPEED)) + { tempValue++; + } GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); @@ -1553,19 +1584,19 @@ RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxVal if (tempValue > maxValue) tempValue = maxValue; // Reset timer - if(active && (time - timer > GUI_SPINNER_HOLD_SPEED || timer == 0.0f || timer > time)) - timer = time; + 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 RAYGUIDEF bool GuiValueBox(Rectangle bounds, int *value, int minValue, int maxValue, bool editMode) { - #define VALUEBOX_MAX_CHARS 32 + #define VALUEBOX_MAX_CHARS 32 - char text[VALUEBOX_MAX_CHARS + 1] = {0}; + char text[VALUEBOX_MAX_CHARS + 1] = { 0 }; sprintf(text, "%i", *value); bool pressed = GuiTextBox(bounds, text, VALUEBOX_MAX_CHARS, editMode); @@ -1589,6 +1620,7 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p { // 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); @@ -1608,7 +1640,7 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p glyphWidth = 0; int next = 1; letter = GetNextCodepoint(&text[i], &next); - if(letter == 0x3f) next = 1; + if (letter == 0x3f) next = 1; index = GetGlyphIndex(font, letter); i += next - 1; @@ -1620,23 +1652,22 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p 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) + 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}; + Rectangle grec = {rec.x + textOffsetX - 1, rec.y, glyphWidth, (font.baseSize + font.baseSize/2)*scaleFactor - 1 }; Vector2 mouse = GetMousePosition(); - if(CheckCollisionPointRec(mouse, grec)) + + 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) + if (mouse.x > (grec.x + glyphWidth/2)) { textOffsetX += glyphWidth; k++; } + break; } } @@ -1647,22 +1678,25 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p } *pos = k; - return rec.x + textOffsetX - 1; + + return (rec.x + textOffsetX - 1); } -static int GetPrevCodepoint(const char* text, const char* start, int* prev) +static int GetPrevCodepoint(const char *text, const char *start, int *prev) { int c = 0x3f; - char* p = (char*)text; + char *p = (char *)text; *prev = 1; - for(int i = 0; p >= start && i < 4; p--, i++) + + for (int i = 0; (p >= start) && (i < 4); p--, i++) { - if((((unsigned char)*p) >> 6) != 2) + if ((((unsigned char)*p) >> 6) != 2) { c = GetNextCodepoint(p, prev); break; } } + return c; } @@ -1672,6 +1706,7 @@ static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int { // 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); @@ -1690,7 +1725,8 @@ static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int { glyphWidth = 0; letter = GetPrevCodepoint(&text[i], &text[0], &prev); - if(letter == 0x3f) prev = 1; + + if (letter == 0x3f) prev = 1; index = GetGlyphIndex(font, letter); i -= prev - 1; @@ -1706,8 +1742,10 @@ static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int textOffsetX += glyphWidth; } + *pos = k; - return i+prev; + + return (i + prev); } @@ -1736,15 +1774,18 @@ static inline int GuiTextBoxMaxCharacters(const char *text, int length, Rectangl static inline unsigned int GuiCountCodepointsUntilNewline(const char *text) { unsigned int len = 0; - char* ptr = (char*)&text[0]; - while(*ptr != '\0' && *ptr != '\n') + char *ptr = (char*)&text[0]; + + while ((*ptr != '\0') && (*ptr != '\n')) { int next = 0; int letter = GetNextCodepoint(ptr, &next); - if(letter == 0x3f) ptr += 1; + + if (letter == 0x3f) ptr += 1; else ptr += next; ++len; } + return len; } @@ -1752,54 +1793,62 @@ static inline void MoveTextBoxCursorRight(const char* text, int length, Rectangl { // FIXME: Counting codepoints each time we press the key is expensive, find another way int count = GuiCountCodepointsUntilNewline(text); - if(guiTextBoxState.cursor < count ) guiTextBoxState.cursor++; + if (guiTextBoxState.cursor < count ) guiTextBoxState.cursor++; + const int max = GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec); - if(guiTextBoxState.cursor - guiTextBoxState.start > max) + + 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.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) + 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; + 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) +RAYGUIDEF int GuiTextBoxGetByteIndex(const char *text, int start, int from, int to) { int i = start, k = from; - while(text[i] != '\0' && k < to) + + while ((text[i] != '\0') && (k < to)) { int j = 0; int letter = GetNextCodepoint(&text[i], &j); - if(letter == 0x3f) j = 1; + + if (letter == 0x3f) j = 1; i += j; ++k; } + return i; } -RAYGUIDEF int GuiTextBoxDelete(char* text, int length, bool before) +RAYGUIDEF int GuiTextBoxDelete(char *text, int length, bool before) { - if(guiTextBoxState.cursor != -1 && text != NULL) + if ((guiTextBoxState.cursor != -1) && (text != NULL)) { int startIdx = 0, endIdx = 0; - if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + if ((guiTextBoxState.select != -1) && (guiTextBoxState.select != guiTextBoxState.cursor)) { // Delete selection - int start = guiTextBoxState.cursor, end = guiTextBoxState.select; - if(guiTextBoxState.cursor > guiTextBoxState.select) + int start = guiTextBoxState.cursor; + int end = guiTextBoxState.select; + + if (guiTextBoxState.cursor > guiTextBoxState.select) { start = guiTextBoxState.select; end = guiTextBoxState.cursor; @@ -1811,25 +1860,26 @@ RAYGUIDEF int GuiTextBoxDelete(char* text, int length, bool before) // 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 + if (guiTextBoxState.select < guiTextBoxState.start) guiTextBoxState.start = -1; // Force to recalculate on the next frame } else { - if(before) + if (before) { // Delete character before cursor - if(guiTextBoxState.cursor != 0) + 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 + + 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)) + if (guiTextBoxState.cursor + 1 <= GuiCountCodepointsUntilNewline(text)) { startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); endIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor+1); @@ -1840,29 +1890,36 @@ RAYGUIDEF int GuiTextBoxDelete(char* text, int length, bool before) memmove(&text[startIdx], &text[endIdx], length - endIdx); text[length - (endIdx - startIdx)] = '\0'; guiTextBoxState.select = -1; // Always deselect - return endIdx - startIdx; + + return (endIdx - startIdx); } + return 0; } -RAYGUIDEF void GuiTextBoxSelectAll(const char* text) +RAYGUIDEF void GuiTextBoxSelectAll(const char *text) { guiTextBoxState.cursor = GuiCountCodepointsUntilNewline(text); - if(guiTextBoxState.cursor > 0) + + if (guiTextBoxState.cursor > 0) { guiTextBoxState.select = 0; guiTextBoxState.start = -1; // Force recalculate on the next frame } - else - guiTextBoxState.select = -1; + else guiTextBoxState.select = -1; } -RAYGUIDEF void GuiTextBoxCopy(const char* text) +RAYGUIDEF void GuiTextBoxCopy(const char *text) { - if(guiTextBoxState.select != -1 && guiTextBoxState.cursor != -1 && guiTextBoxState.select != guiTextBoxState.cursor && text != NULL) + if ((text != NULL) && + (guiTextBoxState.select != -1) && + (guiTextBoxState.cursor != -1) && + (guiTextBoxState.select != guiTextBoxState.cursor)) { - int start = guiTextBoxState.cursor, end = guiTextBoxState.select; - if(guiTextBoxState.cursor > guiTextBoxState.select) + int start = guiTextBoxState.cursor; + int end = guiTextBoxState.select; + + if (guiTextBoxState.cursor > guiTextBoxState.select) { start = guiTextBoxState.select; end = guiTextBoxState.cursor; @@ -1874,55 +1931,67 @@ RAYGUIDEF void GuiTextBoxCopy(const char* text) // 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); + 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) +RAYGUIDEF void GuiTextBoxPaste(char *text, int textSize) { - const char* clipText = GetClipboardText(); // GLFW guaratees this should be UTF8 encoded! + const char *clipText = GetClipboardText(); // GLFW guaratees this should be UTF8 encoded! int length = strlen(text); - if(clipText != NULL && guiTextBoxState.cursor != -1 && text != NULL) + + if ((text != NULL) && (clipText != NULL) && (guiTextBoxState.cursor != -1)) { - if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + 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; + 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++) + + for (int i = 0; i < (startIdx + size); guiTextBoxState.cursor++) { int next = 0; int letter = GetNextCodepoint(&text[i], &next); - if(letter != 0x3f) 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) + if ((text != NULL) && + (guiTextBoxState.select != -1) && + (guiTextBoxState.cursor != -1) && + (guiTextBoxState.select != guiTextBoxState.cursor)) { // First copy selection to clipboard; int start = guiTextBoxState.cursor, end = guiTextBoxState.select; - if(guiTextBoxState.cursor > guiTextBoxState.select) + + if (guiTextBoxState.cursor > guiTextBoxState.select) { start = guiTextBoxState.select; end = guiTextBoxState.cursor; @@ -1934,7 +2003,7 @@ RAYGUIDEF void GuiTextBoxCut(char* text) // 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); + const char *clipText = TextSubtext(text, startIdx, endIdx - startIdx); SetClipboardText(clipText); // Now delete selection (copy data over it) @@ -1944,33 +2013,33 @@ RAYGUIDEF void GuiTextBoxCut(char* text) // 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 + 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) + if (c <= 0x7f) { out[0] = (char)c; len = 1; } - else if(c <= 0x7ff) + else if (c <= 0x7ff) { out[0] = (char)(((c >> 6) & 0x1f) | 0xc0); out[1] = (char)((c & 0x3f) | 0x80); len = 2; } - else if(c <= 0xffff) + 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) + else if (c <= 0x10ffff) { out[0] = (char)(((c >> 18) & 0x07) | 0xf0); out[1] = (char)(((c >> 12) & 0x3f) | 0x80); @@ -1996,19 +2065,22 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // 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) + 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)) + 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) + + if (verticalPadding < 0) { // Make sure the height is sufficient bounds.height = 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(DEFAULT, TEXT_SIZE); @@ -2016,9 +2088,13 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM } // 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 + 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; @@ -2032,41 +2108,42 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM { // Check if we are the global active textbox // A textbox becomes active when the user clicks it :) - if(!active) + if (!active) { - if(CheckCollisionPointRec(mousePoint, bounds) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))) + if (CheckCollisionPointRec(mousePoint, bounds) && + (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))) { // Hurray!!! we just became the active textbox active = true; GuiTextBoxSetActive(bounds); } } - else if(!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) + 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) + 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; + 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) + if (guiTextBoxState.cursor == -1) { // Set cursor to last visible character in textbox guiTextBoxState.cursor = GuiTextBoxMaxCharacters(text, length, textRec); } - if(guiTextBoxState.start == -1) + if (guiTextBoxState.start == -1) { // Force recalculate text start position and text start index @@ -2074,7 +2151,7 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // 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) + if (guiTextBoxState.cursor == 0) { guiTextBoxState.start = guiTextBoxState.index = 0; // No need to recalculate } @@ -2101,21 +2178,22 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // * 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))) + if (IsKeyPressed(KEY_RIGHT) || + (IsKeyDown(KEY_RIGHT) && (framesCounter%GUI_TEXTBOX_CURSOR_SPEED_MODIFIER == 0))) { - if(IsKeyDown(KEY_LEFT_SHIFT)) + if (IsKeyDown(KEY_LEFT_SHIFT)) { // Selecting - if(guiTextBoxState.select == -1) - guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + if (guiTextBoxState.select == -1) guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + MoveTextBoxCursorRight(text, length, textRec); } else { - if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + if (guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) { // Deselect and move cursor to end of selection - if(guiTextBoxState.cursor < guiTextBoxState.select) + if (guiTextBoxState.cursor < guiTextBoxState.select) { guiTextBoxState.cursor = guiTextBoxState.select - 1; MoveTextBoxCursorRight(text, length, textRec); @@ -2126,28 +2204,31 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // Move cursor to the right MoveTextBoxCursorRight(text, length, textRec); } + guiTextBoxState.select = -1; } + framesCounter = 0; } - else if(IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (framesCounter % GUI_TEXTBOX_CURSOR_SPEED_MODIFIER == 0))) + else if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (framesCounter%GUI_TEXTBOX_CURSOR_SPEED_MODIFIER == 0))) { - if(IsKeyDown(KEY_LEFT_SHIFT)) + if (IsKeyDown(KEY_LEFT_SHIFT)) { // Selecting - if(guiTextBoxState.select == -1) - guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + if (guiTextBoxState.select == -1) guiTextBoxState.select = guiTextBoxState.cursor; // Mark selection start + MoveTextBoxCursorLeft(text); } else { - if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + if ((guiTextBoxState.select != -1) && (guiTextBoxState.select != guiTextBoxState.cursor)) { // Deselect and move cursor to start of selection - if(guiTextBoxState.cursor > guiTextBoxState.select) + if (guiTextBoxState.cursor > guiTextBoxState.select) { guiTextBoxState.cursor = guiTextBoxState.select; - if(guiTextBoxState.start > guiTextBoxState.cursor) + + if (guiTextBoxState.start > guiTextBoxState.cursor) { guiTextBoxState.start = guiTextBoxState.cursor; guiTextBoxState.index = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.start); // Recalculate byte index @@ -2159,25 +2240,30 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // 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)) + 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)) + else if (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (framesCounter%GUI_TEXTBOX_CURSOR_SPEED_MODIFIER) == 0)) { GuiTextBoxDelete(text, length, false); } - else if(IsKeyPressed(KEY_HOME)) + else if (IsKeyPressed(KEY_HOME)) { - if(IsKeyDown(KEY_LEFT_SHIFT)) + if (IsKeyDown(KEY_LEFT_SHIFT)) { // Select from start of text to cursor - if(guiTextBoxState.select > guiTextBoxState.cursor || (guiTextBoxState.select == -1 && guiTextBoxState.cursor != 0) ) + if ((guiTextBoxState.select > guiTextBoxState.cursor) || + ((guiTextBoxState.select == -1) && (guiTextBoxState.cursor != 0))) + { guiTextBoxState.select = guiTextBoxState.cursor; + } } else guiTextBoxState.select = -1; // Deselect everything @@ -2185,51 +2271,55 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM guiTextBoxState.cursor = guiTextBoxState.start = guiTextBoxState.index = 0; framesCounter = 0; } - else if(IsKeyPressed(KEY_END)) + else if (IsKeyPressed(KEY_END)) { int max = GuiCountCodepointsUntilNewline(text); - if(IsKeyDown(KEY_LEFT_SHIFT)) + + if (IsKeyDown(KEY_LEFT_SHIFT)) { - if(guiTextBoxState.select == -1 && guiTextBoxState.cursor != max) + if ((guiTextBoxState.select == -1) && (guiTextBoxState.cursor != max)) + { guiTextBoxState.select = guiTextBoxState.cursor; + } } else guiTextBoxState.select = -1; // Deselect everything - guiTextBoxState.cursor = max; + int pos = 0; + guiTextBoxState.cursor = max; 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)) + else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_A)) { // `CTRL + A` Select all GuiTextBoxSelectAll(text); } - else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) + 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)) + 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)) + else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) { // `CTRL + V` Paste clipboard text GuiTextBoxPaste(text, textSize); } - else if(IsKeyPressed(KEY_ENTER)) + else if (IsKeyPressed(KEY_ENTER)) { pressed = true; } else { int key = GetKeyPressed(); - if(key >= 32 && guiTextBoxState.cursor+1 < textSize) + if ((key >= 32) && ((guiTextBoxState.cursor + 1) < textSize)) { - if(guiTextBoxState.select != -1 && guiTextBoxState.select != guiTextBoxState.cursor) + if ((guiTextBoxState.select != -1) && (guiTextBoxState.select != guiTextBoxState.cursor)) { // Delete selection GuiTextBoxDelete(text, length, true); @@ -2238,11 +2328,13 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // Decode codepoint char out[5] = {0}; int sz = EncodeCodepoint(key, &out[0]); - if(sz != 0) + + if (sz != 0) { int startIdx = GuiTextBoxGetByteIndex(text, 0, 0, guiTextBoxState.cursor); int endIdx = startIdx + sz; - if(endIdx <= textSize) + + if (endIdx <= textSize) { guiTextBoxState.cursor++; guiTextBoxState.select = -1; @@ -2250,26 +2342,26 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM memcpy(&text[startIdx], &out[0], sz); length += sz; text[length] = '\0'; - if(guiTextBoxState.start != -1) + + if (guiTextBoxState.start != -1) { - const int max = GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length-guiTextBoxState.index, textRec); - if(guiTextBoxState.cursor - guiTextBoxState.start > max) - 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 (CheckCollisionPointRec(mousePoint, bounds)) { - if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - if(CheckCollisionPointRec(mousePoint, textRec)) + if (CheckCollisionPointRec(mousePoint, textRec)) { GuiTextBoxGetCursorFromMouse(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, &guiTextBoxState.cursor); guiTextBoxState.cursor += guiTextBoxState.start; @@ -2278,16 +2370,16 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM else { // Clicked outside the `textRec` but still inside bounds - if(mousePoint.x <= bounds.x+bounds.width/2) guiTextBoxState.cursor = 0 + guiTextBoxState.start; + 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)) + else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { int cursor = guiTextBoxState.cursor - guiTextBoxState.start; bool move = false; - if(CheckCollisionPointRec(mousePoint, textRec)) + if (CheckCollisionPointRec(mousePoint, textRec)) { GuiTextBoxGetCursorFromMouse(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, &cursor); } @@ -2295,43 +2387,44 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM { // 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) + 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) + 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 ((framesCounter%GUI_TEXTBOX_CURSOR_SPEED_MODIFIER) == 0 && move) { - if(cursor == 0) - { - MoveTextBoxCursorLeft(text); - } - else if(cursor == GuiTextBoxMaxCharacters(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec)) + 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); + cursorPos.x = GuiTextBoxGetCursorCoordinates(&text[guiTextBoxState.index], length - guiTextBoxState.index, textRec, guiTextBoxState.cursor - guiTextBoxState.start); // Update variables textStartIndex = guiTextBoxState.index; - if(guiTextBoxState.select == -1) + + if (guiTextBoxState.select == -1) { selStart = guiTextBoxState.cursor; selLength = 0; } - else if(guiTextBoxState.cursor > guiTextBoxState.select) + else if (guiTextBoxState.cursor > guiTextBoxState.select) { selStart = guiTextBoxState.select; selLength = guiTextBoxState.cursor - guiTextBoxState.select; @@ -2343,15 +2436,14 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM } // We aren't drawing all of the text so make sure `DrawTextRecEx()` is selecting things correctly - if(guiTextBoxState.start > selStart) + if (guiTextBoxState.start > selStart) { selLength -= guiTextBoxState.start - selStart; selStart = 0; } else selStart = selStart - guiTextBoxState.start; } - else - state = GUI_STATE_FOCUSED; + else state = GUI_STATE_FOCUSED; } else { @@ -2361,7 +2453,7 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM if (IsMouseButtonPressed(0)) pressed = true; } - if(active && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) + if (active && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) { // If active copy all text to clipboard even when disabled @@ -2369,11 +2461,12 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM int select = guiTextBoxState.select; int cursor = guiTextBoxState.cursor; int start = guiTextBoxState.start; - if(guiTextBoxState.select == -1 || guiTextBoxState.select == guiTextBoxState.cursor) + 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 @@ -2405,8 +2498,270 @@ RAYGUIDEF bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editM // 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; } +#else // !RAYGUI_TEXTBOX_EXTENDED + +// Spinner control, returns selected value +// NOTE: Requires static variables: framesCounter, valueSpeed - ERROR! +RAYGUIDEF bool GuiSpinner(Rectangle bounds, int *value, int minValue, int maxValue, bool editMode) +{ + bool pressed = false; + int tempValue = *value; + + 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 }; + Rectangle leftButtonBound = { bounds.x, bounds.y, GuiGetStyle(SPINNER, SELECT_BUTTON_WIDTH), bounds.height }; + Rectangle rightButtonBound = { bounds.x + bounds.width - GuiGetStyle(SPINNER, SELECT_BUTTON_WIDTH), bounds.y, GuiGetStyle(SPINNER, SELECT_BUTTON_WIDTH), bounds.height }; + + // Update control + //-------------------------------------------------------------------- + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // TODO: Set Spinner properties for ValueBox + pressed = GuiValueBox(spinner, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + +#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++; +#endif + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + *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"; + 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)); + //-------------------------------------------------------------------- + + return pressed; +} + +// Text Box control, updates input text +// 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) +{ + static int framesCounter = 0; // Required for blinking cursor + + GuiControlState state = guiState; + bool pressed = false; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + 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)) + { + int maxWidth = (bounds.width - (GuiGetStyle(DEFAULT, INNER_PADDING)*2)); + + if (GetTextWidth(text) < (maxWidth - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + if (((key >= 32) && (key <= 125)) || + ((key >= 128) && (key < 255))) + { + text[keyCount] = (char)key; + keyCount++; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + text[keyCount] = '\0'; + framesCounter = 0; + if (keyCount < 0) keyCount = 0; + } + else if (IsKeyDown(KEY_BACKSPACE)) + { + if ((framesCounter > TEXTEDIT_CURSOR_BLINK_FRAMES) && (framesCounter%2) == 0) keyCount--; + text[keyCount] = '\0'; + if (keyCount < 0) keyCount = 0; + } + } + } + + 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(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)); + } + 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)); + //-------------------------------------------------------------------- + + return pressed; +} +#endif // Text Box control with multiple lines RAYGUIDEF bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode)