mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
This code originated from an imgui code editor, where it would make sense to have per-character colors. But for the console, we only ever do 1 color per line. Just store the string and one color instance, this also allows us to just take the string instead of manually reconstructing it which is very slow. The code now also only stores 32bits for color (per line) instead of 128 bits (ImVec4) as we only ever need to convert it once. As a result, a bunch of rendering code could be dropped, and the pass filter is now also very fast as we don't need to reconstruct the string to check for the filter, we just pass a char* in which doesn't cost any perf.
1515 lines
38 KiB
C++
1515 lines
38 KiB
C++
#include <algorithm>
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <regex>
|
|
#include <cmath>
|
|
|
|
#include "imgui_logger.h"
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
|
|
template<class InputIt1, class InputIt2, class BinaryPredicate>
|
|
static bool equals(InputIt1 first1, InputIt1 last1,
|
|
InputIt2 first2, InputIt2 last2, BinaryPredicate p)
|
|
{
|
|
for (; first1 != last1 && first2 != last2; ++first1, ++first2)
|
|
{
|
|
if (!p(*first1, *first2))
|
|
return false;
|
|
}
|
|
return first1 == last1 && first2 == last2;
|
|
}
|
|
|
|
CTextLogger::CTextLogger()
|
|
: m_bAutoScroll(true)
|
|
, m_bScrollToCursor(false)
|
|
, m_bScrollToBottom(true)
|
|
, m_bScrolledToBottom(false)
|
|
, m_bHandleUserInputs(true)
|
|
, m_bWithinLoggingRect(false)
|
|
, m_bLinesOffsetForward(false)
|
|
, m_nLinesOffsetAmount(0)
|
|
, m_nTabSize(4)
|
|
, m_nLeftMargin(0)
|
|
, m_flLineSpacing(1.0f)
|
|
, m_SelectionMode(SelectionMode::Normal)
|
|
, m_flLastClick(-1.0)
|
|
, m_nStartTime(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
|
|
{
|
|
m_Lines.push_back(Line());
|
|
}
|
|
|
|
CTextLogger::~CTextLogger()
|
|
{
|
|
}
|
|
|
|
std::string CTextLogger::GetText(const Coordinates& aStart, const Coordinates& aEnd) const
|
|
{
|
|
std::string result;
|
|
|
|
int lstart = aStart.m_nLine;
|
|
int lend = aEnd.m_nLine;
|
|
int istart = GetCharacterIndex(aStart);
|
|
int iend = GetCharacterIndex(aEnd);
|
|
size_t s = 0;
|
|
|
|
for (int i = lstart; i < lend; i++)
|
|
s += m_Lines[i].Length();
|
|
|
|
result.reserve(s + s / 8);
|
|
|
|
while (istart < iend || lstart < lend)
|
|
{
|
|
if (lstart >= static_cast<int>(m_Lines.size()))
|
|
break;
|
|
|
|
const Line& line = m_Lines[lstart];
|
|
if (istart < line.Length())
|
|
{
|
|
result += line.buffer[istart];
|
|
istart++;
|
|
}
|
|
else
|
|
{
|
|
istart = 0;
|
|
++lstart;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::GetActualLastLineCoordinates() const
|
|
{
|
|
return SanitizeCoordinates(Coordinates(GetTotalLines(), 0));
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::GetActualCursorCoordinates() const
|
|
{
|
|
return SanitizeCoordinates(m_State.m_CursorPosition);
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::SanitizeCoordinates(const Coordinates & aValue) const
|
|
{
|
|
int line = aValue.m_nLine;
|
|
int column = aValue.m_nColumn;
|
|
if (line >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
if (m_Lines.empty())
|
|
{
|
|
line = 0;
|
|
column = 0;
|
|
}
|
|
else
|
|
{
|
|
line = static_cast<int>(m_Lines.size() - 1);
|
|
column = GetLineMaxColumn(line);
|
|
}
|
|
return Coordinates(line, column);
|
|
}
|
|
else
|
|
{
|
|
column = m_Lines.empty() ? 0 : ImMin(column, GetLineMaxColumn(line));
|
|
return Coordinates(line, column);
|
|
}
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/UTF-8
|
|
// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
|
|
static int UTF8CharLength(CTextLogger::Char c)
|
|
{
|
|
if ((c & 0xFE) == 0xFC)
|
|
return 6;
|
|
if ((c & 0xFC) == 0xF8)
|
|
return 5;
|
|
if ((c & 0xF8) == 0xF0)
|
|
return 4;
|
|
else if ((c & 0xF0) == 0xE0)
|
|
return 3;
|
|
else if ((c & 0xE0) == 0xC0)
|
|
return 2;
|
|
return 1;
|
|
}
|
|
|
|
bool UTF8StringValid(const char* pszString)
|
|
{
|
|
size_t byteCount = 0;
|
|
CTextLogger::Char currentByte;
|
|
|
|
while (*pszString)
|
|
{
|
|
currentByte = static_cast<CTextLogger::Char>(*pszString);
|
|
if (byteCount)
|
|
{
|
|
if ((currentByte & 0xC0) != 0x80)
|
|
return false;
|
|
|
|
byteCount--;
|
|
}
|
|
else
|
|
{
|
|
byteCount = UTF8CharLength(currentByte) - 1;
|
|
if (byteCount > 0 && (currentByte & (1 << (7 - byteCount))) == 0)
|
|
return false;
|
|
}
|
|
pszString++;
|
|
}
|
|
|
|
return byteCount == 0;
|
|
}
|
|
|
|
void CTextLogger::Advance(Coordinates& aCoordinates) const
|
|
{
|
|
if (aCoordinates.m_nLine < static_cast<int>(m_Lines.size()))
|
|
{
|
|
const Line& line = m_Lines[aCoordinates.m_nLine];
|
|
int cindex = GetCharacterIndex(aCoordinates);
|
|
|
|
if (cindex + 1 < line.Length())
|
|
{
|
|
int delta = UTF8CharLength(line.buffer[cindex]);
|
|
cindex = ImMin(cindex + delta, static_cast<int>(line.Length() - 1));
|
|
}
|
|
else
|
|
{
|
|
++aCoordinates.m_nLine;
|
|
cindex = 0;
|
|
}
|
|
aCoordinates.m_nColumn = GetCharacterColumn(aCoordinates.m_nLine, cindex);
|
|
}
|
|
}
|
|
|
|
//void CTextLogger::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)
|
|
//{
|
|
// assert(aEnd >= aStart);
|
|
//
|
|
// //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn);
|
|
//
|
|
// if (aEnd == aStart)
|
|
// return;
|
|
//
|
|
// int start = GetCharacterIndex(aStart);
|
|
// int end = GetCharacterIndex(aEnd);
|
|
//
|
|
// if (aStart.m_nLine == aEnd.m_nLine)
|
|
// {
|
|
// Line& line = m_Lines[aStart.m_nLine];
|
|
// int n = GetLineMaxColumn(aStart.m_nLine);
|
|
// if (aEnd.m_nColumn >= n)
|
|
// line.erase(line.begin() + start, line.end());
|
|
// else
|
|
// line.erase(line.begin() + start, line.begin() + end);
|
|
// }
|
|
// else
|
|
// {
|
|
// Line& firstLine = m_Lines[aStart.m_nLine];
|
|
// Line& lastLine = m_Lines[aEnd.m_nLine];
|
|
//
|
|
// firstLine.erase(firstLine.begin() + start, firstLine.end());
|
|
// lastLine.erase(lastLine.begin(), lastLine.begin() + end);
|
|
//
|
|
// if (aStart.m_nLine < aEnd.m_nLine)
|
|
// firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
|
|
//
|
|
// if (aStart.m_nLine < aEnd.m_nLine)
|
|
// RemoveLine(aStart.m_nLine + 1, aEnd.m_nLine + 1);
|
|
// }
|
|
//}
|
|
|
|
void CTextLogger::MarkNewline(Coordinates& aWhere, int aIndex)
|
|
{
|
|
Line& newLine = InsertLine(aWhere.m_nLine + 1);
|
|
Line& line = m_Lines[aWhere.m_nLine];
|
|
|
|
if (aIndex < static_cast<int>(m_Lines[aWhere.m_nLine].buffer.size()))
|
|
{
|
|
newLine.buffer.insert(newLine.buffer.begin(), line.buffer.begin() + aIndex, line.buffer.end());
|
|
line.buffer.erase(line.buffer.begin() + aIndex, line.buffer.end());
|
|
}
|
|
else
|
|
line.buffer.push_back('\n');
|
|
|
|
++aWhere.m_nLine;
|
|
aWhere.m_nColumn = 0;
|
|
}
|
|
|
|
int CTextLogger::InsertTextAt(Coordinates& aWhere, const char* aValue, const ImU32 aColor)
|
|
{
|
|
int cindex = GetCharacterIndex(aWhere);
|
|
int totalLines = 0;
|
|
|
|
if (!UTF8StringValid(aValue))
|
|
{
|
|
assert(0);
|
|
aValue = "Invalid UTF-8 string\n";
|
|
}
|
|
|
|
while (*aValue != '\0')
|
|
{
|
|
assert(!m_Lines.empty());
|
|
|
|
if (*aValue == '\r')
|
|
{
|
|
// skip
|
|
++aValue;
|
|
}
|
|
else if (*aValue == '\n')
|
|
{
|
|
MarkNewline(aWhere, cindex);
|
|
cindex = 0;
|
|
++totalLines;
|
|
++aValue;
|
|
}
|
|
else
|
|
{
|
|
Line& line = m_Lines[aWhere.m_nLine];
|
|
|
|
// if the buffer isn't empty, and the next log isn't from the same context, log it as a new line
|
|
if (!line.buffer.empty() && aColor != line.color)
|
|
{
|
|
MarkNewline(aWhere, cindex);
|
|
cindex = 0;
|
|
++totalLines;
|
|
continue;
|
|
}
|
|
|
|
size_t d = UTF8CharLength(*aValue);
|
|
while (d-- > 0 && *aValue != '\0')
|
|
{
|
|
if (cindex >= 0 && cindex <= line.Length())
|
|
line.buffer.insert(line.buffer.begin() + cindex++, *aValue++);
|
|
else
|
|
++aValue; // Possibly an invalid character
|
|
}
|
|
|
|
line.color = aColor;
|
|
++aWhere.m_nColumn;
|
|
}
|
|
}
|
|
|
|
// TODO[ AMOS ]: should we really pad it with a white space if buf empty?
|
|
if (!*aValue)
|
|
{
|
|
Line& line = m_Lines[aWhere.m_nLine];
|
|
if (!line.buffer.empty() && cindex >= 0 && cindex <= line.Length())
|
|
line.buffer.insert(line.buffer.begin() + cindex, ' ');
|
|
}
|
|
|
|
return totalLines;
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::ScreenPosToCoordinates(const ImVec2& aPosition) const
|
|
{
|
|
ImVec2 origin = ImGui::GetCursorScreenPos();
|
|
ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);
|
|
|
|
int lineNo = ImMax(0, static_cast<int>(ImFloor(local.y / m_CharAdvance.y)));
|
|
int columnCoord = 0;
|
|
|
|
if (lineNo >= 0 && lineNo < static_cast<int>(m_Lines.size()))
|
|
{
|
|
const Line& line = m_Lines[lineNo];
|
|
|
|
int columnIndex = 0;
|
|
float columnX = 0.0f;
|
|
|
|
while (columnIndex < line.Length())
|
|
{
|
|
float columnWidth = 0.0f;
|
|
|
|
if (line.buffer[columnIndex] == '\t')
|
|
{
|
|
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x;
|
|
float oldX = columnX;
|
|
float newColumnX = (1.0f + ImFloor((1.0f + columnX) / (float(m_nTabSize) * spaceSize))) * (float(m_nTabSize) * spaceSize);
|
|
columnWidth = newColumnX - oldX;
|
|
|
|
if (columnX + columnWidth * 0.5f > local.x)
|
|
break;
|
|
|
|
columnX = newColumnX;
|
|
columnCoord = (columnCoord / m_nTabSize) * m_nTabSize + m_nTabSize;
|
|
columnIndex++;
|
|
}
|
|
else
|
|
{
|
|
char buf[7];
|
|
size_t d = UTF8CharLength(line.buffer[columnIndex]);
|
|
size_t i = 0;
|
|
|
|
while (i < 6 && d-- > 0 && columnIndex < line.Length())
|
|
buf[i++] = line.buffer[columnIndex++];
|
|
|
|
buf[i] = '\0';
|
|
columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x;
|
|
|
|
if (columnX + columnWidth * 0.5f > local.x)
|
|
break;
|
|
|
|
columnX += columnWidth;
|
|
columnCoord++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::FindWordStart(const Coordinates & aFrom) const
|
|
{
|
|
Coordinates at = aFrom;
|
|
if (at.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
return at;
|
|
|
|
const Line& line = m_Lines[at.m_nLine];
|
|
int cindex = GetCharacterIndex(at);
|
|
|
|
if (cindex >= line.Length())
|
|
return at;
|
|
|
|
while (cindex > 0 && isspace(line.buffer[cindex]))
|
|
--cindex;
|
|
|
|
while (cindex > 0)
|
|
{
|
|
Char c = line.buffer[cindex];
|
|
if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx
|
|
{
|
|
if (c <= 32 && isspace(c))
|
|
{
|
|
cindex++;
|
|
break;
|
|
}
|
|
}
|
|
--cindex;
|
|
}
|
|
|
|
return Coordinates(at.m_nLine, GetCharacterColumn(at.m_nLine, cindex));
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::FindWordEnd(const Coordinates & aFrom) const
|
|
{
|
|
Coordinates at = aFrom;
|
|
if (at.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
return at;
|
|
|
|
const Line& line = m_Lines[at.m_nLine];
|
|
int cindex = GetCharacterIndex(at);
|
|
|
|
if (cindex >= line.Length())
|
|
return at;
|
|
|
|
bool prevspace = static_cast<bool>(isspace(line.buffer[cindex]));
|
|
while (cindex < line.Length())
|
|
{
|
|
Char c = line.buffer[cindex];
|
|
int d = UTF8CharLength(c);
|
|
|
|
if (prevspace != !!isspace(c))
|
|
{
|
|
if (isspace(c))
|
|
while (cindex < line.Length() && !isspace(line.buffer[cindex]))
|
|
++cindex;
|
|
break;
|
|
}
|
|
cindex += d;
|
|
}
|
|
|
|
return Coordinates(aFrom.m_nLine, GetCharacterColumn(aFrom.m_nLine, cindex));
|
|
}
|
|
|
|
CTextLogger::Coordinates CTextLogger::FindNextWord(const Coordinates & aFrom) const
|
|
{
|
|
Coordinates at = aFrom;
|
|
if (at.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
return at;
|
|
|
|
// skip to the next non-word character
|
|
int cindex = GetCharacterIndex(aFrom);
|
|
bool isword = false;
|
|
bool skip = false;
|
|
|
|
if (cindex < static_cast<int>(m_Lines[at.m_nLine].buffer.size()))
|
|
{
|
|
const Line& line = m_Lines[at.m_nLine];
|
|
isword = isalnum(line.buffer[cindex]);
|
|
skip = isword;
|
|
}
|
|
|
|
while (!isword || skip)
|
|
{
|
|
if (at.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
int l = ImMax(0, static_cast<int>(m_Lines.size() - 1));
|
|
return Coordinates(l, GetLineMaxColumn(l));
|
|
}
|
|
|
|
const Line& line = m_Lines[at.m_nLine];
|
|
if (cindex < line.Length())
|
|
{
|
|
isword = isalnum(line.buffer[cindex]);
|
|
|
|
if (isword && !skip)
|
|
return Coordinates(at.m_nLine, GetCharacterColumn(at.m_nLine, cindex));
|
|
|
|
if (!isword)
|
|
skip = false;
|
|
|
|
cindex++;
|
|
}
|
|
else
|
|
{
|
|
cindex = 0;
|
|
++at.m_nLine;
|
|
skip = false;
|
|
isword = false;
|
|
}
|
|
}
|
|
|
|
return at;
|
|
}
|
|
|
|
int CTextLogger::GetCharacterIndex(const Coordinates& aCoordinates) const
|
|
{
|
|
if (aCoordinates.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
return -1;
|
|
|
|
const Line& line = m_Lines[aCoordinates.m_nLine];
|
|
int c = 0;
|
|
int i = 0;
|
|
|
|
for (; i < line.Length() && c < aCoordinates.m_nColumn;)
|
|
{
|
|
if (line.buffer[i] == '\t')
|
|
c = (c / m_nTabSize) * m_nTabSize + m_nTabSize;
|
|
else
|
|
++c;
|
|
i += UTF8CharLength(line.buffer[i]);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int CTextLogger::GetCharacterColumn(int aLine, int aIndex) const
|
|
{
|
|
if (aLine >= static_cast<int>(m_Lines.size()))
|
|
return 0;
|
|
|
|
const Line& line = m_Lines[aLine];
|
|
int col = 0;
|
|
int i = 0;
|
|
|
|
while (i < aIndex && i < line.Length())
|
|
{
|
|
Char c = line.buffer[i];
|
|
i += UTF8CharLength(c);
|
|
if (c == '\t')
|
|
col = (col / m_nTabSize) * m_nTabSize + m_nTabSize;
|
|
else
|
|
col++;
|
|
}
|
|
return col;
|
|
}
|
|
|
|
int CTextLogger::GetLineCharacterCount(int aLine) const
|
|
{
|
|
if (aLine >= static_cast<int>(m_Lines.size()))
|
|
return 0;
|
|
|
|
const Line& line = m_Lines[aLine];
|
|
int c = 0;
|
|
|
|
for (size_t i = 0; i < line.Length(); c++)
|
|
i += static_cast<size_t>(UTF8CharLength(m_Lines[aLine].buffer[i]));
|
|
return c;
|
|
}
|
|
|
|
int CTextLogger::GetLineMaxColumn(int aLine) const
|
|
{
|
|
if (aLine >= static_cast<int>(m_Lines.size()))
|
|
return 0;
|
|
|
|
const Line& line = m_Lines[aLine];
|
|
int col = 0;
|
|
|
|
for (size_t i = 0; i < line.Length(); )
|
|
{
|
|
Char c = line.buffer[i];
|
|
if (c == '\t')
|
|
col = (col / m_nTabSize) * m_nTabSize + m_nTabSize;
|
|
else
|
|
col++;
|
|
i += static_cast<size_t>(UTF8CharLength(c));
|
|
}
|
|
return col;
|
|
}
|
|
|
|
bool CTextLogger::IsOnWordBoundary(const Coordinates & aAt) const
|
|
{
|
|
if (aAt.m_nLine >= static_cast<int>(m_Lines.size()) || aAt.m_nColumn == 0)
|
|
return true;
|
|
|
|
const Line& line = m_Lines[aAt.m_nLine];
|
|
size_t cindex = static_cast<size_t>(GetCharacterIndex(aAt));
|
|
|
|
if (cindex >= line.Length())
|
|
return true;
|
|
|
|
return isspace(line.buffer[cindex]) != isspace(line.buffer[cindex - 1]);
|
|
}
|
|
|
|
void CTextLogger::RemoveLine(int aStart, int aEnd)
|
|
{
|
|
assert(aEnd >= aStart);
|
|
assert(m_Lines.size() > (size_t)(aEnd - aStart));
|
|
|
|
m_Lines.erase(m_Lines.begin() + aStart, m_Lines.begin() + aEnd);
|
|
assert(!m_Lines.empty());
|
|
}
|
|
|
|
void CTextLogger::RemoveLine(int aIndex)
|
|
{
|
|
assert(m_Lines.size() > 1);
|
|
|
|
m_Lines.erase(m_Lines.begin() + aIndex);
|
|
assert(!m_Lines.empty());
|
|
}
|
|
|
|
// TODO[ AMOS ]: rename to InsertBlankLine ?
|
|
CTextLogger::Line& CTextLogger::InsertLine(int aIndex)
|
|
{
|
|
Line& result = *m_Lines.insert(m_Lines.begin() + aIndex, Line());
|
|
return result;
|
|
}
|
|
|
|
std::string CTextLogger::GetWordUnderCursor() const
|
|
{
|
|
const Coordinates c = GetCursorPosition();
|
|
return GetWordAt(c);
|
|
}
|
|
|
|
std::string CTextLogger::GetWordAt(const Coordinates & aCoords) const
|
|
{
|
|
const Coordinates start = FindWordStart(aCoords);
|
|
const Coordinates end = FindWordEnd(aCoords);
|
|
|
|
std::string r;
|
|
|
|
int istart = GetCharacterIndex(start);
|
|
int iend = GetCharacterIndex(end);
|
|
|
|
for (int it = istart; it < iend; ++it)
|
|
r.push_back(m_Lines[aCoords.m_nLine].buffer[it]);
|
|
|
|
return r;
|
|
}
|
|
|
|
//ImU32 CTextLogger::GetGlyphColor(const Glyph & aGlyph) const
|
|
//{
|
|
// ImVec4 color = aGlyph.m_Color;
|
|
// return ImGui::ColorConvertFloat4ToU32(color);
|
|
//}
|
|
|
|
void CTextLogger::HandleKeyboardInputs(bool bHoveredScrollbar, bool bActiveScrollbar)
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
bool shift = io.KeyShift;
|
|
bool ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
|
bool alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
|
|
|
if (!bActiveScrollbar && ImGui::IsWindowFocused())
|
|
{
|
|
if (!bHoveredScrollbar && ImGui::IsWindowHovered())
|
|
ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
|
|
|
|
io.WantCaptureKeyboard = true;
|
|
io.WantTextInput = true;
|
|
|
|
if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
|
|
MoveUp(1, shift);
|
|
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
|
|
MoveDown(1, shift);
|
|
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
|
|
MoveLeft(1, shift, ctrl);
|
|
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
|
|
MoveRight(1, shift, ctrl);
|
|
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
|
|
MoveUp(GetPageSize() - 4, shift);
|
|
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
|
|
MoveDown(GetPageSize() - 4, shift);
|
|
else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
|
MoveTop(shift);
|
|
else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
|
MoveBottom(shift);
|
|
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
|
MoveHome(shift);
|
|
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
|
MoveEnd(shift);
|
|
else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
|
|
Copy();
|
|
else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
|
|
Copy();
|
|
else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A)))
|
|
SelectAll();
|
|
}
|
|
}
|
|
|
|
void CTextLogger::HandleMouseInputs(bool bHoveredScrollbar, bool bActiveScrollbar)
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
bool bShift = io.KeyShift;
|
|
bool bCtrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
|
bool bAlt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
|
|
|
|
|
if (ImGui::IsMouseClicked(0) && ImGui::IsWindowHovered())
|
|
m_bWithinLoggingRect = true;
|
|
else if (ImGui::IsMouseReleased(0))
|
|
m_bWithinLoggingRect = false;
|
|
|
|
if (!bHoveredScrollbar && !bActiveScrollbar && m_bWithinLoggingRect)
|
|
{
|
|
if (!bShift && !bAlt)
|
|
{
|
|
bool click = ImGui::IsMouseClicked(0);
|
|
bool doubleClick = ImGui::IsMouseDoubleClicked(0);
|
|
|
|
double t = ImGui::GetTime();
|
|
bool tripleClick = click && !doubleClick && (m_flLastClick != -1.0 && (t - m_flLastClick) < io.MouseDoubleClickTime);
|
|
|
|
/*
|
|
Left mouse button triple click
|
|
*/
|
|
|
|
if (tripleClick)
|
|
{
|
|
if (!bCtrl)
|
|
{
|
|
m_State.m_CursorPosition = m_InteractiveStart = m_InteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
|
m_SelectionMode = SelectionMode::Line;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, m_SelectionMode);
|
|
}
|
|
|
|
m_flLastClick = -1.0;
|
|
}
|
|
|
|
/*
|
|
Left mouse button double click
|
|
*/
|
|
|
|
else if (doubleClick)
|
|
{
|
|
if (!bCtrl)
|
|
{
|
|
m_State.m_CursorPosition = m_InteractiveStart = m_InteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
|
|
|
if (m_SelectionMode == SelectionMode::Line)
|
|
m_SelectionMode = SelectionMode::Normal;
|
|
else
|
|
m_SelectionMode = SelectionMode::Word;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, m_SelectionMode);
|
|
}
|
|
|
|
m_flLastClick = ImGui::GetTime();
|
|
}
|
|
|
|
/*
|
|
Left mouse button click
|
|
*/
|
|
else if (click)
|
|
{
|
|
m_State.m_CursorPosition = m_InteractiveStart = m_InteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
|
if (bCtrl)
|
|
m_SelectionMode = SelectionMode::Word;
|
|
else
|
|
m_SelectionMode = SelectionMode::Normal;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, m_SelectionMode);
|
|
|
|
m_flLastClick = ImGui::GetTime();
|
|
}
|
|
// Mouse left button dragging (=> update selection)
|
|
else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
|
|
{
|
|
io.WantCaptureMouse = true;
|
|
m_State.m_CursorPosition = m_InteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
|
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, m_SelectionMode);
|
|
EnsureCursorVisible();
|
|
}
|
|
// Move start position of the selection when entries have been erased/inserted
|
|
if (m_nLinesOffsetAmount && ImGui::IsMouseDown(0))
|
|
{
|
|
Coordinates newStart;
|
|
newStart = m_InteractiveStart;
|
|
|
|
if (m_bLinesOffsetForward)
|
|
{
|
|
newStart.m_nLine += m_nLinesOffsetAmount;
|
|
if (newStart.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
newStart.m_nLine = static_cast<int>(m_Lines.size()) - 1;
|
|
newStart.m_nColumn = GetLineMaxColumn(newStart.m_nLine);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newStart.m_nLine -= m_nLinesOffsetAmount;
|
|
if (newStart.m_nLine < 0)
|
|
{
|
|
newStart.m_nLine = 0;
|
|
newStart.m_nColumn = 0;
|
|
}
|
|
}
|
|
|
|
m_nLinesOffsetAmount = 0;
|
|
m_InteractiveStart = newStart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTextLogger::Render()
|
|
{
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
|
|
|
ImGuiWindow* const pWindow = ImGui::GetCurrentWindow();
|
|
|
|
const ImGuiID activeID = ImGui::GetActiveID();
|
|
const ImGuiID hoveredID = ImGui::GetHoveredID();
|
|
|
|
const bool bHoveredScrollbar = hoveredID && (hoveredID == ImGui::GetWindowScrollbarID(pWindow, ImGuiAxis_X) || hoveredID == ImGui::GetWindowScrollbarID(pWindow, ImGuiAxis_Y));
|
|
const bool bActiveScrollbar = activeID && (activeID == ImGui::GetWindowScrollbarID(pWindow, ImGuiAxis_X) || activeID == ImGui::GetWindowScrollbarID(pWindow, ImGuiAxis_Y));
|
|
|
|
if (m_bHandleUserInputs)
|
|
{
|
|
HandleKeyboardInputs(bHoveredScrollbar, bActiveScrollbar);
|
|
ImGui::PushAllowKeyboardFocus(true);
|
|
|
|
HandleMouseInputs(bHoveredScrollbar, bActiveScrollbar);
|
|
}
|
|
|
|
/* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/
|
|
const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
|
|
m_CharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * m_flLineSpacing);
|
|
|
|
const ImVec2 contentSize = ImGui::GetWindowContentRegionMax();
|
|
ImDrawList* const drawList = ImGui::GetWindowDrawList();
|
|
|
|
const ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
|
|
|
|
float longest = 0.0f;
|
|
const float scrollY = ImGui::GetScrollY();
|
|
|
|
int lineNo = static_cast<int>(ImFloor(scrollY / m_CharAdvance.y));
|
|
const int lineMax = ImMax(0, ImMin(static_cast<int>(m_Lines.size()) - 1, lineNo + static_cast<int>(ImFloor((scrollY + contentSize.y) / m_CharAdvance.y))));
|
|
|
|
if (!m_Lines.empty())
|
|
{
|
|
while (lineNo <= lineMax)
|
|
{
|
|
const ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * m_CharAdvance.y);
|
|
const ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y);
|
|
|
|
const Line& line = m_Lines[lineNo];
|
|
longest = TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo)));
|
|
|
|
Coordinates lineStartCoord(lineNo, 0);
|
|
Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo));
|
|
|
|
// Draw selection for the current line
|
|
float sstart = -1.0f;
|
|
float ssend = -1.0f;
|
|
|
|
assert(m_State.m_SelectionStart <= m_State.m_SelectionEnd);
|
|
if (m_State.m_SelectionStart <= lineEndCoord)
|
|
sstart = m_State.m_SelectionStart > lineStartCoord ? TextDistanceToLineStart(m_State.m_SelectionStart) : 0.0f;
|
|
if (m_State.m_SelectionEnd > lineStartCoord)
|
|
ssend = TextDistanceToLineStart(m_State.m_SelectionEnd < lineEndCoord ? m_State.m_SelectionEnd : lineEndCoord);
|
|
|
|
if (m_State.m_SelectionEnd.m_nLine > lineNo)
|
|
ssend += m_CharAdvance.x;
|
|
|
|
if (sstart != -1 && ssend != -1 && sstart < ssend)
|
|
{
|
|
const ImVec2 vstart(lineStartScreenPos.x + sstart, lineStartScreenPos.y);
|
|
const ImVec2 vend(lineStartScreenPos.x + ssend, lineStartScreenPos.y + m_CharAdvance.y);
|
|
|
|
drawList->AddRectFilled(vstart, vend, ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
|
|
}
|
|
|
|
// Render the cursor
|
|
if (m_State.m_CursorPosition.m_nLine == lineNo && ImGui::IsWindowFocused())
|
|
{
|
|
const ImS64 timeEnd = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
const ImU64 elapsed = timeEnd - m_nStartTime;
|
|
|
|
if (elapsed > 400)
|
|
{
|
|
const float width = 1.0f;
|
|
const float cx = TextDistanceToLineStart(m_State.m_CursorPosition);
|
|
|
|
const ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
|
|
const ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + m_CharAdvance.y);
|
|
|
|
drawList->AddRectFilled(cstart, cend, 0xffe0e0e0);
|
|
|
|
if (elapsed > 800)
|
|
m_nStartTime = timeEnd;
|
|
}
|
|
}
|
|
|
|
if (!line.buffer.empty())
|
|
{
|
|
ImU32 color = line.color;
|
|
|
|
if (m_itFilter.IsActive())
|
|
{
|
|
// Make line dark if it isn't found by the filter
|
|
if (!m_itFilter.PassFilter(line.buffer.c_str()))
|
|
color = 0xff605040;
|
|
}
|
|
|
|
const ImVec2 newOffset(textScreenPos.x, textScreenPos.y);
|
|
drawList->AddText(newOffset, color, line.buffer.c_str());
|
|
}
|
|
|
|
++lineNo;
|
|
}
|
|
}
|
|
|
|
ImGui::Dummy(ImVec2((longest + 2), m_Lines.size() * m_CharAdvance.y));
|
|
m_bScrolledToBottom = ImGui::GetScrollY() >= ImGui::GetScrollMaxY();
|
|
|
|
if (m_bScrollToBottom || (m_bAutoScroll && m_bScrolledToBottom && !m_bScrollToCursor))
|
|
{
|
|
ImGui::SetScrollHereX(0.0f); // TODO[ AMOS ]: only scroll x if a command has been submitted
|
|
ImGui::SetScrollHereY(1.0f);
|
|
|
|
m_bScrollToBottom = false;
|
|
}
|
|
|
|
m_bScrollToCursor = false;
|
|
|
|
if (m_bHandleUserInputs)
|
|
ImGui::PopAllowKeyboardFocus();
|
|
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
void CTextLogger::Copy(bool aCopyAll)
|
|
{
|
|
if (!aCopyAll && HasSelection())
|
|
{
|
|
ImGui::SetClipboardText(GetSelectedText().c_str());
|
|
}
|
|
else if (!aCopyAll)
|
|
{
|
|
if (!m_Lines.empty())
|
|
{
|
|
const Line& line = m_Lines[GetActualCursorCoordinates().m_nLine];
|
|
ImGui::SetClipboardText(line.buffer.c_str());
|
|
}
|
|
}
|
|
else // Copy all lines to clipboard.
|
|
{
|
|
std::string str;
|
|
for (const Line& line : m_Lines)
|
|
{
|
|
str.append(line.buffer);
|
|
}
|
|
|
|
ImGui::SetClipboardText(str.c_str());
|
|
}
|
|
}
|
|
|
|
//void CTextLogger::SetText(const ConLog_t& aText)
|
|
//{
|
|
// m_Lines.clear();
|
|
// m_Lines.emplace_back(Line());
|
|
// for (char chr : aText.m_svConLog)
|
|
// {
|
|
// if (chr == '\r') // ignore the carriage return character
|
|
// continue;
|
|
// else if (chr == '\n')
|
|
// m_Lines.emplace_back(Line());
|
|
// else
|
|
// m_Lines.back().emplace_back(Glyph(chr, aText.m_imColor));
|
|
// }
|
|
//}
|
|
|
|
//void CTextLogger::SetTextLines(const std::vector<ConLog_t>& aLines)
|
|
//{
|
|
// m_Lines.clear();
|
|
//
|
|
// if (aLines.empty())
|
|
// {
|
|
// m_Lines.emplace_back(Line());
|
|
// }
|
|
// else
|
|
// {
|
|
// m_Lines.resize(aLines.size());
|
|
//
|
|
// for (size_t i = 0; i < aLines.size(); ++i)
|
|
// {
|
|
// const std::string & aLine = aLines[i].m_svConLog;
|
|
//
|
|
// m_Lines[i].reserve(aLine.size());
|
|
// for (size_t j = 0; j < aLine.size(); ++j)
|
|
// {
|
|
// m_Lines[i].emplace_back(Glyph(aLine[j], aLines[i].m_imColor));
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
void CTextLogger::MoveCursor(int aLines, bool aForward)
|
|
{
|
|
Coordinates newStart;
|
|
|
|
if (aForward)
|
|
{
|
|
newStart = m_State.m_CursorPosition;
|
|
newStart.m_nLine += aLines;
|
|
|
|
if (newStart.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
newStart.m_nLine = static_cast<int>(m_Lines.size()) - 1;
|
|
newStart.m_nColumn = GetLineMaxColumn(newStart.m_nLine);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newStart = m_State.m_CursorPosition;
|
|
newStart.m_nLine -= aLines;
|
|
|
|
if (newStart.m_nLine < 0)
|
|
{
|
|
newStart.m_nLine = 0;
|
|
newStart.m_nColumn = 0;
|
|
}
|
|
}
|
|
|
|
m_State.m_CursorPosition = newStart;
|
|
}
|
|
|
|
void CTextLogger::SetCursorPosition(const Coordinates & aPosition)
|
|
{
|
|
if (m_State.m_CursorPosition != aPosition)
|
|
{
|
|
m_State.m_CursorPosition = aPosition;
|
|
EnsureCursorVisible();
|
|
}
|
|
}
|
|
|
|
void CTextLogger::SetSelectionStart(const Coordinates & aPosition)
|
|
{
|
|
m_State.m_SelectionStart = SanitizeCoordinates(aPosition);
|
|
if (m_State.m_SelectionStart > m_State.m_SelectionEnd)
|
|
ImSwap(m_State.m_SelectionStart, m_State.m_SelectionEnd);
|
|
}
|
|
|
|
void CTextLogger::SetSelectionEnd(const Coordinates & aPosition)
|
|
{
|
|
m_State.m_SelectionEnd = SanitizeCoordinates(aPosition);
|
|
if (m_State.m_SelectionStart > m_State.m_SelectionEnd)
|
|
ImSwap(m_State.m_SelectionStart, m_State.m_SelectionEnd);
|
|
}
|
|
|
|
void CTextLogger::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode)
|
|
{
|
|
Coordinates oldSelStart = m_State.m_SelectionStart;
|
|
Coordinates oldSelEnd = m_State.m_SelectionEnd;
|
|
|
|
m_State.m_SelectionStart = SanitizeCoordinates(aStart);
|
|
m_State.m_SelectionEnd = SanitizeCoordinates(aEnd);
|
|
|
|
if (m_State.m_SelectionStart > m_State.m_SelectionEnd)
|
|
ImSwap(m_State.m_SelectionStart, m_State.m_SelectionEnd);
|
|
|
|
switch (aMode)
|
|
{
|
|
case CTextLogger::SelectionMode::Normal:
|
|
break;
|
|
case CTextLogger::SelectionMode::Word:
|
|
{
|
|
m_State.m_SelectionStart = FindWordStart(m_State.m_SelectionStart);
|
|
if (!IsOnWordBoundary(m_State.m_SelectionEnd))
|
|
m_State.m_SelectionEnd = FindWordEnd(FindWordStart(m_State.m_SelectionEnd));
|
|
break;
|
|
}
|
|
case CTextLogger::SelectionMode::Line:
|
|
{
|
|
const int lineNo = m_State.m_SelectionEnd.m_nLine;
|
|
//const size_t lineSize = (size_t)lineNo < m_Lines.size() ? m_Lines[lineNo].size() : 0;
|
|
m_State.m_SelectionStart = Coordinates(m_State.m_SelectionStart.m_nLine, 0);
|
|
m_State.m_SelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CTextLogger::SetTabSize(int aValue)
|
|
{
|
|
m_nTabSize = ImMax(0, ImMin(32, aValue));
|
|
}
|
|
|
|
void CTextLogger::InsertText(const char* const text, const ImU32 color)
|
|
{
|
|
IM_ASSERT(text);
|
|
|
|
if (!*text)
|
|
return;
|
|
|
|
Coordinates pos = GetActualLastLineCoordinates();
|
|
|
|
const Coordinates& start = ImMin(pos, m_State.m_SelectionStart);
|
|
int totalLines = pos.m_nLine - start.m_nLine;
|
|
|
|
totalLines += InsertTextAt(pos, text, color);
|
|
}
|
|
|
|
void CTextLogger::MoveUp(int aAmount, bool aSelect)
|
|
{
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
m_State.m_CursorPosition.m_nLine = ImMax(0, m_State.m_CursorPosition.m_nLine - aAmount);
|
|
if (oldPos != m_State.m_CursorPosition)
|
|
{
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
m_InteractiveEnd = oldPos;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
EnsureCursorVisible();
|
|
}
|
|
}
|
|
|
|
void CTextLogger::MoveDown(int aAmount, bool aSelect)
|
|
{
|
|
assert(m_State.m_CursorPosition.m_nColumn >= 0);
|
|
Coordinates oldPos = m_State.m_CursorPosition;
|
|
|
|
m_State.m_CursorPosition.m_nLine = ImMax(0, ImMin(static_cast<int>(m_Lines.size() - 1), m_State.m_CursorPosition.m_nLine + aAmount));
|
|
|
|
if (m_State.m_CursorPosition != oldPos)
|
|
{
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
else if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = oldPos;
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
EnsureCursorVisible();
|
|
}
|
|
}
|
|
|
|
static bool IsUTFSequence(char c)
|
|
{
|
|
return (c & 0xC0) == 0x80;
|
|
}
|
|
|
|
void CTextLogger::MoveLeft(int aAmount, bool aSelect, bool aWordMode)
|
|
{
|
|
if (m_Lines.empty())
|
|
return;
|
|
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
m_State.m_CursorPosition = GetActualCursorCoordinates();
|
|
|
|
int line = m_State.m_CursorPosition.m_nLine;
|
|
int cindex = GetCharacterIndex(m_State.m_CursorPosition);
|
|
|
|
while (aAmount-- > 0)
|
|
{
|
|
if (cindex == 0)
|
|
{
|
|
if (line > 0)
|
|
{
|
|
--line;
|
|
if (static_cast<int>(m_Lines.size()) > line)
|
|
cindex = static_cast<int>(m_Lines[line].buffer.size());
|
|
else
|
|
cindex = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
--cindex;
|
|
if (cindex > 0)
|
|
{
|
|
if (static_cast<int>(m_Lines.size()) > line)
|
|
{
|
|
const Line &lineData = m_Lines[line];
|
|
|
|
while (cindex > 0 && IsUTFSequence(lineData.buffer[cindex]))
|
|
--cindex;
|
|
|
|
// Skip the newline character.
|
|
if (cindex > 0 && lineData.buffer[cindex] == '\n')
|
|
--cindex;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_State.m_CursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
|
|
if (aWordMode)
|
|
{
|
|
m_State.m_CursorPosition = FindWordStart(m_State.m_CursorPosition);
|
|
cindex = GetCharacterIndex(m_State.m_CursorPosition);
|
|
}
|
|
}
|
|
|
|
m_State.m_CursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
|
|
|
|
assert(m_State.m_CursorPosition.m_nColumn >= 0);
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
m_InteractiveEnd = oldPos;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
|
|
|
|
EnsureCursorVisible();
|
|
}
|
|
|
|
void CTextLogger::MoveRight(int aAmount, bool aSelect, bool aWordMode)
|
|
{
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
|
|
if (m_Lines.empty() || oldPos.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
return;
|
|
|
|
int cindex = GetCharacterIndex(m_State.m_CursorPosition);
|
|
while (aAmount-- > 0)
|
|
{
|
|
int lindex = m_State.m_CursorPosition.m_nLine;
|
|
const Line& line = m_Lines[lindex];
|
|
|
|
bool isNewLine = false;
|
|
const bool isLastChar = (cindex >= line.Length()-1);
|
|
|
|
// If the cursor is at the last character before the newline character,
|
|
// we want to skip the newline character and move to the next line.
|
|
if (isLastChar && !line.buffer.empty())
|
|
isNewLine = line.buffer.back() == '\n';
|
|
|
|
if (cindex >= line.Length() || isNewLine)
|
|
{
|
|
if (m_State.m_CursorPosition.m_nLine < static_cast<int>(m_Lines.size()) - 1)
|
|
{
|
|
m_State.m_CursorPosition.m_nLine = ImMax(0, ImMin(static_cast<int>(m_Lines.size()) - 1, m_State.m_CursorPosition.m_nLine + 1));
|
|
m_State.m_CursorPosition.m_nColumn = 0;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
cindex += UTF8CharLength(line.buffer[cindex]);
|
|
m_State.m_CursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
|
|
if (aWordMode)
|
|
m_State.m_CursorPosition = FindNextWord(m_State.m_CursorPosition);
|
|
}
|
|
}
|
|
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = SanitizeCoordinates(m_State.m_CursorPosition);
|
|
else if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = oldPos;
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
|
|
|
|
EnsureCursorVisible();
|
|
}
|
|
|
|
void CTextLogger::MoveTop(bool aSelect)
|
|
{
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
SetCursorPosition(Coordinates(0, 0));
|
|
|
|
if (m_State.m_CursorPosition != oldPos)
|
|
{
|
|
if (aSelect)
|
|
{
|
|
m_InteractiveEnd = oldPos;
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
}
|
|
}
|
|
|
|
void CTextLogger::MoveBottom(bool aSelect)
|
|
{
|
|
const Coordinates oldPos = GetCursorPosition();
|
|
const Coordinates newPos = Coordinates(static_cast<int>(m_Lines.size()) - 1, 0);
|
|
|
|
SetCursorPosition(newPos);
|
|
if (aSelect)
|
|
{
|
|
m_InteractiveStart = oldPos;
|
|
m_InteractiveEnd = newPos;
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = newPos;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
}
|
|
|
|
void CTextLogger::MoveHome(bool aSelect)
|
|
{
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
SetCursorPosition(Coordinates(m_State.m_CursorPosition.m_nLine, 0));
|
|
|
|
if (m_State.m_CursorPosition != oldPos)
|
|
{
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
m_InteractiveEnd = oldPos;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
}
|
|
}
|
|
|
|
void CTextLogger::MoveEnd(bool aSelect)
|
|
{
|
|
const Coordinates oldPos = m_State.m_CursorPosition;
|
|
SetCursorPosition(Coordinates(m_State.m_CursorPosition.m_nLine, GetLineMaxColumn(oldPos.m_nLine)));
|
|
|
|
if (m_State.m_CursorPosition != oldPos)
|
|
{
|
|
if (aSelect)
|
|
{
|
|
if (oldPos == m_InteractiveEnd)
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
else if (oldPos == m_InteractiveStart)
|
|
m_InteractiveStart = m_State.m_CursorPosition;
|
|
else
|
|
{
|
|
m_InteractiveStart = oldPos;
|
|
m_InteractiveEnd = m_State.m_CursorPosition;
|
|
}
|
|
}
|
|
else
|
|
m_InteractiveStart = m_InteractiveEnd = m_State.m_CursorPosition;
|
|
SetSelection(m_InteractiveStart, m_InteractiveEnd);
|
|
}
|
|
}
|
|
|
|
void CTextLogger::SelectWordUnderCursor()
|
|
{
|
|
const Coordinates c = GetCursorPosition();
|
|
SetSelection(FindWordStart(c), FindWordEnd(c));
|
|
}
|
|
|
|
void CTextLogger::SelectAll()
|
|
{
|
|
SetSelection(Coordinates(0, 0), Coordinates(static_cast<int>(m_Lines.size()), 0));
|
|
}
|
|
|
|
bool CTextLogger::HasSelection() const
|
|
{
|
|
return m_State.m_SelectionEnd > m_State.m_SelectionStart;
|
|
}
|
|
|
|
void CTextLogger::MoveSelection(int aLines, bool aForward)
|
|
{
|
|
assert(aLines > 0);
|
|
|
|
if (aLines < 1)
|
|
return;
|
|
|
|
m_bLinesOffsetForward = aForward;
|
|
m_nLinesOffsetAmount = aLines;
|
|
|
|
if (HasSelection())
|
|
{
|
|
Coordinates newStart;
|
|
Coordinates newEnd;
|
|
|
|
newStart = m_State.m_SelectionStart;
|
|
newEnd = m_State.m_SelectionEnd;
|
|
|
|
if (aForward)
|
|
{
|
|
newStart.m_nLine += aLines;
|
|
newEnd.m_nLine += aLines;
|
|
|
|
if (newStart.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
newStart.m_nLine = static_cast<int>(m_Lines.size()) - 1;
|
|
newStart.m_nColumn = GetLineMaxColumn(newStart.m_nLine);
|
|
}
|
|
if (newEnd.m_nLine >= static_cast<int>(m_Lines.size()))
|
|
{
|
|
newEnd.m_nLine = static_cast<int>(m_Lines.size()) - 1;
|
|
newEnd.m_nColumn = GetLineMaxColumn(newEnd.m_nLine);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newStart.m_nLine -= aLines;
|
|
newEnd.m_nLine -= aLines;
|
|
|
|
if (newStart.m_nLine < 0)
|
|
{
|
|
newStart.m_nLine = 0;
|
|
newStart.m_nColumn = 0;
|
|
}
|
|
if (newEnd.m_nLine < 0)
|
|
{
|
|
newEnd.m_nLine = 0;
|
|
newEnd.m_nColumn = 0;
|
|
}
|
|
}
|
|
|
|
SetSelectionStart(newStart);
|
|
SetSelectionEnd(newEnd);
|
|
}
|
|
}
|
|
|
|
std::string CTextLogger::GetText() const
|
|
{
|
|
return GetText(Coordinates(), Coordinates(static_cast<int>(m_Lines.size()), 0));
|
|
}
|
|
|
|
std::vector<std::string> CTextLogger::GetTextLines() const
|
|
{
|
|
std::vector<std::string> result;
|
|
result.reserve(m_Lines.size());
|
|
|
|
for (const Line& line : m_Lines)
|
|
{
|
|
result.emplace_back(line.buffer);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string CTextLogger::GetSelectedText() const
|
|
{
|
|
return GetText(m_State.m_SelectionStart, m_State.m_SelectionEnd);
|
|
}
|
|
|
|
std::string CTextLogger::GetCurrentLineText()const
|
|
{
|
|
int lineLength = GetLineMaxColumn(m_State.m_CursorPosition.m_nLine);
|
|
return GetText(
|
|
Coordinates(m_State.m_CursorPosition.m_nLine, 0),
|
|
Coordinates(m_State.m_CursorPosition.m_nLine, lineLength));
|
|
}
|
|
|
|
float CTextLogger::TextDistanceToLineStart(const Coordinates& aFrom) const
|
|
{
|
|
const Line& line = m_Lines[aFrom.m_nLine];
|
|
float distance = 0.0f;
|
|
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
|
|
int colIndex = GetCharacterIndex(aFrom);
|
|
|
|
for (size_t it = 0u; it < line.Length() && it < colIndex; )
|
|
{
|
|
if (line.buffer[it] == '\t')
|
|
{
|
|
distance = (1.0f + ImFloor((1.0f + distance) / (float(m_nTabSize) * spaceSize))) * (float(m_nTabSize) * spaceSize);
|
|
++it;
|
|
}
|
|
else
|
|
{
|
|
size_t d = UTF8CharLength(line.buffer[it]);
|
|
size_t i = 0;
|
|
char tempCString[7];
|
|
|
|
for (; i < 6 && d-- > 0 && it < line.Length(); i++, it++)
|
|
tempCString[i] = line.buffer[it];
|
|
|
|
tempCString[i] = '\0';
|
|
distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x;
|
|
}
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
void CTextLogger::EnsureCursorVisible()
|
|
{
|
|
m_bScrollToCursor = true;
|
|
Coordinates pos = GetActualCursorCoordinates();
|
|
|
|
float scrollX = ImGui::GetScrollX();
|
|
float scrollY = ImGui::GetScrollY();
|
|
|
|
float width = ImGui::GetWindowWidth();
|
|
float height = ImGui::GetWindowHeight();
|
|
|
|
int top = 1 + static_cast<int>(ImCeil(scrollY / m_CharAdvance.y));
|
|
int bottom = static_cast<int>(ImCeil((scrollY + height) / m_CharAdvance.y));
|
|
|
|
int left = static_cast<int>(ImCeil(scrollX / m_CharAdvance.x));
|
|
int right = static_cast<int>(ImCeil((scrollX + width) / m_CharAdvance.x));
|
|
|
|
if (pos.m_nColumn < left)
|
|
ImGui::SetScrollX(ImMax(0.0f, (pos.m_nColumn) * m_CharAdvance.x));
|
|
if (pos.m_nColumn > right - 3)
|
|
ImGui::SetScrollX(ImMax(0.0f, (pos.m_nColumn + 3) * m_CharAdvance.x - width));
|
|
if (pos.m_nLine < top)
|
|
ImGui::SetScrollY(ImMax(0.0f, (pos.m_nLine) * m_CharAdvance.y));
|
|
if (pos.m_nLine > bottom - 2)
|
|
ImGui::SetScrollY(ImMax(0.0f, (pos.m_nLine + 2) * m_CharAdvance.y - height));
|
|
}
|
|
|
|
int CTextLogger::GetPageSize() const
|
|
{
|
|
float height = ImGui::GetWindowHeight() - 20.0f;
|
|
return static_cast<int>(ImFloor(height / m_CharAdvance.y));
|
|
}
|