diff --git a/README.md b/README.md index fba7c6c..07e800c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Styles can be loaded at runtime using raygui `GuiLoadStyle()` function. Simple a ## raygui icons -`raygui` supports custom icons provided as an external array of data. To support icons just define `RAYGUI_SUPPORT_ICONS` before including `raygui`. +`raygui` supports custom icons provided as an external array of data. To support icons just define `RAYGUI_SUPPORT_RICONS` before including `raygui`. A set of custom handcrafted icons is provided in [`ricons`](src/ricons.h). This set of icons can be created and customized using [rGuiIcons](https://raylibtech.itch.io/rguiicons) tool. @@ -55,7 +55,7 @@ A set of custom handcrafted icons is provided in [`ricons`](src/ricons.h). This ```c #define RAYGUI_IMPLEMENTATION -#define RAYGUI_SUPPORT_ICONS +#define RAYGUI_SUPPORT_RICONS #include "raygui.h" ``` To use any of those icons in your gui, just preprend *iconId* to any text written within `raygui` controls: @@ -71,7 +71,7 @@ if (GuiButton(rec, GuiIconText(RICON_FILE_OPEN, "Open Image"))) { /* ACTION */ } `raygui` is intended to be used as a portable library to be integrated in code form into the target project but some users could require a shared/dynamic version of the library, for example, to create bindings. In that case, `raygui` can be built as a shared library using: ``` -mv src/raygui.h src/raygui.c && gcc -shared -fpic -DRAYGUI_SUPPORT_ICONS -DRAYGUI_IMPLEMENTATION -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 src/raygui.c -o raygui.so +mv src/raygui.h src/raygui.c && gcc -shared -fpic -DRAYGUI_SUPPORT_RICONS -DRAYGUI_IMPLEMENTATION -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 src/raygui.c -o raygui.so ``` license diff --git a/examples/custom_file_dialog/custom_file_dialog.c b/examples/custom_file_dialog/custom_file_dialog.c index 5f8f48f..869c596 100644 --- a/examples/custom_file_dialog/custom_file_dialog.c +++ b/examples/custom_file_dialog/custom_file_dialog.c @@ -18,7 +18,7 @@ #include "raylib.h" #define RAYGUI_IMPLEMENTATION -#define RAYGUI_SUPPORT_ICONS +#define RAYGUI_SUPPORT_RICONS #include "../../src/raygui.h" #undef RAYGUI_IMPLEMENTATION // Avoid including raygui implementation again diff --git a/examples/portable_window/portable_window.c b/examples/portable_window/portable_window.c index 4127c18..b69655a 100644 --- a/examples/portable_window/portable_window.c +++ b/examples/portable_window/portable_window.c @@ -18,7 +18,7 @@ #include "raylib.h" #define RAYGUI_IMPLEMENTATION -#define RAYGUI_SUPPORT_ICONS +#define RAYGUI_SUPPORT_RICONS #include "../../src/raygui.h" //------------------------------------------------------------------------------------ diff --git a/examples/property_list/dm_property_list.h b/examples/property_list/dm_property_list.h index 81b13e5..d126d25 100644 --- a/examples/property_list/dm_property_list.h +++ b/examples/property_list/dm_property_list.h @@ -398,7 +398,7 @@ double GuiDMSpinner(Rectangle bounds, double value, double minValue, double maxV GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); -#if defined(RAYGUI_SUPPORT_ICONS) +#if defined(RAYGUI_SUPPORT_RICONS) if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) value -= step; if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) value += step; #else @@ -417,7 +417,7 @@ double GuiDMSpinner(Rectangle bounds, double value, double minValue, double maxV void GuiDMPropertyList(Rectangle bounds, GuiDMProperty* props, int count, int* focus, int* scrollIndex) { - #ifdef RAYGUI_SUPPORT_ICONS + #ifdef RAYGUI_SUPPORT_RICONS #define PROPERTY_COLLAPSED_ICON "#120#" #define PROPERTY_EXPANDED_ICON "#121#" #else diff --git a/examples/property_list/property_list.c b/examples/property_list/property_list.c index ddb5506..1854075 100644 --- a/examples/property_list/property_list.c +++ b/examples/property_list/property_list.c @@ -18,7 +18,7 @@ #include "raylib.h" #define RAYGUI_IMPLEMENTATION -#define RAYGUI_SUPPORT_ICONS +#define RAYGUI_SUPPORT_RICONS #include "../../src/raygui.h" #undef RAYGUI_IMPLEMENTATION // Avoid including raygui implementation again diff --git a/examples/text_editor/text_editor.c b/examples/text_editor/text_editor.c index 6bde712..e697bba 100644 --- a/examples/text_editor/text_editor.c +++ b/examples/text_editor/text_editor.c @@ -21,7 +21,7 @@ #include "raylib.h" #define RAYGUI_IMPLEMENTATION -#define RAYGUI_SUPPORT_ICONS +#define RAYGUI_SUPPORT_RICONS #include "../../src/raygui.h" @@ -131,7 +131,7 @@ bool GuiTextEditor(Rectangle bounds, char *text, int textSize, bool editMode) bool textWrap = true; // TODO: Word-Wrap vs Char-Wrap -> textWrapMode { NO_WRAP_LOCK, NO_WRAP_OVERFLOW, CHAR_WRAP, WORD_WRAP } // WARNING: First string full traversal - int codepointCount = GetCodepointsCount(text); + int codepointCount = GetCodepointCount(text); int textLen = strlen(text); // Text length in bytes @@ -214,8 +214,8 @@ bool GuiTextEditor(Rectangle bounds, char *text, int textSize, bool editMode) int codepoint = GetCodepoint(&text[i], &codepointByteCount); int index = GetGlyphIndex(font, codepoint); - Rectangle rec = { bounds.x + textOffsetX + font.chars[index].offsetX*scaleFactor, - bounds.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + Rectangle rec = { bounds.x + textOffsetX + font.glyphs[index].offsetX*scaleFactor, + bounds.y + textOffsetY + font.glyphs[index].offsetY*scaleFactor, font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor }; // Automatic line break to wrap text inside box @@ -225,8 +225,8 @@ bool GuiTextEditor(Rectangle bounds, char *text, int textSize, bool editMode) textOffsetX = 0.0f; // Recalculate drawing rectangle position - rec = (Rectangle){ bounds.x + textOffsetX + font.chars[index].offsetX*scaleFactor, - bounds.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + rec = (Rectangle){ bounds.x + textOffsetX + font.glyphs[index].offsetX*scaleFactor, + bounds.y + textOffsetY + font.glyphs[index].offsetY*scaleFactor, font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor }; } @@ -280,8 +280,8 @@ bool GuiTextEditor(Rectangle bounds, char *text, int textSize, bool editMode) // TODO: Consider spacing when drawing selected characters background if (editMode && (selectStartCp != -1) && ((cp >= selectStartCp) && (cp <= (selectStartCp + selectLengthCp)))) DrawRectangleRec(rec, MAROON); - if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); - else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); i += (codepointByteCount - 1); // Move text bytes counter to next codepoint cp++; diff --git a/src/gui_textbox_extended.h b/src/gui_textbox_extended.h index 7a36413..ff17de4 100644 --- a/src/gui_textbox_extended.h +++ b/src/gui_textbox_extended.h @@ -31,7 +31,6 @@ #ifndef GUI_TEXTBOX_EXTENDED_H #define GUI_TEXTBOX_EXTENDED_H - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -78,6 +77,10 @@ RAYGUIDEF int GuiTextBoxGetByteIndex(const char *text, int start, int from, int RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool editMode); +RAYGUIDEF 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 +RAYGUIDEF 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 +RAYGUIDEF 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 @@ -96,6 +99,146 @@ RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool edi #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 //---------------------------------------------------------------------------------- @@ -178,9 +321,9 @@ RAYGUIDEF void GuiTextBoxSetSelection(int start, int length) RAYGUIDEF 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){ guiTextBoxState.select, guiTextBoxState.cursor - guiTextBoxState.select }; + else if (guiTextBoxState.cursor > guiTextBoxState.select) return RAYGUI_CLITERAL(Vector2){ (float)guiTextBoxState.select, (float)guiTextBoxState.cursor - guiTextBoxState.select }; - return RAYGUI_CLITERAL(Vector2){ guiTextBoxState.cursor, guiTextBoxState.select - guiTextBoxState.cursor }; + 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 @@ -436,7 +579,7 @@ RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool edi 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)), - GuiGetStyle(DEFAULT, TEXT_SIZE) }; + (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 @@ -806,7 +949,7 @@ RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool edi if (pressed) framesCounter = 0; } - + // Draw control //-------------------------------------------------------------------- DrawRectangleLinesEx(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha)); @@ -816,9 +959,9 @@ RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool edi 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/TEXTEDIT_CURSOR_BLINK_FRAMES)%2 == 0) && selLength == 0) + if (editMode && active && ((framesCounter/30)%2 == 0) && selLength == 0) { - DrawRectangle(cursorPos.x, cursorPos.y, 1, GuiGetStyle(DEFAULT, TEXT_SIZE)*2, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + DrawRectangle(cursorPos.x, cursorPos.y-1, 1, GuiGetStyle(DEFAULT, TEXT_SIZE)+2, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); } } else if (state == GUI_STATE_DISABLED) @@ -827,8 +970,8 @@ RAYGUIDEF bool GuiTextBoxEx(Rectangle bounds, char *text, int textSize, bool edi } // Finally draw the text and selection - DrawTextBoxedSelectable(guiFont, &text[textStartIndex], textRec, GuiGetStyle(DEFAULT, TEXT_SIZE), GuiGetStyle(DEFAULT, TEXT_SPACING), false, Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha), selStart, selLength, GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_FG)), GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_BG))); - + DrawTextRecEx(guiFont, &text[textStartIndex], textRec, GuiGetStyle(DEFAULT, TEXT_SIZE), GuiGetStyle(DEFAULT, TEXT_SPACING), false, Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha), selStart, selLength, GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_FG)), GetColor(GuiGetStyle(TEXTBOX, COLOR_SELECTED_BG))); + return pressed; } @@ -904,9 +1047,9 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p if (letter != '\n') { - glyphWidth = (font.chars[index].advanceX == 0)? + glyphWidth = (font.glyphs[index].advanceX == 0)? (int)(font.recs[index].width*scaleFactor + spacing): - (int)(font.chars[index].advanceX*scaleFactor + spacing); + (int)(font.glyphs[index].advanceX*scaleFactor + spacing); if ((textOffsetX + glyphWidth + 1) >= rec.width) break; @@ -914,7 +1057,7 @@ static int GuiMeasureTextBox(const char *text, int length, Rectangle rec, int *p else if (mode == GUI_MEASURE_MODE_CURSOR_COORDS) { // Check if the mouse pointer is inside the glyph rect - Rectangle grec = {rec.x + textOffsetX - 1, rec.y, glyphWidth, (font.baseSize + font.baseSize/2)*scaleFactor - 1 }; + Rectangle grec = {rec.x + textOffsetX - 1, rec.y, (float)glyphWidth, (font.baseSize + font.baseSize/2)*scaleFactor - 1 }; Vector2 mouse = GetMousePosition(); if (CheckCollisionPointRec(mouse, grec)) @@ -970,9 +1113,9 @@ static int GuiMeasureTextBoxRev(const char *text, int length, Rectangle rec, int if (letter != '\n') { - glyphWidth = (font.chars[index].advanceX == 0)? + glyphWidth = (font.glyphs[index].advanceX == 0)? (int)(font.recs[index].width*scaleFactor + spacing): - (int)(font.chars[index].advanceX*scaleFactor + spacing); + (int)(font.glyphs[index].advanceX*scaleFactor + spacing); if ((textOffsetX + glyphWidth + 1) >= rec.width) break; }