Compare commits

3 Commits

Author SHA1 Message Date
Ray
efee2f5dcf Some cleaning 2026-06-17 13:01:30 +02:00
Ray
f65a824eb3 Update raygui.h 2026-06-17 12:13:39 +02:00
9248c0a8b2 Fix GuiGetTextWidth() to measure multi-line text correctly (#540)
Resolves #415.

GuiGetTextWidth() stopped at the first '\n', so auto-sized widgets
(GuiLabel, GuiMessageBox, ...) clipped multi-line text.

Factor the existing single-line measurement into a new static
GetLineWidth() helper and turn GuiGetTextWidth() into a wrapper that
iterates '\n'-separated lines and returns the maximum line width.

GuiDrawText() now calls GetLineWidth() directly for per-line text
positioning, so its existing per-line alignment behavior is preserved
byte-for-byte (the icon is already stripped by GetTextIcon() before
measurement, so GetLineWidth() takes no icon path there).

Incidental corrections in GetLineWidth() vs. the old GuiGetTextWidth()
body, to bring it into agreement with GetTextIcon():

- The icon-marker skip now uses text += (pos + 1) to land past the
  closing '#', matching GetTextIcon(). Previously it was
  text += textIconOffset where textIconOffset was the index OF the
  closing '#', leaving that '#' in the measured string and
  over-measuring icon-prefixed labels by ~1 glyph.

- The icon marker is recognised only as '#' + 1..3 digits + '#',
  matching GetTextIcon(). The old detector accepted any character
  between the hashes, so e.g. "#abc#Text" was misclassified as an
  icon and the label width was computed incorrectly.

Co-authored-by: Ray <raysan5@gmail.com>
2026-06-17 12:13:03 +02:00

View File

@ -1549,7 +1549,6 @@ static Color GetColor(int hexValue); // Returns a Color struct fr
static int ColorToInt(Color color); // Returns hexadecimal value for a Color
static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle
static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed'
static char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings
static int TextToInteger(const char *text); // Get integer value from text
static float TextToFloat(const char *text); // Get float value from text
@ -1810,7 +1809,7 @@ int GuiTabBar(Rectangle bounds, char **text, int count, int *active)
//GuiState state = guiState;
int tabItemsWidth = GuiGetStyle(TABBAR, TAB_ITEMS_WIDTH);
Rectangle tabBounds = { bounds.x, bounds.y, tabItemsWidth, bounds.height };
Rectangle tabBounds = { bounds.x, bounds.y, (float)tabItemsWidth, bounds.height };
if (*active < 0) *active = 0;
else if (*active > count - 1) *active = count - 1;
@ -2192,7 +2191,7 @@ int GuiToggleGroup(Rectangle bounds, const char *text, int *active)
bool toggle = false; // Required for individual toggles
char *textPtr = text;
const char *textPtr = text;
bool itemReady = false;
float initBoundsX = bounds.x;
float initBoundsY = bounds.y;
@ -3119,7 +3118,6 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int
}
// Value Box control, updates input text with numbers
// NOTE: Requires static variables: frameCounter
int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
{
#if !defined(RAYGUI_VALUEBOX_MAX_CHARS)
@ -3260,7 +3258,6 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in
}
// Floating point Value Box control, updates input val_str with numbers
// NOTE: Requires static variables: frameCounter
int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode)
{
#if !defined(RAYGUI_VALUEBOX_MAX_CHARS)
@ -4947,7 +4944,7 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName)
{
fileData = (unsigned char *)RL_CALLOC(size, sizeof(unsigned char));
// WARNING: File can be partially loaded but ignoring it for simplicity
dataSize = fread(fileData, sizeof(unsigned char), size, rgiFile);
dataSize = (int)fread(fileData, sizeof(unsigned char), size, rgiFile);
guiIconsName = GuiLoadIconsFromMemory(fileData, dataSize, loadIconsName);
}
@ -5036,10 +5033,11 @@ void GuiSetIconScale(int scale)
if (scale >= 1) guiIconScale = scale;
}
#endif // !RAYGUI_NO_ICONS
// Get text width considering gui style and icon size (if required)
int GuiGetTextWidth(const char *text)
// Get the width of a single line of gui text (stops at '\n' or '\0'),
// considering the gui font/style and an optional icon marker '#NNN#' at
// the start. Icon detection matches GetTextIcon(): 1..3 digits, skip past
// the closing '#'.
static int GetLineWidth(const char *text)
{
#if !defined(ICON_TEXT_PADDING)
#define ICON_TEXT_PADDING 4
@ -5050,16 +5048,12 @@ int GuiGetTextWidth(const char *text)
if ((text != NULL) && (text[0] != '\0'))
{
// Icon marker: '#' + 1..3 digits + '#' (matches GetTextIcon())
if (text[0] == '#')
{
for (int i = 1; (i < 5) && (text[i] != '\0'); i++)
{
if (text[i] == '#')
{
if (TextToInteger(&text[1]) < RAYGUI_ICON_MAX_ICONS) textIconOffset = i;
break;
}
}
int pos = 1;
while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) pos++;
if (text[pos] == '#') textIconOffset = pos + 1;
}
text += textIconOffset;
@ -5067,10 +5061,10 @@ int GuiGetTextWidth(const char *text)
// Make sure guiFont is set, GuiGetStyle() initializes it lazynessly
float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
// Custom MeasureText() implementation
// Custom MeasureText() implementation -- single line only
if ((guiFont.texture.id > 0) && (text != NULL))
{
// Get size in bytes of text, considering end of line and line break
// Get size in bytes of the line, considering end of line and line break
int size = 0;
for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++)
{
@ -5100,6 +5094,34 @@ int GuiGetTextWidth(const char *text)
return (int)textSize.x;
}
// Get text width considering gui style and icon size (if required).
// For multi-line text (containing '\n'), returns the width of the widest line.
int GuiGetTextWidth(const char *text)
{
if (text == NULL) return 0;
int maxWidth = 0;
const char *linePtr = text;
while ((linePtr[0] != '\0') && ((linePtr - text) < MAX_LINE_BUFFER_SIZE))
{
int lineWidth = GetLineWidth(linePtr);
if (lineWidth > maxWidth) maxWidth = lineWidth;
// Skip to the next '\n' (or end of string/buffer)
while ((linePtr[0] != '\0') && (linePtr[0] != '\n') && ((linePtr - text) < MAX_LINE_BUFFER_SIZE))
{
linePtr++;
}
// Advance past the '\n' delimiter to the start of the next line
if (linePtr[0] == '\n') linePtr++;
}
return maxWidth;
}
#endif // !RAYGUI_NO_ICONS
//----------------------------------------------------------------------------------
// Module Internal Functions Definition
//----------------------------------------------------------------------------------
@ -5269,9 +5291,9 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C
Vector2 textBoundsPosition = { textBounds.x, textBounds.y };
float textBoundsWidthOffset = 0.0f;
// NOTE: Get text size after icon has been processed
// WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed?
int textSizeX = GuiGetTextWidth(lines[i]);
// NOTE: Icon was already stripped above by GetTextIcon(); GetLineWidth()
// takes no icon path here and returns only the glyph width of this line.
int textSizeX = GetLineWidth(lines[i]);
// If text requires an icon, add size to measure
if (iconId >= 0)
@ -5515,7 +5537,7 @@ static char **GuiTextSplit(const char *text, char delimiter, int *count)
#define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024
#endif
static char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data)
static char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { 0 }; // String pointers array (points to buffer data)
static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added)
memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE);
@ -5904,53 +5926,6 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co
DrawRectangleGradientEx(bounds, color1, color2, color2, color1);
}
// Split string into multiple strings
char **TextSplit(const char *text, char delimiter, int *count)
{
// NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter)
// inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated,
// all used memory is static... it has some limitations:
// 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS
// 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE
#if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS)
#define RAYGUI_TEXTSPLIT_MAX_ITEMS 128
#endif
#if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE)
#define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024
#endif
static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL };
static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 };
memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE);
result[0] = buffer;
int counter = 0;
if (text != NULL)
{
counter = 1;
// Count how many substrings text contains and point to every one of them
for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++)
{
buffer[i] = text[i];
if (buffer[i] == '\0') break;
else if (buffer[i] == delimiter)
{
buffer[i] = '\0'; // Set an end of string at this point
result[counter] = buffer + i + 1;
counter++;
if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break;
}
}
}
*count = counter;
return result;
}
// Get integer value from text
// NOTE: This function replaces atoi() [stdlib.h]
static int TextToInteger(const char *text)