mirror of
https://github.com/raysan5/raygui.git
synced 2025-12-25 10:22:33 -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:
241
src/raygui.h
241
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;
|
||||||
if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
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)
|
||||||
{
|
{
|
||||||
int nextCodepointSize = 0;
|
if ((puctuation && !ispunct(nextCodepoint & 0xFF)) || (!puctuation && (isspace(nextCodepoint & 0xFF) || ispunct(nextCodepoint & 0xFF))))
|
||||||
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
// Move backward text from cursor position
|
accCodepointSize += nextCodepointSize;
|
||||||
for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize];
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
|
||||||
textLength -= nextCodepointSize;
|
|
||||||
if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength;
|
|
||||||
|
|
||||||
// Make sure text last character is EOL
|
|
||||||
text[textLength] = '\0';
|
|
||||||
}
|
}
|
||||||
|
// Check whitespace to delete (ASCII only)
|
||||||
|
while (offset < textLength)
|
||||||
|
{
|
||||||
|
if (!isspace(nextCodepoint & 0xFF))
|
||||||
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
|
accCodepointSize += nextCodepointSize;
|
||||||
|
nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
||||||
|
|
||||||
|
// Move text after cursor forward (including final null terminator)
|
||||||
|
for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i];
|
||||||
|
|
||||||
|
textLength -= nextCodepointSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
textLength -= accCodepointSize;
|
||||||
for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize];
|
textBoxCursorIndex -= accCodepointSize;
|
||||||
|
|
||||||
// Prevent cursor index from decrementing past 0
|
|
||||||
if (textBoxCursorIndex > 0)
|
|
||||||
{
|
|
||||||
textBoxCursorIndex -= accCodepointSize;
|
|
||||||
textLength -= accCodepointSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure text last character is EOL
|
|
||||||
text[textLength] = '\0';
|
|
||||||
}
|
}
|
||||||
else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))))
|
// Delete single codepoint from text, before current cursor position
|
||||||
|
else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger)))
|
||||||
{
|
{
|
||||||
autoCursorDelayCounter++;
|
int prevCodepointSize = 0;
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
||||||
{
|
|
||||||
int prevCodepointSize = 0;
|
|
||||||
|
|
||||||
// Prevent cursor index from decrementing past 0
|
// Move text after cursor forward (including final null terminator)
|
||||||
if (textBoxCursorIndex > 0)
|
for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i];
|
||||||
{
|
|
||||||
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
|
||||||
|
|
||||||
// Move backward text from cursor position
|
textLength -= prevCodepointSize;
|
||||||
for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize];
|
textBoxCursorIndex -= prevCodepointSize;
|
||||||
|
|
||||||
textBoxCursorIndex -= prevCodepointSize;
|
|
||||||
textLength -= 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)
|
||||||
{
|
{
|
||||||
int prevCodepointSize = 0;
|
prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize);
|
||||||
if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
if (!isspace(prevCodepoint & 0xFF))
|
||||||
|
break;
|
||||||
if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize;
|
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 (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))
|
else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger)))
|
||||||
{
|
{
|
||||||
autoCursorDelayCounter++;
|
int prevCodepointSize = 0;
|
||||||
|
GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize);
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames
|
textBoxCursorIndex -= prevCodepointSize;
|
||||||
|
}
|
||||||
|
else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)))
|
||||||
|
{
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
int nextCodepointSize = 0;
|
if ((puctuation && !ispunct(nextCodepoint & 0xFF)) || (!puctuation && (isspace(nextCodepoint & 0xFF) || ispunct(nextCodepoint & 0xFF))))
|
||||||
GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize);
|
break;
|
||||||
|
offset += nextCodepointSize;
|
||||||
if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
textBoxCursorIndex = offset;
|
||||||
|
}
|
||||||
|
else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger)))
|
||||||
|
{
|
||||||
|
int nextCodepointSize = 0;
|
||||||
|
GetCodepointNext(text + 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