From 24c00eab4ffda20422718f950a2ec4cccf764ee4 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:41:23 +0200 Subject: [PATCH 01/13] allow holding enter for multiline textbox --- src/raygui.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index 5e668967..36c15376 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2553,7 +2553,8 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } int codepoint = GetCharPressed(); // Get Unicode codepoint - if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + int enterPressed = IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER); + if (multiline && enterPressed) codepoint = (int)'\n'; // Encode codepoint as UTF-8 int codepointSize = 0; From 18d5225611317f90401f2d9225b91ec9d7495fa9 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:10:59 +0200 Subject: [PATCH 02/13] don't call IsKeyPressedRepeat if RAYGUI_STANDALONE --- src/raygui.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index 36c15376..e2827dd5 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2553,7 +2553,11 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } int codepoint = GetCharPressed(); // Get Unicode codepoint - int enterPressed = IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER); + int enterPressed = IsKeyPressed(KEY_ENTER); + #if !defined(RAYGUI_STANDALONE) + enterPressed |= IsKeyPressedRepeat(KEY_ENTER); + #endif + if (multiline && enterPressed) codepoint = (int)'\n'; // Encode codepoint as UTF-8 From 6b502a78b0ed5050a3c944ea4ae9a4e5e0ae170f Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:54:38 +0200 Subject: [PATCH 03/13] offset cursor for multiline text --- src/raygui.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index e2827dd5..194fd5bc 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2468,6 +2468,14 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod return result; // Mouse click: result = 1 } +int GetTextLines(char *text) { + int linesTotal = 0; + for (int i = 0; i < textBoxCursorIndex; i++) { + if (text[i]=='\n') linesTotal++; + } + return linesTotal; +} + // Text Box control // NOTE: Returns true on ENTER pressed (useful for data validation) int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) @@ -2707,7 +2715,10 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Recalculate cursor position.y depending on textBoxCursorIndex cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); - //if (multiline) cursor.y = GetTextLines() + if (multiline) { + cursor.y = GetTextLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); + } // Finish text editing on ENTER or mouse click outside bounds if ((!multiline && IsKeyPressed(KEY_ENTER)) || From 6ffeb786bccfced736702c5a37e2beb1ee0c593e Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:46:01 +0200 Subject: [PATCH 04/13] offset cursor by current line size --- src/raygui.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index 194fd5bc..174b09f3 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2714,7 +2714,16 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) else mouseCursor.x = -1; // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + int lineStart = textIndexOffset; + if (multiline) { + // calculate starting position of current line + lineStart = textBoxCursorIndex; + while (text[lineStart]!='\n'&&lineStart>=0) { + lineStart--; + } + lineStart++; + } + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { cursor.y = GetTextLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); From 1d1873c0b02d88ce6576fe82c0f5132d1490b614 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:00:57 +0200 Subject: [PATCH 05/13] adjust style --- src/raygui.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 174b09f3..75e9c4a0 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2468,7 +2468,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod return result; // Mouse click: result = 1 } -int GetTextLines(char *text) { +int GetTextLines(char *text) +{ int linesTotal = 0; for (int i = 0; i < textBoxCursorIndex; i++) { if (text[i]=='\n') linesTotal++; @@ -2715,16 +2716,19 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Recalculate cursor position.y depending on textBoxCursorIndex int lineStart = textIndexOffset; - if (multiline) { + if (multiline) + { // calculate starting position of current line lineStart = textBoxCursorIndex; - while (text[lineStart]!='\n'&&lineStart>=0) { + while (text[lineStart]!='\n'&&lineStart>=0) + { lineStart--; } lineStart++; } cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); - if (multiline) { + if (multiline) + { cursor.y = GetTextLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); } From 5fa231db438fb844ce1be831f5bc250a56651c7f Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:15:11 +0200 Subject: [PATCH 06/13] GetLineWidth and GetTextWidth fix --- src/raygui.h | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 75e9c4a0..c2c140e0 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -1484,6 +1484,7 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) static int GetTextWidth(const char *text); // Gui get text width using gui font and style +static int GetLineWidth(const char *text); // Gui get first line width using gui font and style static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor @@ -4697,8 +4698,8 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) } } -// Gui get text width considering icon -static int GetTextWidth(const char *text) +// Gui get line width considering icon +static int GetLineWidth(const char *text) { #if !defined(ICON_TEXT_PADDING) #define ICON_TEXT_PADDING 4 @@ -4759,6 +4760,22 @@ static int GetTextWidth(const char *text) return (int)textSize.x; } +// Get maximum text width from all lines +static int GetTextWidth(const char *text) +{ + int widestLine = GetLineWidth(text); + while (*text!='\0') + { + if (*text=='\n') + { + int thisLineWidth = GetLineWidth(text+1); + if (thisLineWidth>widestLine) widestLine = thisLineWidth; + } + text++; + } + return widestLine; +} + // Get text bounds considering control bounds static Rectangle GetTextBounds(int control, Rectangle bounds) { From 2d938fae6aa6ea2fdf5c7be9d4e0a764bccb7571 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:16:11 +0200 Subject: [PATCH 07/13] use GetLineWidth --- src/raygui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index c2c140e0..798f7288 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2727,7 +2727,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } lineStart++; } - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { cursor.y = GetTextLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); From eb75de155ae0c39ccf1f6bd4f186cd7a7e9bafb9 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:26:05 +0200 Subject: [PATCH 08/13] change function name to GetTextTotalLines --- src/raygui.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 798f7288..03ad7340 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2469,7 +2469,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod return result; // Mouse click: result = 1 } -int GetTextLines(char *text) +int GetTextTotalLines(char *text) { int linesTotal = 0; for (int i = 0; i < textBoxCursorIndex; i++) { @@ -2730,7 +2730,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { - cursor.y = GetTextLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y = GetTextTotalLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); } From a9e51ef20543c6cc45c01ae0afd422fffc642b8b Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:35:38 +0200 Subject: [PATCH 09/13] fix GetTextTotalLines and add limit --- src/raygui.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 03ad7340..b6e21dae 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2469,11 +2469,15 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod return result; // Mouse click: result = 1 } -int GetTextTotalLines(char *text) +// limit = -1 -> no limit +int GetTextTotalLines(char *text, signed int limit) { int linesTotal = 0; - for (int i = 0; i < textBoxCursorIndex; i++) { + while(*text!='\0' && limit!=0) + { if (text[i]=='\n') linesTotal++; + text++; + limit--; } return linesTotal; } @@ -2730,7 +2734,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { - cursor.y = GetTextTotalLines(text) * GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); } From 55f247d10f84e75da0e880e1f7057101a8d2308e Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:02:33 +0200 Subject: [PATCH 10/13] use line width instead of text width --- src/raygui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raygui.h b/src/raygui.h index b6e21dae..91b96645 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2731,7 +2731,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } lineStart++; } - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetLineWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); From 9f87b360288e70d6a335d249b633ab6174af3313 Mon Sep 17 00:00:00 2001 From: kotek900 <43883812+kotek900@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:33:44 +0200 Subject: [PATCH 11/13] fixed indentations --- src/raygui.h | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 91b96645..04923fa0 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -2472,14 +2472,14 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod // limit = -1 -> no limit int GetTextTotalLines(char *text, signed int limit) { - int linesTotal = 0; - while(*text!='\0' && limit!=0) - { - if (text[i]=='\n') linesTotal++; - text++; - limit--; - } - return linesTotal; + int linesTotal = 0; + while(*text!='\0' && limit!=0) + { + if (text[i]=='\n') linesTotal++; + text++; + limit--; + } + return linesTotal; } // Text Box control @@ -2568,8 +2568,8 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int codepoint = GetCharPressed(); // Get Unicode codepoint int enterPressed = IsKeyPressed(KEY_ENTER); - #if !defined(RAYGUI_STANDALONE) - enterPressed |= IsKeyPressedRepeat(KEY_ENTER); + #if !defined(RAYGUI_STANDALONE) + enterPressed |= IsKeyPressedRepeat(KEY_ENTER); #endif if (multiline && enterPressed) codepoint = (int)'\n'; @@ -2724,18 +2724,18 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (multiline) { // calculate starting position of current line - lineStart = textBoxCursorIndex; - while (text[lineStart]!='\n'&&lineStart>=0) + lineStart = textBoxCursorIndex; + while (text[lineStart]!='\n'&&lineStart>=0) { - lineStart--; - } - lineStart++; - } + lineStart--; + } + lineStart++; + } cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetLineWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); if (multiline) { - cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); - cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); } // Finish text editing on ENTER or mouse click outside bounds @@ -4767,17 +4767,17 @@ static int GetLineWidth(const char *text) // Get maximum text width from all lines static int GetTextWidth(const char *text) { - int widestLine = GetLineWidth(text); - while (*text!='\0') - { - if (*text=='\n') - { - int thisLineWidth = GetLineWidth(text+1); - if (thisLineWidth>widestLine) widestLine = thisLineWidth; - } - text++; - } - return widestLine; + int widestLine = GetLineWidth(text); + while (*text!='\0') + { + if (*text=='\n') + { + int thisLineWidth = GetLineWidth(text+1); + if (thisLineWidth>widestLine) widestLine = thisLineWidth; + } + text++; + } + return widestLine; } // Get text bounds considering control bounds From 7c132f33f20cda489dadf4c8656a60ee4a67cf60 Mon Sep 17 00:00:00 2001 From: mrkubax10 Date: Sat, 12 Oct 2024 16:03:37 +0200 Subject: [PATCH 12/13] Move code that can be reused to functions --- src/raygui.h | 1184 +++++++++++++++++++++++++------------------------- 1 file changed, 603 insertions(+), 581 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 04923fa0..4b031792 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -742,6 +742,7 @@ RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int min RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text +RAYGUIAPI int GuiTextBoxMultiline(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control @@ -1484,6 +1485,8 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) static int GetTextWidth(const char *text); // Gui get text width using gui font and style +static int GetLineStart(const char* text, int offset); +static int GetLineEnd(const char* text, int offset); static int GetLineWidth(const char *text); // Gui get first line width using gui font and style static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor @@ -1496,6 +1499,7 @@ static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static inline int GuiTextBoxInternal(Rectangle bounds, char *text, int textSize, bool multiline, bool editMode); // other GuiTextBox* functions call this static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor @@ -2475,288 +2479,196 @@ int GetTextTotalLines(char *text, signed int limit) int linesTotal = 0; while(*text!='\0' && limit!=0) { - if (text[i]=='\n') linesTotal++; + if (*text=='\n') linesTotal++; text++; limit--; } return linesTotal; } -// Text Box control -// NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) { - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement - #endif - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement - #endif + bool pressed = false; - int result = 0; - GuiState state = guiState; + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); - bool multiline = false; // TODO: Consider multiline text input - int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); - Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length - int thisCursorIndex = textBoxCursorIndex; - if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); - int textIndexOffset = 0; // Text index offset to start drawing in the box + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); - // Cursor rectangle - // NOTE: Position X value should be updated - Rectangle cursor = { - textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), - textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), - 2, - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 - }; + return pressed; +} +*/ - if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; - if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; - // Mouse cursor rectangle - // NOTE: Initialized outside of screen - Rectangle mouseCursor = cursor; - mouseCursor.x = -1; - mouseCursor.width = 1; + int tempValue = *value; - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); } - // Blink-cursor frame counter - //if (!autoCursorMode) blinkCursorFrameCounter++; - //else blinkCursorFrameCounter = 0; - // Update control //-------------------------------------------------------------------- - // WARNING: Text editing is only supported under certain conditions: - if ((state != STATE_DISABLED) && // Control not disabled - !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode - !guiLocked && // Gui not locked - !guiControlExclusiveMode && // No gui slider on dragging - (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { - Vector2 mousePosition = GetMousePosition(); + Vector2 mousePoint = GetMousePosition(); - if (editMode) + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) { - state = STATE_PRESSED; - - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // If text does not fit in the textbox and current cursor position is out of bounds, - // we add an index offset to text for drawing only what requires depending on cursor - while (textWidth >= textBounds.width) - { - int nextCodepointSize = 0; - GetCodepointNext(text + textIndexOffset, &nextCodepointSize); - - textIndexOffset += nextCodepointSize; + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); - } +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif - int codepoint = GetCharPressed(); // Get Unicode codepoint - int enterPressed = IsKeyPressed(KEY_ENTER); - #if !defined(RAYGUI_STANDALONE) - enterPressed |= IsKeyPressedRepeat(KEY_ENTER); - #endif + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- - if (multiline && enterPressed) codepoint = (int)'\n'; + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); - // Encode codepoint as UTF-8 - int codepointSize = 0; - const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) - { - // Move forward data from cursor position - for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - // Add new codepoint in current cursor position - for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- - textBoxCursorIndex += codepointSize; - textLength += codepointSize; + *value = tempValue; + return result; +} - // Make sure text last character is EOL - text[textLength] = '\0'; - } +// 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) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif - // Move cursor to start - if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + int result = 0; + GuiState state = guiState; - // Move cursor to end - if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + sprintf(textValue, "%i", *value); - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) - { - autoCursorDelayCounter++; + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; + bool valueHasChanged = false; - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + if (editMode) + { + state = STATE_PRESSED; - // Make sure text last character is EOL - text[textLength] = '\0'; - } - } + int keyCount = (int)strlen(textValue); - // Delete codepoint from text, before current cursor position - if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + if (GetTextWidth(textValue) < bounds.width) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - // TODO Check: >= cursor+codepointsize and <= length-codepointsize - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) { - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; } - - // Make sure text last character is EOL - text[textLength] = '\0'; } } - // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + // Delete text + if (keyCount > 0) { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + if (IsKeyPressed(KEY_BACKSPACE)) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; } } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) - { - autoCursorDelayCounter++; - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; - } - } - - // Move cursor position with mouse - if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text - { - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; - int codepointIndex = 0; - float glyphWidth = 0.0f; - float widthToMouseX = 0; - int mouseCursorIndex = 0; - - for (int i = textIndexOffset; i < textLength; i++) - { - codepoint = GetCodepointNext(&text[i], &codepointSize); - codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) - { - mouseCursor.x = textBounds.x + widthToMouseX; - mouseCursorIndex = i; - break; - } - - widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - - // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); - if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) - { - mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = textLength; - } + if (valueHasChanged) *value = TextToInteger(textValue); - // Place cursor at required index on mouse click - if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - cursor.x = mouseCursor.x; - textBoxCursorIndex = mouseCursorIndex; - } - } - else mouseCursor.x = -1; + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; - // Recalculate cursor position.y depending on textBoxCursorIndex - int lineStart = textIndexOffset; - if (multiline) - { - // calculate starting position of current line - lineStart = textBoxCursorIndex; - while (text[lineStart]!='\n'&&lineStart>=0) - { - lineStart--; - } - lineStart++; - } - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetLineWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); - if (multiline) + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) { - cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); - cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); - } + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; - // Finish text editing on ENTER or mouse click outside bounds - if ((!multiline && IsKeyPressed(KEY_ENTER)) || - (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index result = 1; } } else { - if (CheckCollisionPointRec(mousePosition, bounds)) + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) { state = STATE_FOCUSED; - - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text - result = 1; - } + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; } } } @@ -2764,134 +2676,31 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Draw control //-------------------------------------------------------------------- - if (state == STATE_PRESSED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); - } - else if (state == STATE_DISABLED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); - } - else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - // Draw text considering index offset if required - // NOTE: Text index offset depends on cursor position - GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); // Draw cursor - if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) - { - //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - - // Draw mouse position cursor (if required) - if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - } - else if (state == STATE_FOCUSED) GuiTooltip(bounds); - //-------------------------------------------------------------------- - - return result; // Mouse button pressed: result = 1 -} - -/* -// Text Box control with multiple lines and word-wrap -// NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) -{ - bool pressed = false; - - GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); - - // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, textSize, editMode); - - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); - GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); - - return pressed; -} -*/ - -// Spinner control, returns selected value -int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) -{ - int result = 1; - GuiState state = guiState; - - int tempValue = *value; - - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - - Rectangle textBounds = { 0 }; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check spinner state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - -#if defined(RAYGUI_NO_ICONS) - if (GuiButton(leftButtonBound, "<")) tempValue--; - if (GuiButton(rightButtonBound, ">")) tempValue++; -#else - if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; - if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; -#endif - - if (!editMode) + if (editMode) { - if (tempValue < minValue) tempValue = minValue; - if (tempValue > maxValue) tempValue = maxValue; + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); - - // Draw value selector custom buttons - // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); //-------------------------------------------------------------------- - *value = tempValue; return result; } -// Value Box control, updates input text with numbers +// Floating point Value Box control, updates input val_str with numbers // NOTE: Requires static variables: frameCounter -int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) { #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) #define RAYGUI_VALUEBOX_MAX_CHARS 32 @@ -2900,10 +2709,10 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int result = 0; GuiState state = guiState; - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); - Rectangle textBounds = { 0 }; + Rectangle textBounds = {0}; if (text != NULL) { textBounds.width = (float)GetTextWidth(text) + 2; @@ -2933,19 +2742,23 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in if (GetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) { textValue[keyCount] = (char)key; keyCount++; + valueHasChanged = true; } } } - // Delete text - if (keyCount > 0) + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) { - if (IsKeyPressed(KEY_BACKSPACE)) + if (keyCount > 0) { keyCount--; textValue[keyCount] = '\0'; @@ -2953,25 +2766,12 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in } } - if (valueHasChanged) *value = TextToInteger(textValue); - - // NOTE: We are not clamp values until user input finishes - //if (*value > maxValue) *value = maxValue; - //else if (*value < minValue) *value = minValue; - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; + if (valueHasChanged) *value = TextToFloat(textValue); - result = 1; - } + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; } else { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - if (CheckCollisionPointRec(mousePoint, bounds)) { state = STATE_FOCUSED; @@ -2994,116 +2794,9 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); - } - - // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// 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) - #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif - - int result = 0; - GuiState state = guiState; - - //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); - - Rectangle textBounds = {0}; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = STATE_PRESSED; - - int keyCount = (int)strlen(textValue); - - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) - { - if (GetTextWidth(textValue) < bounds.width) - { - int key = GetCharPressed(); - if (((key >= 48) && (key <= 57)) || - (key == '.') || - ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position - ((keyCount == 0) && (key == '-'))) - { - textValue[keyCount] = (char)key; - keyCount++; - - valueHasChanged = true; - } - } - } - - // Pressed backspace - if (IsKeyPressed(KEY_BACKSPACE)) - { - if (keyCount > 0) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = TextToFloat(textValue); - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; - } - else - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - Color baseColor = BLANK; - if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); - else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - - GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); - GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode) - { - // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, - bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, - bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } @@ -3116,6 +2809,16 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float return result; } +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + return GuiTextBoxInternal(bounds, text, textSize, false, editMode); +} + +int GuiTextBoxMultiline(Rectangle bounds, char *text, int textSize, bool editMode) +{ + return GuiTextBoxInternal(bounds, text, textSize, true, editMode); +} + // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) @@ -4764,6 +4467,20 @@ static int GetLineWidth(const char *text) return (int)textSize.x; } +static int GetLineStart(const char* text, int offset) +{ + while (offset > 0 && text[offset-1] != '\n') + offset--; + return offset + 1; +} + +static int GetLineEnd(const char* text, int offset) +{ + while (text[offset] != '\0' && text[offset] != '\n') + offset++; + return offset; +} + // Get maximum text width from all lines static int GetTextWidth(const char *text) { @@ -5364,162 +5081,467 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; - // Arrow buttons [<] [>] [∧] [∨] - Rectangle arrowUpLeft = { 0 }; - Rectangle arrowDownRight = { 0 }; + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +static inline int GuiTextBoxInternal(Rectangle bounds, char *text, int textSize, bool multiline, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Auto-cursor movement logic + // NOTE: Cursor moves automatically when key down after some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; + else + { + autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter + autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter + } + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + int enterPressed = IsKeyPressed(KEY_ENTER); + #if !defined(RAYGUI_STANDALONE) + enterPressed |= IsKeyPressedRepeat(KEY_ENTER); + #endif + + if (multiline && enterPressed) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Add codepoint to text, at current cursor position + // NOTE: Make sure we do not overflow buffer size + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete codepoint from text, after current cursor position + if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; - // Actual area of the scrollbar excluding the arrow buttons - Rectangle scrollbar = { 0 }; + if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - // Slider bar that moves --[///]----- - Rectangle slider = { 0 }; + // Move backward text from cursor position + for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; + textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - int valueRange = maxValue - minValue; - if (valueRange <= 0) valueRange = 1; + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } - int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); - if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + // Delete codepoint from text, before current cursor position + if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; - // Calculate rectangles for all of the components - arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ - (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)spinnerSize, (float)spinnerSize }; + if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (isVertical) - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + // Move backward text from cursor position + for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), - bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), - (float)sliderSize }; - } - else // horizontal - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + // TODO Check: >= cursor+codepointsize and <= length-codepointsize - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), - bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - (float)sliderSize, - bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; - } + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + textBoxCursorIndex -= codepointSize; + textLength -= codepointSize; + } - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && - !CheckCollisionPointRec(mousePoint, arrowUpLeft) && - !CheckCollisionPointRec(mousePoint, arrowDownRight)) + // Move cursor position with keys + if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames { - state = STATE_PRESSED; + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; } } - else + else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; + autoCursorDelayCounter++; - // Handle mouse wheel - int wheel = (int)GetMouseWheelMove(); - if (wheel != 0) value += wheel; + if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - // Handle mouse button down - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + } + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text { - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; - // Check arrows click - if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (!CheckCollisionPointRec(mousePoint, slider)) + for (int i = textIndexOffset; i < textLength; i++) { - // If click on scrollbar position but not on slider, place slider directly on that position - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); } - state = STATE_PRESSED; + // Check if mouse cursor is at the last position + int textEndWidth = GetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } } + else mouseCursor.x = -1; - // Keyboard control on mouse hover scrollbar - /* - if (isVertical) + // Recalculate cursor position.y depending on textBoxCursorIndex + int lineStart = textIndexOffset; + if (multiline) { - if (IsKeyDown(KEY_DOWN)) value += 5; - else if (IsKeyDown(KEY_UP)) value -= 5; + // calculate starting position of current line + lineStart = GetLineStart(text, textBoxCursorIndex); } - else + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetLineWidth(text + lineStart) - GetLineWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + if (multiline) { - if (IsKeyDown(KEY_RIGHT)) value += 5; - else if (IsKeyDown(KEY_LEFT)) value -= 5; + cursor.y = GetTextTotalLines(text, textBoxCursorIndex) * GuiGetStyle(DEFAULT, TEXT_SIZE); + cursor.y+= textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + result = 1; } - */ } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + result = 1; + } + } + } } //-------------------------------------------------------------------- // Draw control //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); - GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background - GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); - // Draw arrows (using icon if available) - if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) { -#if defined(RAYGUI_NO_ICONS) - GuiDrawText(isVertical? "^" : "<", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); - GuiDrawText(isVertical? "v" : ">", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); -#else - GuiDrawText(isVertical? "#121#" : "#118#", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL - GuiDrawText(isVertical? "#120#" : "#119#", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL -#endif + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); //-------------------------------------------------------------------- - return value; + return result; // Mouse button pressed: result = 1 } // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f From 6a535908e5f3c11258517d68b171aa17e386ccf9 Mon Sep 17 00:00:00 2001 From: mrkubax10 Date: Tue, 3 Dec 2024 19:48:39 +0100 Subject: [PATCH 13/13] GuiTextBox: When in multiline mode draw each line separately --- src/raygui.h | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/raygui.h b/src/raygui.h index 4b031792..843c1cf5 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -5525,9 +5525,35 @@ static inline int GuiTextBoxInternal(Rectangle bounds, char *text, int textSize, } else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); - // Draw text considering index offset if required - // NOTE: Text index offset depends on cursor position - GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + if (multiline) + { + Rectangle lineBounds = textBounds; + lineBounds.height = GuiGetStyle(DEFAULT, TEXT_SIZE); + int lineStart = 0; + while(text[lineStart] != '\0' && lineBounds.y + lineBounds.height <= textBounds.y + textBounds.height) + { + int lineEnd = GetLineEnd(text, lineStart); + const char lineEnding = text[lineEnd]; + if(textIndexOffset > lineEnd - lineStart) + { + lineStart = lineEnd + (lineEnding == '\0' ? 0 : 1); + continue; + } + + // Temporarily write null to not render beyond current line + text[lineEnd] = '\0'; + GuiDrawText(text + lineStart + textIndexOffset, lineBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + text[lineEnd] = lineEnding; + lineBounds.y += lineBounds.height; + lineStart = lineEnd + (lineEnding == '\0' ? 0 : 1); + } + } + else + { + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + } // Draw cursor if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY))