4171: Add basic selection support for ui ex edit boxes r=def- a=Jupeyy

This changes the hotkey for "exclude" to CTRL + SHIFT + X, because else it collides with Cutting strings out of a selection

This is defs not perfect, it basically ignores keyboard keys that could also alter selection (e.g. shift + right) but i didnt want to break the IME stuff, bcs i don't understand it

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
bors[bot] 2021-10-04 18:07:09 +00:00 committed by GitHub
commit 26d54f8c8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 670 additions and 232 deletions

View file

@ -176,6 +176,9 @@ struct STextContainer
int m_RenderFlags;
bool m_HasCursor;
bool m_HasSelection;
void Reset()
{
m_pFont = NULL;
@ -193,6 +196,9 @@ struct STextContainer
m_UnscaledFontSize = 0.f;
m_RenderFlags = 0;
m_HasCursor = false;
m_HasSelection = false;
}
};
@ -212,6 +218,8 @@ class CTextRender : public IEngineTextRender
std::vector<CFont *> m_Fonts;
CFont *m_pCurFont;
int64_t m_CursorRenderTime;
int GetFreeTextContainerIndex()
{
if(m_FirstFreeTextContainerIndex == -1)
@ -614,10 +622,8 @@ public:
m_pDefaultFont = 0;
m_FTLibrary = 0;
// GL_LUMINANCE can be good for debugging
//m_FontTextureFormat = GL_ALPHA;
m_RenderFlags = 0;
m_CursorRenderTime = time_get_microseconds();
}
virtual ~CTextRender()
@ -809,6 +815,9 @@ public:
pCursor->m_ReleaseMouseY = 0;
pCursor->m_SelectionStart = 0;
pCursor->m_SelectionEnd = 0;
pCursor->m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE;
pCursor->m_CursorCharacter = -1;
}
virtual void MoveCursor(CTextCursor *pCursor, float x, float y)
@ -969,7 +978,7 @@ public:
AppendTextContainer(pCursor, ContainerIndex, pText, Length);
if(TextContainer.m_StringInfo.m_CharacterQuads.size() == 0 && IsRendered)
if(TextContainer.m_StringInfo.m_CharacterQuads.size() == 0 && TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1 && IsRendered)
{
FreeTextContainer(ContainerIndex);
return -1;
@ -977,7 +986,7 @@ public:
else
{
TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_CharacterQuads.size();
if(Graphics()->IsTextBufferingEnabled() && IsRendered)
if(Graphics()->IsTextBufferingEnabled() && IsRendered && !TextContainer.m_StringInfo.m_CharacterQuads.empty())
{
if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_AUTOMATIC_QUAD_UPLOAD) == 0)
{
@ -1064,6 +1073,13 @@ public:
bool IsRendered = (pCursor->m_Flags & TEXTFLAG_RENDER) != 0;
IGraphics::CQuadItem CursorQuads[2];
bool HasCursor = false;
float CursorInnerWidth = (((ScreenX1 - ScreenX0) / Graphics()->ScreenWidth())) * 2;
float CursorOuterWidth = CursorInnerWidth * 2;
float CursorOuterInnerDiff = (CursorOuterWidth - CursorInnerWidth) / 2;
std::vector<IGraphics::CQuadItem> SelectionQuads;
bool SelectionStarted = false;
bool SelectionUsedPress = false;
@ -1071,15 +1087,22 @@ public:
int SelectionStartChar = -1;
int SelectionEndChar = -1;
auto &&CheckInsideChar = [&](bool CheckOuter, int CursorX, int CursorY, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool {
if((LastCharX - LastCharWidth / 2 <= CursorX &&
CharX + CharWidth / 2 > CursorX &&
CharY - Size <= CursorY &&
CharY > CursorY) ||
(CheckOuter &&
CharY - Size > CursorY))
{
return true;
}
return false;
};
auto &&CheckSelectionStart = [&](bool CheckOuter, int CursorX, int CursorY, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) {
if(!SelectionStarted && !SelectionUsedCase)
{
if((LastCharX - LastCharWidth / 2 <= CursorX &&
CharX + CharWidth / 2 > CursorX &&
CharY - Size <= CursorY &&
CharY > CursorY) ||
(CheckOuter &&
CharY - Size > CursorY))
if(CheckInsideChar(CheckOuter, CursorX, CursorY, LastCharX, LastCharWidth, CharX, CharWidth, CharY))
{
SelectionChar = CharacterCounter;
SelectionStarted = !SelectionStarted;
@ -1087,14 +1110,21 @@ public:
}
}
};
auto &&CheckOutsideChar = [&](bool CheckOuter, int CursorX, int CursorY, float CharX, float CharWidth, float CharY) -> bool {
if((CharX + CharWidth / 2 > CursorX &&
CharY - Size <= CursorY &&
CharY > CursorY) ||
(CheckOuter &&
CharY <= CursorY))
{
return true;
}
return false;
};
auto &&CheckSelectionEnd = [&](bool CheckOuter, int CursorX, int CursorY, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) {
if(SelectionStarted && !SelectionUsedCase)
{
if((CharX + CharWidth / 2 > CursorX &&
CharY - Size <= CursorY &&
CharY > CursorY) ||
(CheckOuter &&
CharY <= CursorY))
if(CheckOutsideChar(CheckOuter, CursorX, CursorY, CharX, CharWidth, CharY))
{
SelectionChar = CharacterCounter;
SelectionStarted = !SelectionStarted;
@ -1123,13 +1153,17 @@ public:
++LineCount;
};
if(pCursor->m_CalculateSelectionMode != TEXT_CURSOR_SELECTION_MODE_NONE)
if(pCursor->m_CalculateSelectionMode != TEXT_CURSOR_SELECTION_MODE_NONE || pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE)
{
if(IsRendered)
{
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1)
Graphics()->QuadContainerReset(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex);
}
// if in calculate mode, also calculate the cursor
if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE)
pCursor->m_CursorCharacter = -1;
}
while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines))
@ -1141,6 +1175,7 @@ public:
int Wlen = minimum(WordLength((char *)pCurrent), (int)(pEnd - pCurrent));
CTextCursor Compare = *pCursor;
Compare.m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE;
Compare.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE;
Compare.m_X = DrawX;
Compare.m_Y = DrawY;
Compare.m_Flags &= ~TEXTFLAG_RENDER;
@ -1152,6 +1187,7 @@ public:
// word can't be fitted in one line, cut it
CTextCursor Cutter = *pCursor;
Cutter.m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE;
Cutter.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE;
Cutter.m_GlyphCount = 0;
Cutter.m_CharCount = 0;
Cutter.m_X = DrawX;
@ -1284,12 +1320,20 @@ public:
float SelWidth = (CharX + maximum(Advance, CharWidth - OutLineRealDiff / 2)) - (LastSelX + LastSelWidth);
float SelX = (LastSelX + LastSelWidth);
if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE)
{
if(pCursor->m_CursorCharacter == -1 && CheckInsideChar(CharacterCounter == 0, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, CharacterCounter == 0 ? std::numeric_limits<float>::lowest() : LastCharX, LastCharWidth, CharX, CharWidth, TmpY))
{
pCursor->m_CursorCharacter = CharacterCounter;
}
}
if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE)
{
if(CharacterCounter == 0)
{
CheckSelectionStart(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, LastCharX, LastCharWidth, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, LastCharX, LastCharWidth, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, std::numeric_limits<float>::lowest(), 0, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, std::numeric_limits<float>::lowest(), 0, CharX, CharWidth, TmpY);
}
// if selection didn't start and the mouse pos is atleast on 50% of the right side of the character start
@ -1303,15 +1347,27 @@ public:
if((int)CharacterCounter == pCursor->m_SelectionStart)
{
SelectionStarted = !SelectionStarted;
SelectionStartChar = CharacterCounter;
SelectionUsedPress = true;
}
if((int)CharacterCounter == pCursor->m_SelectionEnd)
{
SelectionStarted = !SelectionStarted;
SelectionEndChar = CharacterCounter;
SelectionUsedRelease = true;
}
}
if(pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE)
{
if((int)CharacterCounter == pCursor->m_CursorCharacter)
{
HasCursor = true;
CursorQuads[0] = IGraphics::CQuadItem(SelX - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size);
CursorQuads[1] = IGraphics::CQuadItem(SelX, DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2);
}
}
pCursor->m_MaxCharacterHeight = maximum(pCursor->m_MaxCharacterHeight, CharHeight + BearingY);
if(NextCharacter == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE) != 0 && Character != ' ')
@ -1377,25 +1433,52 @@ public:
}
else if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET)
{
if((int)CharacterCounter == pCursor->m_SelectionStart)
{
SelectionStarted = !SelectionStarted;
SelectionStartChar = CharacterCounter;
SelectionUsedPress = true;
}
if((int)CharacterCounter == pCursor->m_SelectionEnd)
{
SelectionStarted = !SelectionStarted;
SelectionEndChar = CharacterCounter;
SelectionUsedRelease = true;
}
}
if(!SelectionQuads.empty() && SelectionUsedPress && SelectionUsedRelease)
if(pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE)
{
if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && pCursor->m_CursorCharacter == -1 && CheckOutsideChar(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, std::numeric_limits<float>::max(), 0, DrawY + Size))
{
pCursor->m_CursorCharacter = CharacterCounter;
}
if((int)CharacterCounter == pCursor->m_CursorCharacter)
{
HasCursor = true;
CursorQuads[0] = IGraphics::CQuadItem((LastSelX + LastSelWidth) - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size);
CursorQuads[1] = IGraphics::CQuadItem((LastSelX + LastSelWidth), DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2);
}
}
bool HasSelection = !SelectionQuads.empty() && SelectionUsedPress && SelectionUsedRelease;
if((HasSelection || HasCursor) && IsRendered)
{
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
if(SelectionQuads.size() > 0)
{
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1)
TextContainer.m_StringInfo.m_SelectionQuadContainerIndex = Graphics()->CreateQuadContainer();
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1)
TextContainer.m_StringInfo.m_SelectionQuadContainerIndex = Graphics()->CreateQuadContainer(false);
if(HasCursor)
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, CursorQuads, 2);
if(HasSelection)
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, &SelectionQuads[0], (int)SelectionQuads.size());
Graphics()->QuadContainerUpload(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex);
pCursor->m_SelectionStart = SelectionStartChar;
pCursor->m_SelectionEnd = SelectionEndChar;
}
TextContainer.m_HasCursor = HasCursor;
TextContainer.m_HasSelection = HasSelection;
pCursor->m_SelectionStart = SelectionStartChar;
pCursor->m_SelectionEnd = SelectionEndChar;
}
// even if no text is drawn the cursor position will be adjusted
@ -1407,7 +1490,8 @@ public:
}
// just deletes and creates text container
virtual void RecreateTextContainer(CTextCursor *pCursor, int TextContainerIndex, const char *pText, int Length = -1)
virtual void
RecreateTextContainer(CTextCursor *pCursor, int TextContainerIndex, const char *pText, int Length = -1)
{
DeleteTextContainer(TextContainerIndex);
CreateTextContainer(pCursor, pText, Length);
@ -1459,70 +1543,89 @@ public:
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1)
{
Graphics()->TextureClear();
Graphics()->SetColor(m_SelectionColor);
Graphics()->RenderQuadContainerEx(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, 0, -1, 0, 0);
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
/*static int64_t s_CursorRenderTime = time_get_microseconds();
if((time_get_microseconds() - s_CursorRenderTime) > 500000)
Graphics()->RenderQuadContainer(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, 0, 1);
if((time_get_microseconds() - s_CursorRenderTime) > 1000000)
s_CursorRenderTime = time_get_microseconds();*/
}
if(Graphics()->IsTextBufferingEnabled())
{
Graphics()->TextureClear();
// render buffered text
Graphics()->RenderText(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, TextContainer.m_StringInfo.m_QuadNum, pFont->m_CurTextureDimensions[0], pFont->m_aTextures[0].Id(), pFont->m_aTextures[1].Id(), (float *)pTextColor, (float *)pTextOutlineColor);
}
else
{
// render tiles
float UVScale = 1.0f / pFont->m_CurTextureDimensions[0];
Graphics()->FlushVertices();
Graphics()->TextureSet(pFont->m_aTextures[1]);
Graphics()->QuadsBegin();
for(size_t i = 0; i < TextContainer.m_StringInfo.m_QuadNum; ++i)
if(TextContainer.m_HasSelection)
{
STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_CharacterQuads[i];
Graphics()->SetColor(TextCharQuad.m_Vertices[0].m_Color.m_R / 255.f * pTextOutlineColor->m_R, TextCharQuad.m_Vertices[0].m_Color.m_G / 255.f * pTextOutlineColor->m_G, TextCharQuad.m_Vertices[0].m_Color.m_B / 255.f * pTextOutlineColor->m_B, TextCharQuad.m_Vertices[0].m_Color.m_A / 255.f * pTextOutlineColor->m_A);
Graphics()->QuadsSetSubset(TextCharQuad.m_Vertices[0].m_U * UVScale, TextCharQuad.m_Vertices[0].m_V * UVScale, TextCharQuad.m_Vertices[2].m_U * UVScale, TextCharQuad.m_Vertices[2].m_V * UVScale);
IGraphics::CQuadItem QuadItem(TextCharQuad.m_Vertices[0].m_X, TextCharQuad.m_Vertices[0].m_Y, TextCharQuad.m_Vertices[1].m_X - TextCharQuad.m_Vertices[0].m_X, TextCharQuad.m_Vertices[2].m_Y - TextCharQuad.m_Vertices[0].m_Y);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->TextureClear();
Graphics()->SetColor(m_SelectionColor);
Graphics()->RenderQuadContainerEx(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, TextContainer.m_HasCursor ? 2 : 0, -1, 0, 0);
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
}
}
if(pTextColor->m_A != 0)
if(TextContainer.m_StringInfo.m_QuadNum > 0)
{
if(Graphics()->IsTextBufferingEnabled())
{
Graphics()->QuadsEndKeepVertices();
Graphics()->TextureClear();
// render buffered text
Graphics()->RenderText(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, TextContainer.m_StringInfo.m_QuadNum, pFont->m_CurTextureDimensions[0], pFont->m_aTextures[0].Id(), pFont->m_aTextures[1].Id(), (float *)pTextColor, (float *)pTextOutlineColor);
}
else
{
// render tiles
float UVScale = 1.0f / pFont->m_CurTextureDimensions[0];
Graphics()->TextureSet(pFont->m_aTextures[0]);
Graphics()->FlushVertices();
Graphics()->TextureSet(pFont->m_aTextures[1]);
Graphics()->QuadsBegin();
for(size_t i = 0; i < TextContainer.m_StringInfo.m_QuadNum; ++i)
{
STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_CharacterQuads[i];
unsigned char CR = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_R) * pTextColor->m_R);
unsigned char CG = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_G) * pTextColor->m_G);
unsigned char CB = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_B) * pTextColor->m_B);
unsigned char CA = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_A) * pTextColor->m_A);
Graphics()->ChangeColorOfQuadVertices((int)i, CR, CG, CB, CA);
Graphics()->SetColor(TextCharQuad.m_Vertices[0].m_Color.m_R / 255.f * pTextOutlineColor->m_R, TextCharQuad.m_Vertices[0].m_Color.m_G / 255.f * pTextOutlineColor->m_G, TextCharQuad.m_Vertices[0].m_Color.m_B / 255.f * pTextOutlineColor->m_B, TextCharQuad.m_Vertices[0].m_Color.m_A / 255.f * pTextOutlineColor->m_A);
Graphics()->QuadsSetSubset(TextCharQuad.m_Vertices[0].m_U * UVScale, TextCharQuad.m_Vertices[0].m_V * UVScale, TextCharQuad.m_Vertices[2].m_U * UVScale, TextCharQuad.m_Vertices[2].m_V * UVScale);
IGraphics::CQuadItem QuadItem(TextCharQuad.m_Vertices[0].m_X, TextCharQuad.m_Vertices[0].m_Y, TextCharQuad.m_Vertices[1].m_X - TextCharQuad.m_Vertices[0].m_X, TextCharQuad.m_Vertices[2].m_Y - TextCharQuad.m_Vertices[0].m_Y);
Graphics()->QuadsDrawTL(&QuadItem, 1);
}
// render non outlined
Graphics()->QuadsDrawCurrentVertices(false);
}
else
Graphics()->QuadsEnd();
if(pTextColor->m_A != 0)
{
Graphics()->QuadsEndKeepVertices();
// reset
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
Graphics()->TextureSet(pFont->m_aTextures[0]);
for(size_t i = 0; i < TextContainer.m_StringInfo.m_QuadNum; ++i)
{
STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_CharacterQuads[i];
unsigned char CR = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_R) * pTextColor->m_R);
unsigned char CG = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_G) * pTextColor->m_G);
unsigned char CB = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_B) * pTextColor->m_B);
unsigned char CA = (unsigned char)((float)(TextCharQuad.m_Vertices[0].m_Color.m_A) * pTextColor->m_A);
Graphics()->ChangeColorOfQuadVertices((int)i, CR, CG, CB, CA);
}
// render non outlined
Graphics()->QuadsDrawCurrentVertices(false);
}
else
Graphics()->QuadsEnd();
// reset
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
}
}
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1)
{
if(TextContainer.m_HasCursor)
{
int64_t CurTime = time_get_microseconds();
Graphics()->TextureClear();
if((CurTime - m_CursorRenderTime) > 500000)
{
Graphics()->SetColor(*pTextOutlineColor);
Graphics()->RenderQuadContainerEx(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, 0, 1, 0, 0);
Graphics()->SetColor(*pTextColor);
Graphics()->RenderQuadContainerEx(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, 1, 1, 0, 0);
}
if((CurTime - m_CursorRenderTime) > 1000000)
m_CursorRenderTime = time_get_microseconds();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
}
}
}
@ -1735,6 +1838,69 @@ public:
return OffUTF8Start != -1 && OffUTF8End != -1;
}
virtual bool UTF8OffToDecodedOff(const char *pText, int UTF8Off, int &DecodedOff)
{
const char *pIt = pText;
DecodedOff = -1;
int CharCount = 0;
while(*pIt)
{
if((int)(intptr_t)(pIt - pText) == UTF8Off)
{
DecodedOff = CharCount;
return true;
}
const char *pTmp = pIt;
int Character = str_utf8_decode(&pTmp);
if(Character == -1)
return false;
pIt = pTmp;
++CharCount;
}
if((int)(std::intptr_t)(pIt - pText) == UTF8Off)
{
DecodedOff = CharCount;
return true;
}
return false;
}
virtual bool DecodedOffToUTF8Off(const char *pText, int DecodedOff, int &UTF8Off)
{
const char *pIt = pText;
UTF8Off = -1;
int CharCount = 0;
while(*pIt)
{
const char *pTmp = pIt;
int Character = str_utf8_decode(&pTmp);
if(Character == -1)
return false;
if(CharCount == DecodedOff)
{
UTF8Off = (int)((std::intptr_t)(pIt - pText));
return true;
}
pIt = pTmp;
++CharCount;
}
if(CharCount == DecodedOff)
UTF8Off = (int)((std::intptr_t)(pIt - pText));
return UTF8Off != -1;
}
virtual void OnWindowResize()
{
bool FoundTextContainer = false;

View file

@ -45,6 +45,16 @@ enum ETextCursorSelectionMode
TEXT_CURSOR_SELECTION_MODE_SET,
};
enum ETextCursorCursorMode
{
// ignore any kind of cursor
TEXT_CURSOR_CURSOR_MODE_NONE = 0,
// calculates the cursor based on the mouse release cursor position
TEXT_CURSOR_CURSOR_MODE_CALCULATE,
// sets the cursor based on the current character (this value has to be decoded character offset)
TEXT_CURSOR_CURSOR_MODE_SET,
};
class CTextCursor
{
public:
@ -71,6 +81,7 @@ public:
// these coordinates are repsected if selection mode is set to calculate @see ETextCursorSelectionMode
int m_PressMouseX;
int m_PressMouseY;
// these coordinates are repsected if selection/cursor mode is set to calculate @see ETextCursorSelectionMode / @see ETextCursorCursorMode
int m_ReleaseMouseX;
int m_ReleaseMouseY;
@ -78,6 +89,10 @@ public:
// also note, that these are the character offsets decoded
int m_SelectionStart;
int m_SelectionEnd;
ETextCursorCursorMode m_CursorMode;
// note this is the decoded character offset
int m_CursorCharacter;
};
struct STextRenderColor
@ -155,6 +170,8 @@ public:
virtual int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontHeight) = 0;
virtual bool SelectionToUTF8OffSets(const char *pText, int SelStart, int SelEnd, int &OffUTF8Start, int &OffUTF8End) = 0;
virtual bool UTF8OffToDecodedOff(const char *pText, int UTF8Off, int &DecodedOff) = 0;
virtual bool DecodedOffToUTF8Off(const char *pText, int DecodedOff, int &UTF8Off) = 0;
// old foolish interface
virtual void TextColor(float r, float g, float b, float a) = 0;

