Improve text line spacing and console text selection

Consider line spacing to belong to the previous line when calculating and rendering text selection. Instead of handling spacing between entries separately in the console, also include line spacing for the last line in the height calculation. Pixel align the line spacing in addition to the font size, as previously some gaps between the entries were larger than others due to missing pixel alignment. This allows rendering the text selection in the console smoothly without any gaps between the console entries/lines.

Closes #7617.
This commit is contained in:
Robert Müller 2023-12-11 22:14:45 +01:00
parent c9b2872459
commit 4c223a0002
3 changed files with 30 additions and 31 deletions

View file

@ -1268,7 +1268,9 @@ public:
pCursor->m_GlyphCount = 0;
pCursor->m_CharCount = 0;
pCursor->m_MaxLines = 0;
pCursor->m_LineSpacing = 0;
pCursor->m_AlignedLineSpacing = 0;
pCursor->m_StartX = x;
pCursor->m_StartY = y;
@ -1481,7 +1483,7 @@ public:
const float CursorY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y;
const int ActualSize = round_truncate(pCursor->m_FontSize * FakeToScreen.y);
pCursor->m_AlignedFontSize = ActualSize / FakeToScreen.y;
const float LineSpacing = pCursor->m_LineSpacing;
pCursor->m_AlignedLineSpacing = round_truncate(pCursor->m_LineSpacing * FakeToScreen.y) / FakeToScreen.y;
// string length
if(Length < 0)
@ -1539,38 +1541,34 @@ public:
const auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool {
return (LastCharX - LastCharWidth / 2 <= CursorPos.x &&
CharX + CharWidth / 2 > CursorPos.x &&
CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y &&
CharY + LineSpacing > CursorPos.y) ||
CursorPos.y >= CharY - pCursor->m_AlignedFontSize &&
CursorPos.y < CharY + pCursor->m_AlignedLineSpacing) ||
(CheckOuter &&
CharY - pCursor->m_AlignedFontSize + LineSpacing > CursorPos.y);
CursorPos.y <= CharY - pCursor->m_AlignedFontSize);
};
const auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) {
if(!SelectionStarted && !SelectionUsedCase)
if(!SelectionStarted && !SelectionUsedCase &&
CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY))
{
if(CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY))
{
SelectionChar = pCursor->m_GlyphCount;
SelectionStarted = !SelectionStarted;
SelectionUsedCase = true;
}
SelectionChar = pCursor->m_GlyphCount;
SelectionStarted = !SelectionStarted;
SelectionUsedCase = true;
}
};
const auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool {
return (CharX + CharWidth / 2 > CursorPos.x &&
CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y &&
CharY + LineSpacing > CursorPos.y) ||
CursorPos.y >= CharY - pCursor->m_AlignedFontSize &&
CursorPos.y < CharY + pCursor->m_AlignedLineSpacing) ||
(CheckOuter &&
CharY - LineSpacing <= CursorPos.y);
CursorPos.y >= CharY + pCursor->m_AlignedLineSpacing);
};
const auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) {
if(SelectionStarted && !SelectionUsedCase)
if(SelectionStarted && !SelectionUsedCase &&
CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY))
{
if(CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY))
{
SelectionChar = pCursor->m_GlyphCount;
SelectionStarted = !SelectionStarted;
SelectionUsedCase = true;
}
SelectionChar = pCursor->m_GlyphCount;
SelectionStarted = !SelectionStarted;
SelectionUsedCase = true;
}
};
@ -1585,7 +1583,7 @@ public:
return false;
DrawX = pCursor->m_StartX;
DrawY += pCursor->m_AlignedFontSize + pCursor->m_LineSpacing;
DrawY += pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing;
if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0)
{
DrawX = round_to_int(DrawX * FakeToScreen.x) / FakeToScreen.x; // realign
@ -1868,7 +1866,8 @@ public:
if(SelectionStarted && IsRendered)
{
vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * pCursor->m_AlignedFontSize, SelWidth, pCursor->m_SelectionHeightFactor * pCursor->m_AlignedFontSize);
const float SelectionHeight = pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing;
vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * SelectionHeight, SelWidth, pCursor->m_SelectionHeightFactor * SelectionHeight);
}
LastSelX = SelX;
@ -1957,9 +1956,9 @@ public:
if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1)
TextContainer.m_StringInfo.m_SelectionQuadContainerIndex = Graphics()->CreateQuadContainer(false);
if(HasCursor)
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, aCursorQuads, 2);
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, aCursorQuads, std::size(aCursorQuads));
if(HasSelection)
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, vSelectionQuads.data(), (int)vSelectionQuads.size());
Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, vSelectionQuads.data(), vSelectionQuads.size());
Graphics()->QuadContainerUpload(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex);
TextContainer.m_HasCursor = HasCursor;

View file

@ -212,6 +212,7 @@ public:
float m_FontSize;
float m_AlignedFontSize;
float m_LineSpacing;
float m_AlignedLineSpacing;
ETextCursorSelectionMode m_CalculateSelectionMode;
float m_SelectionHeightFactor;
@ -237,7 +238,7 @@ public:
float Height() const
{
return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing;
return m_LineCount * (m_AlignedFontSize + m_AlignedLineSpacing);
}
STextBoundingBox BoundingBox() const

View file

@ -575,7 +575,7 @@ void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) c
Cursor.m_MaxLines = 10;
Cursor.m_LineSpacing = LINE_SPACING;
m_pGameConsole->TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
pEntry->m_YOffset = Cursor.Height() + LINE_SPACING;
pEntry->m_YOffset = Cursor.Height();
pEntry->m_LineCount = Cursor.m_LineCount;
}
@ -909,9 +909,9 @@ void CGameConsole::OnRender()
{
// Get height of 1 line
float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H + LINE_SPACING;
float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H;
float RowHeight = FONT_SIZE * 1.25f;
float RowHeight = FONT_SIZE * 1.5f;
float x = 3;
float y = ConsoleHeight - RowHeight - 5.0f;
@ -1104,8 +1104,7 @@ void CGameConsole::OnRender()
if(First)
{
int Diff = pConsole->m_BacklogLastActiveLine - SkippedLines;
OffsetY -= Diff * LineHeight - LINE_SPACING;
OffsetY -= (pConsole->m_BacklogLastActiveLine - SkippedLines) * LineHeight;
}
float LocalOffsetY = OffsetY + pEntry->m_YOffset / (float)pEntry->m_LineCount;