diff --git a/src/extras/gui_textbox_extended.h b/src/extras/gui_textbox_extended.h deleted file mode 100644 index d8d0f06..0000000 --- a/src/extras/gui_textbox_extended.h +++ /dev/null @@ -1,1217 +0,0 @@ -/******************************************************************************************* -* -* Text box extended (cursor positioning and editing) -* -* MODULE USAGE: -* #define GUI_TEXTBOX_EXTENDED_IMPLEMENTATION -* #include "gui_textbox_extended.h" -* -* On game draw call: GuiTextBoxEx(...); -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2019 Vlad Adrian (@Demizdor) and Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef GUI_TEXTBOX_EXTENDED_H -#define GUI_TEXTBOX_EXTENDED_H -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- - -// Text box state data -typedef struct GuiTextBoxState { - 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; - -#ifdef __cplusplus -extern "C" { // Prevents name mangling of functions -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- -RAYGUIAPI void GuiTextBoxSetActive(Rectangle bounds); // Sets the active textbox -RAYGUIAPI Rectangle GuiTextBoxGetActive(void); // Get bounds of active textbox - -RAYGUIAPI void GuiTextBoxSetCursor(int cursor); // Set cursor position of active textbox -RAYGUIAPI int GuiTextBoxGetCursor(void); // Get cursor position of active textbox - -RAYGUIAPI void GuiTextBoxSetSelection(int start, int length); // Set selection of active textbox -RAYGUIAPI Vector2 GuiTextBoxGetSelection(void); // Get selection of active textbox (x - selection start y - selection length) - -RAYGUIAPI bool GuiTextBoxIsActive(Rectangle bounds); // Returns true if a textbox control with specified `bounds` is the active textbox -RAYGUIAPI GuiTextBoxState GuiTextBoxGetState(void); // Get state for the active textbox -RAYGUIAPI void GuiTextBoxSetState(GuiTextBoxState state); // Set state for the active textbox (state must be valid else things will break) - -RAYGUIAPI void GuiTextBoxSelectAll(const char *text); // Select all characters in the active textbox (same as pressing `CTRL` + `A`) -RAYGUIAPI void GuiTextBoxCopy(const char *text); // Copy selected text to clipboard from the active textbox (same as pressing `CTRL` + `C`) -RAYGUIAPI void GuiTextBoxPaste(char *text, int textSize); // Paste text from clipboard into the textbox (same as pressing `CTRL` + `V`) -RAYGUIAPI void GuiTextBoxCut(char *text); // Cut selected text in the active textbox and copy it to clipboard (same as pressing `CTRL` + `X`) -RAYGUIAPI int GuiTextBoxDelete(char *text, int length, bool before); // Deletes a character or selection before from the active textbox (depending on `before`). Returns bytes deleted. -RAYGUIAPI 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`. - -RAYGUIAPI bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode); - -RAYGUIAPI static void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits -RAYGUIAPI static void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Draw text using font inside rectangle limits with support for text selection -RAYGUIAPI static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Alias for above - -#ifdef __cplusplus -} -#endif - -#endif // GUI_TEXTBOX_EXTENDED_H - -/*********************************************************************************** -* -* GUI TEXTBOX EXTENDED IMPLEMENTATION -* -************************************************************************************/ - -#if defined(GUI_TEXTBOX_EXTENDED_IMPLEMENTATION) - -#ifndef RAYGUI_H -#include "raygui.h" -#endif - -// Draw text using font inside rectangle limits -static void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) -{ - DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE); -} - -// Draw text using font inside rectangle limits with support for text selection -static void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) -{ - int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop - - float textOffsetY = 0; // Offset between lines (on line break '\n') - float textOffsetX = 0.0f; // Offset X to next character to draw - - float scaleFactor = fontSize/(float)font.baseSize; // Character rectangle scaling factor - - // Word/character wrapping mechanism variables - enum { MEASURE_STATE = 0, DRAW_STATE = 1 }; - int state = wordWrap? MEASURE_STATE : DRAW_STATE; - - int startLine = -1; // Index where to begin drawing (where a line begins) - int endLine = -1; // Index where to stop drawing (where a line ends) - int lastk = -1; // Holds last value of the character position - - for (int i = 0, k = 0; i < length; i++, k++) - { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetCodepoint(&text[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointByteCount = 1; - i += (codepointByteCount - 1); - - float glyphWidth = 0; - if (codepoint != '\n') - { - glyphWidth = (font.glyphs[index].advanceX == 0) ? font.recs[index].width*scaleFactor : font.glyphs[index].advanceX*scaleFactor; - - if (i + 1 < length) glyphWidth = glyphWidth + spacing; - } - - // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container - // We store this info in startLine and endLine, then we change states, draw the text between those two variables - // and change states again and again recursively until the end of the text (or until we get outside of the container). - // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately - // and begin drawing on the next line before we can get outside the container. - if (state == MEASURE_STATE) - { - // TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more - // Ref: http://jkorpela.fi/chars/spaces.html - if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i; - - if ((textOffsetX + glyphWidth) > rec.width) - { - endLine = (endLine < 1)? i : endLine; - if (i == endLine) endLine -= codepointByteCount; - if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount); - - state = !state; - } - else if ((i + 1) == length) - { - endLine = i; - state = !state; - } - else if (codepoint == '\n') state = !state; - - if (state == DRAW_STATE) - { - textOffsetX = 0; - i = startLine; - glyphWidth = 0; - - // Save character position when we switch states - int tmp = lastk; - lastk = k - 1; - k = tmp; - } - } - else - { - if (codepoint == '\n') - { - if (!wordWrap) - { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; - textOffsetX = 0; - } - } - else - { - if (!wordWrap && ((textOffsetX + glyphWidth) > rec.width)) - { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; - textOffsetX = 0; - } - - // When text overflows rectangle height limit, just stop drawing - if ((textOffsetY + font.baseSize*scaleFactor) > rec.height) break; - - // Draw selection background - bool isGlyphSelected = false; - if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) - { - DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, glyphWidth, (float)font.baseSize*scaleFactor }, selectBackTint); - isGlyphSelected = true; - } - - // Draw current character glyph - if ((codepoint != ' ') && (codepoint != '\t')) - { - DrawTextCodepoint(font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, isGlyphSelected? selectTint : tint); - } - } - - if (wordWrap && (i == endLine)) - { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; - textOffsetX = 0; - startLine = endLine; - endLine = -1; - glyphWidth = 0; - selectStart += lastk - k; - k = lastk; - - state = !state; - } - } - - textOffsetX += glyphWidth; - } -} - -static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) { - DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, selectStart, selectLength, selectTint, selectBackTint); -} - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -// Cursor measure mode -typedef enum { - GUI_MEASURE_MODE_CURSOR_END = 0xA, - GUI_MEASURE_MODE_CURSOR_POS, - GUI_MEASURE_MODE_CURSOR_COORDS -} GuiMeasureMode; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static Rectangle guiTextBoxActive = { 0 }; // Area of the currently active textbox - -static GuiTextBoxState guiTextBoxState = { // Keeps state of the active textbox - .cursor = -1, - .start = 0, - .index = 0, - .select = -1 -}; - -//---------------------------------------------------------------------------------- -// Module Internal Functions Declaration -//---------------------------------------------------------------------------------- -static int GetPrevCodepoint(const char *text, const char *start, int *prev); -static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *pos, int mode); -static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int *pos); // Highly synchronized with calculations in DrawTextBoxedSelectable() - -static inline int GuiTextBoxGetCursorCoordinates(const char *text, int length, Rectangle rec, int pos); // Calculate cursor coordinates based on the cursor position `pos` inside the `text`. -static inline int GuiTextBoxGetCursorFromMouse(const char *text, int length, Rectangle rec, int *pos); // Calculate cursor position in textbox based on mouse coordinates. -static inline int GuiTextBoxMaxCharacters(const char *text, int length, Rectangle rec); // Calculates how many characters is the textbox able to draw inside rec -static inline unsigned int GuiCountCodepointsUntilNewline(const char *text); // Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` or a `\n` is found. - -static inline void MoveTextBoxCursorRight(const char *text, int length, Rectangle textRec); -static inline void MoveTextBoxCursorLeft(const char *text); - -static int EncodeCodepoint(unsigned int c, char out[5]); - -//---------------------------------------------------------------------------------- -// Module Functions Definition -//---------------------------------------------------------------------------------- - -// Sets the active textbox (reseting state of the previous active textbox) -RAYGUIAPI void GuiTextBoxSetActive(Rectangle bounds) -{ - guiTextBoxActive = bounds; - guiTextBoxState = (GuiTextBoxState){ .cursor = -1, .start = 0, .index = 0, .select = -1 }; -} - -// Gets bounds of active textbox -RAYGUIAPI Rectangle GuiTextBoxGetActive(void) { return guiTextBoxActive; } - -// Set cursor position of active textbox -RAYGUIAPI void GuiTextBoxSetCursor(int cursor) -{ - guiTextBoxState.cursor = (cursor < 0) ? -1 : cursor; - guiTextBoxState.start = -1; // Mark this to be recalculated -} - -// Get cursor position of active textbox -RAYGUIAPI int GuiTextBoxGetCursor(void) { return guiTextBoxState.cursor; } - -// Set selection of active textbox -RAYGUIAPI 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 -RAYGUIAPI Vector2 GuiTextBoxGetSelection(void) -{ - if (guiTextBoxState.select == -1 || guiTextBoxState.select == guiTextBoxState.cursor) return RAYGUI_CLITERAL(Vector2){ 0 }; - else if (guiTextBoxState.cursor > guiTextBoxState.select) return RAYGUI_CLITERAL(Vector2){ (float)guiTextBoxState.select, (float)guiTextBoxState.cursor - guiTextBoxState.select }; - - return RAYGUI_CLITERAL(Vector2){ (float)guiTextBoxState.cursor, (float)guiTextBoxState.select - guiTextBoxState.cursor }; -} - -// Returns true if a textbox control with specified `bounds` is the active textbox -RAYGUIAPI bool GuiTextBoxIsActive(Rectangle bounds) -{ - return (bounds.x == guiTextBoxActive.x && bounds.y == guiTextBoxActive.y && - bounds.width == guiTextBoxActive.width && bounds.height == guiTextBoxActive.height); -} - -RAYGUIAPI GuiTextBoxState GuiTextBoxGetState(void) { return guiTextBoxState; } -RAYGUIAPI void GuiTextBoxSetState(GuiTextBoxState state) -{ - // NOTE: should we check if state values are valid ?!? - guiTextBoxState = state; -} - -RAYGUIAPI 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 = GetCodepoint(&text[i], &j); - - if (letter == 0x3f) j = 1; - i += j; - ++k; - } - - return i; -} - -RAYGUIAPI 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; - int 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; -} - -RAYGUIAPI 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; -} - -RAYGUIAPI void GuiTextBoxCopy(const char *text) -{ - if ((text != NULL) && - (guiTextBoxState.select != -1) && - (guiTextBoxState.cursor != -1) && - (guiTextBoxState.select != guiTextBoxState.cursor)) - { - int start = guiTextBoxState.cursor; - int 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 TEXTSPLIT_MAX_TEXT_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 -RAYGUIAPI void GuiTextBoxPaste(char *text, int textSize) -{ - const char *clipText = GetClipboardText(); // GLFW guaratees this should be UTF8 encoded! - int length = strlen(text); - - if ((text != NULL) && (clipText != NULL) && (guiTextBoxState.cursor != -1)) - { - 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 = GetCodepoint(&text[i], &next); - if (letter != 0x3f) i += next; - else i += 1; - } - - guiTextBoxState.start = -1; // Force to recalculate on the next frame - } -} - -RAYGUIAPI void GuiTextBoxCut(char* text) -{ - 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) - { - 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 TEXTSPLIT_MAX_TEXT_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 - } -} - -// 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) -RAYGUIAPI bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode) -{ - // Define the cursor movement/selection speed when movement keys are held/pressed - #define TEXTBOX_CURSOR_COOLDOWN 5 - - static int framesCounter = 0; // Required for blinking cursor - - GuiState 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, TEXT_INNER_PADDING)) < GuiGetStyle(DEFAULT, TEXT_SIZE)) - { - bounds.width = GuiGetStyle(DEFAULT, TEXT_SIZE) + 2*GuiGetStyle(TEXTBOX, TEXT_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, TEXT_INNER_PADDING), - bounds.y + verticalPadding + GuiGetStyle(TEXTBOX, BORDER_WIDTH), - bounds.width - 2*(GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING) + GuiGetStyle(TEXTBOX, BORDER_WIDTH)), - (float)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 != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (editMode) - { - // Check if we are the global active textbox - // A textbox becomes active when the user clicks it :) - if (!active) - { - 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)) - { - // When active and the right mouse is clicked outside the textbox we should deactivate it - // NOTE: We set a dummy rect as the active textbox bounds - GuiTextBoxSetActive(RAYGUI_CLITERAL(Rectangle){ 0, 0, -1, -1 }); - active = false; - } - - if (active) - { - state = 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) - { - 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; - } - } - - // ----------------- - // 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%TEXTBOX_CURSOR_COOLDOWN == 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; - } - else if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (framesCounter%TEXTBOX_CURSOR_COOLDOWN == 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%TEXTBOX_CURSOR_COOLDOWN) == 0)) - { - GuiTextBoxDelete(text, length, true); - } - else if (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (framesCounter%TEXTBOX_CURSOR_COOLDOWN) == 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 - - 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)) GuiTextBoxSelectAll(text); // CTRL + A > Select all - else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) GuiTextBoxCopy(text); // CTRL + C > Copy selected text to clipboard - else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_X)) GuiTextBoxCut(text); // CTRL + X > Cut selected text - else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) GuiTextBoxPaste(text, textSize); // CTRL + V > Paste clipboard text - 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 && length < textSize - 1) - { - 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%TEXTBOX_CURSOR_COOLDOWN) == 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); - - // 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 `DrawTextBoxedSelectable()` is selecting things correctly - if (guiTextBoxState.start > selStart) - { - selLength -= guiTextBoxState.start - selStart; - selStart = 0; - } - else selStart = selStart - guiTextBoxState.start; - } - else state = STATE_FOCUSED; - - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(0))) pressed = true; - } - else - { - 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; - } - - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (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 == 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 && active && ((framesCounter/30)%2 == 0) && selLength == 0) - { - DrawRectangle(cursorPos.x, cursorPos.y-1, 1, GuiGetStyle(DEFAULT, TEXT_SIZE)+2, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); - } - } - else if (state == 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)); - } - - // Finally draw the text and selection - 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, TEXT_COLOR_FOCUSED)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_FOCUSED))); - - return pressed; -} - -//---------------------------------------------------------------------------------- -// Module Internal Functions Definition -//---------------------------------------------------------------------------------- - -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 = GetCodepoint(p, prev); - break; - } - } - - return c; -} - -// 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 = GetCodepoint(ptr, &next); - - if (letter == 0x3f) ptr += 1; - else ptr += next; - ++len; - } - - return len; -} - -// Highly synchronized with calculations in DrawTextBoxedSelectable() -static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *pos, int mode) -{ - // Get gui font properties - 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 = GetCodepoint(&text[i], &next); - if (letter == 0x3f) next = 1; - index = GetGlyphIndex(font, letter); - i += next - 1; - - if (letter != '\n') - { - glyphWidth = (font.glyphs[index].advanceX == 0)? - (int)(font.recs[index].width*scaleFactor + spacing): - (int)(font.glyphs[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, (float)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); -} - -// Required by GuiTextBoxEx() -// Highly synchronized with calculations in DrawTextBoxedSelectable() -static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int *pos) -{ - // Get gui font properties - 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.glyphs[index].advanceX == 0)? - (int)(font.recs[index].width*scaleFactor + spacing): - (int)(font.glyphs[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; -} - -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; - } -} - -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; -} - -#endif // GUI_TEXTBOX_EXTENDED_IMPLEMENTATION