diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index 7bdf3eeb7..e48a81bc1 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -109,6 +109,21 @@ void CInput::MouseModeRelative() SDL_GetRelativeMouseState(0x0, 0x0); } +void CInput::NativeMousePos(int *x, int *y) const +{ + int nx = 0, ny = 0; + SDL_GetMouseState(&nx, &ny); + + *x = nx; + *y = ny; +} + +bool CInput::NativeMousePressed(int index) +{ + int i = SDL_GetMouseState(NULL, NULL); + return (i & SDL_BUTTON(index)) != 0; +} + int CInput::MouseDoubleClick() { if(m_ReleaseDelta >= 0 && m_ReleaseDelta < (time_freq() / 3)) diff --git a/src/engine/client/input.h b/src/engine/client/input.h index e371ec286..18de323a3 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -54,6 +54,8 @@ public: virtual void MouseRelative(float *x, float *y); virtual void MouseModeAbsolute(); virtual void MouseModeRelative(); + virtual void NativeMousePos(int *x, int *y) const; + virtual bool NativeMousePressed(int index); virtual int MouseDoubleClick(); virtual const char *GetClipboardText(); virtual void SetClipboardText(const char *Text); diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 30e638eaf..548c0e3fe 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -2,6 +2,8 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include +#include +#include #include #include #include @@ -11,6 +13,8 @@ #include #include FT_FREETYPE_H +#include + // TODO: Refactor: clean this up enum { @@ -28,6 +32,8 @@ struct SFontSizeChar // width * font_size == real_size float m_Width; float m_Height; + float m_CharWidth; + float m_CharHeight; float m_OffsetX; float m_OffsetY; float m_AdvanceX; @@ -197,7 +203,7 @@ class CTextRender : public IEngineTextRender unsigned int m_RenderFlags; - std::vector m_TextContainers; + std::vector m_TextContainers; std::vector m_TextContainerIndices; int m_FirstFreeTextContainerIndex; @@ -231,7 +237,7 @@ class CTextRender : public IEngineTextRender void FreeTextContainer(int Index) { - m_TextContainers[Index].Reset(); + m_TextContainers[Index]->Reset(); FreeTextContainerIndex(Index); } @@ -241,10 +247,10 @@ class CTextRender : public IEngineTextRender { int Size = (int)m_TextContainers.size(); for(int i = 0; i < (Index + 1) - Size; ++i) - m_TextContainers.push_back(STextContainer()); + m_TextContainers.push_back(new STextContainer()); } - return m_TextContainers[Index]; + return *m_TextContainers[Index]; } int WordLength(const char *pText) @@ -263,6 +269,7 @@ class CTextRender : public IEngineTextRender ColorRGBA m_Color; ColorRGBA m_OutlineColor; + ColorRGBA m_SelectionColor; CFont *m_pDefaultFont; FT_Library m_FTLibrary; @@ -454,8 +461,8 @@ class CTextRender : public IEngineTextRender { FT_Bitmap *pBitmap; - int x = 1; - int y = 1; + int x = 0; + int y = 0; unsigned int px, py; FT_Face FtFace = pFont->m_FtFace; @@ -502,37 +509,49 @@ class CTextRender : public IEngineTextRender pBitmap = &FtFace->glyph->bitmap; // ignore_convention + unsigned int RealWidth = pBitmap->width; + unsigned int RealHeight = pBitmap->rows; + // adjust spacing - int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); - x += OutlineThickness; - y += OutlineThickness; + int OutlineThickness = 0; + if(RealWidth > 0) + { + OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); - unsigned int Width = pBitmap->width + x * 2; - unsigned int Height = pBitmap->rows + y * 2; + x += (OutlineThickness + 1); + y += (OutlineThickness + 1); + } - // prepare glyph data - mem_zero(ms_aGlyphData, Width * Height); + unsigned int Width = RealWidth + x * 2; + unsigned int Height = RealHeight + y * 2; - for(py = 0; py < pBitmap->rows; py++) // ignore_convention - for(px = 0; px < pBitmap->width; px++) // ignore_convention - ms_aGlyphData[(py + y) * Width + px + x] = pBitmap->buffer[py * pBitmap->width + px]; // ignore_convention - - // upload the glyph int X = 0; int Y = 0; - while(!GetCharacterSpace(pFont, 0, (int)Width, (int)Height, X, Y)) - { - IncreaseFontTexture(pFont, 0); - } - UploadGlyph(pFont, 0, X, Y, (int)Width, (int)Height, ms_aGlyphData); - Grow(ms_aGlyphData, ms_aGlyphDataOutlined, Width, Height, OutlineThickness); - - while(!GetCharacterSpace(pFont, 1, (int)Width, (int)Height, X, Y)) + if(Width > 0 && Height > 0) { - IncreaseFontTexture(pFont, 1); + // prepare glyph data + mem_zero(ms_aGlyphData, Width * Height); + + for(py = 0; py < pBitmap->rows; py++) // ignore_convention + for(px = 0; px < pBitmap->width; px++) // ignore_convention + ms_aGlyphData[(py + y) * Width + px + x] = pBitmap->buffer[py * pBitmap->width + px]; // ignore_convention + + // upload the glyph + while(!GetCharacterSpace(pFont, 0, (int)Width, (int)Height, X, Y)) + { + IncreaseFontTexture(pFont, 0); + } + UploadGlyph(pFont, 0, X, Y, (int)Width, (int)Height, ms_aGlyphData); + + Grow(ms_aGlyphData, ms_aGlyphDataOutlined, Width, Height, OutlineThickness); + + while(!GetCharacterSpace(pFont, 1, (int)Width, (int)Height, X, Y)) + { + IncreaseFontTexture(pFont, 1); + } + UploadGlyph(pFont, 1, X, Y, (int)Width, (int)Height, ms_aGlyphDataOutlined); } - UploadGlyph(pFont, 1, X, Y, (int)Width, (int)Height, ms_aGlyphDataOutlined); // set char info { @@ -543,6 +562,8 @@ class CTextRender : public IEngineTextRender pFontchr->m_ID = Chr; pFontchr->m_Height = Height; pFontchr->m_Width = Width; + pFontchr->m_CharHeight = RealHeight; + pFontchr->m_CharWidth = RealWidth; pFontchr->m_OffsetX = (FtFace->glyph->metrics.horiBearingX >> 6); // ignore_convention pFontchr->m_OffsetY = -((FtFace->glyph->metrics.height >> 6) - (FtFace->glyph->metrics.horiBearingY >> 6)); pFontchr->m_AdvanceX = (FtFace->glyph->advance.x >> 6); // ignore_convention @@ -585,8 +606,9 @@ public: { m_pGraphics = 0; - m_Color = ColorRGBA(1, 1, 1, 1); - m_OutlineColor = ColorRGBA(0, 0, 0, 0.3f); + m_Color = DefaultTextColor(); + m_OutlineColor = DefaultTextOutlineColor(); + m_SelectionColor = DefaultSelectionColor(); m_pCurFont = 0; m_pDefaultFont = 0; @@ -600,6 +622,13 @@ public: virtual ~CTextRender() { + for(auto *pTextCont : m_TextContainers) + { + pTextCont->Reset(); + delete pTextCont; + } + m_TextContainers.clear(); + for(auto &pFont : m_Fonts) { FT_Done_Face(pFont->m_FtFace); @@ -772,6 +801,14 @@ public: pCursor->m_CharCount = 0; pCursor->m_MaxCharacterHeight = 0; pCursor->m_LongestLineWidth = 0; + + pCursor->m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE; + pCursor->m_PressMouseX = 0; + pCursor->m_PressMouseY = 0; + pCursor->m_ReleaseMouseX = 0; + pCursor->m_ReleaseMouseY = 0; + pCursor->m_SelectionStart = 0; + pCursor->m_SelectionEnd = 0; } virtual void MoveCursor(CTextCursor *pCursor, float x, float y) @@ -846,277 +883,32 @@ public: } virtual void TextOutlineColor(ColorRGBA rgb) { m_OutlineColor = rgb; }; + virtual void TextSelectionColor(float r, float g, float b, float a) + { + m_SelectionColor.r = r; + m_SelectionColor.g = g; + m_SelectionColor.b = b; + m_SelectionColor.a = a; + } + virtual void TextSelectionColor(ColorRGBA rgb) { m_SelectionColor = rgb; }; + virtual ColorRGBA GetTextColor() { return m_Color; } virtual ColorRGBA GetTextOutlineColor() { return m_OutlineColor; } + virtual ColorRGBA GetTextSelectionColor() { return m_SelectionColor; } virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) { - dbg_assert(pText != NULL, "null text pointer"); - - if(!*pText) - return; - - CFont *pFont = pCursor->m_pFont; - CFontSizeData *pSizeData = NULL; - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - float FakeToScreenX, FakeToScreenY; - - int ActualSize; - int GotNewLine = 0; - int GotNewLineLast = 0; - float DrawX = 0.0f, DrawY = 0.0f; - int LineCount = 0; - float CursorX, CursorY; - - float Size = pCursor->m_FontSize; - - // calculate the font size of the displayed glyphs - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); - FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - - int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); - int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); - CursorX = ActualX / FakeToScreenX; - CursorY = ActualY / FakeToScreenY; - - // same with size - ActualSize = (int)(Size * FakeToScreenY); - Size = ActualSize / FakeToScreenY; - - pCursor->m_AlignedFontSize = Size; - - // fetch pFont data - if(!pFont) - pFont = m_pCurFont; - - if(!pFont) - return; - - pSizeData = pFont->GetFontSize(ActualSize); - - // set length - if(Length < 0) - Length = str_length(pText); - - float Scale = 1.0f / pSizeData->m_FontSize; - - //the outlined texture is always the same size as the current - float UVScale = 1.0f / pFont->m_CurTextureDimensions[0]; - - const char *pCurrent = pText; - const char *pEnd = pCurrent + Length; - - if((m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) != 0) + int TextCont = CreateTextContainer(pCursor, pText, Length); + if(TextCont != -1) { - DrawX = pCursor->m_X; - DrawY = pCursor->m_Y; + if((pCursor->m_Flags & TEXTFLAG_RENDER) != 0) + { + STextRenderColor TextColor = DefaultTextColor(); + STextRenderColor TextColorOutline = DefaultTextOutlineColor(); + RenderTextContainer(TextCont, &TextColor, &TextColorOutline); + } + DeleteTextContainer(TextCont); } - else - { - DrawX = CursorX; - DrawY = CursorY; - } - - LineCount = pCursor->m_LineCount; - - if(pCursor->m_Flags & TEXTFLAG_RENDER) - { - // make sure there are no vertices - Graphics()->FlushVertices(); - - if(Graphics()->IsTextBufferingEnabled()) - { - Graphics()->TextureClear(); - Graphics()->TextQuadsBegin(); - Graphics()->SetColor(m_Color); - } - else - { - Graphics()->TextureSet(pFont->m_aTextures[1]); - Graphics()->QuadsBegin(); - Graphics()->SetColor(m_OutlineColor.r, m_OutlineColor.g, m_OutlineColor.b, m_OutlineColor.a * m_Color.a); - } - } - - FT_UInt LastCharGlyphIndex = 0; - size_t CharacterCounter = 0; - - while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines)) - { - int NewLine = 0; - const char *pBatchEnd = pEnd; - if(pCursor->m_LineWidth > 0 && !(pCursor->m_Flags & TEXTFLAG_STOP_AT_END)) - { - int Wlen = minimum(WordLength(pCurrent), (int)(pEnd - pCurrent)); - CTextCursor Compare = *pCursor; - Compare.m_X = DrawX; - Compare.m_Y = DrawY; - Compare.m_Flags &= ~TEXTFLAG_RENDER; - Compare.m_LineWidth = -1; - TextEx(&Compare, pCurrent, Wlen); - - if(Compare.m_X - DrawX > pCursor->m_LineWidth) - { - // word can't be fitted in one line, cut it - CTextCursor Cutter = *pCursor; - Cutter.m_GlyphCount = 0; - Cutter.m_CharCount = 0; - Cutter.m_X = DrawX; - Cutter.m_Y = DrawY; - Cutter.m_Flags &= ~TEXTFLAG_RENDER; - Cutter.m_Flags |= TEXTFLAG_STOP_AT_END; - - TextEx(&Cutter, pCurrent, Wlen); - int WordGlyphs = Cutter.m_GlyphCount; - Wlen = Cutter.m_CharCount; - NewLine = 1; - - if(WordGlyphs <= 3 && GotNewLineLast == 0) // if we can't place 3 chars of the word on this line, take the next - Wlen = 0; - } - else if(Compare.m_X - pCursor->m_StartX > pCursor->m_LineWidth && GotNewLineLast == 0) - { - NewLine = 1; - Wlen = 0; - } - - pBatchEnd = pCurrent + Wlen; - } - - const char *pTmp = pCurrent; - int NextCharacter = str_utf8_decode(&pTmp); - while(pCurrent < pBatchEnd) - { - pCursor->m_CharCount += pTmp - pCurrent; - int Character = NextCharacter; - pCurrent = pTmp; - NextCharacter = str_utf8_decode(&pTmp); - - if(Character == '\n') - { - ++CharacterCounter; - LastCharGlyphIndex = 0; - - DrawX = pCursor->m_StartX; - DrawY += Size; - if((m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } - ++LineCount; - if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) - break; - continue; - } - - SFontSizeChar *pChr = GetChar(pFont, pSizeData, Character); - if(pChr) - { - bool ApplyBearingX = !(((m_RenderFlags & TEXT_RENDER_FLAG_NO_X_BEARING) != 0) || (CharacterCounter == 0 && (m_RenderFlags & TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING) != 0)); - float Advance = ((((m_RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pChr->m_Width) : (pChr->m_AdvanceX + ((!ApplyBearingX) ? (-pChr->m_OffsetX) : 0.f)))) * Scale; - - float CharKerning = 0.f; - if((m_RenderFlags & TEXT_RENDER_FLAG_KERNING) != 0) - CharKerning = Kerning(pFont, LastCharGlyphIndex, pChr->m_GlyphIndex) * Scale * Size; - - LastCharGlyphIndex = pChr->m_GlyphIndex; - if(pCursor->m_Flags & TEXTFLAG_STOP_AT_END && (DrawX + CharKerning) + Advance * Size - pCursor->m_StartX > pCursor->m_LineWidth) - { - // we hit the end of the line, no more to render or count - pCurrent = pEnd; - break; - } - - float BearingX = (!ApplyBearingX ? 0.f : pChr->m_OffsetX) * Scale * Size; - float CharWidth = pChr->m_Width * Scale * Size; - - float BearingY = 0.f; - BearingY = (((m_RenderFlags & TEXT_RENDER_FLAG_NO_Y_BEARING) != 0) ? 0.f : (pChr->m_OffsetY * Scale * Size)); - float CharHeight = pChr->m_Height * Scale * Size; - - if((m_RenderFlags & TEXT_RENDER_FLAG_NO_OVERSIZE) != 0) - { - if(CharHeight + BearingY > Size) - { - BearingY = 0; - float ScaleChar = (CharHeight + BearingY) / Size; - CharHeight = Size; - CharWidth /= ScaleChar; - } - } - - if(pCursor->m_Flags & TEXTFLAG_RENDER && m_Color.a != 0.f) - { - if(Graphics()->IsTextBufferingEnabled()) - Graphics()->QuadsSetSubset(pChr->m_aUVs[0], pChr->m_aUVs[3], pChr->m_aUVs[2], pChr->m_aUVs[1]); - else - Graphics()->QuadsSetSubset(pChr->m_aUVs[0] * UVScale, pChr->m_aUVs[3] * UVScale, pChr->m_aUVs[2] * UVScale, pChr->m_aUVs[1] * UVScale); - float Y = (DrawY + Size); - - IGraphics::CQuadItem QuadItem((DrawX + CharKerning) + BearingX, Y - BearingY, CharWidth, -CharHeight); - Graphics()->QuadsDrawTL(&QuadItem, 1); - } - - pCursor->m_MaxCharacterHeight = maximum(pCursor->m_MaxCharacterHeight, CharHeight + BearingY); - - if(NextCharacter == 0 && (m_RenderFlags & TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE) != 0 && Character != ' ') - DrawX += BearingX + CharKerning + CharWidth; - else - DrawX += Advance * Size + CharKerning; - pCursor->m_GlyphCount++; - - ++CharacterCounter; - } - - if(DrawX > pCursor->m_LongestLineWidth) - pCursor->m_LongestLineWidth = DrawX; - } - - if(NewLine) - { - DrawX = pCursor->m_StartX; - DrawY += Size; - if((m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } - GotNewLine = 1; - GotNewLineLast = 1; - ++LineCount; - } - else - GotNewLineLast = 0; - } - - if(pCursor->m_Flags & TEXTFLAG_RENDER) - { - if(Graphics()->IsTextBufferingEnabled()) - { - float OutlineColor[4] = {m_OutlineColor.r, m_OutlineColor.g, m_OutlineColor.b, m_OutlineColor.a * m_Color.a}; - Graphics()->TextQuadsEnd(pFont->m_CurTextureDimensions[0], pFont->m_aTextures[0].Id(), pFont->m_aTextures[1].Id(), OutlineColor); - } - else - { - Graphics()->QuadsEndKeepVertices(); - - Graphics()->TextureSet(pFont->m_aTextures[0]); - Graphics()->ChangeColorOfCurrentQuadVertices(m_Color.r, m_Color.g, m_Color.b, m_Color.a); - - // render non outlined - Graphics()->QuadsDrawCurrentVertices(false); - } - } - - pCursor->m_X = DrawX; - pCursor->m_LineCount = LineCount; - - if(GotNewLine) - pCursor->m_Y = DrawY; } virtual int CreateTextContainer(CTextCursor *pCursor, const char *pText, int Length = -1) @@ -1130,6 +922,8 @@ public: if(!pFont) return -1; + bool IsRendered = (pCursor->m_Flags & TEXTFLAG_RENDER) != 0; + int ContainerIndex = GetFreeTextContainerIndex(); STextContainer &TextContainer = GetTextContainer(ContainerIndex); TextContainer.m_pFont = pFont; @@ -1175,7 +969,7 @@ public: AppendTextContainer(pCursor, ContainerIndex, pText, Length); - if(TextContainer.m_StringInfo.m_CharacterQuads.size() == 0) + if(TextContainer.m_StringInfo.m_CharacterQuads.size() == 0 && IsRendered) { FreeTextContainer(ContainerIndex); return -1; @@ -1183,7 +977,7 @@ public: else { TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_CharacterQuads.size(); - if(Graphics()->IsTextBufferingEnabled()) + if(Graphics()->IsTextBufferingEnabled() && IsRendered) { if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_AUTOMATIC_QUAD_UPLOAD) == 0) { @@ -1268,6 +1062,76 @@ public: FT_UInt LastCharGlyphIndex = 0; size_t CharacterCounter = 0; + bool IsRendered = (pCursor->m_Flags & TEXTFLAG_RENDER) != 0; + + std::vector SelectionQuads; + bool SelectionStarted = false; + bool SelectionUsedPress = false; + bool SelectionUsedRelease = false; + int SelectionStartChar = -1; + int SelectionEndChar = -1; + + 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)) + { + SelectionChar = CharacterCounter; + SelectionStarted = !SelectionStarted; + SelectionUsedCase = true; + } + } + }; + 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)) + { + SelectionChar = CharacterCounter; + SelectionStarted = !SelectionStarted; + SelectionUsedCase = true; + } + } + }; + + float LastSelX = DrawX; + float LastSelWidth = 0; + float LastCharX = DrawX; + float LastCharWidth = 0; + + auto &&StartNewLine = [&]() { + DrawX = pCursor->m_StartX; + DrawY += Size; + if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) + { + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; + } + LastSelX = DrawX; + LastSelWidth = 0; + LastCharX = DrawX; + LastCharWidth = 0; + ++LineCount; + }; + + if(pCursor->m_CalculateSelectionMode != TEXT_CURSOR_SELECTION_MODE_NONE) + { + if(IsRendered) + { + if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1) + Graphics()->QuadContainerReset(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex); + } + } + while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines)) { int NewLine = 0; @@ -1276,6 +1140,7 @@ public: { int Wlen = minimum(WordLength((char *)pCurrent), (int)(pEnd - pCurrent)); CTextCursor Compare = *pCursor; + Compare.m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE; Compare.m_X = DrawX; Compare.m_Y = DrawY; Compare.m_Flags &= ~TEXTFLAG_RENDER; @@ -1286,6 +1151,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_GlyphCount = 0; Cutter.m_CharCount = 0; Cutter.m_X = DrawX; @@ -1315,7 +1181,7 @@ public: while(pCurrent < pBatchEnd) { - TextContainer.m_CharCount += pTmp - pCurrent; + pCursor->m_CharCount += pTmp - pCurrent; int Character = NextCharacter; pCurrent = pTmp; NextCharacter = str_utf8_decode(&pTmp); @@ -1324,15 +1190,7 @@ public: { LastCharGlyphIndex = 0; ++CharacterCounter; - - DrawX = pCursor->m_StartX; - DrawY += Size; - if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } - ++LineCount; + StartNewLine(); if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) break; continue; @@ -1342,14 +1200,16 @@ public: if(pChr) { bool ApplyBearingX = !(((RenderFlags & TEXT_RENDER_FLAG_NO_X_BEARING) != 0) || (CharacterCounter == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING) != 0)); - float Advance = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pChr->m_Width) : (pChr->m_AdvanceX + ((!ApplyBearingX) ? (-pChr->m_OffsetX) : 0.f)))) * Scale; + float Advance = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pChr->m_Width) : (pChr->m_AdvanceX + ((!ApplyBearingX) ? (-pChr->m_OffsetX) : 0.f)))) * Scale * Size; + + float OutLineRealDiff = (pChr->m_Width - pChr->m_CharWidth) * Scale * Size; float CharKerning = 0.f; if((RenderFlags & TEXT_RENDER_FLAG_KERNING) != 0) CharKerning = Kerning(TextContainer.m_pFont, LastCharGlyphIndex, pChr->m_GlyphIndex) * Scale * Size; LastCharGlyphIndex = pChr->m_GlyphIndex; - if(pCursor->m_Flags & TEXTFLAG_STOP_AT_END && (DrawX + CharKerning) + Advance * Size - pCursor->m_StartX > pCursor->m_LineWidth) + if(pCursor->m_Flags & TEXTFLAG_STOP_AT_END && (DrawX + CharKerning) + Advance - pCursor->m_StartX > pCursor->m_LineWidth) { // we hit the end of the line, no more to render or count pCurrent = pEnd; @@ -1373,16 +1233,18 @@ public: } } + float TmpY = (DrawY + Size); + float CharX = (DrawX + CharKerning) + BearingX; + float CharY = TmpY - BearingY; + // don't add text that isn't drawn, the color overwrite is used for that - if(m_Color.a != 0.f) + if(m_Color.a != 0.f && IsRendered) { TextContainer.m_StringInfo.m_CharacterQuads.push_back(STextCharQuad()); STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_CharacterQuads.back(); - float Y = (DrawY + Size); - - TextCharQuad.m_Vertices[0].m_X = (DrawX + CharKerning) + BearingX; - TextCharQuad.m_Vertices[0].m_Y = Y - BearingY; + TextCharQuad.m_Vertices[0].m_X = CharX; + TextCharQuad.m_Vertices[0].m_Y = CharY; TextCharQuad.m_Vertices[0].m_U = pChr->m_aUVs[0]; TextCharQuad.m_Vertices[0].m_V = pChr->m_aUVs[3]; TextCharQuad.m_Vertices[0].m_Color.m_R = (unsigned char)(m_Color.r * 255.f); @@ -1390,8 +1252,8 @@ public: TextCharQuad.m_Vertices[0].m_Color.m_B = (unsigned char)(m_Color.b * 255.f); TextCharQuad.m_Vertices[0].m_Color.m_A = (unsigned char)(m_Color.a * 255.f); - TextCharQuad.m_Vertices[1].m_X = (DrawX + CharKerning) + BearingX + CharWidth; - TextCharQuad.m_Vertices[1].m_Y = Y - BearingY; + TextCharQuad.m_Vertices[1].m_X = CharX + CharWidth; + TextCharQuad.m_Vertices[1].m_Y = CharY; TextCharQuad.m_Vertices[1].m_U = pChr->m_aUVs[2]; TextCharQuad.m_Vertices[1].m_V = pChr->m_aUVs[3]; TextCharQuad.m_Vertices[1].m_Color.m_R = (unsigned char)(m_Color.r * 255.f); @@ -1399,8 +1261,8 @@ public: TextCharQuad.m_Vertices[1].m_Color.m_B = (unsigned char)(m_Color.b * 255.f); TextCharQuad.m_Vertices[1].m_Color.m_A = (unsigned char)(m_Color.a * 255.f); - TextCharQuad.m_Vertices[2].m_X = (DrawX + CharKerning) + BearingX + CharWidth; - TextCharQuad.m_Vertices[2].m_Y = Y - BearingY - CharHeight; + TextCharQuad.m_Vertices[2].m_X = CharX + CharWidth; + TextCharQuad.m_Vertices[2].m_Y = CharY - CharHeight; TextCharQuad.m_Vertices[2].m_U = pChr->m_aUVs[2]; TextCharQuad.m_Vertices[2].m_V = pChr->m_aUVs[1]; TextCharQuad.m_Vertices[2].m_Color.m_R = (unsigned char)(m_Color.r * 255.f); @@ -1408,8 +1270,8 @@ public: TextCharQuad.m_Vertices[2].m_Color.m_B = (unsigned char)(m_Color.b * 255.f); TextCharQuad.m_Vertices[2].m_Color.m_A = (unsigned char)(m_Color.a * 255.f); - TextCharQuad.m_Vertices[3].m_X = (DrawX + CharKerning) + BearingX; - TextCharQuad.m_Vertices[3].m_Y = Y - BearingY - CharHeight; + TextCharQuad.m_Vertices[3].m_X = CharX; + TextCharQuad.m_Vertices[3].m_Y = CharY - CharHeight; TextCharQuad.m_Vertices[3].m_U = pChr->m_aUVs[0]; TextCharQuad.m_Vertices[3].m_V = pChr->m_aUVs[1]; TextCharQuad.m_Vertices[3].m_Color.m_R = (unsigned char)(m_Color.r * 255.f); @@ -1418,14 +1280,57 @@ public: TextCharQuad.m_Vertices[3].m_Color.m_A = (unsigned char)(m_Color.a * 255.f); } + // calculate the full width from the last selection point to the end of this selection draw on screen + float SelWidth = (CharX + maximum(Advance, CharWidth - OutLineRealDiff / 2)) - (LastSelX + LastSelWidth); + float SelX = (LastSelX + LastSelWidth); + + 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); + } + + // if selection didn't start and the mouse pos is atleast on 50% of the right side of the character start + CheckSelectionStart(false, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); + CheckSelectionStart(false, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); + CheckSelectionEnd(false, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, CharX, CharWidth, TmpY); + CheckSelectionEnd(false, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, CharX, CharWidth, TmpY); + } + if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET) + { + if((int)CharacterCounter == pCursor->m_SelectionStart) + { + SelectionStarted = !SelectionStarted; + SelectionUsedPress = true; + } + if((int)CharacterCounter == pCursor->m_SelectionEnd) + { + SelectionStarted = !SelectionStarted; + SelectionUsedRelease = true; + } + } + pCursor->m_MaxCharacterHeight = maximum(pCursor->m_MaxCharacterHeight, CharHeight + BearingY); if(NextCharacter == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE) != 0 && Character != ' ') DrawX += BearingX + CharKerning + CharWidth; else - DrawX += Advance * Size + CharKerning; + DrawX += Advance + CharKerning; + pCursor->m_GlyphCount++; ++CharacterCounter; + + if(SelectionStarted && IsRendered) + { + SelectionQuads.push_back(IGraphics::CQuadItem(SelX, DrawY, SelWidth, Size)); + } + + LastSelX = SelX; + LastSelWidth = SelWidth; + LastCharX = CharX; + LastCharWidth = CharWidth; } if(DrawX > pCursor->m_LongestLineWidth) @@ -1434,22 +1339,15 @@ public: if(NewLine) { - DrawX = pCursor->m_StartX; - DrawY += Size; - if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } + StartNewLine(); GotNewLine = 1; GotNewLineLast = 1; - ++LineCount; } else GotNewLineLast = 0; } - if(TextContainer.m_StringInfo.m_CharacterQuads.size() != 0) + if(TextContainer.m_StringInfo.m_CharacterQuads.size() != 0 && IsRendered) { TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_CharacterQuads.size(); // setup the buffers @@ -1466,6 +1364,40 @@ public: } } + if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) + { + pCursor->m_SelectionStart = -1; + pCursor->m_SelectionEnd = -1; + + if(SelectionStarted) + { + CheckSelectionEnd(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::max(), 0, DrawY + Size); + CheckSelectionEnd(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, std::numeric_limits::max(), 0, DrawY + Size); + } + } + else if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET) + { + if((int)CharacterCounter == pCursor->m_SelectionEnd) + { + SelectionStarted = !SelectionStarted; + SelectionUsedRelease = true; + } + } + + if(!SelectionQuads.empty() && SelectionUsedPress && SelectionUsedRelease) + { + 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(); + Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, &SelectionQuads[0], (int)SelectionQuads.size()); + + pCursor->m_SelectionStart = SelectionStartChar; + pCursor->m_SelectionEnd = SelectionEndChar; + } + } + // even if no text is drawn the cursor position will be adjusted pCursor->m_X = DrawX; pCursor->m_LineCount = LineCount; @@ -1490,223 +1422,6 @@ public: AppendTextContainer(pCursor, TextContainerIndex, pText, Length); } - virtual void SetTextContainerSelection(int TextContainerIndex, const char *pText, int CursorPos, int SelectionStart, int SelectionEnd) - { - STextContainer &TextContainer = GetTextContainer(TextContainerIndex); - - CFontSizeData *pSizeData = NULL; - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - float FakeToScreenX, FakeToScreenY; - - int ActualSize; - float DrawX = 0.0f, DrawY = 0.0f; - int LineCount = 0; - - float Size = TextContainer.m_UnscaledFontSize; - - // calculate the font size of the displayed glyphs - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); - FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - - // same with size - ActualSize = (int)(Size * FakeToScreenY); - Size = ActualSize / FakeToScreenY; - - pSizeData = TextContainer.m_pFont->GetFontSize(TextContainer.m_FontSize); - - FT_Set_Pixel_Sizes(TextContainer.m_pFont->m_FtFace, 0, TextContainer.m_FontSize); - - // string length - int Length = str_length(pText); - - float Scale = 1.0f / pSizeData->m_FontSize; - float MaxRowHeight = (TextContainer.m_pFont->m_FtFace->size->metrics.height >> 6) * Scale * Size; - - const char *pCurrent = (char *)pText; - const char *pCurrentLast = (char *)pText; - const char *pEnd = pCurrent + Length; - - int RenderFlags = TextContainer.m_RenderFlags; - - if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) != 0) - { - DrawX = TextContainer.m_X; - DrawY = TextContainer.m_Y; - } - else - { - DrawX = TextContainer.m_AlignedStartX; - DrawY = TextContainer.m_AlignedStartY; - } - - LineCount = TextContainer.m_LineCount; - - if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1) - TextContainer.m_StringInfo.m_SelectionQuadContainerIndex = Graphics()->CreateQuadContainer(); - - Graphics()->QuadContainerReset(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex); - - std::vector SelectionQuads; - IGraphics::CQuadItem CursorQuad; - - FT_UInt LastCharGlyphIndex = 0; - size_t CharacterCounter = 0; - - while(pCurrent < pEnd && (TextContainer.m_MaxLines < 1 || LineCount <= TextContainer.m_MaxLines)) - { - int NewLine = 0; - const char *pBatchEnd = pEnd; - if(TextContainer.m_LineWidth > 0 && !(TextContainer.m_Flags & TEXTFLAG_STOP_AT_END)) - { - CTextCursor FakeCursor; - SetCursor(&FakeCursor, DrawX, DrawY, TextContainer.m_UnscaledFontSize, TextContainer.m_Flags); - FakeCursor.m_LineCount = TextContainer.m_LineCount; - FakeCursor.m_GlyphCount = TextContainer.m_GlyphCount; - FakeCursor.m_CharCount = TextContainer.m_CharCount; - FakeCursor.m_MaxLines = TextContainer.m_MaxLines; - FakeCursor.m_StartX = TextContainer.m_StartX; - FakeCursor.m_StartY = TextContainer.m_StartY; - FakeCursor.m_LineWidth = TextContainer.m_LineWidth; - FakeCursor.m_pFont = TextContainer.m_pFont; - - int Wlen = minimum(WordLength((char *)pCurrent), (int)(pEnd - pCurrent)); - CTextCursor Compare = FakeCursor; - Compare.m_X = DrawX; - Compare.m_Y = DrawY; - Compare.m_Flags &= ~TEXTFLAG_RENDER; - Compare.m_LineWidth = -1; - TextEx(&Compare, pCurrent, Wlen); - - if(Compare.m_X - DrawX > TextContainer.m_LineWidth) - { - // word can't be fitted in one line, cut it - CTextCursor Cutter = FakeCursor; - Cutter.m_GlyphCount = 0; - Cutter.m_CharCount = 0; - Cutter.m_X = DrawX; - Cutter.m_Y = DrawY; - Cutter.m_Flags &= ~TEXTFLAG_RENDER; - Cutter.m_Flags |= TEXTFLAG_STOP_AT_END; - - TextEx(&Cutter, pCurrent, Wlen); - int WordGlyphs = Cutter.m_GlyphCount; - Wlen = Cutter.m_CharCount; - NewLine = 1; - - if(WordGlyphs <= 3) // if we can't place 3 chars of the word on this line, take the next - Wlen = 0; - } - else if(Compare.m_X - TextContainer.m_StartX > TextContainer.m_LineWidth) - { - NewLine = 1; - Wlen = 0; - } - - pBatchEnd = pCurrent + Wlen; - } - - pCurrentLast = pCurrent; - const char *pTmp = pCurrent; - int NextCharacter = str_utf8_decode(&pTmp); - while(pCurrent < pBatchEnd) - { - TextContainer.m_CharCount += pTmp - pCurrent; - int Character = NextCharacter; - pCurrent = pTmp; - NextCharacter = str_utf8_decode(&pTmp); - - if(Character == '\n') - { - LastCharGlyphIndex = 0; - ++CharacterCounter; - - DrawX = TextContainer.m_StartX; - DrawY += Size; - if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } - ++LineCount; - if(TextContainer.m_MaxLines > 0 && LineCount > TextContainer.m_MaxLines) - break; - continue; - } - - SFontSizeChar *pChr = GetChar(TextContainer.m_pFont, pSizeData, Character); - if(pChr) - { - bool ApplyBearingX = !(((RenderFlags & TEXT_RENDER_FLAG_NO_X_BEARING) != 0) || (CharacterCounter == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING) != 0)); - float Advance = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pChr->m_Width) : (pChr->m_AdvanceX + (!ApplyBearingX ? (-pChr->m_OffsetX) : 0.f)))) * Scale; - - float CharKerning = 0.f; - if((RenderFlags & TEXT_RENDER_FLAG_KERNING) != 0) - CharKerning = Kerning(TextContainer.m_pFont, LastCharGlyphIndex, pChr->m_GlyphIndex) * Scale * Size; - LastCharGlyphIndex = pChr->m_GlyphIndex; - - if(TextContainer.m_Flags & TEXTFLAG_STOP_AT_END && (DrawX + CharKerning) + Advance * Size - TextContainer.m_StartX > TextContainer.m_LineWidth) - { - // we hit the end of the line, no more to render or count - pCurrent = pEnd; - break; - } - - int CharBytePos = (int)((size_t)(pCurrentLast - pText)); - - if(CharBytePos == CursorPos) - { - CursorQuad.Set((DrawX + CharKerning), DrawY, 2.f * Scale * Size, MaxRowHeight); - } - - if(CharBytePos >= SelectionStart && CharBytePos < SelectionEnd) - { - SelectionQuads.push_back(IGraphics::CQuadItem((DrawX + CharKerning), DrawY, Advance * Size, MaxRowHeight)); - } - - float BearingX = (!ApplyBearingX ? 0.f : pChr->m_OffsetX) * Scale * Size; - float CharWidth = pChr->m_Width * Scale * Size; - - if(NextCharacter == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE) != 0) - DrawX += BearingX + CharKerning + CharWidth; - else - DrawX += Advance * Size + CharKerning; - - TextContainer.m_GlyphCount++; - ++CharacterCounter; - } - pCurrentLast = pCurrent; - } - - if(NewLine) - { - DrawX = TextContainer.m_StartX; - DrawY += Size; - if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) - { - DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign - DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; - } - ++LineCount; - } - } - - int CharBytePos = (int)((size_t)(pCurrentLast - pText)); - if(CharBytePos == CursorPos) - { - CursorQuad.Set(DrawX, DrawY, 2.f * Scale * Size, MaxRowHeight); - } - - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, &CursorQuad, 1); - Graphics()->SetColor(0.f, 0.f, 1.f, 0.8f); - if(SelectionQuads.size() > 0) - Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, &SelectionQuads[0], (int)SelectionQuads.size()); - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - } - virtual void DeleteTextContainer(int TextContainerIndex) { STextContainer &TextContainer = GetTextContainer(TextContainerIndex); @@ -1714,9 +1429,9 @@ public: { if(TextContainer.m_StringInfo.m_QuadBufferContainerIndex != -1) Graphics()->DeleteBufferContainer(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, true); - if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1) - Graphics()->DeleteQuadContainer(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex); } + if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1) + Graphics()->DeleteQuadContainer(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex); FreeTextContainer(TextContainerIndex); } @@ -1741,15 +1456,17 @@ public: if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex != -1) { - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - Graphics()->RenderQuadContainer(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, 1, -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(); + /*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(); + s_CursorRenderTime = time_get_microseconds();*/ } if(Graphics()->IsTextBufferingEnabled()) @@ -1981,11 +1698,45 @@ public: return WidthOfText; } + virtual bool SelectionToUTF8OffSets(const char *pText, int SelStart, int SelEnd, int &OffUTF8Start, int &OffUTF8End) + { + const char *pIt = pText; + + OffUTF8Start = -1; + OffUTF8End = -1; + + int CharCount = 0; + while(*pIt) + { + const char *pTmp = pIt; + int Character = str_utf8_decode(&pTmp); + if(Character == -1) + return false; + + if(CharCount == SelStart) + OffUTF8Start = (int)((std::intptr_t)(pIt - pText)); + + if(CharCount == SelEnd) + OffUTF8End = (int)((std::intptr_t)(pIt - pText)); + + pIt = pTmp; + ++CharCount; + } + + if(CharCount == SelStart) + OffUTF8Start = (int)((std::intptr_t)(pIt - pText)); + + if(CharCount == SelEnd) + OffUTF8End = (int)((std::intptr_t)(pIt - pText)); + + return OffUTF8Start != -1 && OffUTF8End != -1; + } + virtual void OnWindowResize() { bool FoundTextContainer = false; - for(auto &TextContainer : m_TextContainers) - if(TextContainer.m_StringInfo.m_QuadBufferContainerIndex != -1) + for(auto *pTextContainer : m_TextContainers) + if(pTextContainer->m_StringInfo.m_QuadBufferContainerIndex != -1) FoundTextContainer = true; if(FoundTextContainer) { diff --git a/src/engine/input.h b/src/engine/input.h index 36f971195..eccf30613 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -68,6 +68,8 @@ public: virtual void Clear() = 0; // + virtual void NativeMousePos(int *mx, int *my) const = 0; + virtual bool NativeMousePressed(int index) = 0; virtual void MouseModeRelative() = 0; virtual void MouseModeAbsolute() = 0; virtual int MouseDoubleClick() = 0; diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 059f0fcae..79eed9683 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -35,6 +35,16 @@ enum class CFont; +enum ETextCursorSelectionMode +{ + // ignore any kind of selection + TEXT_CURSOR_SELECTION_MODE_NONE = 0, + // calculates the selection based on the mouse press and release cursor position + TEXT_CURSOR_SELECTION_MODE_CALCULATE, + // sets the selection based on the character start and end count(these values have to be decoded character offsets) + TEXT_CURSOR_SELECTION_MODE_SET, +}; + class CTextCursor { public: @@ -55,6 +65,19 @@ public: CFont *m_pFont; float m_FontSize; float m_AlignedFontSize; + + ETextCursorSelectionMode m_CalculateSelectionMode; + + // these coordinates are repsected if selection mode is set to calculate @see ETextCursorSelectionMode + int m_PressMouseX; + int m_PressMouseY; + int m_ReleaseMouseX; + int m_ReleaseMouseY; + + // note m_SelectionStart can be bigger than m_SelectionEnd, depending on how the mouse cursor was dragged + // also note, that these are the character offsets decoded + int m_SelectionStart; + int m_SelectionEnd; }; struct STextRenderColor @@ -111,6 +134,7 @@ public: ColorRGBA DefaultTextColor() { return ColorRGBA(1, 1, 1, 1); } ColorRGBA DefaultTextOutlineColor() { return ColorRGBA(0, 0, 0, 0.3f); } + ColorRGBA DefaultSelectionColor() { return ColorRGBA(0, 0, 1.0f, 1.0f); } // virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) = 0; @@ -119,7 +143,6 @@ public: // just deletes and creates text container virtual void RecreateTextContainer(CTextCursor *pCursor, int TextContainerIndex, const char *pText, int Length = -1) = 0; virtual void RecreateTextContainerSoft(CTextCursor *pCursor, int TextContainerIndex, const char *pText, int Length = -1) = 0; - virtual void SetTextContainerSelection(int TextContainerIndex, const char *pText, int CursorPos, int SelectionStart, int SelectionEnd) = 0; virtual void DeleteTextContainer(int TextContainerIndex) = 0; virtual void UploadTextContainer(int TextContainerIndex) = 0; @@ -131,17 +154,22 @@ public: virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) = 0; 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; + // old foolish interface virtual void TextColor(float r, float g, float b, float a) = 0; virtual void TextColor(ColorRGBA rgb) = 0; virtual void TextOutlineColor(float r, float g, float b, float a) = 0; virtual void TextOutlineColor(ColorRGBA rgb) = 0; + virtual void TextSelectionColor(float r, float g, float b, float a) = 0; + virtual void TextSelectionColor(ColorRGBA rgb) = 0; virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, float LineWidth) = 0; virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int StrLength, float LineWidth, float *pAlignedHeight = NULL, float *pMaxCharacterHeightInLine = NULL) = 0; virtual int TextLineCount(void *pFontSetV, float Size, const char *pText, float LineWidth) = 0; virtual ColorRGBA GetTextColor() = 0; virtual ColorRGBA GetTextOutlineColor() = 0; + virtual ColorRGBA GetTextSelectionColor() = 0; virtual void OnWindowResize() = 0; diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index eb8d50fa2..a6fe76e71 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -33,6 +33,8 @@ #include +#include + #include "console.h" CGameConsole::CInstance::CInstance(int Type) @@ -277,9 +279,11 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) else if(Event.m_Key == KEY_PAGEUP) { ++m_BacklogActPage; + m_pGameConsole->m_HasSelection = false; } else if(Event.m_Key == KEY_PAGEDOWN) { + m_pGameConsole->m_HasSelection = false; --m_BacklogActPage; if(m_BacklogActPage < 0) m_BacklogActPage = 0; @@ -289,10 +293,12 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) else if(Event.m_Key == KEY_HOME && m_Input.GetString()[0] == '\0') { m_BacklogActPage = INT_MAX; + m_pGameConsole->m_HasSelection = false; } else if(Event.m_Key == KEY_END && m_Input.GetString()[0] == '\0') { m_BacklogActPage = 0; + m_pGameConsole->m_HasSelection = false; } else if(Event.m_Key == KEY_LSHIFT) { @@ -355,6 +361,8 @@ void CGameConsole::CInstance::PrintLine(const char *pLine, ColorRGBA PrintColor) pEntry->m_PrintColor = PrintColor; mem_copy(pEntry->m_aText, pLine, Len); pEntry->m_aText[Len] = 0; + if(m_pGameConsole->m_ConsoleType == m_Type) + m_pGameConsole->m_NewLineCounter++; } CGameConsole::CGameConsole() : @@ -650,6 +658,11 @@ void CGameConsole::OnRender() float OffsetY = 0.0f; float LineOffset = 1.0f; + bool WantsSelectionCopy = false; + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C)) + WantsSelectionCopy = true; + std::string SelectionString; + for(int Page = 0; Page <= pConsole->m_BacklogActPage; ++Page, OffsetY = 0.0f) { while(pEntry) @@ -662,7 +675,7 @@ void CGameConsole::OnRender() TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FontSize, 0); Cursor.m_LineWidth = Screen.w - 10; TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - pEntry->m_YOffset = Cursor.m_Y + Cursor.m_FontSize + LineOffset; + pEntry->m_YOffset = Cursor.m_Y + Cursor.m_AlignedFontSize + LineOffset; } OffsetY += pEntry->m_YOffset; @@ -670,17 +683,74 @@ void CGameConsole::OnRender() if(y - OffsetY <= RowHeight) break; + if(!m_MouseIsPress && Input()->NativeMousePressed(1)) + { + m_MouseIsPress = true; + Input()->NativeMousePos(&m_MousePressX, &m_MousePressY); + m_MousePressX = (m_MousePressX / (float)Graphics()->ScreenWidth()) * Screen.w; + m_MousePressY = (m_MousePressY / (float)Graphics()->ScreenHeight()) * Screen.h; + } + if(m_MouseIsPress) + { + Input()->NativeMousePos(&m_MouseCurX, &m_MouseCurY); + m_MouseCurX = (m_MouseCurX / (float)Graphics()->ScreenWidth()) * Screen.w; + m_MouseCurY = (m_MouseCurY / (float)Graphics()->ScreenHeight()) * Screen.h; + } + if(m_MouseIsPress && !Input()->NativeMousePressed(1)) + { + m_MouseIsPress = false; + } + // just render output from actual backlog page (render bottom up) if(Page == pConsole->m_BacklogActPage) { + if(m_NewLineCounter > 0 && (m_HasSelection || m_MouseIsPress)) + { + m_MousePressY -= OffsetY; + if(!m_MouseIsPress) + m_MouseCurY -= OffsetY; + } TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = Screen.w - 10.0f; + Cursor.m_CalculateSelectionMode = (m_MouseIsPress || (m_CurSelStart != m_CurSelEnd) || m_HasSelection) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; + Cursor.m_PressMouseX = m_MousePressX; + Cursor.m_PressMouseY = m_MousePressY; + Cursor.m_ReleaseMouseX = m_MouseCurX; + Cursor.m_ReleaseMouseY = m_MouseCurY; TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); + if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) + { + m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); + m_CurSelEnd = maximum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); + } + if(m_CurSelStart != m_CurSelEnd) + { + if(WantsSelectionCopy) + { + bool HasNewLine = false; + if(!SelectionString.empty()) + HasNewLine = true; + int OffUTF8Start = 0; + int OffUTF8End = 0; + if(TextRender()->SelectionToUTF8OffSets(pEntry->m_aText, m_CurSelStart, m_CurSelEnd, OffUTF8Start, OffUTF8End)) + { + SelectionString.insert(0, (std::string(&pEntry->m_aText[OffUTF8Start], OffUTF8End - OffUTF8Start) + (HasNewLine ? "\n" : ""))); + } + } + m_HasSelection = true; + } } pEntry = pConsole->m_Backlog.Prev(pEntry); // reset color TextRender()->TextColor(1, 1, 1, 1); + if(m_NewLineCounter > 0) + --m_NewLineCounter; + } + + if(WantsSelectionCopy && !SelectionString.empty()) + { + Input()->SetClipboardText(SelectionString.c_str()); } // actual backlog page number is too high, render last available page (current checked one, render top down) @@ -773,7 +843,8 @@ void CGameConsole::Toggle(int Type) Input()->SetIMEState(false); } } - + if(m_ConsoleType != Type) + m_HasSelection = false; m_ConsoleType = Type; } diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 7f332b4d1..808fa5c01 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -84,6 +84,16 @@ class CGameConsole : public CComponent float m_StateChangeEnd; float m_StateChangeDuration; + bool m_MouseIsPress = false; + int m_MousePressX = 0; + int m_MousePressY = 0; + int m_MouseCurX = 0; + int m_MouseCurY = 0; + int m_CurSelStart = 0; + int m_CurSelEnd = 0; + bool m_HasSelection = false; + int m_NewLineCounter = 0; + void Toggle(int Type); void Dump(int Type);