diff --git a/examples/controls_test_suite/controls_test_suite.c b/examples/controls_test_suite/controls_test_suite.c index 739e8b1..e63b2b7 100644 --- a/examples/controls_test_suite/controls_test_suite.c +++ b/examples/controls_test_suite/controls_test_suite.c @@ -35,6 +35,9 @@ #include "raylib.h" +//#define RAYGUI_DEBUG_RECS_BOUNDS +//#define RAYGUI_DEBUG_TEXT_BOUNDS + #define RAYGUI_IMPLEMENTATION //#define RAYGUI_CUSTOM_ICONS // It requires providing gui_icons.h in the same directory //#include "gui_icons.h" // External icons data provided, it can be generated with rGuiIcons tool @@ -56,7 +59,7 @@ int main() { // Initialization //--------------------------------------------------------------------------------------- - const int screenWidth = 690; + const int screenWidth = 960; const int screenHeight = 560; InitWindow(screenWidth, screenHeight, "raygui - controls test suite"); @@ -79,6 +82,9 @@ int main() char textBoxText[64] = "Text box"; bool textBoxEditMode = false; + char textBoxMultiText[1024] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + bool textBoxMultiEditMode = false; + int listViewScrollIndex = 0; int listViewActive = -1; @@ -243,7 +249,7 @@ int main() GuiSlider((Rectangle){ 355, 400, 165, 20 }, "TEST", TextFormat("%2.2f", sliderValue), &sliderValue, -50, 100); GuiSliderBar((Rectangle){ 320, 430, 200, 20 }, NULL, TextFormat("%i", (int)sliderBarValue), &sliderBarValue, 0, 100); - GuiProgressBar((Rectangle){ 320, 460, 200, 20 }, NULL, TextFormat(" %i%%", (int)(progressValue*100)), &progressValue, 0.0f, 1.0f); + GuiProgressBar((Rectangle){ 320, 460, 200, 20 }, NULL, TextFormat("%i%%", (int)(progressValue*100)), &progressValue, 0.0f, 1.0f); GuiEnable(); // NOTE: View rectangle could be used to perform some scissor test @@ -251,12 +257,20 @@ int main() GuiScrollPanel((Rectangle){ 560, 25, 102, 354 }, NULL, (Rectangle){ 560, 25, 300, 1200 }, &viewScroll, &view); Vector2 mouseCell = { 0 }; - GuiGrid((Rectangle) { 560, 25 + 180 + 195, 100, 120 }, NULL, 20, 2, &mouseCell); + GuiGrid((Rectangle) { 560, 25 + 180 + 195, 100, 120 }, NULL, 20, 3, &mouseCell); - GuiStatusBar((Rectangle){ 0, (float)GetScreenHeight() - 20, (float)GetScreenWidth(), 20 }, "This is a status bar"); + //GuiStatusBar((Rectangle){ 0, (float)GetScreenHeight() - 20, (float)GetScreenWidth(), 20 }, "This is a status bar"); GuiColorBarAlpha((Rectangle){ 320, 490, 200, 30 }, NULL, &alphaValue); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT_VERTICAL, 1); // 0-CENTERED, 1-TOP, 2-BOTTOM (does not work as expected in case of vertical alignment) + GuiSetStyle(TEXTBOX, TEXT_MULTILINE, 1); // 0-OFF (single line), 1-OFF (multiple text lines supported) + GuiSetStyle(TEXTBOX, TEXT_WRAP_MODE, 2); // 0-NO_WRAP, 1-CHAR_WRAP, 2-WORD_WRAP (if wrap mode enabled, text editing is not supported) + if (GuiTextBox((Rectangle){ 678, 25, 258, 492 }, textBoxMultiText, 1024, textBoxMultiEditMode)) textBoxMultiEditMode = !textBoxMultiEditMode; + GuiSetStyle(TEXTBOX, TEXT_WRAP_MODE, 0); + GuiSetStyle(TEXTBOX, TEXT_MULTILINE, 0); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT_VERTICAL, 0); + if (showMessageBox) { DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(RAYWHITE, 0.8f)); @@ -269,7 +283,7 @@ int main() if (showTextInputBox) { DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(RAYWHITE, 0.8f)); - int result = GuiTextInputBox((Rectangle){ (float)GetScreenWidth()/2 - 120, (float)GetScreenHeight()/2 - 60, 240, 140 }, "Save", GuiIconText(ICON_FILE_SAVE, "Save file as..."), "Ok;Cancel", textInput, 255, NULL); + int result = GuiTextInputBox((Rectangle){ (float)GetScreenWidth()/2 - 120, (float)GetScreenHeight()/2 - 60, 240, 140 }, GuiIconText(ICON_FILE_SAVE, "Save file as..."), "Introduce output file name:", "Ok;Cancel", textInput, 255, NULL); if (result == 1) { diff --git a/src/raygui.h b/src/raygui.h index a9d5d96..3d61be0 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -132,6 +132,9 @@ * Includes custom ricons.h header defining a set of custom icons, * this file can be generated using rGuiIcons tool * +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* * #define RAYGUI_DEBUG_TEXT_BOUNDS * Draw text bounds rectangles for debug * @@ -1380,7 +1383,7 @@ static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position -static Color GuiFade(Color color, float alpha); +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor //---------------------------------------------------------------------------------- // Gui Setup Functions Definition @@ -2330,6 +2333,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) int alignmentVertical = GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT_VERTICAL); int multiline = GuiGetStyle(TEXTBOX, TEXT_MULTILINE); + int wrapMode = GuiGetStyle(TEXTBOX, TEXT_WRAP_MODE); // Cursor rectangle // NOTE: Position X value should be updated @@ -2366,7 +2370,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging && (GuiGetStyle(TEXTBOX, TEXT_READONLY) == 0)) + // WARNING: If wrapMode is enabled, text editing is not supported + if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging && (GuiGetStyle(TEXTBOX, TEXT_READONLY) == 0) && (wrapMode == 0)) { Vector2 mousePoint = GetMousePosition(); @@ -3725,8 +3730,9 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect Vector2 mousePoint = GetMousePosition(); Vector2 currentMouseCell = { 0 }; - int linesV = ((int)(bounds.width/spacing))*subdivs + subdivs; - int linesH = ((int)(bounds.height/spacing))*subdivs + subdivs; + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; // Update control //-------------------------------------------------------------------- @@ -3930,6 +3936,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(BUTTON, BORDER_WIDTH, 2); GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); @@ -4513,6 +4520,35 @@ const char **GetTextLines(const char *text, int *count) return lines; } +// Get text width to next space for provided string +static int GetNextSpaceWidth(const char *text, int nextSpaceIndex) +{ + int width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + nextSpaceIndex = i; + break; + } + } + + return width; +} + // Gui draw text using default font static void GuiDrawText(const char *text, Rectangle bounds, int alignment, Color tint) { @@ -4522,267 +4558,171 @@ static void GuiDrawText(const char *text, Rectangle bounds, int alignment, Color #define ICON_TEXT_PADDING 4 #endif - int wrapMode = GuiGetStyle(TEXTBOX, TEXT_WRAP_MODE); + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // TextBox specific variables + int wrapMode = GuiGetStyle(TEXTBOX, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing int alignmentVertical = GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT_VERTICAL); - // We process the text lines one by one - if ((text != NULL) && (text[0] != '\0')) + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) { - // Get text lines ('\n' delimiter) to process lines individually - // NOTE: We can't use GuiTextSplit() because it can be already used before calling - // GuiDrawText() and static buffer would be overriden :( - int lineCount = 0; - const char **lines = GetTextLines(text, &lineCount); + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor - //Rectangle textBounds = GetTextBounds(LABEL, bounds); - float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); - float posOffsetY = 0; + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 boundsPos = { bounds.x, bounds.y }; - for (int i = 0; i < lineCount; i++) + // NOTE: We get text size after icon has been processed + // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) { - int iconId = 0; - lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; - // Get text position depending on alignment and iconId - //--------------------------------------------------------------------------------- - Vector2 boundsPos = { bounds.x, bounds.y }; - - // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); - - // If text requires an icon, add size to measure - if (iconId >= 0) - { - textSizeX += RAYGUI_ICON_SIZE*guiIconScale; - - // WARNING: If only icon provided, text could be pointing to EOF character: '\0' + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' #if !defined(RAYGUI_NO_ICONS) - if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; #endif - } - - // Check guiTextAlign global variables - switch (alignment) - { - case TEXT_ALIGN_LEFT: boundsPos.x = bounds.x; break; - case TEXT_ALIGN_CENTER: boundsPos.x = bounds.x + bounds.width/2 - textSizeX/2; break; - case TEXT_ALIGN_RIGHT: boundsPos.x = bounds.x + bounds.width - textSizeX; break; - default: break; - } - - switch (alignmentVertical) - { - case 0: boundsPos.y = bounds.y + posOffsetY + bounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); break; // CENTERED - case 1: boundsPos.y = bounds.y + posOffsetY; break; // UP - case 2: boundsPos.y = bounds.y + posOffsetY + bounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); break; // DOWN - default: break; - } - - // NOTE: Make sure we get pixel-perfect coordinates, - // In case of decimals we got weird text positioning - boundsPos.x = (float)((int)boundsPos.x); - boundsPos.y = (float)((int)boundsPos.y); - //--------------------------------------------------------------------------------- - - // Draw text (with icon if available) - //--------------------------------------------------------------------------------- -#if !defined(RAYGUI_NO_ICONS) - if (iconId >= 0) - { - // NOTE: We consider icon height, probably different than text size - GuiDrawIcon(iconId, (int)boundsPos.x, (int)(bounds.y + bounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height)), guiIconScale, tint); - boundsPos.x += (RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); - } -#endif - // Get size in bytes of text, - // considering end of line and line break - int lineSize = 0; - for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n'); c++, lineSize++){ } - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; - - /* - // TODO: WARNING: For wordwrap, text must be measured from space to space before being drawn! - int wrapModeState = 0; // 0-TEXT_MEASURING, 1-TEXT_DRAWING - - float textOffsetY = 0.0f; // Offset between wordwrap lines - float textOffsetX = 0.0f; // Offset X to next character to draw - - 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 < lineSize; 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(guiFont, 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 = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; - - if (i + 1 < lineSize) glyphWidth = glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_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 (wrapModeState == 0) // TEXT_MEASURING - { - // 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) > bounds.width) - { - endLine = (endLine < 1)? i : endLine; - if (i == endLine) endLine -= codepointByteCount; - if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount); - - wrapModeState = !wrapModeState; - } - else if ((i + 1) == lineSize) - { - endLine = i; - wrapModeState = !wrapModeState; - } - //else if (codepoint == '\n') state = !state; - - if (wrapModeState == 1) // TEXT_DRAWING - { - 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 (wrapMode != 2) // WORD_WRAP - { - textOffsetY += (guiFont.baseSize + guiFont.baseSize/2)*scaleFactor; - textOffsetX = 0; - } - } - else - { - if ((wrapMode == 1) && ((textOffsetX + glyphWidth) > bounds.width)) // CHAR_WRAP - { - textOffsetY += (guiFont.baseSize + guiFont.baseSize/2)*scaleFactor; - textOffsetX = 0; - } - - // When text overflows rectangle height limit, just stop drawing - if ((textOffsetY + guiFont.baseSize*scaleFactor) > bounds.height) break; - - // Draw text selected 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(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ boundsPos.x + textOffsetX, boundsPos.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), tint); // isGlyphSelected? selectTint : tint); - } - } - - if ((wrapMode == 2) && (i == endLine)) // WORD_WRAP - { - textOffsetY += (guiFont.baseSize + guiFont.baseSize/2)*scaleFactor; - textOffsetX = 0; - startLine = endLine; - endLine = -1; - glyphWidth = 0; - //selectStart += lastk - k; - k = lastk; - - wrapModeState = !wrapModeState; - } - } - - if ((textOffsetX != 0) || (codepoint != ' ')) textOffsetX += glyphWidth; // Avoid leading spaces - } - */ - - int lastSpacePos = 0; - int textOffsetY = 0; - float textOffsetX = 0.0f; - for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) - { - int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); - int index = GetGlyphIndex(guiFont, 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) codepointSize = 1; - - if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint - else - { - if ((codepoint != ' ') && (codepoint != '\t')) - { - if (wrapMode == 0) // 0-NO_WRAP - { - // Draw only required text glyphs fitting the bounds.width - if (textOffsetX < (bounds.width - guiFont.recs[index].width)) - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ boundsPos.x + textOffsetX, boundsPos.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - else if (wrapMode == 1) // 1-CHAR_WRAP - { - // Get glyph width to check if it goes out of bounds - float glyphWidth = 0; - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - - // Jump to next line if current character reach end of the box limits - if ((boundsPos.x + textOffsetX + glyphWidth) > (bounds.x + bounds.width)) - { - textOffsetX = 0.0f; - textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ boundsPos.x + textOffsetX, boundsPos.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - else if (wrapMode == 2) // 2-WORD_WRAP - { - // TODO: Word wrap mode requires previously measured text to last space! - } - } - else lastSpacePos = c; - - if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - //--------------------------------------------------------------------------------- } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: boundsPos.x = bounds.x; break; + case TEXT_ALIGN_CENTER: boundsPos.x = bounds.x + bounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: boundsPos.x = bounds.x + bounds.width - textSizeX; break; + default: break; + } + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case 0: boundsPos.y = bounds.y + posOffsetY + bounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); break; // CENTERED + case 1: boundsPos.y = bounds.y + posOffsetY; break; // UP + case 2: boundsPos.y = bounds.y + posOffsetY + bounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); break; // DOWN + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + boundsPos.x = (float)((int)boundsPos.x); + boundsPos.y = (float)((int)boundsPos.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)boundsPos.x, (int)(bounds.y + bounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height)), guiIconScale, tint); + boundsPos.x += (RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, 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) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Wrap mode text measuring to space to validate if it can be drawn or + // a new line is required + if (wrapMode == 1) + { + // Get glyph width to check if it goes out of bounds + float glyphWidth = 0; + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > bounds.width) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + else if (wrapMode == 2) // 2-WORD_WRAP + { + // Get width to next space in line + int nextSpaceIndex = 0; + int nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + if ((textOffsetX + nextSpaceWidth) > bounds.width) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + + // TODO: Consider case: (nextSpaceWidth >= bounds.width) + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == 0) // 0-NO_WRAP + { + // Draw only required text glyphs fitting the bounds.width + if (textOffsetX < (bounds.width - guiFont.recs[index].width)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ boundsPos.x + textOffsetX, boundsPos.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if (wrapMode >= 1) // 2-WORD_WRAP + { + // Draw only glyphs inside the bounds + if ((boundsPos.y + textOffsetY) <= (bounds.y + bounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ boundsPos.x + textOffsetX, boundsPos.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == 0) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if (wrapMode >= 1) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- } + #if defined(RAYGUI_DEBUG_TEXT_BOUNDS) - GuiDrawRectangle(bounds, 0, WHITE, Fade(RED, 0.4f)); + GuiDrawRectangle(bounds, 0, WHITE, Fade(BLUE, 0.4f)); #endif } @@ -4803,6 +4743,10 @@ static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif } // Draw tooltip using control bounds