View file

@ -4,6 +4,7 @@
#include <base/tl/sorted_array.h>
#include <limits.h>
#include <limits>
#include <math.h>
#include <game/generated/client_data.h>
@ -571,7 +572,8 @@ void CGameConsole::OnRender()
Info.m_Offset = pConsole->m_CompletionRenderOffset;
Info.m_Width = Screen.w;
Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer;
TextRender()->SetCursor(&Info.m_Cursor, x + Info.m_Offset, y + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER);
TextRender()->SetCursor(&Info.m_Cursor, x + Info.m_Offset, y + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Info.m_Cursor.m_LineWidth = std::numeric_limits<float>::max();
// render prompt
CTextCursor Cursor;
@ -636,9 +638,7 @@ void CGameConsole::OnRender()
Cursor.m_LineWidth = Screen.w - 10.0f - x;
TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset(Editing));
static float MarkerOffset = TextRender()->TextWidth(0, FontSize, "|", -1, -1.0f) / 3;
CTextCursor Marker = Cursor;
Marker.m_X -= MarkerOffset;
Marker.m_LineWidth = -1;
TextRender()->TextEx(&Marker, "|", -1);
TextRender()->TextEx(&Cursor, aInputString + pConsole->m_Input.GetCursorOffset(Editing), -1);

