Add advanced textbox and input box based on stb_textedit

This commit is contained in:
Dan Bechard 2021-12-06 17:03:57 -05:00
parent ef6959ed54
commit 84cdb2acb8

View File

@ -453,6 +453,10 @@ typedef enum {
HUEBAR_SELECTOR_OVERFLOW // Right hue bar selector overflow
} GuiColorPickerProperty;
typedef struct {
void *state; // internal state
} GuiTextBoxAdvancedState;
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
@ -475,6 +479,7 @@ RAYGUIAPI bool GuiIsLocked(void); // Check
RAYGUIAPI void GuiFade(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f
RAYGUIAPI void GuiSetState(int state); // Set gui state (global state)
RAYGUIAPI int GuiGetState(void); // Get gui state (global state)
RAYGUIAPI void GuiSetActiveTextbox(GuiTextBoxAdvancedState *textbox); // Set active advanced textbox (global state)
// Font set/get functions
RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state)
@ -504,6 +509,7 @@ RAYGUIAPI bool GuiSpinner(Rectangle bounds, const char *text, int *value, int mi
RAYGUIAPI bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers
RAYGUIAPI bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text
RAYGUIAPI bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control with multiple lines
RAYGUIAPI bool GuiTextBoxAdvanced(GuiTextBoxAdvancedState *textboxState, Rectangle bounds, char *text, int *textSize, int textCapacity, bool readOnly); // Advanced textbox with navigation/selection
RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider control, returns selected value
RAYGUIAPI float GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider Bar control, returns selected value
RAYGUIAPI float GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Progress Bar control, shows current progress value
@ -518,6 +524,7 @@ RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex,
RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *focus, int *scrollIndex, int active); // List View with extended parameters
RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message
RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text); // Text Input Box control, ask for text
RAYGUIAPI int GuiTextInputBoxAdvanced(GuiTextBoxAdvancedState *textboxState, Rectangle bounds, const char *title, const char *message, const char *buttons, int submitButton, char *text, int *textSize, int textCapacity, bool readOnly); // Advanced input box with navigation/selection
RAYGUIAPI Color GuiColorPicker(Rectangle bounds, Color color); // Color Picker control (multiple color controls)
RAYGUIAPI Color GuiColorPanel(Rectangle bounds, Color color); // Color Panel control
RAYGUIAPI float GuiColorBarAlpha(Rectangle bounds, float alpha); // Color Bar Alpha control
@ -1137,6 +1144,152 @@ static unsigned int guiIcons[RICON_MAX_ICONS*RICON_DATA_ELEMENTS] = {
#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of standard properties
#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties
//----------------------------------------------------------------------------------
// stb_textedit configuration and type definitions
//----------------------------------------------------------------------------------
// Symbols that must be the same in header-file and implementation mode:
//
// STB_TEXTEDIT_CHARTYPE the character type
// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position
// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow
// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer
//
//----------------------------------------------------------------------------------
// NOTE(dbechard): It may not be safe to use anything other than "int". These defines were added
// to stb_textedit after-the-fact and there are a few places that e.g. STB_TEXTEDIT_POSITIONTYPE
// interacts with ints in a hard-coded way, which is why I've let these default to int instead of
// specifying a shorter type here (which would reduce the memory overhead of the undo stack).
#if 0
#define STB_TEXTEDIT_CHARTYPE int
#define STB_TEXTEDIT_KEYTYPE int
#define STB_TEXTEDIT_POSITIONTYPE int
#endif
// NOTE(dbechrd): These should be user-defined per textbox (requires stb_textedit refactor and
// dynamic allocation for undo stack).
#define STB_TEXTEDIT_UNDOSTATECOUNT 32
#define STB_TEXTEDIT_UNDOCHARCOUNT 512
//----------------------------------------------------------------------------------
// Symbols you must define for implementation mode:
//
// STB_TEXTEDIT_STRING the type of object representing a string being edited,
// typically this is a wrapper object with other data you need
//
// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))
// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters
// starting from character #n (see discussion below)
// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character
// to the xpos of the i+1'th char for a line of characters
// starting at character #n (i.e. accounts for kerning
// with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert)
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning
//
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
//
//----------------------------------------------------------------------------------
#define STB_TEXTEDIT_STRING rstb_String
#define STB_TEXTEDIT_STRINGLEN(str) rstb_StringLen(str)
#define STB_TEXTEDIT_LAYOUTROW(row, str, start) rstb_LayoutRow(row, str, start)
#define STB_TEXTEDIT_GETWIDTH(str, start, offset) rstb_GetWidth(str, start + offset)
#define STB_TEXTEDIT_KEYTOTEXT(keycode) rstb_KeyToText(keycode)
#define STB_TEXTEDIT_GETCHAR(str, index) rstb_GetChar(str, index)
#define STB_TEXTEDIT_NEWLINE ((int)'\n')
#define STB_TEXTEDIT_DELETECHARS(str, start, count) rstb_DeleteChars(str, start, count)
#define STB_TEXTEDIT_INSERTCHARS(str, start, chars, count) rstb_InsertChars(str, start, chars, count)
#define STB_TEXTEDIT_IS_SPACE(c) rstb_IsSpace(c)
//----------------------------------------------------------------------------------
//
// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift keycode
//
// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left
// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
// STB_TEXTEDIT_K_UP keyboard input to move cursor up
// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME
// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END
// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME
// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END
// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor
// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor
// STB_TEXTEDIT_K_UNDO keyboard input to perform undo
// STB_TEXTEDIT_K_REDO keyboard input to perform redo
//
//----------------------------------------------------------------------------------
#define KMOD_SHIFT (1 << 10) // 1024, bit flag to represent shift modifier
#define KMOD_CTRL (1 << 11) // 2048, bit flag to represent control modifier
#define STB_TEXTEDIT_K_SHIFT KMOD_SHIFT
#define STB_TEXTEDIT_K_LEFT KEY_LEFT
#define STB_TEXTEDIT_K_RIGHT KEY_RIGHT
#define STB_TEXTEDIT_K_UP KEY_UP
#define STB_TEXTEDIT_K_DOWN KEY_DOWN
#define STB_TEXTEDIT_K_LINESTART KEY_HOME
#define STB_TEXTEDIT_K_LINEEND KEY_END
#define STB_TEXTEDIT_K_TEXTSTART (KMOD_CTRL | KEY_HOME)
#define STB_TEXTEDIT_K_TEXTEND (KMOD_CTRL | KEY_END)
#define STB_TEXTEDIT_K_DELETE KEY_DELETE
#define STB_TEXTEDIT_K_BACKSPACE KEY_BACKSPACE
#define STB_TEXTEDIT_K_UNDO (KMOD_CTRL | KEY_Z)
#define STB_TEXTEDIT_K_REDO (KMOD_CTRL | KMOD_SHIFT | KEY_Z)
//----------------------------------------------------------------------------------
// Optional:
// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode
// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'),
// required for default WORDLEFT/WORDRIGHT handlers
// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to
// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to
// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT
// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT
// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line
// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
//----------------------------------------------------------------------------------
//#define STB_TEXTEDIT_K_INSERT KEY_INSERT // who actually uses insert mode..? disabled for now
//#define STB_TEXTEDIT_MOVEWORDLEFT <default> // see: stb_textedit_move_to_word_previous
//#define STB_TEXTEDIT_MOVEWORDRIGHT <default> // see: stb_textedit_move_to_word_next
#define STB_TEXTEDIT_K_WORDLEFT (KMOD_CTRL | KEY_LEFT)
#define STB_TEXTEDIT_K_WORDRIGHT (KMOD_CTRL | KEY_RIGHT)
//#define STB_TEXTEDIT_K_LINESTART2 <not used>
//#define STB_TEXTEDIT_K_LINEEND2 <not used>
//#define STB_TEXTEDIT_K_TEXTSTART2 <not used>
//#define STB_TEXTEDIT_K_TEXTEND2 <not used>
#include "stb_textedit.h"
typedef struct {
char *buffer; // text edit buffer
// TODO(dbechrd): Characters, not bytes, need to handle this correctly for Unicode
int used; // # of characters in use
int capacity; // size of buffer in bytes, last byte reserverd for nil terminator
} rstb_String;
static int rstb_StringLen(rstb_String *str);
static void rstb_LayoutRow(StbTexteditRow *row, rstb_String *str, int start);
static float rstb_GetWidth(rstb_String *str, int index);
static int rstb_KeyToText(int key);;
static int rstb_GetChar(rstb_String *str, int index);
static void rstb_DeleteChars(rstb_String *str, int start, int count);
static int rstb_InsertChars(rstb_String *str, int start, int *chars, int count);
static int rstb_IsSpace(int c);
#define STB_TEXTEDIT_IMPLEMENTATION
#include "stb_textedit.h"
typedef struct {
STB_TexteditState stb_state;
bool focused;
} rGuiTextBoxAdvancedState;
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
@ -1168,6 +1321,163 @@ static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI
static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization
static GuiTextBoxAdvancedState *guiActiveTextbox = 0;
//----------------------------------------------------------------------------------
// stb_textedit function implementations
//----------------------------------------------------------------------------------
#include <cassert>
static int rstb_StringLen(rstb_String *str) {
return str->used;
//return (int)strnlen(str, str->textSize);
}
static void rstb_LayoutRow(StbTexteditRow *row, rstb_String *str, int start)
{
// float x0,x1; // starting x location, end x location (allows for align=right, etc)
// float baseline_y_delta; // position of baseline relative to previous row's baseline
// float ymin,ymax; // height of row above and below baseline
// int num_chars;
// HACK(dbechrd): Assuming single-line mode
UNUSED(start);
assert(start == 0);
Vector2 textSize = MeasureTextEx(guiFont, str->buffer, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING));
row->x0 = 0.0f;
row->x1 = textSize.x;
row->baseline_y_delta = (float)guiFont.baseSize;
// TODO(dbechrd): Not sure what STB's coordinate system is here... might need to swap these or make one negative
// for multi-line mode to work properly.
row->ymin = 0.0f;
row->ymax = (float)guiFont.baseSize;
row->num_chars = str->used;
}
static float rstb_GetWidth(rstb_String *str, int index)
{
// Get next codepoint from byte string and glyph index in font
assert(index < str->used);
int codepointByteCount = 0;
int codepoint = GetCodepoint(&str->buffer[index], &codepointByteCount);
int glpyhIndex = GetGlyphIndex(guiFont, codepoint);
// TODO: scaleFactor and spacing should probably be configurable?
float scaleFactor = 1.0f;
float spacing = 1.0f;
float glyphWidth = (float)guiFont.glyphs[glpyhIndex].advanceX*scaleFactor + spacing;
return glyphWidth;
}
static int rstb_KeyToText(int key)
{
// Clear modifier keycode flags
int c = key & ~(KMOD_CTRL | KMOD_SHIFT);
// TODO(dbechrd): What should the filter be?
if (c < 32 || c >= 128)
{
c = -1; // not valid to insert
}
return c;
}
static int rstb_GetChar(rstb_String *str, int index)
{
assert(index >= 0);
assert(index < str->used);
// TODO: Unicode (requires O(n) traversal)
int c = (int)str->buffer[index];
return c;
}
static void rstb_DeleteChars(rstb_String *str, int start, int count)
{
assert(start >= 0);
assert(count > 0);
assert(start + count <= str->used);
// TODO(dbechrd): "start" and "count" are probably meant to be Unicode character indices, not byte indices
// NOTE: If moveBytes is 0, we're deleting from end of string and don't need to shift anything left
int moveBytes = str->used - (start + count);
if (moveBytes > 0)
{
// Shift tail of string left to overwrite deleted section
char *dst = str->buffer + start;
char *src = str->buffer + start + count;
for (int i = 0; i < moveBytes; i++)
{
*dst = *src;
dst++;
src++;
}
}
str->used -= count;
// Clear deleted/shifted characters at end of string
assert(str->used + count < str->capacity);
memset(str->buffer + str->used, 0, count);
}
static int rstb_InsertChars(rstb_String *str, int start, int *chars, int count)
{
assert(start >= 0);
assert(count > 0);
assert(str->used + count < str->capacity);
int success = 0;
// TODO(dbechrd): Unicode support, currently assumes ASCII
if (count + str->used < str->capacity)
{
if (start == str->used)
{
// Inserting at end of string is easy, just append
for (int i = 0; i < count; i++)
{
assert(chars[i] >= 32);
assert(chars[i] < 128);
str->buffer[str->used] = (char)chars[i];
}
}
else
{
// Shift text at insertion location to the right
char *dst = str->buffer + str->used + count - 1;
char *src = str->buffer + str->used + count - 2;
int moveBytes = str->used - (start + count - 1);
for (int i = moveBytes; i > 0; i--)
{
*dst = *src;
dst--;
src--;
}
// Copy new text into the gap we just created
memcpy(str->buffer + start, chars, count);
}
success = 1;
}
str->used += count;
assert(str->used < str->capacity);
return success;
}
static int rstb_IsSpace(int c)
{
int isSpace = 0;
switch (c)
{
case ' ': case '\t': case '\r': case '\n':
isSpace = 1;
}
return isSpace;
}
//----------------------------------------------------------------------------------
// Standalone Mode Functions Declaration
//
@ -1239,6 +1549,7 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
static int GetTextWidth(const char *text); // Gui get text width using default font
static int GetTextSubstringWidth(rstb_String *str, int start, int length); // Get text width of a substring of an STB string
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
@ -1282,6 +1593,8 @@ void GuiSetState(int state) { guiState = (GuiControlState)state; }
// Get gui state (global state)
int GuiGetState(void) { return guiState; }
void GuiSetActiveTextbox(GuiTextBoxAdvancedState *textbox) { guiActiveTextbox = textbox; }
// Set custom gui font
// NOTE: Font loading/unloading is external to raygui
void GuiSetFont(Font font)
@ -2042,6 +2355,250 @@ bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
return pressed;
}
// Text Box control, updates input text
// NOTE 1: Requires static variables: framesCounter
// NOTE 2: Returns if KEY_ENTER validate (useful for data validation)
bool GuiTextBoxAdvanced(GuiTextBoxAdvancedState *textboxState, Rectangle bounds, char *text, int *textSize, int textCapacity, bool readOnly)
{
assert(text);
assert(textSize && *textSize >= 0);
assert(textCapacity > 0);
assert(*textSize < textCapacity);
static int framesCounter = 0; // Required for blinking cursor
static double lastKeyPress = 0;
static int keyRepeat = 0;
// TODO: This has to be per-textbox, but storing it here during prototyping to keep dependency internal to raygui
if (!textboxState->state) {
textboxState->state = RAYGUI_CALLOC(1, sizeof(rGuiTextBoxAdvancedState));
}
rGuiTextBoxAdvancedState *rTextboxState = ((rGuiTextBoxAdvancedState *)textboxState->state);
if (!rTextboxState->stb_state.initialized || *textSize == 0) {
stb_textedit_initialize_state(&rTextboxState->stb_state, 1);
}
rstb_String str = { 0 };
str.buffer = text;
str.used = *textSize;
str.capacity = textCapacity;
GuiControlState state = guiState;
bool submit = false;
Rectangle textBounds = GetTextBounds(TEXTBOX, bounds);
#if 0
Rectangle cursor = {
bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text) + 2,
bounds.y + GuiGetStyle(DEFAULT, TEXT_SIZE),
1,
(float)GuiGetStyle(DEFAULT, TEXT_SIZE) * 1.5f
};
#endif
// Update control
//--------------------------------------------------------------------
if ((state != GUI_STATE_DISABLED) && !guiLocked && !readOnly) {
Vector2 mousePoint = GetMousePosition();
state = GUI_STATE_NORMAL;
// Check for mouse click on/off control, and mouse hover
if (CheckCollisionPointRec(mousePoint, bounds)) {
state = GUI_STATE_FOCUSED;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
// User clicked on this control
guiActiveTextbox = textboxState;
}
} else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
// User clicked somewhere else
if (guiActiveTextbox == textboxState) {
guiActiveTextbox = 0;
}
framesCounter = 0;
}
// Handle input when textbox in edit mode
if (guiActiveTextbox == textboxState && !readOnly) {
state = GUI_STATE_PRESSED;
framesCounter++;
int ctrlDown = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
int shiftDown = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
int keyMods = (ctrlDown > 0 ? KMOD_CTRL : 0) | (shiftDown > 0 ? KMOD_SHIFT : 0);
int codepoint = GetCharPressed(); // Returns codepoint as Unicode
while (codepoint) {
// TODO(dbechrd): What is max value returned by GetCharPressed()? Are these bits safe to overwrite?
assert(codepoint < KMOD_CTRL);
assert(codepoint < KMOD_SHIFT);
stb_textedit_key(&str, &rTextboxState->stb_state, codepoint | keyMods);
codepoint = GetCharPressed();
}
bool left = IsKeyDown(KEY_LEFT);
bool right = IsKeyDown(KEY_RIGHT);
if (left && right) {
left = false;
right = false;
}
bool up = IsKeyDown(KEY_UP);
bool down = IsKeyDown(KEY_DOWN);
if (up && down) {
up = false;
down = false;
}
bool home = IsKeyDown(KEY_HOME);
bool end = IsKeyDown(KEY_END);
if (home && end) {
home = false;
end = false;
}
bool del = IsKeyDown(KEY_DELETE);
bool backspace = IsKeyDown(KEY_BACKSPACE);
if (del && backspace) {
del = false;
backspace = false;
}
bool z = IsKeyDown(KEY_Z);
bool insert = IsKeyDown(KEY_INSERT);
bool any = left || right || up || down || home || end || del || backspace || z || insert;
if (any) {
const double delayBeforeRepeat = 0.15;
const double delayDuringRepeat = 0.03;
const double timeSinceLastKeyPress = GetTime() - lastKeyPress;
//if (timeSinceLastKeyPress > delayBeforeRepeat * 2)
//{
// keyRepeat = 0;
//}
double keyRepeatDelay = 0;
if (keyRepeat > 1) {
keyRepeatDelay = delayDuringRepeat;
} else if (keyRepeat == 1) {
keyRepeatDelay = delayBeforeRepeat;
}
if (timeSinceLastKeyPress > keyRepeatDelay) {
if (left) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_LEFT);
if (right) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_RIGHT);
if (up) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_UP);
if (down) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_DOWN);
if (home) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_HOME);
if (end) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_END);
if (del) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_DELETE);
if (backspace) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_BACKSPACE);
if (z) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_INSERT);
if (insert) stb_textedit_key(&str, &rTextboxState->stb_state, keyMods | KEY_Z);
keyRepeat++;
lastKeyPress = GetTime();
}
framesCounter = 0;
} else {
keyRepeat = 0;
}
if (ctrlDown && IsKeyPressed(KEY_A)) {
rTextboxState->stb_state.select_start = 0;
rTextboxState->stb_state.select_end = str.used;
}
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
stb_textedit_click(&str, &rTextboxState->stb_state, (float)GetMouseX() - textBounds.x, (float)GetMouseY() - textBounds.y);
} else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
stb_textedit_drag(&str, &rTextboxState->stb_state, (float)GetMouseX() - textBounds.x, (float)GetMouseY() - textBounds.y);
}
// If enter key was pressed, we're done editing
if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {
framesCounter = 0;
submit = true;
}
#if 0
// Check text alignment to position cursor properly
int textAlignment = GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT);
if (textAlignment == GUI_TEXT_ALIGN_CENTER) cursor.x = bounds.x + GetTextWidth(text) / 2 + bounds.width / 2 + 1;
else if (textAlignment == GUI_TEXT_ALIGN_RIGHT) cursor.x = bounds.x + bounds.width - GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING);
#else
// TODO: Check text alignment to position cursor properly
#endif
}
}
//--------------------------------------------------------------------
assert(str.used < str.capacity);
text[str.used] = '\0';
// Draw control
//--------------------------------------------------------------------
int selectionStart = MIN(rTextboxState->stb_state.select_start, rTextboxState->stb_state.select_end);
int selectionLength = ABS(rTextboxState->stb_state.select_end - rTextboxState->stb_state.select_start);
GuiControlProperty baseColorProperty = BASE_COLOR_NORMAL;
switch (state) {
case GUI_STATE_PRESSED: baseColorProperty = BASE_COLOR_PRESSED; break;
case GUI_STATE_DISABLED: baseColorProperty = BASE_COLOR_DISABLED; break;
case GUI_STATE_FOCUSED: baseColorProperty = BASE_COLOR_FOCUSED; break;
}
GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH),
//Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha),
//Fade(GetColor(GuiGetStyle(TEXTBOX, baseColorProperty)), guiAlpha)
Fade(BLACK, 0.8f),
Fade({ 50, 50, 50, 255 }, 0.6f)
);
if (guiActiveTextbox == textboxState) {
if (selectionLength) {
// Draw selection highlight
Color selectedHighlight = ORANGE; //(Color){ 52, 105, 218, 255 };
int selectionOffsetX = GetTextSubstringWidth(&str, 0, selectionStart);
int selectionWidth = GetTextSubstringWidth(&str, selectionStart, selectionLength);
Rectangle cursor = {
bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + selectionOffsetX + 2,
bounds.y + bounds.height / 2 - GuiGetStyle(DEFAULT, TEXT_SIZE) + GuiGetStyle(TEXTBOX, TEXT_PADDING),
(float)selectionWidth,
(float)GuiGetStyle(DEFAULT, TEXT_SIZE) + GuiGetStyle(TEXTBOX, TEXT_PADDING) / 2
};
GuiDrawRectangle(cursor, 0, BLANK, Fade(selectedHighlight, guiAlpha));
} else {
// Draw blinking cursor
int cursorOffetX = GetTextSubstringWidth(&str, 0, rTextboxState->stb_state.cursor);
Rectangle cursor = {
bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + cursorOffetX + 2,
bounds.y + bounds.height / 2 - GuiGetStyle(DEFAULT, TEXT_SIZE) + GuiGetStyle(TEXTBOX, TEXT_PADDING),
1,
(float)GuiGetStyle(DEFAULT, TEXT_SIZE) + GuiGetStyle(TEXTBOX, TEXT_PADDING) / 2
};
if (!readOnly && ((framesCounter / 20) % 2 == 0)) {
//GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha));
GuiDrawRectangle(cursor, 0, BLANK, Fade(RAYWHITE, guiAlpha));
}
}
}
//Color textColor = GetColor(GuiGetStyle(TEXTBOX, TEXT + (state * 3)));
Color textColor = RAYWHITE;
if (selectionLength) {
//textColor = RAYWHITE;
}
GuiDrawText(text, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), Fade(textColor, guiAlpha));
//--------------------------------------------------------------------
*textSize = str.used;
return submit;
}
// Spinner control, returns selected value
bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode)
{
@ -2079,6 +2636,14 @@ bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, in
}
}
#if defined(RAYGUI_NO_RICONS)
if (GuiButton(leftButtonBound, "<")) tempValue--;
if (GuiButton(rightButtonBound, ">")) tempValue++;
#else
if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) tempValue--;
if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) tempValue++;
#endif
if (!editMode)
{
if (tempValue < minValue) tempValue = minValue;
@ -2098,13 +2663,7 @@ bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, in
GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH));
GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER);
#if defined(RAYGUI_NO_RICONS)
if (GuiButton(leftButtonBound, "<")) tempValue--;
if (GuiButton(rightButtonBound, ">")) tempValue++;
#else
if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) tempValue--;
if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) tempValue++;
#endif
GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign);
GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth);
@ -2184,6 +2743,9 @@ bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, i
if (valueHasChanged) *value = TextToInteger(textValue);
if (*value > maxValue) *value = maxValue;
else if (*value < minValue) *value = minValue;
if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) pressed = true;
}
else
@ -2349,7 +2911,7 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode)
{
int glyphWidth = 0;
if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX;
else glyphWidth += (atlasRec.width + glyphInfo.offsetX);
else glyphWidth += (int)(atlasRec.width + glyphInfo.offsetX);
// Jump line if the end of the text box area has been reached
if ((cursorPos.x + (glyphWidth*scaleFactor)) > (textAreaBounds.x + textAreaBounds.width))
@ -2382,7 +2944,7 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode)
int glyphWidth = 0;
if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX;
else glyphWidth += (atlasRec.width + glyphInfo.offsetX);
else glyphWidth += (int)(atlasRec.width + glyphInfo.offsetX);
cursorPos.x += (glyphWidth*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING));
//if (i > lastSpacePos) lastSpaceWidth += (atlasRec.width + (float)GuiGetStyle(DEFAULT, TEXT_SPACING));
@ -3219,6 +3781,108 @@ int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, co
return btnIndex;
}
// Text Input Box control, ask for text
int GuiTextInputBoxAdvanced(GuiTextBoxAdvancedState *textboxState, Rectangle bounds, const char *title, const char *message, const char *buttons, int submitButton, char *text, int *textLength, int textCapacity, bool readOnly)
{
int btnIndex = -1;
const float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
const float margin = 4.0f;
const float buttonSpacing = 4.0f;
const float buttonPadding = (float)GuiGetStyle(BUTTON, TEXT_PADDING);
const float buttonHeight = fontSize + buttonPadding * 2;
Vector2 messageSize = { 0 };
float messageWidth = 0.0f;
if (message != NULL) {
messageSize = MeasureTextEx(guiFont, message, fontSize, 1);
messageWidth = messageSize.x + buttonPadding * 2;
}
int buttonsCount = 0;
const char **buttonsText = GuiTextSplit(buttons, &buttonsCount, NULL);
float widestButton = 0;
for (int i = 0; i < buttonsCount; i++) {
float buttonWidth = MeasureTextEx(guiFont, buttonsText[i], fontSize, 1).x;
widestButton = MAX(widestButton, buttonWidth + buttonPadding * 2);
}
const float buttonRowWidth = (float)GuiGetStyle(STATUSBAR, BORDER_WIDTH) * 2 + margin * 2 + widestButton * buttonsCount + buttonSpacing * (buttonsCount - 1);
const float minWidth = MAX(messageWidth, buttonRowWidth);
if (minWidth > bounds.width) {
bounds.width = minWidth;
}
float contextX = bounds.x + (float)GuiGetStyle(STATUSBAR, BORDER_WIDTH) + margin;
float contentY = bounds.y + (float)GuiGetStyle(STATUSBAR, BORDER_WIDTH) + WINDOW_STATUSBAR_HEIGHT + margin;
float contentWidth = bounds.width - (float)GuiGetStyle(STATUSBAR, BORDER_WIDTH) * 2 - margin * 2;
Rectangle messageBounds = { 0 };
if (message != NULL) {
messageSize = MeasureTextEx(guiFont, message, fontSize, 1);
messageBounds.x = contextX;
messageBounds.y = contentY;
messageBounds.width = messageWidth;
messageBounds.height = messageSize.y;
contentY += messageBounds.height + margin;
}
Rectangle textBoxBounds = { 0 };
textBoxBounds.x = contextX;
textBoxBounds.y = contentY;
textBoxBounds.width = contentWidth;
textBoxBounds.height = fontSize + (float)GuiGetStyle(TEXTBOX, TEXT_PADDING) * 2;
contentY += textBoxBounds.height + buttonPadding;
contentY += buttonHeight + buttonPadding;
float minHeight = contentY + (float)GuiGetStyle(STATUSBAR, BORDER_WIDTH) + margin - bounds.y;
if (minHeight > bounds.height) {
bounds.height = minHeight;
}
// NOTE: Buttons are anchored to bottom, so this has to happen after minHeight calculation
Rectangle buttonBounds = { 0 };
buttonBounds.x = contextX + contentWidth - widestButton * buttonsCount - buttonSpacing * (buttonsCount - 1);
buttonBounds.y = bounds.y + bounds.height - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - buttonHeight - margin;
buttonBounds.width = widestButton;
buttonBounds.height = buttonHeight;
// Draw control
//--------------------------------------------------------------------
if (GuiWindowBox(bounds, title)) btnIndex = 0;
// Draw message if available
if (message != NULL) {
//int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT);
//GuiSetStyle(LABEL, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER);
GuiLabel(messageBounds, message);
//GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment);
}
if (GuiTextBoxAdvanced(textboxState, textBoxBounds, text, textLength, textCapacity, readOnly)) {
btnIndex = submitButton;
}
int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT);
GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER);
for (int i = 0; i < buttonsCount; i++) {
if (GuiButton(buttonBounds, buttonsText[i])) {
btnIndex = i + 1;
}
buttonBounds.x += (buttonBounds.width + buttonSpacing);
}
GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment);
//--------------------------------------------------------------------
if (btnIndex >= 0 && guiActiveTextbox == textboxState) {
guiActiveTextbox = 0;
}
return btnIndex;
}
// Grid control
// NOTE: Returns grid mouse-hover selected cell
// About drawing lines at subpixel spacing, simple put, not easy solution:
@ -3738,6 +4402,37 @@ static int GetTextWidth(const char *text)
return (int)size.x;
}
// Get text width of a substring of an STB string
static int GetTextSubstringWidth(rstb_String *str, int start, int length)
{
assert(start >= 0);
assert(length >= 0);
assert(start + length <= str->used);
if (length == 0) {
return 0;
}
// HACK(dbechrd): Save character at cursor so that we can replace it with nil and call GetTextWidth() to
// get the width of a partial string
// TODO: Implement MeasureTextSubstring() that takes a starting index and offset, and only measures the
// substring (should it return early if it encounters '\0'? .. probably)
char charAtCursor = 0;
int end = start + length;
if (end < str->used) {
charAtCursor = str->buffer[end];
str->buffer[end] = '\0';
}
int width = GetTextWidth(str->buffer + start);
if (end < str->capacity) {
str->buffer[end] = charAtCursor;
}
return width;
}
// Get text bounds considering control bounds
static Rectangle GetTextBounds(int control, Rectangle bounds)
{