mirror of
https://github.com/raysan5/raygui.git
synced 2026-02-03 12:49:17 -05:00
GuiTextBox() rework (#466)
* GuiTextBox: Fix possible overflow when handling backspace and delete Simplify code a bit by removing unnecessary checks * GuiTextBox: Fix CTRL+Backspace behavior Remove undefined behavior (previously called isspace with oob values) Fix unable to delete first character Fix handling of symbols like any other "standard" input field or text editor on Windows does it * GuiTextBox: Add CTRL+DELETE, CTRL+LEFT and CTRL+RIGHT handling Copy behavior from programs like notepad, Notepad++, OneNote, address bar in Edge, etc. * GuiTextBox: Simplify and improve auto-cursor code Remove one global variable and compact checks into one bool variable Fix auto cursor triggering immediately when button is held when clicking edit box Tuned the values, so they match cooldown and delay when entering text * GuiTextBox: Bring some checks in-line with the rest of the function
This commit is contained in:
217
src/raygui.h
217
src/raygui.h
@ -1395,8 +1395,7 @@ static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds
|
|||||||
|
|
||||||
static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*()
|
static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*()
|
||||||
//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking
|
//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking
|
||||||
static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down
|
static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay)
|
||||||
static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Style data array for all gui style properties (allocated on data segment by default)
|
// Style data array for all gui style properties (allocated on data segment by default)
|
||||||
@ -2489,10 +2488,10 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod
|
|||||||
int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
||||||
{
|
{
|
||||||
#if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
|
#if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)
|
||||||
#define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement
|
#define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 30 // Frames to wait for autocursor movement
|
||||||
#endif
|
#endif
|
||||||
#if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY)
|
#if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY)
|
||||||
#define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement
|
#define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 2 // Frames delay for autocursor movement
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@ -2526,15 +2525,6 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
|||||||
mouseCursor.x = -1;
|
mouseCursor.x = -1;
|
||||||
mouseCursor.width = 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
|
// Blink-cursor frame counter
|
||||||
//if (!autoCursorMode) blinkCursorFrameCounter++;
|
//if (!autoCursorMode) blinkCursorFrameCounter++;
|
||||||
//else blinkCursorFrameCounter = 0;
|
//else blinkCursorFrameCounter = 0;
|
||||||
@ -2552,6 +2542,15 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
|||||||
|
|
||||||
if (editMode)
|
if (editMode)
|
||||||
{
|
{
|
||||||
|
// GLOBAL: Auto-cursor movement logic
|
||||||
|
// NOTE: Keystrokes are handled repeatedly when button is held down for some time
|
||||||
|
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
autoCursorCounter = 0;
|
||||||
|
}
|
||||||
|
bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0);
|
||||||
|
|
||||||
state = STATE_PRESSED;
|
state = STATE_PRESSED;
|
||||||
|
|
||||||
if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
|
if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
|
||||||
@ -2629,113 +2628,175 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
|||||||
// Move cursor to end
|
// Move cursor to end
|
||||||
if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength;
|
if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength;
|
||||||
|
|
||||||
// Delete codepoint from text, after current cursor position
|
// Delete related codepoints from text, after current cursor position
|
||||||
if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))))
|
if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
||||||
{
|
{
|
||||||
autoCursorDelayCounter++;
|
int offset = textBoxCursorIndex;
|
||||||
|
int accCodepointSize = 0;
|
||||||
|
int nextCodepointSize;
|
||||||
|
int nextCodepoint;
|
||||||
|
// Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
|
||||||
|
// Not using isalnum() since it only works on ASCII characters
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
bool puctuation = ispunct(nextCodepoint & 0xFF);
|
||||||
|
while (offset < textLength)
|
||||||
|
{
|
||||||
|
if ((puctuation && !ispunct(nextCodepoint & 0xFF)) || (!puctuation && (isspace(nextCodepoint & 0xFF) || ispunct(nextCodepoint & 0xFF))))
|
||||||
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
|
accCodepointSize += nextCodepointSize;
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
}
|
||||||
|
// Check whitespace to delete (ASCII only)
|
||||||
|
while (offset < textLength)
|
||||||
|
{
|
||||||
|
if (!isspace(nextCodepoint & 0xFF))
|
||||||
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
|
accCodepointSize += nextCodepointSize;
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
// Move text after cursor forward (including final null terminator)
|
||||||
|
for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i];
|
||||||
|
|
||||||
|
textLength -= accCodepointSize;
|
||||||
|
}
|
||||||
|
// Delete single codepoint from text, after current cursor position
|
||||||
|
else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger)))
|
||||||
{
|
{
|
||||||
int nextCodepointSize = 0;
|
int nextCodepointSize = 0;
|
||||||
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
||||||
|
|
||||||
// Move backward text from cursor position
|
// Move text after cursor forward (including final null terminator)
|
||||||
for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize];
|
for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i];
|
||||||
|
|
||||||
textLength -= nextCodepointSize;
|
textLength -= nextCodepointSize;
|
||||||
if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
|
|
||||||
|
|
||||||
// Make sure text last character is EOL
|
|
||||||
text[textLength] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete related codepoints from text, before current cursor position
|
// Delete related codepoints from text, before current cursor position
|
||||||
if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
||||||
{
|
{
|
||||||
int i = textBoxCursorIndex - 1;
|
int offset = textBoxCursorIndex;
|
||||||
int accCodepointSize = 0;
|
int accCodepointSize = 0;
|
||||||
|
int prevCodepointSize;
|
||||||
|
int prevCodepoint;
|
||||||
|
|
||||||
// Move cursor to the end of word if on space already
|
// Check whitespace to delete (ASCII only)
|
||||||
while ((i > 0) && isspace(text[i]))
|
while (offset > 0)
|
||||||
{
|
{
|
||||||
int prevCodepointSize = 0;
|
prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
|
||||||
GetCodepointPrevious(text + i, &prevCodepointSize);
|
if (!isspace(prevCodepoint & 0xFF))
|
||||||
i -= prevCodepointSize;
|
break;
|
||||||
|
offset -= prevCodepointSize;
|
||||||
|
accCodepointSize += prevCodepointSize;
|
||||||
|
}
|
||||||
|
// Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace)
|
||||||
|
// Not using isalnum() since it only works on ASCII characters
|
||||||
|
bool puctuation = ispunct(prevCodepoint & 0xFF);
|
||||||
|
while (offset > 0)
|
||||||
|
{
|
||||||
|
prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
|
||||||
|
if ((puctuation && !ispunct(prevCodepoint & 0xFF)) || (!puctuation && (isspace(prevCodepoint & 0xFF) || ispunct(prevCodepoint & 0xFF))))
|
||||||
|
break;
|
||||||
|
offset -= prevCodepointSize;
|
||||||
accCodepointSize += prevCodepointSize;
|
accCodepointSize += prevCodepointSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move cursor to the start of the word
|
// Move text after cursor forward (including final null terminator)
|
||||||
while ((i > 0) && !isspace(text[i]))
|
for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i];
|
||||||
{
|
|
||||||
int prevCodepointSize = 0;
|
|
||||||
GetCodepointPrevious(text + i, &prevCodepointSize);
|
|
||||||
i -= prevCodepointSize;
|
|
||||||
accCodepointSize += prevCodepointSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move forward text from cursor position
|
|
||||||
for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize];
|
|
||||||
|
|
||||||
// Prevent cursor index from decrementing past 0
|
|
||||||
if (textBoxCursorIndex > 0)
|
|
||||||
{
|
|
||||||
textBoxCursorIndex -= accCodepointSize;
|
|
||||||
textLength -= accCodepointSize;
|
textLength -= accCodepointSize;
|
||||||
|
textBoxCursorIndex -= accCodepointSize;
|
||||||
}
|
}
|
||||||
|
// Delete single codepoint from text, before current cursor position
|
||||||
// Make sure text last character is EOL
|
else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger)))
|
||||||
text[textLength] = '\0';
|
|
||||||
}
|
|
||||||
else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))))
|
|
||||||
{
|
|
||||||
autoCursorDelayCounter++;
|
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
|
||||||
{
|
{
|
||||||
int prevCodepointSize = 0;
|
int prevCodepointSize = 0;
|
||||||
|
|
||||||
// Prevent cursor index from decrementing past 0
|
|
||||||
if (textBoxCursorIndex > 0)
|
|
||||||
{
|
|
||||||
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
||||||
|
|
||||||
// Move backward text from cursor position
|
// Move text after cursor forward (including final null terminator)
|
||||||
for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize];
|
for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i];
|
||||||
|
|
||||||
textBoxCursorIndex -= prevCodepointSize;
|
|
||||||
textLength -= prevCodepointSize;
|
textLength -= prevCodepointSize;
|
||||||
}
|
textBoxCursorIndex -= prevCodepointSize;
|
||||||
|
|
||||||
// Make sure text last character is EOL
|
|
||||||
text[textLength] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move cursor position with keys
|
// Move cursor position with keys
|
||||||
if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))
|
if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
||||||
{
|
{
|
||||||
autoCursorDelayCounter++;
|
int offset = textBoxCursorIndex;
|
||||||
|
int accCodepointSize = 0;
|
||||||
|
int prevCodepointSize;
|
||||||
|
int prevCodepoint;
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
// Check whitespace to skip (ASCII only)
|
||||||
|
while (offset > 0)
|
||||||
|
{
|
||||||
|
prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
|
||||||
|
if (!isspace(prevCodepoint & 0xFF))
|
||||||
|
break;
|
||||||
|
offset -= prevCodepointSize;
|
||||||
|
accCodepointSize += prevCodepointSize;
|
||||||
|
}
|
||||||
|
// Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
|
||||||
|
// Not using isalnum() since it only works on ASCII characters
|
||||||
|
bool puctuation = ispunct(prevCodepoint & 0xFF);
|
||||||
|
while (offset > 0)
|
||||||
|
{
|
||||||
|
prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
|
||||||
|
if ((puctuation && !ispunct(prevCodepoint & 0xFF)) || (!puctuation && (isspace(prevCodepoint & 0xFF) || ispunct(prevCodepoint & 0xFF))))
|
||||||
|
break;
|
||||||
|
offset -= prevCodepointSize;
|
||||||
|
accCodepointSize += prevCodepointSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
textBoxCursorIndex = offset;
|
||||||
|
}
|
||||||
|
else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger)))
|
||||||
{
|
{
|
||||||
int prevCodepointSize = 0;
|
int prevCodepointSize = 0;
|
||||||
if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
||||||
|
|
||||||
if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize;
|
textBoxCursorIndex -= prevCodepointSize;
|
||||||
}
|
}
|
||||||
}
|
else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
||||||
else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))
|
|
||||||
{
|
{
|
||||||
autoCursorDelayCounter++;
|
int offset = textBoxCursorIndex;
|
||||||
|
int accCodepointSize = 0;
|
||||||
|
int nextCodepointSize;
|
||||||
|
int nextCodepoint;
|
||||||
|
// Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace)
|
||||||
|
// Not using isalnum() since it only works on ASCII characters
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
bool puctuation = ispunct(nextCodepoint & 0xFF);
|
||||||
|
while (offset < textLength)
|
||||||
|
{
|
||||||
|
if ((puctuation && !ispunct(nextCodepoint & 0xFF)) || (!puctuation && (isspace(nextCodepoint & 0xFF) || ispunct(nextCodepoint & 0xFF))))
|
||||||
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
|
accCodepointSize += nextCodepointSize;
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
}
|
||||||
|
// Check whitespace to skip (ASCII only)
|
||||||
|
while (offset < textLength)
|
||||||
|
{
|
||||||
|
if (!isspace(nextCodepoint & 0xFF))
|
||||||
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
|
accCodepointSize += nextCodepointSize;
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
textBoxCursorIndex = offset;
|
||||||
|
}
|
||||||
|
else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger)))
|
||||||
{
|
{
|
||||||
int nextCodepointSize = 0;
|
int nextCodepointSize = 0;
|
||||||
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
||||||
|
|
||||||
if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize;
|
textBoxCursorIndex += nextCodepointSize;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move cursor position with mouse
|
// Move cursor position with mouse
|
||||||
@ -2791,6 +2852,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
|||||||
(!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
|
(!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)))
|
||||||
{
|
{
|
||||||
textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index
|
textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index
|
||||||
|
autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2803,6 +2865,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode)
|
|||||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
|
||||||
{
|
{
|
||||||
textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text
|
textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text
|
||||||
|
autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user