From 0619571149f1fde5500dec4b64a94541ef0981f2 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 29 Dec 2018 14:44:28 +0100 Subject: [PATCH] ADDED: DrawTextRec() and example --- examples/text/text_draw_inside_rectangle.c | 120 ++++++++++++++++++ examples/text/text_draw_inside_rectangle.png | Bin 0 -> 17844 bytes src/raylib.h | 3 +- src/text.c | 125 +++++++++++++++++++ 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 examples/text/text_draw_inside_rectangle.c create mode 100644 examples/text/text_draw_inside_rectangle.png diff --git a/examples/text/text_draw_inside_rectangle.c b/examples/text/text_draw_inside_rectangle.c new file mode 100644 index 000000000..e60fa5e56 --- /dev/null +++ b/examples/text/text_draw_inside_rectangle.c @@ -0,0 +1,120 @@ +/******************************************************************************************* +* +* raylib [text] example - Draw text inside a rectangle +* +* This example has been created using raylib 2.3 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2018 Vlad Adrian (@demizdor) +* +********************************************************************************************/ + +#include "raylib.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [text] example - draw text inside a rectangle"); + + char text[] = "Text cannot escape\tthis container\t...word wrap also works when active so here's\ + a long text for testing.\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ + tempor incididunt ut labore et dolore magna aliqua. Nec ullamcorper sit amet risus nullam eget felis eget."; + + bool resizing = false; + bool wordWrap = true; + + Rectangle container = { 25, 25, screenWidth - 50, screenHeight - 250}; + Rectangle resizer = { container.x + container.width - 17, container.y + container.height - 17, 14, 14 }; + + // Minimum width and heigh for the container rectangle + const int minWidth = 60; + const int minHeight = 60; + const int maxWidth = screenWidth - 50; + const int maxHeight = screenHeight - 160; + + Vector2 lastMouse = { 0, 0 }; // Stores last mouse coordinates + + Color borderColor = MAROON; // Container border color + + Font font = GetFontDefault(); // Get default system font + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed(KEY_SPACE)) wordWrap = !wordWrap; + + Vector2 mouse = GetMousePosition(); + + // Check if the mouse is inside the container and toggle border color + if (CheckCollisionPointRec(mouse, container)) borderColor = Fade(MAROON, 0.4f); + else if (!resizing) borderColor = MAROON; + + // Container resizing logic + if (resizing) + { + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) resizing = false; + + int width = container.width + (mouse.x - lastMouse.x); + container.width = (width > minWidth)? ((width < maxWidth)? width : maxWidth) : minWidth; + + int height = container.height + (mouse.y - lastMouse.y); + container.height = (height > minHeight)? ((height < maxHeight)? height : maxHeight) : minHeight; + } + else + { + // Check if we're resizing + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(mouse, resizer)) resizing = true; + } + + // Move resizer rectangle properly + resizer.x = container.x + container.width - 17; + resizer.y = container.y + container.height - 17; + + lastMouse = mouse; // Update mouse + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawRectangleLinesEx(container, 3, borderColor); // Draw container border + + // Draw text in container (add some padding) + DrawTextRec(font, text, + (Rectangle){ container.x + 4, container.y + 4, container.width - 4, container.height - 4 }, + 20.0f, 2.0f, wordWrap, GRAY); + + DrawRectangleRec(resizer, borderColor); // Draw the resize box + + // Draw info + DrawText("Word Wrap: ", 313, screenHeight-115, 20, BLACK); + if (wordWrap) DrawText("ON", 447, screenHeight - 115, 20, RED); + else DrawText("OFF", 447, screenHeight - 115, 20, BLACK); + DrawText("Press [SPACE] to toggle word wrap", 218, screenHeight - 91, 20, GRAY); + + DrawRectangle(0, screenHeight - 54, screenWidth, 54, GRAY); + DrawText("Click hold & drag the to resize the container", 155, screenHeight - 38, 20, RAYWHITE); + DrawRectangleRec((Rectangle){ 382, screenHeight - 34, 12, 12 }, MAROON); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/text/text_draw_inside_rectangle.png b/examples/text/text_draw_inside_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..f46b10967d74f6f4e4b194ba9aab1b65c4822c49 GIT binary patch literal 17844 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 Dhee32 literal 0 HcmV?d00001 diff --git a/src/raylib.h b/src/raylib.h index 8535f8d5c..0e9e0155e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1110,7 +1110,8 @@ RLAPI void UnloadFont(Font font); // Text drawing functions RLAPI void DrawFPS(int posX, int posY); // Shows current FPS RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits // Text misc. functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font diff --git a/src/text.c b/src/text.c index ebf2599ab..0b343af32 100644 --- a/src/text.c +++ b/src/text.c @@ -779,6 +779,131 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f } } +// Draw text using font inside rectangle limits +void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) +{ + int length = strlen(text); + int textOffsetX = 0; // Offset between characters + int textOffsetY = 0; // Required for line break! + float scaleFactor = 0.0f; + + unsigned char letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + enum { MEASURE_WORD = 0, DRAW_WORD = 1 }; + int state = wordWrap ? MEASURE_WORD : DRAW_WORD; + int lastTextOffsetX = 0; + int wordStart = 0; + + bool firstWord = true; + + for (int i = 0; i < length; i++) + { + int glyphWidth = 0; + letter = (unsigned char)text[i]; + + if (letter != '\n') + { + if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! + { + // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) + letter = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)letter); + i++; + } + else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! + { + // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) + letter = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)letter + 64); + i++; + } + else index = GetGlyphIndex(font, (unsigned char)text[i]); + + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.chars[index].rec.width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + } + + // NOTE: When word wrap is active first we measure a `word`(measure until a ' ','\n','\t' is found) + // then set all the variables back to what they were before the measurement, change the state to + // draw that word then change the state again and repeat until the end of the string...when the word + // doesn't fit inside the rect we simple increase `textOffsetY` to draw it on the next line + if (state == MEASURE_WORD) + { + // Measuring state + if ((letter == ' ') || (letter == '\n') || (letter == '\t') || ((i + 1) == length)) + { + int t = textOffsetX + glyphWidth; + + if (textOffsetX+1>=rec.width) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + lastTextOffsetX = t - lastTextOffsetX; + textOffsetX = 0; + } + else + { + textOffsetX = lastTextOffsetX; + lastTextOffsetX = t; + } + + glyphWidth = 0; + state = !state; // Change state + t = i; + i = firstWord?-1:wordStart; + wordStart = t; + } + } + else + { + // Drawing state + int t = textOffsetX + glyphWidth; + + if (letter == '\n') + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + lastTextOffsetX = t - lastTextOffsetX; + if (lastTextOffsetX < 0) lastTextOffsetX = 0; + textOffsetX = 0; + } + else if ((letter != ' ') && (letter != '\t')) + { + if ((t + 1) >= rec.width) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + + if ((textOffsetY + (int)((font.baseSize + font.baseSize/2)*scaleFactor)) > rec.height) break; + + DrawTexturePro(font.texture, font.chars[index].rec, + (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor, + rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + font.chars[index].rec.width*scaleFactor, + font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); + } + + if (wordWrap) + { + if ((letter == ' ') || (letter == '\n') || (letter == '\t')) + { + // After drawing a word change the state + firstWord = false; + i = wordStart; + textOffsetX = lastTextOffsetX; + glyphWidth = 0; + state = !state; + } + } + } + + textOffsetX += glyphWidth; + } +} + // Measure string width for default font int MeasureText(const char *text, int fontSize) {