View file

@ -555,7 +555,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
static int s_ClearButton = 0;
static float Offset = 0.0f;
if(Input()->KeyPress(KEY_X) && (Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL)))
if(Input()->KeyPress(KEY_X) && (Input()->KeyPress(KEY_LSHIFT) || Input()->KeyPress(KEY_RSHIFT)) && (Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL)))
UI()->SetActiveItem(&g_Config.m_BrExcludeString);
if(DoClearableEditBox(&g_Config.m_BrExcludeString, &s_ClearButton, &QuickExclude, g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString), 12.0f, &Offset, false, CUI::CORNER_ALL))
Client()->ServerBrowserUpdate();

View file

@ -55,12 +55,17 @@ void CLineInput::Add(const char *pString)
m_CursorPos = m_Len;
}
bool CLineInput::Manipulate(IInput::CEvent Event, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr)
static bool IsNotAWordChar(signed char c)
{
return (c > 0 && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z'); // all non chars in ascii -- random
}
int32_t CLineInput::Manipulate(IInput::CEvent Event, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr, int32_t ModifyFlags, int ModifierKey)
{
int NumChars = *pNumCharsPtr;
int CursorPos = *pCursorPosPtr;
int Len = *pStrLenPtr;
bool Changes = false;
int32_t Changes = 0;
if(CursorPos > Len)
CursorPos = Len;
@ -91,7 +96,7 @@ bool CLineInput::Manipulate(IInput::CEvent Event, char *pStr, int StrMaxSize, in
CursorPos += CharSize;
Len += CharSize;
NumChars += CharCount;
Changes = true;
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_STRING;
}
}
}
@ -99,35 +104,90 @@ bool CLineInput::Manipulate(IInput::CEvent Event, char *pStr, int StrMaxSize, in
if(Event.m_Flags & IInput::FLAG_PRESS)
{
int Key = Event.m_Key;
if(Key == KEY_BACKSPACE && CursorPos > 0)
if(Key == KEY_BACKSPACE)
{
int NewCursorPos = str_utf8_rewind(pStr, CursorPos);
int CharSize = CursorPos - NewCursorPos;
mem_move(pStr + NewCursorPos, pStr + CursorPos, Len - NewCursorPos - CharSize + 1); // +1 == null term
CursorPos = NewCursorPos;
Len -= CharSize;
if(CharSize > 0)
--NumChars;
Changes = true;
if((ModifyFlags & LINE_INPUT_MODIFY_DONT_DELETE) == 0 && CursorPos > 0)
{
int NewCursorPos = str_utf8_rewind(pStr, CursorPos);
int CharSize = CursorPos - NewCursorPos;
mem_move(pStr + NewCursorPos, pStr + CursorPos, Len - NewCursorPos - CharSize + 1); // +1 == null term
CursorPos = NewCursorPos;
Len -= CharSize;
if(CharSize > 0)
--NumChars;
}
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CHARACTERS_DELETE;
}
else if(Key == KEY_DELETE && CursorPos < Len)
else if(Key == KEY_DELETE)
{
int p = str_utf8_forward(pStr, CursorPos);
int CharSize = p - CursorPos;
mem_move(pStr + CursorPos, pStr + CursorPos + CharSize, Len - CursorPos - CharSize + 1); // +1 == null term
Len -= CharSize;
if(CharSize > 0)
--NumChars;
Changes = true;
if((ModifyFlags & LINE_INPUT_MODIFY_DONT_DELETE) == 0 && CursorPos < Len)
{
int p = str_utf8_forward(pStr, CursorPos);
int CharSize = p - CursorPos;
mem_move(pStr + CursorPos, pStr + CursorPos + CharSize, Len - CursorPos - CharSize + 1); // +1 == null term
Len -= CharSize;
if(CharSize > 0)
--NumChars;
}
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CHARACTERS_DELETE;
}
else if(Key == KEY_LEFT)
{
if(ModifierKey == KEY_LCTRL)
{
bool MovedCursor = false;
int OldCursorPos = CursorPos;
CursorPos = str_utf8_rewind(pStr, CursorPos);
if(OldCursorPos != CursorPos)
MovedCursor = true;
bool WasNonWordChar = IsNotAWordChar(pStr[CursorPos]);
while((!WasNonWordChar && !IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && IsNotAWordChar(pStr[CursorPos])))
{
CursorPos = str_utf8_rewind(pStr, CursorPos);
if(CursorPos == 0)
break;
}
if(MovedCursor && ((!WasNonWordChar && IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && !IsNotAWordChar(pStr[CursorPos]))))
CursorPos = str_utf8_forward(pStr, CursorPos);
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR;
}
else
{
if(CursorPos > 0)
CursorPos = str_utf8_rewind(pStr, CursorPos);
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CURSOR;
}
}
else if(Key == KEY_RIGHT)
{
if(ModifierKey == KEY_LCTRL)
{
bool WasNonWordChar = IsNotAWordChar(pStr[CursorPos]);
while((!WasNonWordChar && !IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && IsNotAWordChar(pStr[CursorPos])))
{
CursorPos = str_utf8_forward(pStr, CursorPos);
if(CursorPos >= Len)
break;
}
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR;
}
else
{
if(CursorPos < Len)
CursorPos = str_utf8_forward(pStr, CursorPos);
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CURSOR;
}
}
else if(Key == KEY_LEFT && CursorPos > 0)
CursorPos = str_utf8_rewind(pStr, CursorPos);
else if(Key == KEY_RIGHT && CursorPos < Len)
CursorPos = str_utf8_forward(pStr, CursorPos);
else if(Key == KEY_HOME)
{
CursorPos = 0;
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR;
}
else if(Key == KEY_END)
{
CursorPos = Len;
Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR;
}
}
*pNumCharsPtr = NumChars;
@ -155,5 +215,5 @@ void CLineInput::DeleteFromCursor()
void CLineInput::ProcessInput(IInput::CEvent e)
{
Manipulate(e, m_aStr, MAX_SIZE, MAX_CHARS, &m_Len, &m_CursorPos, &m_NumChars);
Manipulate(e, m_aStr, MAX_SIZE, MAX_CHARS, &m_Len, &m_CursorPos, &m_NumChars, 0, 0);
}

View file

@ -23,7 +23,22 @@ class CLineInput
int m_FakeCursorPos;
public:
static bool Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr);
enum ELineInputChanges
{
// string was changed
LINE_INPUT_CHANGE_STRING = 1 << 0,
// characters were removed from the string
LINE_INPUT_CHANGE_CHARACTERS_DELETE = 1 << 1,
// cursor was changed or tried to change(e.g. pressing right at the last char in the string)
LINE_INPUT_CHANGE_CURSOR = 1 << 2,
LINE_INPUT_CHANGE_WARP_CURSOR = 1 << 3,
};
enum ELineInputModifyFlags
{
// don't delete characters
LINE_INPUT_MODIFY_DONT_DELETE = 1 << 0,
};
static int32_t Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr, int32_t ModifyFlags, int ModifierKey);
class CCallback
{

View file

@ -430,7 +430,7 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *
return 1;
}
float CUI::DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, float MaxWidth, int AlignVertically, bool StopAtEnd)
float CUI::DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, float MaxWidth, int AlignVertically, bool StopAtEnd, class CTextCursor *pSelCursor)
{
float AlignedSize = 0;
float MaxCharacterHeightInLine = 0;
@ -460,15 +460,33 @@ float CUI::DoTextLabel(float x, float y, float w, float h, const char *pText, fl
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, AlignmentHori, AlignmentVert, Size, Flags);
Cursor.m_LineWidth = (float)MaxWidth;
if(pSelCursor)
{
Cursor.m_CursorMode = pSelCursor->m_CursorMode;
Cursor.m_CursorCharacter = pSelCursor->m_CursorCharacter;
Cursor.m_CalculateSelectionMode = pSelCursor->m_CalculateSelectionMode;
Cursor.m_PressMouseX = pSelCursor->m_PressMouseX;
Cursor.m_PressMouseY = pSelCursor->m_PressMouseY;
Cursor.m_ReleaseMouseX = pSelCursor->m_ReleaseMouseX;
Cursor.m_ReleaseMouseY = pSelCursor->m_ReleaseMouseY;
Cursor.m_SelectionStart = pSelCursor->m_SelectionStart;
Cursor.m_SelectionEnd = pSelCursor->m_SelectionEnd;
}
TextRender()->TextEx(&Cursor, pText, -1);
if(pSelCursor)
{
*pSelCursor = Cursor;
}
return tw;
}
void CUI::DoLabel(const CUIRect *r, const char *pText, float Size, int Align, float MaxWidth, int AlignVertically)
void CUI::DoLabel(const CUIRect *r, const char *pText, float Size, int Align, float MaxWidth, int AlignVertically, CTextCursor *pSelCursor)
{
DoTextLabel(r->x, r->y, r->w, r->h, pText, Size, Align, MaxWidth, AlignVertically);
DoTextLabel(r->x, r->y, r->w, r->h, pText, Size, Align, MaxWidth, AlignVertically, false, pSelCursor);
}
void CUI::DoLabelScaled(const CUIRect *r, const char *pText, float Size, int Align, float MaxWidth, int AlignVertically)

