From cf45f9bccfdbb573b921b43b71451fe092946181 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 1 Sep 2021 00:52:57 +0200 Subject: [PATCH] REVIEWED: GuiTextBoxMulti() - REMOVED: framesCounter (static variable) - REVIEWED: Cursors width - Minor tweaks --- src/raygui.h | 241 +++++++++++++++++++++++++++------------------------ 1 file changed, 127 insertions(+), 114 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index bb19190..002e7b6 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -220,8 +220,14 @@ int format; // Data format (PixelFormat type) } Texture2D; - // Font character info - typedef struct GlyphInfo GlyphInfo; + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() // It should be redesigned to be provided by user @@ -406,7 +412,7 @@ typedef enum { extern "C" { // Prevents name mangling of functions #endif -// State modification functions +// Global gui state control functions RAYGUIDEF void GuiEnable(void); // Enable gui controls (global state) RAYGUIDEF void GuiDisable(void); // Disable gui controls (global state) RAYGUIDEF void GuiLock(void); // Lock gui controls (global state) @@ -1067,7 +1073,7 @@ static unsigned int guiIcons[RICON_MAX_ICONS*RICON_DATA_ELEMENTS] = { #endif // RAYGUI_SUPPORT_RICONS #ifndef RICON_SIZE - #define RICON_SIZE 0 + #define RICON_SIZE 0 #endif #define RAYGUI_MAX_CONTROLS 16 // Maximum number of standard controls @@ -1076,7 +1082,7 @@ static unsigned int guiIcons[RICON_MAX_ICONS*RICON_DATA_ELEMENTS] = { // TODO: Avoid animations and time-based states // Functions using it: GuiTextBox(), GuiValueBox(), GuiTextBoxMulti() -#define TEXTEDIT_CURSOR_BLINK_FRAMES 20 // Text edit controls cursor blink timming +//#define TEXTEDIT_CURSOR_BLINK_FRAMES 20 // Text edit controls cursor blink timming //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -1594,7 +1600,7 @@ bool GuiImageButtonEx(Rectangle bounds, const char *text, Texture2D texture, Rec //-------------------------------------------------------------------- GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(BUTTON, BASE + (state*3))), guiAlpha)); - if (text != NULL) GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))), guiAlpha)); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))), guiAlpha)); if (texture.id > 0) DrawTextureRec(texture, texSource, RAYGUI_CLITERAL(Vector2){ bounds.x + bounds.width/2 - texSource.width/2, bounds.y + bounds.height/2 - texSource.height/2 }, Fade(GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))), guiAlpha)); //------------------------------------------------------------------ @@ -1730,7 +1736,7 @@ bool GuiCheckBox(Rectangle bounds, const char *text, bool checked) GuiDrawRectangle(check, 0, BLANK, Fade(GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3)), guiAlpha)); } - if (text != NULL) GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); //-------------------------------------------------------------------- return checked; @@ -1913,19 +1919,16 @@ bool GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMo } // Text Box control, updates input text -// NOTE 1: Requires static variables: framesCounter // NOTE 2: Returns if KEY_ENTER pressed (useful for data validation) bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { - static int framesCounter = 0; // Blinking cursor counter - GuiControlState state = guiState; bool pressed = false; Rectangle cursor = { bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text) + 2, bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), - 1, + 4, (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 }; @@ -1938,8 +1941,7 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (editMode) { state = GUI_STATE_PRESSED; - framesCounter++; - + int key = GetCharPressed(); // Returns codepoint as Unicode int keyCount = (int)strlen(text); @@ -1970,13 +1972,6 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { 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; } } @@ -1996,8 +1991,6 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; } } - - if (pressed) framesCounter = 0; } //-------------------------------------------------------------------- @@ -2006,9 +1999,6 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (state == GUI_STATE_PRESSED) { GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)), guiAlpha)); - - // Draw blinking cursor - if (editMode && ((framesCounter/20)%2 == 0)) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); } else if (state == GUI_STATE_DISABLED) { @@ -2017,6 +2007,9 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) else GuiDrawRectangle(bounds, 1, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), BLANK); GuiDrawText(text, GetTextBounds(TEXTBOX, bounds), GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + + // Draw cursor + if (editMode) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); //-------------------------------------------------------------------- return pressed; @@ -2090,7 +2083,7 @@ bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, in GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - if (text != NULL) GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); //-------------------------------------------------------------------- *value = tempValue; @@ -2105,8 +2098,6 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i #define VALUEBOX_MAX_CHARS 32 #endif - static int framesCounter = 0; // Blinking cursor counter - GuiControlState state = guiState; bool pressed = false; @@ -2135,8 +2126,6 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i { state = GUI_STATE_PRESSED; - framesCounter++; - int keyCount = (int)strlen(textValue); // Only allow keys in range [48..57] @@ -2161,14 +2150,6 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i { keyCount--; textValue[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--; - textValue[keyCount] = '\0'; if (keyCount < 0) keyCount = 0; valueHasChanged = true; } @@ -2189,8 +2170,6 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; } } - - if (pressed) framesCounter = 0; } //-------------------------------------------------------------------- @@ -2204,16 +2183,16 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), guiAlpha), baseColor); GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3))), guiAlpha)); - // Draw blinking cursor - if ((state == GUI_STATE_PRESSED) && (editMode && ((framesCounter/20)%2 == 0))) + // Draw cursor + if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 2, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 1, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 2, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED)), guiAlpha)); } // Draw text label if provided - if (text != NULL) GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); //-------------------------------------------------------------------- return pressed; @@ -2222,8 +2201,6 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i // Text Box control with multiple lines bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) { - static int framesCounter = 0; // Blinking cursor counter - GuiControlState state = guiState; bool pressed = false; @@ -2235,10 +2212,9 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) }; // Cursor position, [x, y] values should be updated - Rectangle cursor = { 0, 0, 1, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) + 2 }; - - int textWidth = 0; - int currentLine = 0; + Rectangle cursor = { 0, -1, 4, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) + 2 }; + + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; // Character rectangle scaling factor // Update control //-------------------------------------------------------------------- @@ -2249,77 +2225,52 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) if (editMode) { state = GUI_STATE_PRESSED; - framesCounter++; - int character = GetCharPressed(); - int keyCount = (int)strlen(text); + // We get an Unicode codepoint + int codepoint = GetCharPressed(); + int textLength = (int)strlen(text); // Length in bytes (UTF-8 string) // Introduce characters - if (keyCount < (textSize - 1)) + if (textLength < (textSize - 1)) { - Vector2 textSize = MeasureTextEx(guiFont, text, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - - if (textSize.y < (textAreaBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + if (IsKeyPressed(KEY_ENTER)) { - if (IsKeyPressed(KEY_ENTER)) - { - text[keyCount] = '\n'; - keyCount++; - } - else if (((character >= 32) && (character < 255))) // TODO: Support Unicode inputs - { - text[keyCount] = (char)character; - keyCount++; - } + text[textLength] = '\n'; + textLength++; + } + else if (codepoint >= 32) + { + // Supports Unicode inputs -> Encoded to UTF-8 + int charUTF8Length = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &charUTF8Length); + memcpy(text + textLength, charEncoded, charUTF8Length); + textLength += charUTF8Length; } } // Delete characters - if (keyCount > 0) + if (textLength > 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 ((unsigned char)text[textLength - 1] < 127) + { + // Remove ASCII equivalent character (1 byte) + textLength--; + text[textLength] = '\0'; + } + else + { + // Remove latest UTF-8 unicode character introduced (n bytes) + int charUTF8Length = 0; + while (((unsigned char)text[textLength - 1 - charUTF8Length] & 0b01000000) == 0) charUTF8Length++; + + textLength -= (charUTF8Length + 1); + text[textLength] = '\0'; + } } } - // Calculate cursor position considering text - char oneCharText[2] = { 0 }; - int lastBreakingPos = -1; - - for (int i = 0; i < keyCount && currentLine < keyCount; i++) - { - oneCharText[0] = text[i]; - textWidth += (GetTextWidth(oneCharText) + GuiGetStyle(DEFAULT, TEXT_SPACING)); - - if (text[i] == ' ' || text[i] == '\n') lastBreakingPos = i; - - if ( text[i] == '\n' || textWidth >= textAreaBounds.width) - { - currentLine++; - textWidth = 0; - - if (lastBreakingPos > 0) i = lastBreakingPos; - else textWidth += (GetTextWidth(oneCharText) + GuiGetStyle(DEFAULT, TEXT_SPACING)); - - lastBreakingPos = -1; - } - } - - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING) + textWidth - GuiGetStyle(DEFAULT, TEXT_SPACING); - cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING)/2 + ((GuiGetStyle(DEFAULT, TEXT_SIZE) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING))*currentLine); - // Exit edit mode if (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; } @@ -2331,8 +2282,6 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; } } - - if (pressed) framesCounter = 0; // Reset blinking cursor } //-------------------------------------------------------------------- @@ -2341,9 +2290,6 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) if (state == GUI_STATE_PRESSED) { GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)), guiAlpha)); - - // Draw blinking cursor - if (editMode && ((framesCounter/20)%2 == 0)) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); } else if (state == GUI_STATE_DISABLED) { @@ -2351,9 +2297,76 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) } else GuiDrawRectangle(bounds, 1, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), BLANK); - // TODO: Review to support boxed text with word-wrap - //DrawTextBoxed(guiFont, text, textAreaBounds, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING), true, Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); - DrawTextEx(guiFont, text, (Vector2){ textAreaBounds.x, textAreaBounds.y }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + int wrapMode = 1; // 0-No wrap, 1-Char wrap, 2-Word wrap + Vector2 cursorPos = { textAreaBounds.x, textAreaBounds.y }; + + //int lastSpacePos = 0; + //int lastSpaceWidth = 0; + //int lastSpaceCursorPos = 0; + + for (int i = 0, codepointLength = 0; text[i] != '\0'; i += codepointLength) + { + int codepoint = GetCodepoint(text + i, &codepointLength); + int index = GetGlyphIndex(guiFont, codepoint); // If requested codepoint is not found, we get '?' (0x3f) -> TODO: review that case! + Rectangle atlasRec = guiFont.recs[index]; + GlyphInfo glyphInfo = guiFont.chars[index]; // Glyph measures + + if ((codepointLength == 1) && (codepoint == '\n')) + { + cursorPos.y += (guiFont.baseSize*scaleFactor + GuiGetStyle(TEXTBOX, TEXT_LINES_PADDING)); // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + else + { + if (wrapMode == 1) + { + int glyphWidth = 0; + if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX; + else glyphWidth += (atlasRec.width + glyphInfo.offsetX); + + // Jump line if the end of the text box area has been reached + if ((cursorPos.x + (glyphWidth*scaleFactor)) > (textAreaBounds.x + textAreaBounds.width)) + { + cursorPos.y += (guiFont.baseSize*scaleFactor + GuiGetStyle(TEXTBOX, TEXT_LINES_PADDING)); // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + } + else if (wrapMode == 2) + { + /* + if ((codepointLength == 1) && (codepoint == ' ')) + { + lastSpacePos = i; + lastSpaceWidth = 0; + lastSpaceCursorPos = cursorPos.x; + } + + // Jump line if last word reaches end of text box area + if ((lastSpaceCursorPos + lastSpaceWidth) > (textAreaBounds.x + textAreaBounds.width)) + { + cursorPos.y += 12; // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + */ + } + + // Draw current character glyph + DrawTextCodepoint(guiFont, codepoint, cursorPos, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + + int glyphWidth = 0; + if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX; + else glyphWidth += (atlasRec.width + glyphInfo.offsetX); + + cursorPos.x += (glyphWidth*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + //if (i > lastSpacePos) lastSpaceWidth += (atlasRec.width + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + cursor.x = cursorPos.x; + cursor.y = cursorPos.y; + + // Draw cursor position considering text glyphs + if (editMode) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); //-------------------------------------------------------------------- return pressed;