2021-05-14 05:59:33 +00:00
/*******************************************************************************************
*
* Text box extended ( cursor positioning and editing )
*
* MODULE USAGE :
* # define GUI_TEXTBOX_EXTENDED_IMPLEMENTATION
* # include " gui_textbox_extended.h "
*
* On game draw call : GuiTextBoxEx ( . . . ) ;
*
* LICENSE : zlib / libpng
*
* Copyright ( c ) 2019 Vlad Adrian ( @ Demizdor ) and Ramon Santamaria ( @ raysan5 )
*
* This software is provided " as-is " , without any express or implied warranty . In no event
* will the authors be held liable for any damages arising from the use of this software .
*
* Permission is granted to anyone to use this software for any purpose , including commercial
* applications , and to alter it and redistribute it freely , subject to the following restrictions :
*
* 1. The origin of this software must not be misrepresented ; you must not claim that you
* wrote the original software . If you use this software in a product , an acknowledgment
* in the product documentation would be appreciated but is not required .
*
* 2. Altered source versions must be plainly marked as such , and must not be misrepresented
* as being the original software .
*
* 3. This notice may not be removed or altered from any source distribution .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# ifndef GUI_TEXTBOX_EXTENDED_H
# define GUI_TEXTBOX_EXTENDED_H
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Text box state data
typedef struct GuiTextBoxState {
int cursor ; // Cursor position in text
int start ; // Text start position (from where we begin drawing the text)
int index ; // Text start index (index inside the text of `start` always in sync)
int select ; // Marks position of cursor when selection has started
} GuiTextBoxState ;
# ifdef __cplusplus
extern " C " { // Prevents name mangling of functions
# endif
2021-05-15 16:41:30 +00:00
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
RAYGUIDEF void GuiTextBoxSetActive ( Rectangle bounds ) ; // Sets the active textbox
RAYGUIDEF Rectangle GuiTextBoxGetActive ( void ) ; // Get bounds of active textbox
RAYGUIDEF void GuiTextBoxSetCursor ( int cursor ) ; // Set cursor position of active textbox
RAYGUIDEF int GuiTextBoxGetCursor ( void ) ; // Get cursor position of active textbox
RAYGUIDEF void GuiTextBoxSetSelection ( int start , int length ) ; // Set selection of active textbox
RAYGUIDEF Vector2 GuiTextBoxGetSelection ( void ) ; // Get selection of active textbox (x - selection start y - selection length)
RAYGUIDEF bool GuiTextBoxIsActive ( Rectangle bounds ) ; // Returns true if a textbox control with specified `bounds` is the active textbox
RAYGUIDEF GuiTextBoxState GuiTextBoxGetState ( void ) ; // Get state for the active textbox
RAYGUIDEF void GuiTextBoxSetState ( GuiTextBoxState state ) ; // Set state for the active textbox (state must be valid else things will break)
RAYGUIDEF void GuiTextBoxSelectAll ( const char * text ) ; // Select all characters in the active textbox (same as pressing `CTRL` + `A`)
RAYGUIDEF void GuiTextBoxCopy ( const char * text ) ; // Copy selected text to clipboard from the active textbox (same as pressing `CTRL` + `C`)
RAYGUIDEF void GuiTextBoxPaste ( char * text , int textSize ) ; // Paste text from clipboard into the textbox (same as pressing `CTRL` + `V`)
RAYGUIDEF void GuiTextBoxCut ( char * text ) ; // Cut selected text in the active textbox and copy it to clipboard (same as pressing `CTRL` + `X`)
RAYGUIDEF int GuiTextBoxDelete ( char * text , int length , bool before ) ; // Deletes a character or selection before from the active textbox (depending on `before`). Returns bytes deleted.
RAYGUIDEF int GuiTextBoxGetByteIndex ( const char * text , int start , int from , int to ) ; // Get the byte index for a character starting at position `from` with index `start` until position `to`.
RAYGUIDEF bool GuiTextBoxEx ( Rectangle bounds , char * text , int textSize , bool editMode ) ;
2021-05-14 05:59:33 +00:00
# ifdef __cplusplus
}
# endif
# endif // GUI_TEXTBOX_EXTENDED_H
/***********************************************************************************
*
* GUI TEXTBOX EXTENDED IMPLEMENTATION
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# if defined(GUI_TEXTBOX_EXTENDED_IMPLEMENTATION)
# ifndef RAYGUI_H
# include "raygui.h"
# endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
//...
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Cursor measure mode
typedef enum {
GUI_MEASURE_MODE_CURSOR_END = 0xA ,
GUI_MEASURE_MODE_CURSOR_POS ,
GUI_MEASURE_MODE_CURSOR_COORDS
} GuiMeasureMode ;
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
static Rectangle guiTextBoxActive = { 0 } ; // Area of the currently active textbox
static GuiTextBoxState guiTextBoxState = { // Keeps state of the active textbox
. cursor = - 1 ,
. start = 0 ,
. index = 0 ,
. select = - 1
} ;
//----------------------------------------------------------------------------------
// Module Internal Functions Declaration
//----------------------------------------------------------------------------------
static int GetPrevCodepoint ( const char * text , const char * start , int * prev ) ;
static int GuiMeasureTextBox ( const char * text , int length , Rectangle rec , int * pos , int mode ) ;
static int GuiMeasureTextBoxRev ( const char * text , int length , Rectangle rec , int * pos ) ; // Highly synchronized with calculations in DrawTextRecEx()
static inline int GuiTextBoxGetCursorCoordinates ( const char * text , int length , Rectangle rec , int pos ) ; // Calculate cursor coordinates based on the cursor position `pos` inside the `text`.
static inline int GuiTextBoxGetCursorFromMouse ( const char * text , int length , Rectangle rec , int * pos ) ; // Calculate cursor position in textbox based on mouse coordinates.
static inline int GuiTextBoxMaxCharacters ( const char * text , int length , Rectangle rec ) ; // Calculates how many characters is the textbox able to draw inside rec
static inline unsigned int GuiCountCodepointsUntilNewline ( const char * text ) ; // Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` or a `\n` is found.
static inline void MoveTextBoxCursorRight ( const char * text , int length , Rectangle textRec ) ;
static inline void MoveTextBoxCursorLeft ( const char * text ) ;
static int EncodeCodepoint ( unsigned int c , char out [ 5 ] ) ;
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Sets the active textbox (reseting state of the previous active textbox)
RAYGUIDEF void GuiTextBoxSetActive ( Rectangle bounds )
{
guiTextBoxActive = bounds ;
guiTextBoxState = ( GuiTextBoxState ) { . cursor = - 1 , . start = 0 , . index = 0 , . select = - 1 } ;
}
// Gets bounds of active textbox
RAYGUIDEF Rectangle GuiTextBoxGetActive ( void ) { return guiTextBoxActive ; }
// Set cursor position of active textbox
RAYGUIDEF void GuiTextBoxSetCursor ( int cursor )
{
guiTextBoxState . cursor = ( cursor < 0 ) ? - 1 : cursor ;
guiTextBoxState . start = - 1 ; // Mark this to be recalculated
}
// Get cursor position of active textbox
RAYGUIDEF int GuiTextBoxGetCursor ( void ) { return guiTextBoxState . cursor ; }
// Set selection of active textbox
RAYGUIDEF void GuiTextBoxSetSelection ( int start , int length )
{
if ( start < 0 ) start = 0 ;
if ( length < 0 ) length = 0 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
GuiTextBoxSetCursor ( start + length ) ;
guiTextBoxState . select = start ;
}
// Get selection of active textbox
RAYGUIDEF Vector2 GuiTextBoxGetSelection ( void )
{
if ( guiTextBoxState . select = = - 1 | | guiTextBoxState . select = = guiTextBoxState . cursor ) return RAYGUI_CLITERAL ( Vector2 ) { 0 } ;
else if ( guiTextBoxState . cursor > guiTextBoxState . select ) return RAYGUI_CLITERAL ( Vector2 ) { guiTextBoxState . select , guiTextBoxState . cursor - guiTextBoxState . select } ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return RAYGUI_CLITERAL ( Vector2 ) { guiTextBoxState . cursor , guiTextBoxState . select - guiTextBoxState . cursor } ;
}
// Returns true if a textbox control with specified `bounds` is the active textbox
RAYGUIDEF bool GuiTextBoxIsActive ( Rectangle bounds )
{
return ( bounds . x = = guiTextBoxActive . x & & bounds . y = = guiTextBoxActive . y & &
bounds . width = = guiTextBoxActive . width & & bounds . height = = guiTextBoxActive . height ) ;
}
RAYGUIDEF GuiTextBoxState GuiTextBoxGetState ( void ) { return guiTextBoxState ; }
RAYGUIDEF void GuiTextBoxSetState ( GuiTextBoxState state )
{
// NOTE: should we check if state values are valid ?!?
guiTextBoxState = state ;
}
RAYGUIDEF int GuiTextBoxGetByteIndex ( const char * text , int start , int from , int to )
{
int i = start , k = from ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
while ( ( text [ i ] ! = ' \0 ' ) & & ( k < to ) )
{
int j = 0 ;
int letter = GetNextCodepoint ( & text [ i ] , & j ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( letter = = 0x3f ) j = 1 ;
i + = j ;
+ + k ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return i ;
}
RAYGUIDEF int GuiTextBoxDelete ( char * text , int length , bool before )
{
if ( ( guiTextBoxState . cursor ! = - 1 ) & & ( text ! = NULL ) )
{
int startIdx = 0 , endIdx = 0 ;
if ( ( guiTextBoxState . select ! = - 1 ) & & ( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
// Delete selection
int start = guiTextBoxState . cursor ;
int end = guiTextBoxState . select ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor > guiTextBoxState . select )
{
start = guiTextBoxState . select ;
end = guiTextBoxState . cursor ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Convert to byte indexes
startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , start ) ;
endIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , end ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Adjust text box state
guiTextBoxState . cursor = start ; // Always set cursor to start of selection
if ( guiTextBoxState . select < guiTextBoxState . start ) guiTextBoxState . start = - 1 ; // Force to recalculate on the next frame
}
else
{
if ( before )
{
// Delete character before cursor
if ( guiTextBoxState . cursor ! = 0 )
{
endIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
guiTextBoxState . cursor - - ;
startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor < guiTextBoxState . start ) guiTextBoxState . start = - 1 ; // Force to recalculate on the next frame
}
}
else
{
// Delete character after cursor
2021-05-15 16:41:30 +00:00
if ( guiTextBoxState . cursor + 1 < = ( int ) GuiCountCodepointsUntilNewline ( text ) )
2021-05-14 05:59:33 +00:00
{
startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
endIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor + 1 ) ;
}
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
memmove ( & text [ startIdx ] , & text [ endIdx ] , length - endIdx ) ;
text [ length - ( endIdx - startIdx ) ] = ' \0 ' ;
guiTextBoxState . select = - 1 ; // Always deselect
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return ( endIdx - startIdx ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return 0 ;
}
RAYGUIDEF void GuiTextBoxSelectAll ( const char * text )
{
guiTextBoxState . cursor = GuiCountCodepointsUntilNewline ( text ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor > 0 )
{
guiTextBoxState . select = 0 ;
guiTextBoxState . start = - 1 ; // Force recalculate on the next frame
}
else guiTextBoxState . select = - 1 ;
}
RAYGUIDEF void GuiTextBoxCopy ( const char * text )
{
if ( ( text ! = NULL ) & &
( guiTextBoxState . select ! = - 1 ) & &
( guiTextBoxState . cursor ! = - 1 ) & &
( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
int start = guiTextBoxState . cursor ;
int end = guiTextBoxState . select ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor > guiTextBoxState . select )
{
start = guiTextBoxState . select ;
end = guiTextBoxState . cursor ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Convert to byte indexes
start = GuiTextBoxGetByteIndex ( text , 0 , 0 , start ) ;
end = GuiTextBoxGetByteIndex ( text , 0 , 0 , end ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// FIXME: `TextSubtext()` only lets use copy TEXTSPLIT_MAX_TEXT_LENGTH (1024) bytes
// maybe modify `SetClipboardText()` so we can use it only on part of a string
const char * clipText = TextSubtext ( text , start , end - start ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
SetClipboardText ( clipText ) ;
}
}
// Paste text from clipboard into the active textbox.
// `text` is the pointer to the buffer used by the textbox while `textSize` is the text buffer max size
RAYGUIDEF void GuiTextBoxPaste ( char * text , int textSize )
{
const char * clipText = GetClipboardText ( ) ; // GLFW guaratees this should be UTF8 encoded!
int length = strlen ( text ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( ( text ! = NULL ) & & ( clipText ! = NULL ) & & ( guiTextBoxState . cursor ! = - 1 ) )
{
if ( ( guiTextBoxState . select ! = - 1 ) & & ( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
// If there's a selection we'll have to delete it first
length - = GuiTextBoxDelete ( text , length , true ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int clipLen = strlen ( clipText ) ; // We want the length in bytes
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Calculate how many bytes can we copy from clipboard text before we run out of space
int size = ( ( length + clipLen ) < = textSize ) ? clipLen : textSize - length ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Make room by shifting to right the bytes after cursor
int startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
int endIdx = startIdx + size ;
memmove ( & text [ endIdx ] , & text [ startIdx ] , length - startIdx ) ;
text [ length + size ] = ' \0 ' ; // Set the NULL char
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// At long last copy the clipboard text
memcpy ( & text [ startIdx ] , clipText , size ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Set cursor position at the end of the pasted text
guiTextBoxState . cursor = 0 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
for ( int i = 0 ; i < ( startIdx + size ) ; guiTextBoxState . cursor + + )
{
int next = 0 ;
int letter = GetNextCodepoint ( & text [ i ] , & next ) ;
if ( letter ! = 0x3f ) i + = next ;
else i + = 1 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
guiTextBoxState . start = - 1 ; // Force to recalculate on the next frame
}
}
RAYGUIDEF void GuiTextBoxCut ( char * text )
{
if ( ( text ! = NULL ) & &
( guiTextBoxState . select ! = - 1 ) & &
( guiTextBoxState . cursor ! = - 1 ) & &
( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
// First copy selection to clipboard;
int start = guiTextBoxState . cursor , end = guiTextBoxState . select ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor > guiTextBoxState . select )
{
start = guiTextBoxState . select ;
end = guiTextBoxState . cursor ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Convert to byte indexes
int startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , start ) ;
int endIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , end ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// FIXME: `TextSubtext()` only lets use copy TEXTSPLIT_MAX_TEXT_LENGTH (1024) bytes
// maybe modify `SetClipboardText()` so we can use it only on parts of a string
const char * clipText = TextSubtext ( text , startIdx , endIdx - startIdx ) ;
SetClipboardText ( clipText ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Now delete selection (copy data over it)
int len = strlen ( text ) ;
memmove ( & text [ startIdx ] , & text [ endIdx ] , len - endIdx ) ;
text [ len - ( endIdx - startIdx ) ] = ' \0 ' ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Adjust text box state
guiTextBoxState . cursor = start ; // Always set cursor to start of selection
if ( guiTextBoxState . select < guiTextBoxState . start ) guiTextBoxState . start = - 1 ; // Force to recalculate
guiTextBoxState . select = - 1 ; // Deselect
}
}
// A text box control supporting text selection, cursor positioning and commonly used keyboard shortcuts.
// NOTE 1: Requires static variables: framesCounter
// NOTE 2: Returns if KEY_ENTER pressed (useful for data validation)
RAYGUIDEF bool GuiTextBoxEx ( Rectangle bounds , char * text , int textSize , bool editMode )
{
// Define the cursor movement/selection speed when movement keys are held/pressed
2021-05-15 16:41:30 +00:00
# define TEXTBOX_CURSOR_COOLDOWN 5
2021-05-14 05:59:33 +00:00
static int framesCounter = 0 ; // Required for blinking cursor
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
GuiControlState state = guiState ;
bool pressed = false ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Make sure length doesn't exceed `textSize`. `textSize` is actually the max amount of characters the textbox can handle.
int length = strlen ( text ) ;
if ( length > textSize )
{
text [ textSize ] = ' \0 ' ;
length = textSize ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Make sure we have enough room to draw at least 1 character
if ( ( bounds . width - 2 * GuiGetStyle ( TEXTBOX , TEXT_INNER_PADDING ) ) < GuiGetStyle ( DEFAULT , TEXT_SIZE ) )
{
bounds . width = GuiGetStyle ( DEFAULT , TEXT_SIZE ) + 2 * GuiGetStyle ( TEXTBOX , TEXT_INNER_PADDING ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Center the text vertically
int verticalPadding = ( bounds . height - 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) - GuiGetStyle ( DEFAULT , TEXT_SIZE ) ) / 2 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( verticalPadding < 0 )
{
// Make sure the height is sufficient
bounds . height = 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) + GuiGetStyle ( DEFAULT , TEXT_SIZE ) ;
verticalPadding = 0 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Calculate the drawing area for the text inside the control `bounds`
Rectangle textRec = { bounds . x + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) + GuiGetStyle ( TEXTBOX , TEXT_INNER_PADDING ) ,
2021-05-15 16:41:30 +00:00
bounds . y + verticalPadding + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) ,
bounds . width - 2 * ( GuiGetStyle ( TEXTBOX , TEXT_INNER_PADDING ) + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) ) ,
GuiGetStyle ( DEFAULT , TEXT_SIZE ) } ;
2021-05-14 05:59:33 +00:00
Vector2 cursorPos = { textRec . x , textRec . y } ; // This holds the coordinates inside textRec of the cursor at current position and will be recalculated later
bool active = GuiTextBoxIsActive ( bounds ) ; // Check if this textbox is the global active textbox
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int selStart = 0 , selLength = 0 , textStartIndex = 0 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Update control
//--------------------------------------------------------------------
if ( ( state ! = GUI_STATE_DISABLED ) & & ! guiLocked )
{
Vector2 mousePoint = GetMousePosition ( ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( editMode )
{
// Check if we are the global active textbox
// A textbox becomes active when the user clicks it :)
if ( ! active )
{
if ( CheckCollisionPointRec ( mousePoint , bounds ) & & ( IsMouseButtonPressed ( MOUSE_LEFT_BUTTON ) | | IsMouseButtonPressed ( MOUSE_RIGHT_BUTTON ) ) )
{
// Hurray!!! we just became the active textbox
active = true ;
GuiTextBoxSetActive ( bounds ) ;
}
}
else if ( ! CheckCollisionPointRec ( mousePoint , bounds ) & & IsMouseButtonPressed ( MOUSE_RIGHT_BUTTON ) )
{
// When active and the right mouse is clicked outside the textbox we should deactivate it
// NOTE: We set a dummy rect as the active textbox bounds
GuiTextBoxSetActive ( RAYGUI_CLITERAL ( Rectangle ) { 0 , 0 , - 1 , - 1 } ) ;
active = false ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( active )
{
state = GUI_STATE_PRESSED ;
framesCounter + + ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Make sure state doesn't have invalid values
if ( guiTextBoxState . cursor > length ) guiTextBoxState . cursor = - 1 ;
if ( guiTextBoxState . select > length ) guiTextBoxState . select = - 1 ;
if ( guiTextBoxState . start > length ) guiTextBoxState . start = - 1 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Check textbox state for changes and recalculate if necesary
if ( guiTextBoxState . cursor = = - 1 )
{
// Set cursor to last visible character in textbox
guiTextBoxState . cursor = GuiTextBoxMaxCharacters ( text , length , textRec ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . start = = - 1 )
{
// Force recalculate text start position and text start index
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// NOTE: start and index are always in sync
// start will hold the starting character position from where the text will be drawn
// while index will hold the byte index inside the text for that character
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor = = 0 )
{
guiTextBoxState . start = guiTextBoxState . index = 0 ; // No need to recalculate
}
else
{
int pos = 0 ;
int len = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
guiTextBoxState . index = GuiMeasureTextBoxRev ( text , len , textRec , & pos ) ;
guiTextBoxState . start = guiTextBoxState . cursor - pos + 1 ;
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// -----------------
// HANDLE KEY INPUT
// -----------------
// * -> | LSHIFT + -> move cursor to the right | increase selection by one
// * <- | LSHIFT + <- move cursor to the left | decrease selection by one
// * HOME | LSHIFT + HOME moves cursor to start of text | selects text from cursor to start of text
// * END | LSHIFT + END move cursor to end of text | selects text from cursor until end of text
// * CTRL + A select all characters in text
// * CTRL + C copy selected text
// * CTRL + X cut selected text
// * CTRL + V remove selected text, if any, then paste clipboard data
// * DEL delete character or selection after cursor
// * BACKSPACE delete character or selection before cursor
// TODO: Add more shortcuts (insert mode, select word, moveto/select prev/next word ...)
if ( IsKeyPressed ( KEY_RIGHT ) | | ( IsKeyDown ( KEY_RIGHT ) & & ( framesCounter % TEXTBOX_CURSOR_COOLDOWN = = 0 ) ) )
{
if ( IsKeyDown ( KEY_LEFT_SHIFT ) )
{
// Selecting
if ( guiTextBoxState . select = = - 1 ) guiTextBoxState . select = guiTextBoxState . cursor ; // Mark selection start
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
MoveTextBoxCursorRight ( text , length , textRec ) ;
}
else
{
if ( guiTextBoxState . select ! = - 1 & & guiTextBoxState . select ! = guiTextBoxState . cursor )
{
// Deselect and move cursor to end of selection
if ( guiTextBoxState . cursor < guiTextBoxState . select )
{
guiTextBoxState . cursor = guiTextBoxState . select - 1 ;
MoveTextBoxCursorRight ( text , length , textRec ) ;
}
}
else
{
// Move cursor to the right
MoveTextBoxCursorRight ( text , length , textRec ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
guiTextBoxState . select = - 1 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
framesCounter = 0 ;
}
else if ( IsKeyPressed ( KEY_LEFT ) | | ( IsKeyDown ( KEY_LEFT ) & & ( framesCounter % TEXTBOX_CURSOR_COOLDOWN = = 0 ) ) )
{
if ( IsKeyDown ( KEY_LEFT_SHIFT ) )
{
// Selecting
if ( guiTextBoxState . select = = - 1 ) guiTextBoxState . select = guiTextBoxState . cursor ; // Mark selection start
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
MoveTextBoxCursorLeft ( text ) ;
}
else
{
if ( ( guiTextBoxState . select ! = - 1 ) & & ( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
// Deselect and move cursor to start of selection
if ( guiTextBoxState . cursor > guiTextBoxState . select )
{
guiTextBoxState . cursor = guiTextBoxState . select ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . start > guiTextBoxState . cursor )
{
guiTextBoxState . start = guiTextBoxState . cursor ;
guiTextBoxState . index = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . start ) ; // Recalculate byte index
}
}
}
else
{
// Move cursor to the left
MoveTextBoxCursorLeft ( text ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
guiTextBoxState . select = - 1 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
framesCounter = 0 ;
}
else if ( IsKeyPressed ( KEY_BACKSPACE ) | | ( IsKeyDown ( KEY_BACKSPACE ) & & ( framesCounter % TEXTBOX_CURSOR_COOLDOWN ) = = 0 ) )
{
GuiTextBoxDelete ( text , length , true ) ;
}
else if ( IsKeyPressed ( KEY_DELETE ) | | ( IsKeyDown ( KEY_DELETE ) & & ( framesCounter % TEXTBOX_CURSOR_COOLDOWN ) = = 0 ) )
{
GuiTextBoxDelete ( text , length , false ) ;
}
else if ( IsKeyPressed ( KEY_HOME ) )
{
if ( IsKeyDown ( KEY_LEFT_SHIFT ) )
{
// Select from start of text to cursor
if ( ( guiTextBoxState . select > guiTextBoxState . cursor ) | | ( ( guiTextBoxState . select = = - 1 ) & & ( guiTextBoxState . cursor ! = 0 ) ) )
{
guiTextBoxState . select = guiTextBoxState . cursor ;
}
}
else guiTextBoxState . select = - 1 ; // Deselect everything
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Move cursor to start of text
guiTextBoxState . cursor = guiTextBoxState . start = guiTextBoxState . index = 0 ;
framesCounter = 0 ;
}
else if ( IsKeyPressed ( KEY_END ) )
{
int max = GuiCountCodepointsUntilNewline ( text ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( IsKeyDown ( KEY_LEFT_SHIFT ) )
{
if ( ( guiTextBoxState . select = = - 1 ) & & ( guiTextBoxState . cursor ! = max ) )
{
guiTextBoxState . select = guiTextBoxState . cursor ;
}
}
else guiTextBoxState . select = - 1 ; // Deselect everything
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int pos = 0 ;
guiTextBoxState . cursor = max ;
int len = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
guiTextBoxState . index = GuiMeasureTextBoxRev ( text , len , textRec , & pos ) ;
guiTextBoxState . start = guiTextBoxState . cursor - pos + 1 ;
}
else if ( IsKeyDown ( KEY_LEFT_CONTROL ) & & IsKeyPressed ( KEY_A ) ) GuiTextBoxSelectAll ( text ) ; // CTRL + A > Select all
else if ( IsKeyDown ( KEY_LEFT_CONTROL ) & & IsKeyPressed ( KEY_C ) ) GuiTextBoxCopy ( text ) ; // CTRL + C > Copy selected text to clipboard
else if ( IsKeyDown ( KEY_LEFT_CONTROL ) & & IsKeyPressed ( KEY_X ) ) GuiTextBoxCut ( text ) ; // CTRL + X > Cut selected text
else if ( IsKeyDown ( KEY_LEFT_CONTROL ) & & IsKeyPressed ( KEY_V ) ) GuiTextBoxPaste ( text , textSize ) ; // CTRL + V > Paste clipboard text
else if ( IsKeyPressed ( KEY_ENTER ) ) pressed = true ;
else
{
int key = GetKeyPressed ( ) ;
if ( ( key > = 32 ) & & ( ( guiTextBoxState . cursor + 1 ) < textSize ) )
{
if ( ( guiTextBoxState . select ! = - 1 ) & & ( guiTextBoxState . select ! = guiTextBoxState . cursor ) )
{
// Delete selection
GuiTextBoxDelete ( text , length , true ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Decode codepoint
char out [ 5 ] = { 0 } ;
int sz = EncodeCodepoint ( key , & out [ 0 ] ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( sz ! = 0 )
{
int startIdx = GuiTextBoxGetByteIndex ( text , 0 , 0 , guiTextBoxState . cursor ) ;
int endIdx = startIdx + sz ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( endIdx < = textSize & & length < textSize - 1 )
{
guiTextBoxState . cursor + + ;
guiTextBoxState . select = - 1 ;
memmove ( & text [ endIdx ] , & text [ startIdx ] , length - startIdx ) ;
memcpy ( & text [ startIdx ] , & out [ 0 ] , sz ) ;
length + = sz ;
text [ length ] = ' \0 ' ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . start ! = - 1 )
{
const int max = GuiTextBoxMaxCharacters ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( ( guiTextBoxState . cursor - guiTextBoxState . start ) > max ) guiTextBoxState . start = - 1 ;
}
}
}
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// -------------
// HANDLE MOUSE
// -------------
if ( CheckCollisionPointRec ( mousePoint , bounds ) )
{
if ( IsMouseButtonPressed ( MOUSE_LEFT_BUTTON ) )
{
if ( CheckCollisionPointRec ( mousePoint , textRec ) )
{
GuiTextBoxGetCursorFromMouse ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec , & guiTextBoxState . cursor ) ;
guiTextBoxState . cursor + = guiTextBoxState . start ;
guiTextBoxState . select = - 1 ;
}
else
{
// Clicked outside the `textRec` but still inside bounds
if ( mousePoint . x < = bounds . x + bounds . width / 2 ) guiTextBoxState . cursor = 0 + guiTextBoxState . start ;
else guiTextBoxState . cursor = guiTextBoxState . start + GuiTextBoxMaxCharacters ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec ) ;
guiTextBoxState . select = - 1 ;
}
}
else if ( IsMouseButtonDown ( MOUSE_LEFT_BUTTON ) )
{
int cursor = guiTextBoxState . cursor - guiTextBoxState . start ;
bool move = false ;
if ( CheckCollisionPointRec ( mousePoint , textRec ) )
{
GuiTextBoxGetCursorFromMouse ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec , & cursor ) ;
}
else
{
// Clicked outside the `textRec` but still inside bounds, this means that we must move the text
move = true ;
if ( mousePoint . x > bounds . x + bounds . width / 2 )
{
cursor = GuiTextBoxMaxCharacters ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec ) ;
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
guiTextBoxState . cursor = cursor + guiTextBoxState . start ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . select = = - 1 )
{
// Mark start of selection
guiTextBoxState . select = guiTextBoxState . cursor ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Move the text when cursor is positioned before or after the text
if ( ( framesCounter % TEXTBOX_CURSOR_COOLDOWN ) = = 0 & & move )
{
if ( cursor = = 0 ) MoveTextBoxCursorLeft ( text ) ;
else if ( cursor = = GuiTextBoxMaxCharacters ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec ) )
{
MoveTextBoxCursorRight ( text , length , textRec ) ;
}
}
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Calculate X coordinate of the blinking cursor
cursorPos . x = GuiTextBoxGetCursorCoordinates ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec , guiTextBoxState . cursor - guiTextBoxState . start ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Update variables
textStartIndex = guiTextBoxState . index ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . select = = - 1 )
{
selStart = guiTextBoxState . cursor ;
selLength = 0 ;
}
else if ( guiTextBoxState . cursor > guiTextBoxState . select )
{
selStart = guiTextBoxState . select ;
selLength = guiTextBoxState . cursor - guiTextBoxState . select ;
}
else
{
selStart = guiTextBoxState . cursor ;
selLength = guiTextBoxState . select - guiTextBoxState . cursor ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// We aren't drawing all of the text so make sure `DrawTextRecEx()` is selecting things correctly
if ( guiTextBoxState . start > selStart )
{
selLength - = guiTextBoxState . start - selStart ;
selStart = 0 ;
}
else selStart = selStart - guiTextBoxState . start ;
}
else state = GUI_STATE_FOCUSED ;
if ( IsKeyPressed ( KEY_ENTER ) | | ( ! CheckCollisionPointRec ( mousePoint , bounds ) & & IsMouseButtonPressed ( 0 ) ) ) pressed = true ;
}
else
{
if ( active & & IsKeyDown ( KEY_LEFT_CONTROL ) & & IsKeyPressed ( KEY_C ) )
{
// If active copy all text to clipboard even when disabled
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Backup textbox state
int select = guiTextBoxState . select ;
int cursor = guiTextBoxState . cursor ;
int start = guiTextBoxState . start ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( ( guiTextBoxState . select = = - 1 ) | | ( guiTextBoxState . select = = guiTextBoxState . cursor ) )
{
// If no selection then mark all text to be copied to clipboard
GuiTextBoxSelectAll ( text ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
GuiTextBoxCopy ( text ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Restore textbox state
guiTextBoxState . select = select ;
guiTextBoxState . cursor = cursor ;
guiTextBoxState . start = start ;
}
if ( CheckCollisionPointRec ( mousePoint , bounds ) )
{
state = GUI_STATE_FOCUSED ;
if ( IsMouseButtonPressed ( 0 ) ) pressed = true ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
}
if ( pressed ) framesCounter = 0 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Draw control
//--------------------------------------------------------------------
DrawRectangleLinesEx ( bounds , GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , Fade ( GetColor ( GuiGetStyle ( TEXTBOX , BORDER + ( state * 3 ) ) ) , guiAlpha ) ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( state = = GUI_STATE_PRESSED )
{
DrawRectangle ( bounds . x + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . y + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . width - 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . height - 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , Fade ( GetColor ( GuiGetStyle ( TEXTBOX , BASE_COLOR_FOCUSED ) ) , guiAlpha ) ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Draw blinking cursor
if ( editMode & & active & & ( ( framesCounter / TEXTEDIT_CURSOR_BLINK_FRAMES ) % 2 = = 0 ) & & selLength = = 0 )
{
DrawRectangle ( cursorPos . x , cursorPos . y , 1 , GuiGetStyle ( DEFAULT , TEXT_SIZE ) * 2 , Fade ( GetColor ( GuiGetStyle ( TEXTBOX , BORDER_COLOR_PRESSED ) ) , guiAlpha ) ) ;
}
}
else if ( state = = GUI_STATE_DISABLED )
{
DrawRectangle ( bounds . x + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . y + GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . width - 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , bounds . height - 2 * GuiGetStyle ( TEXTBOX , BORDER_WIDTH ) , Fade ( GetColor ( GuiGetStyle ( TEXTBOX , BASE_COLOR_DISABLED ) ) , guiAlpha ) ) ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
// Finally draw the text and selection
DrawTextRecEx ( guiFont , & text [ textStartIndex ] , textRec , GuiGetStyle ( DEFAULT , TEXT_SIZE ) , GuiGetStyle ( DEFAULT , TEXT_SPACING ) , false , Fade ( GetColor ( GuiGetStyle ( TEXTBOX , TEXT + ( state * 3 ) ) ) , guiAlpha ) , selStart , selLength , GetColor ( GuiGetStyle ( TEXTBOX , COLOR_SELECTED_FG ) ) , GetColor ( GuiGetStyle ( TEXTBOX , COLOR_SELECTED_BG ) ) ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return pressed ;
}
//----------------------------------------------------------------------------------
// Module Internal Functions Definition
//----------------------------------------------------------------------------------
static int GetPrevCodepoint ( const char * text , const char * start , int * prev )
{
int c = 0x3f ;
char * p = ( char * ) text ;
* prev = 1 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
for ( int i = 0 ; ( p > = start ) & & ( i < 4 ) ; p - - , i + + )
{
if ( ( ( ( unsigned char ) * p ) > > 6 ) ! = 2 )
{
c = GetNextCodepoint ( p , prev ) ;
break ;
}
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return c ;
}
// Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` or a `\n` is found.
// NOTE: If a invalid UTF8 sequence is encountered a `?`(0x3f) codepoint is counted instead.
static inline unsigned int GuiCountCodepointsUntilNewline ( const char * text )
{
unsigned int len = 0 ;
char * ptr = ( char * ) & text [ 0 ] ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
while ( ( * ptr ! = ' \0 ' ) & & ( * ptr ! = ' \n ' ) )
{
int next = 0 ;
int letter = GetNextCodepoint ( ptr , & next ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( letter = = 0x3f ) ptr + = 1 ;
else ptr + = next ;
+ + len ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return len ;
}
// Highly synchronized with calculations in DrawTextRecEx()
static int GuiMeasureTextBox ( const char * text , int length , Rectangle rec , int * pos , int mode )
{
// Get gui font properties
const Font font = guiFont ;
const float fontSize = GuiGetStyle ( DEFAULT , TEXT_SIZE ) ;
const float spacing = GuiGetStyle ( DEFAULT , TEXT_SPACING ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int textOffsetX = 0 ; // Offset between characters
float scaleFactor = 0.0f ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int letter = 0 ; // Current character
int index = 0 ; // Index position in sprite font
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
scaleFactor = fontSize / font . baseSize ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int i = 0 , k = 0 ;
int glyphWidth = 0 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
for ( i = 0 ; i < length ; i + + , k + + )
{
glyphWidth = 0 ;
int next = 1 ;
letter = GetNextCodepoint ( & text [ i ] , & next ) ;
if ( letter = = 0x3f ) next = 1 ;
index = GetGlyphIndex ( font , letter ) ;
i + = next - 1 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( letter ! = ' \n ' )
{
glyphWidth = ( font . chars [ index ] . advanceX = = 0 ) ?
2021-05-15 16:41:30 +00:00
( int ) ( font . recs [ index ] . width * scaleFactor + spacing ) :
( int ) ( font . chars [ index ] . advanceX * scaleFactor + spacing ) ;
2021-05-14 05:59:33 +00:00
if ( ( textOffsetX + glyphWidth + 1 ) > = rec . width ) break ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( ( mode = = GUI_MEASURE_MODE_CURSOR_POS ) & & ( * pos = = k ) ) break ;
else if ( mode = = GUI_MEASURE_MODE_CURSOR_COORDS )
{
// Check if the mouse pointer is inside the glyph rect
Rectangle grec = { rec . x + textOffsetX - 1 , rec . y , glyphWidth , ( font . baseSize + font . baseSize / 2 ) * scaleFactor - 1 } ;
Vector2 mouse = GetMousePosition ( ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( CheckCollisionPointRec ( mouse , grec ) )
{
// Smooth selection by dividing the glyph rectangle into 2 equal parts and checking where the mouse resides
if ( mouse . x > ( grec . x + glyphWidth / 2 ) )
{
textOffsetX + = glyphWidth ;
k + + ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
break ;
}
}
}
else break ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
textOffsetX + = glyphWidth ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
* pos = k ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return ( rec . x + textOffsetX - 1 ) ;
}
// Required by GuiTextBoxEx()
// Highly synchronized with calculations in DrawTextRecEx()
static int GuiMeasureTextBoxRev ( const char * text , int length , Rectangle rec , int * pos )
{
// Get gui font properties
const Font font = guiFont ;
const float fontSize = GuiGetStyle ( DEFAULT , TEXT_SIZE ) ;
const float spacing = GuiGetStyle ( DEFAULT , TEXT_SPACING ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int textOffsetX = 0 ; // Offset between characters
float scaleFactor = 0.0f ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int letter = 0 ; // Current character
int index = 0 ; // Index position in sprite font
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
scaleFactor = fontSize / font . baseSize ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
int i = 0 , k = 0 ;
int glyphWidth = 0 , prev = 1 ;
for ( i = length ; i > = 0 ; i - - , k + + )
{
glyphWidth = 0 ;
letter = GetPrevCodepoint ( & text [ i ] , & text [ 0 ] , & prev ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( letter = = 0x3f ) prev = 1 ;
index = GetGlyphIndex ( font , letter ) ;
i - = prev - 1 ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( letter ! = ' \n ' )
{
glyphWidth = ( font . chars [ index ] . advanceX = = 0 ) ?
2021-05-15 16:41:30 +00:00
( int ) ( font . recs [ index ] . width * scaleFactor + spacing ) :
( int ) ( font . chars [ index ] . advanceX * scaleFactor + spacing ) ;
2021-05-14 05:59:33 +00:00
if ( ( textOffsetX + glyphWidth + 1 ) > = rec . width ) break ;
}
else break ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
textOffsetX + = glyphWidth ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
* pos = k ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
return ( i + prev ) ;
}
// Calculate cursor coordinates based on the cursor position `pos` inside the `text`.
static inline int GuiTextBoxGetCursorCoordinates ( const char * text , int length , Rectangle rec , int pos )
{
return GuiMeasureTextBox ( text , length , rec , & pos , GUI_MEASURE_MODE_CURSOR_POS ) ;
}
// Calculate cursor position in textbox based on mouse coordinates.
static inline int GuiTextBoxGetCursorFromMouse ( const char * text , int length , Rectangle rec , int * pos )
{
return GuiMeasureTextBox ( text , length , rec , pos , GUI_MEASURE_MODE_CURSOR_COORDS ) ;
}
// Calculates how many characters is the textbox able to draw inside rec
static inline int GuiTextBoxMaxCharacters ( const char * text , int length , Rectangle rec )
{
int pos = - 1 ;
GuiMeasureTextBox ( text , length , rec , & pos , GUI_MEASURE_MODE_CURSOR_END ) ;
return pos ;
}
static inline void MoveTextBoxCursorRight ( const char * text , int length , Rectangle textRec )
{
// FIXME: Counting codepoints each time we press the key is expensive, find another way
int count = GuiCountCodepointsUntilNewline ( text ) ;
if ( guiTextBoxState . cursor < count ) guiTextBoxState . cursor + + ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
const int max = GuiTextBoxMaxCharacters ( & text [ guiTextBoxState . index ] , length - guiTextBoxState . index , textRec ) ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( ( guiTextBoxState . cursor - guiTextBoxState . start ) > max )
{
const int cidx = GuiTextBoxGetByteIndex ( text , guiTextBoxState . index , guiTextBoxState . start , guiTextBoxState . cursor ) ;
int pos = 0 ;
guiTextBoxState . index = GuiMeasureTextBoxRev ( text , cidx - 1 , textRec , & pos ) ;
guiTextBoxState . start = guiTextBoxState . cursor - pos ;
}
}
static inline void MoveTextBoxCursorLeft ( const char * text )
{
if ( guiTextBoxState . cursor > 0 ) guiTextBoxState . cursor - - ;
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
if ( guiTextBoxState . cursor < guiTextBoxState . start )
{
int prev = 0 ;
int letter = GetPrevCodepoint ( & text [ guiTextBoxState . index - 1 ] , text , & prev ) ;
if ( letter = = 0x3f ) prev = 1 ;
guiTextBoxState . start - - ;
guiTextBoxState . index - = prev ;
}
}
static int EncodeCodepoint ( unsigned int c , char out [ 5 ] )
{
int len = 0 ;
if ( c < = 0x7f )
{
out [ 0 ] = ( char ) c ;
len = 1 ;
}
else if ( c < = 0x7ff )
{
out [ 0 ] = ( char ) ( ( ( c > > 6 ) & 0x1f ) | 0xc0 ) ;
out [ 1 ] = ( char ) ( ( c & 0x3f ) | 0x80 ) ;
len = 2 ;
}
else if ( c < = 0xffff )
{
out [ 0 ] = ( char ) ( ( ( c > > 12 ) & 0x0f ) | 0xe0 ) ;
out [ 1 ] = ( char ) ( ( ( c > > 6 ) & 0x3f ) | 0x80 ) ;
out [ 2 ] = ( char ) ( ( c & 0x3f ) | 0x80 ) ;
len = 3 ;
}
else if ( c < = 0x10ffff )
{
out [ 0 ] = ( char ) ( ( ( c > > 18 ) & 0x07 ) | 0xf0 ) ;
out [ 1 ] = ( char ) ( ( ( c > > 12 ) & 0x3f ) | 0x80 ) ;
out [ 2 ] = ( char ) ( ( ( c > > 6 ) & 0x3f ) | 0x80 ) ;
out [ 3 ] = ( char ) ( ( c & 0x3f ) | 0x80 ) ;
len = 4 ;
}
2021-05-15 16:41:30 +00:00
2021-05-14 05:59:33 +00:00
out [ len ] = 0 ;
return len ;
}
# endif // GUI_TEXTBOX_EXTENDED_IMPLEMENTATION