View file

@ -273,8 +273,8 @@ public:
int DoButtonLogic(const void *pID, const char *pText /* TODO: Refactor: Remove */, int Checked, const CUIRect *pRect);
int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY);
float DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, bool StopAtEnd = false);
void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1);
float DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, bool StopAtEnd = false, class CTextCursor *pSelCursor = NULL);
void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, class CTextCursor *pSelCursor = NULL);
void DoLabelScaled(const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1);
void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, bool StopAtEnd = false, int StrLen = -1, class CTextCursor *pReadCursor = NULL);

View file

@ -1,5 +1,10 @@
#include "ui_ex.h"
#include <base/system.h>
#include <base/math.h>
#include <engine/textrender.h>
#include <engine/client/input.h>
#include <engine/keys.h>
@ -31,126 +36,216 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
int Inside = UI()->MouseInside(pRect);
bool ReturnValue = false;
bool UpdateOffset = false;
static int s_AtIndex = 0;
static bool s_DoScroll = false;
static float s_ScrollStart = 0.0f;
FontSize *= UI()->Scale();
if(UI()->LastActiveItem() == pID)
{
int Len = str_length(pStr);
if(Len == 0)
s_AtIndex = 0;
m_CurCursor = minimum(str_length(pStr), m_CurCursor);
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V))
bool IsShiftPressed = Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT);
bool IsCtrlPressed = Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL);
if(!IsShiftPressed && IsCtrlPressed && Input()->KeyPress(KEY_V))
{
const char *Text = Input()->GetClipboardText();
if(Text)
const char *pText = Input()->GetClipboardText();
if(pText)
{
int Offset = str_length(pStr);
int CharsLeft = StrSize - Offset;
char *pCur = pStr + Offset;
str_utf8_copy(pCur, Text, CharsLeft);
for(int i = 0; i < CharsLeft; i++)
int OffsetL = minimum(str_length(pStr), m_CurCursor);
int OffsetR = OffsetL;
if(m_HasSelection)
{
if(pCur[i] == 0)
break;
else if(pCur[i] == '\r')
pCur[i] = ' ';
else if(pCur[i] == '\n')
pCur[i] = ' ';
int SelLeft = minimum(m_CurSelStart, m_CurSelEnd);
int SelRight = maximum(m_CurSelStart, m_CurSelEnd);
int UTF8SelLeft = -1;
int UTF8SelRight = -1;
if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft, SelRight, UTF8SelLeft, UTF8SelRight))
{
OffsetL = UTF8SelLeft;
OffsetR = UTF8SelRight;
m_HasSelection = false;
}
}
s_AtIndex = str_length(pStr);
std::string NewStr(pStr, OffsetL);
int WrittenChars = 0;
const char *pIt = pText;
while(*pIt)
{
const char *pTmp = pIt;
int Character = str_utf8_decode(&pTmp);
if(Character == -1 || Character == 0)
break;
if(Character == '\r' || Character == '\n')
{
NewStr.append(1, ' ');
++WrittenChars;
}
else
{
NewStr.append(pIt, (std::intptr_t)(pTmp - pIt));
WrittenChars += (int)(std::intptr_t)(pTmp - pIt);
}
pIt = pTmp;
}
NewStr.append(pStr + OffsetR);
str_copy(pStr, NewStr.c_str(), StrSize);
m_CurCursor = OffsetL + WrittenChars;
ReturnValue = true;
}
}
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C))
if(!IsShiftPressed && IsCtrlPressed && (Input()->KeyPress(KEY_C) || Input()->KeyPress(KEY_X)))
{
Input()->SetClipboardText(pStr);
}
/* TODO: Doesn't work, SetClipboardText doesn't retain the string quickly enough?
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_X))
{
Input()->SetClipboardText(pStr);
pStr[0] = '\0';
s_AtIndex = 0;
ReturnValue = true;
}
*/
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_U))
{
pStr[0] = '\0';
s_AtIndex = 0;
ReturnValue = true;
}
if(Inside && UI()->MouseButton(0))
{
s_DoScroll = true;
s_ScrollStart = UI()->MouseX();
int MxRel = (int)(UI()->MouseX() - pRect->x);
for(int i = 1; i <= Len; i++)
if(m_HasSelection)
{
if(TextRender()->TextWidth(0, FontSize, pStr, i, std::numeric_limits<float>::max()) - *Offset > MxRel)
int SelLeft = minimum(m_CurSelStart, m_CurSelEnd);
int SelRight = maximum(m_CurSelStart, m_CurSelEnd);
int UTF8SelLeft = -1;
int UTF8SelRight = -1;
if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft, SelRight, UTF8SelLeft, UTF8SelRight))
{
s_AtIndex = i - 1;
break;
std::string NewStr(&pStr[UTF8SelLeft], UTF8SelRight - UTF8SelLeft);
Input()->SetClipboardText(NewStr.c_str());
if(Input()->KeyPress(KEY_X))
{
NewStr = std::string(pStr, UTF8SelLeft) + std::string(pStr + UTF8SelRight);
str_copy(pStr, NewStr.c_str(), StrSize);
m_HasSelection = false;
if(m_CurCursor > UTF8SelLeft)
m_CurCursor = maximum(0, m_CurCursor - (UTF8SelRight - UTF8SelLeft));
else
m_CurCursor = UTF8SelLeft;
}
}
if(i == Len)
s_AtIndex = Len;
}
else
Input()->SetClipboardText(pStr);
}
else if(!UI()->MouseButton(0))
s_DoScroll = false;
else if(s_DoScroll)
if(!IsShiftPressed && IsCtrlPressed && Input()->KeyPress(KEY_A))
{
// do scrolling
if(UI()->MouseX() < pRect->x && s_ScrollStart - UI()->MouseX() > 10.0f)
{
s_AtIndex = maximum(0, s_AtIndex - 1);
s_ScrollStart = UI()->MouseX();
UpdateOffset = true;
}
else if(UI()->MouseX() > pRect->x + pRect->w && UI()->MouseX() - s_ScrollStart > 10.0f)
{
s_AtIndex = minimum(Len, s_AtIndex + 1);
s_ScrollStart = UI()->MouseX();
UpdateOffset = true;
}
m_CurSelStart = 0;
int StrLen = str_length(pStr);
TextRender()->UTF8OffToDecodedOff(pStr, StrLen, m_CurSelEnd);
m_HasSelection = true;
m_CurCursor = StrLen;
}
if(!IsShiftPressed && IsCtrlPressed && Input()->KeyPress(KEY_U))
{
pStr[0] = '\0';
m_CurCursor = 0;
ReturnValue = true;
}
for(int i = 0; i < *m_pInputEventCount; i++)
{
Len = str_length(pStr);
int32_t ManipulateChanges = 0;
int LastCursor = m_CurCursor;
int Len = str_length(pStr);
int NumChars = Len;
ReturnValue |= CLineInput::Manipulate(m_pInputEventsArray[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars);
}
}
ManipulateChanges = CLineInput::Manipulate(m_pInputEventsArray[i], pStr, StrSize, StrSize, &Len, &m_CurCursor, &NumChars, m_HasSelection ? CLineInput::LINE_INPUT_MODIFY_DONT_DELETE : 0, IsCtrlPressed ? KEY_LCTRL : 0);
ReturnValue |= (ManipulateChanges & (CLineInput::LINE_INPUT_CHANGE_STRING | CLineInput::LINE_INPUT_CHANGE_CHARACTERS_DELETE)) != 0;
bool JustGotActive = false;
// if cursor changed, reset selection
if(ManipulateChanges != 0)
{
if(m_HasSelection && (ManipulateChanges & (CLineInput::LINE_INPUT_CHANGE_STRING | CLineInput::LINE_INPUT_CHANGE_CHARACTERS_DELETE)) != 0)
{
int OffsetL = 0;
int OffsetR = 0;
if(UI()->ActiveItem() == pID)
{
if(!UI()->MouseButton(0))
{
s_AtIndex = minimum(s_AtIndex, str_length(pStr));
s_DoScroll = false;
UI()->SetActiveItem(0);
}
}
else if(UI()->HotItem() == pID)
{
if(UI()->MouseButton(0))
{
if(UI()->LastActiveItem() != pID)
JustGotActive = true;
UI()->SetActiveItem(pID);
bool IsReverseSel = m_CurSelStart > m_CurSelEnd;
int ExtraNew = 0;
int ExtraOld = 0;
// selection correction from added chars
if(IsReverseSel)
{
TextRender()->UTF8OffToDecodedOff(pStr, m_CurCursor, ExtraNew);
TextRender()->UTF8OffToDecodedOff(pStr, LastCursor, ExtraOld);
}
int SelLeft = minimum(m_CurSelStart, m_CurSelEnd);
int SelRight = maximum(m_CurSelStart, m_CurSelEnd);
int UTF8SelLeft = -1;
int UTF8SelRight = -1;
if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft + (ExtraNew - ExtraOld), SelRight + (ExtraNew - ExtraOld), UTF8SelLeft, UTF8SelRight))
{
OffsetL = UTF8SelLeft;
OffsetR = UTF8SelRight;
m_HasSelection = false;
}
std::string NewStr(pStr, OffsetL);
NewStr.append(pStr + OffsetR);
str_copy(pStr, NewStr.c_str(), StrSize);
if(!IsReverseSel)
m_CurCursor = clamp<int>(m_CurCursor - (UTF8SelRight - UTF8SelLeft), 0, NewStr.length());
}
if(IsShiftPressed && (ManipulateChanges & CLineInput::LINE_INPUT_CHANGE_STRING) == 0)
{
int CursorPosDecoded = -1;
int LastCursorPosDecoded = -1;
if(!m_HasSelection)
{
m_CurSelStart = -1;
m_CurSelEnd = -1;
}
if(TextRender()->UTF8OffToDecodedOff(pStr, m_CurCursor, CursorPosDecoded))
{
if(TextRender()->UTF8OffToDecodedOff(pStr, LastCursor, LastCursorPosDecoded))
{
if(!m_HasSelection)
{
m_CurSelStart = LastCursorPosDecoded;
m_CurSelEnd = LastCursorPosDecoded;
}
m_CurSelEnd += (CursorPosDecoded - LastCursorPosDecoded);
}
}
if(m_CurSelStart == m_CurSelEnd)
m_HasSelection = false;
else
m_HasSelection = true;
}
else
{
if(m_HasSelection && (ManipulateChanges & CLineInput::LINE_INPUT_CHANGE_CURSOR) != 0)
{
if(m_CurSelStart < m_CurSelEnd)
{
if(m_CurCursor >= LastCursor)
m_CurCursor = LastCursor;
else
TextRender()->DecodedOffToUTF8Off(pStr, m_CurSelStart, m_CurCursor);
}
else
{
if(m_CurCursor <= LastCursor)
m_CurCursor = LastCursor;
else
TextRender()->DecodedOffToUTF8Off(pStr, m_CurSelStart, m_CurCursor);
}
}
m_HasSelection = false;
}
}
}
}
@ -179,7 +274,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
}
char aDispEditingText[128 + IInput::INPUT_TEXT_SIZE + 2] = {0};
int DispCursorPos = s_AtIndex;
int DispCursorPos = m_CurCursor;
if(UI()->LastActiveItem() == pID && Input()->GetIMEEditingTextLength() > -1)
{
int EditingTextCursor = Input()->GetEditingCursor();
@ -198,11 +293,11 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
int NewTextLen = str_length(aEditingText);
int CharsLeft = (int)sizeof(aDispEditingText) - str_length(aDispEditingText) - 1;
int FillCharLen = minimum(NewTextLen, CharsLeft);
for(int i = str_length(aDispEditingText) - 1; i >= s_AtIndex; i--)
for(int i = str_length(aDispEditingText) - 1; i >= m_CurCursor; i--)
aDispEditingText[i + FillCharLen] = aDispEditingText[i];
for(int i = 0; i < FillCharLen; i++)
aDispEditingText[s_AtIndex + i] = aEditingText[i];
DispCursorPos = s_AtIndex + EditingTextCursor + 1;
aDispEditingText[m_CurCursor + i] = aEditingText[i];
DispCursorPos = m_CurCursor + EditingTextCursor + 1;
pDisplayStr = aDispEditingText;
UpdateOffset = true;
}
@ -215,6 +310,24 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
DispCursorPos = minimum(DispCursorPos, str_length(pDisplayStr));
bool JustGotActive = false;
if(UI()->ActiveItem() == pID)
{
if(!UI()->MouseButton(0))
{
UI()->SetActiveItem(0);
}
}
else if(UI()->HotItem() == pID)
{
if(UI()->MouseButton(0))
{
if(UI()->LastActiveItem() != pID)
JustGotActive = true;
UI()->SetActiveItem(pID);
}
}
// check if the text has to be moved
if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || *m_pInputEventCount))
{
@ -240,34 +353,71 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
UI()->ClipEnable(pRect);
Textbox.x -= *Offset;
UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1);
CTextCursor SelCursor;
TextRender()->SetCursor(&SelCursor, 0, 0, 16, 0);
bool HasMouseSel = false;
if(UI()->LastActiveItem() == pID)
{
if(!m_MouseIsPress && UI()->MouseButtonClicked(0))
{
m_MouseIsPress = true;
m_MousePressX = UI()->MouseX();
m_MousePressY = UI()->MouseY();
}
}
if(m_MouseIsPress)
{
m_MouseCurX = UI()->MouseX();
m_MouseCurY = UI()->MouseY();
}
HasMouseSel = m_MouseIsPress;
if(m_MouseIsPress && UI()->MouseButtonReleased(0))
{
m_MouseIsPress = false;
}
if(UI()->LastActiveItem() == pID)
{
int CursorPos = -1;
TextRender()->UTF8OffToDecodedOff(pDisplayStr, DispCursorPos, CursorPos);
SelCursor.m_CursorMode = HasMouseSel ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET;
SelCursor.m_CursorCharacter = CursorPos;
SelCursor.m_CalculateSelectionMode = HasMouseSel ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : (m_HasSelection ? TEXT_CURSOR_SELECTION_MODE_SET : TEXT_CURSOR_SELECTION_MODE_NONE);
SelCursor.m_PressMouseX = m_MousePressX;
SelCursor.m_PressMouseY = m_MousePressY;
SelCursor.m_ReleaseMouseX = m_MouseCurX;
SelCursor.m_ReleaseMouseY = m_MouseCurY;
SelCursor.m_SelectionStart = m_CurSelStart;
SelCursor.m_SelectionEnd = m_CurSelEnd;
}
UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1, -1, 1, &SelCursor);
if(UI()->LastActiveItem() == pID)
{
if(SelCursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE)
{
m_CurSelStart = SelCursor.m_SelectionStart;
m_CurSelEnd = SelCursor.m_SelectionEnd;
m_HasSelection = m_CurSelStart != m_CurSelEnd;
}
if(SelCursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE)
{
TextRender()->DecodedOffToUTF8Off(pDisplayStr, SelCursor.m_CursorCharacter, DispCursorPos);
m_CurCursor = DispCursorPos;
}
}
TextRender()->TextColor(1, 1, 1, 1);
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
float OnePixelWidth = ((ScreenX1 - ScreenX0) / Graphics()->ScreenWidth());
// render the cursor
// set the ime cursor
if(UI()->LastActiveItem() == pID && !JustGotActive)
{
float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, DispCursorPos, std::numeric_limits<float>::max());
Textbox.x += w;
if((2 * time_get() / time_freq()) % 2)
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0, 0, 0, 0.3f);
float PosToMid = (Textbox.h - FontSize) / 2.0f;
IGraphics::CQuadItem CursorTBack(Textbox.x - (OnePixelWidth * 2.0f) / 2.0f, Textbox.y + PosToMid, OnePixelWidth * 2 * 2.0f, FontSize);
Graphics()->QuadsDrawTL(&CursorTBack, 1);
Graphics()->SetColor(1, 1, 1, 1);
IGraphics::CQuadItem CursorT(Textbox.x, Textbox.y + PosToMid + OnePixelWidth * 1.5f, OnePixelWidth * 2.0f, FontSize - OnePixelWidth * 1.5f * 2);
Graphics()->QuadsDrawTL(&CursorT, 1);
Graphics()->QuadsEnd();
}
Input()->SetEditingPosition(Textbox.x, Textbox.y + FontSize);
}

View file

@ -25,6 +25,18 @@ class CUIEx
IInput::CEvent *m_pInputEventsArray;
int *m_pInputEventCount;
bool m_MouseIsPress;
bool m_HasSelection;
int m_MousePressX = 0;
int m_MousePressY = 0;
int m_MouseCurX = 0;
int m_MouseCurY = 0;
int m_CurSelStart = 0;
int m_CurSelEnd = 0;
int m_CurCursor = 0;
protected:
CUI *UI() { return m_pUI; }
IInput *Input() { return m_pInput; }