fix: Jumpy text editor scrolling (#2023)
Two major improvements: 1) see through scrollbars when not hovered. 2) un-scrollable line numbers. Also enlarged display region by eliminating padding. There is still a problem with lines jumping when the scrollbar is dragged but it is limited to one line and probably due to floating point error for scroll bar number. It is much less noticeable than the previous jumping which could involve several pages. --------- Co-authored-by: WerWolv <werwolv98@gmail.com>
This commit is contained in:
parent
1c5a50c8d8
commit
20cb74364f
@ -576,7 +576,7 @@ private:
|
||||
|
||||
void HandleKeyboardInputs();
|
||||
void HandleMouseInputs();
|
||||
void Render();
|
||||
void RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize);
|
||||
|
||||
float mLineSpacing = 1.0F;
|
||||
Lines mLines;
|
||||
@ -597,6 +597,7 @@ private:
|
||||
bool mScrollToTop = false;
|
||||
bool mTextChanged = false;
|
||||
bool mColorizerEnabled = true;
|
||||
float mLineNumberFieldWidth = 0.0F;
|
||||
float mLongest = 0.0F;
|
||||
float mTextStart = 20.0F; // position (in pixels) where a code line starts relative to the left of the TextEditor.
|
||||
int mLeftMargin = 10;
|
||||
|
@ -328,7 +328,7 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 &aPositi
|
||||
float oldX = columnX;
|
||||
float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
|
||||
columnWidth = newColumnX - oldX;
|
||||
if (mTextStart + columnX + columnWidth * 0.5f > local.x)
|
||||
if (columnX + columnWidth > local.x)
|
||||
break;
|
||||
columnX = newColumnX;
|
||||
columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize;
|
||||
@ -341,7 +341,7 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 &aPositi
|
||||
buf[i++] = line[columnIndex++].mChar;
|
||||
buf[i] = '\0';
|
||||
columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x;
|
||||
if (mTextStart + columnX + columnWidth * 0.5f > local.x)
|
||||
if (columnX + columnWidth > local.x)
|
||||
break;
|
||||
columnX += columnWidth;
|
||||
columnCoord++;
|
||||
@ -349,7 +349,7 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 &aPositi
|
||||
}
|
||||
}
|
||||
|
||||
return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
|
||||
return SanitizeCoordinates(Coordinates(lineNo, columnCoord - (columnCoord != 0)));
|
||||
}
|
||||
|
||||
void TextEditor::DeleteWordLeft() {
|
||||
@ -860,7 +860,7 @@ inline void TextUnformattedColoredAt(const ImVec2 &pos, const ImU32 &color, cons
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void TextEditor::Render() {
|
||||
void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize) {
|
||||
/* Compute mCharAdvance regarding scaled font size (Ctrl + mouse wheel)*/
|
||||
const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
|
||||
mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing);
|
||||
@ -874,7 +874,7 @@ void TextEditor::Render() {
|
||||
|
||||
IM_ASSERT(mLineBuffer.empty());
|
||||
|
||||
auto contentSize = ImGui::GetCurrentWindowRead()->ContentRegionRect.Max - ImGui::GetWindowPos();
|
||||
auto contentSize = textEditorSize;
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
mNumberOfLinesDisplayed = GetPageSize();
|
||||
|
||||
@ -888,16 +888,18 @@ void TextEditor::Render() {
|
||||
ImGui::SetScrollY(ImGui::GetScrollMaxY());
|
||||
}
|
||||
|
||||
ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos() + ImVec2(0, mTopMargin);
|
||||
ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 position = lineNumbersStartPos;
|
||||
auto scrollX = ImGui::GetScrollX();
|
||||
auto scrollY = ImGui::GetScrollY();
|
||||
if (mSetTopLine)
|
||||
SetTopLine();
|
||||
else
|
||||
mTopLine = std::max<int>(0, std::floor((scrollY-mTopMargin) / mCharAdvance.y) - 1);
|
||||
mTopLine = std::max<int>(0, std::floor((scrollY-mTopMargin) / mCharAdvance.y));
|
||||
auto lineNo = mTopLine;
|
||||
int globalLineMax = mLines.size();
|
||||
auto lineMax = std::clamp(lineNo + mNumberOfLinesDisplayed, 0, globalLineMax - 1);
|
||||
int totalDigitCount = std::floor(std::log10(globalLineMax)) + 1;
|
||||
mLongest = GetLongestLineLength() * mCharAdvance.x;
|
||||
|
||||
// Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
|
||||
@ -909,12 +911,12 @@ void TextEditor::Render() {
|
||||
buf[0] = '\0';
|
||||
mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;
|
||||
|
||||
if (lineNo <= lineMax) {
|
||||
if (!mLines.empty()) {
|
||||
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
|
||||
|
||||
while (lineNo <= lineMax) {
|
||||
ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
|
||||
ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);
|
||||
ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x + mLeftMargin, cursorScreenPos.y + lineNo * mCharAdvance.y);
|
||||
ImVec2 textScreenPos = lineStartScreenPos;
|
||||
|
||||
auto &line = mLines[lineNo];
|
||||
auto columnNo = 0;
|
||||
@ -935,40 +937,57 @@ void TextEditor::Render() {
|
||||
ssend += mCharAdvance.x;
|
||||
|
||||
if (sstart != -1 && ssend != -1 && sstart < ssend) {
|
||||
ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y);
|
||||
ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y);
|
||||
ImVec2 vstart(lineStartScreenPos.x + sstart, lineStartScreenPos.y);
|
||||
ImVec2 vend(lineStartScreenPos.x + ssend, lineStartScreenPos.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]);
|
||||
}
|
||||
|
||||
auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
|
||||
|
||||
float startPos = 0;
|
||||
if (scrollY < mTopMargin)
|
||||
startPos = mTopMargin - scrollY;
|
||||
ImVec2 lineNoStartScreenPos = ImVec2(position.x, startPos + position.y + (lineNo - mTopLine) * mCharAdvance.y);
|
||||
auto start = ImVec2(lineNoStartScreenPos.x + mLineNumberFieldWidth, lineNoStartScreenPos.y);
|
||||
bool focused = ImGui::IsWindowFocused();
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
// Draw line number (right aligned)
|
||||
if (mShowLineNumbers) {
|
||||
snprintf(buf, 16, "%d ", lineNo + 1);
|
||||
ImGui::SetCursorScreenPos(position);
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::BeginChild("##lineNumbers");
|
||||
|
||||
auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x;
|
||||
TextUnformattedColoredAt(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y),mPalette[(int) PaletteIndex::LineNumber],buf);
|
||||
int padding = totalDigitCount - std::floor(std::log10(lineNo + 1)) - 1;
|
||||
std::string space = " ";
|
||||
while (padding-- > 0) {
|
||||
space += " ";
|
||||
}
|
||||
std::string lineNoStr = space + std::to_string(lineNo + 1);
|
||||
TextUnformattedColoredAt(ImVec2(mLeftMargin + lineNoStartScreenPos.x, lineNoStartScreenPos.y), mPalette[(int) PaletteIndex::LineNumber], lineNoStr.c_str());
|
||||
}
|
||||
|
||||
// Draw breakpoints
|
||||
if (mBreakpoints.count(lineNo + 1) != 0) {
|
||||
auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(start + ImVec2(mTextStart, 0), end, mPalette[(int)PaletteIndex::Breakpoint]);
|
||||
auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + mLineNumberFieldWidth, lineNoStartScreenPos.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(ImVec2(lineNumbersStartPos.x, lineNoStartScreenPos.y), end, mPalette[(int)PaletteIndex::Breakpoint]);
|
||||
|
||||
drawList->AddCircleFilled(start + ImVec2(0, mCharAdvance.y) / 2, mCharAdvance.y / 3, mPalette[(int)PaletteIndex::Breakpoint]);
|
||||
drawList->AddCircle(start + ImVec2(0, mCharAdvance.y) / 2, mCharAdvance.y / 3, mPalette[(int)PaletteIndex::Default]);
|
||||
}
|
||||
|
||||
if (mState.mCursorPosition.mLine == lineNo && mShowCursor) {
|
||||
bool focused = ImGui::IsWindowFocused();
|
||||
|
||||
// Highlight the current line (where the cursor is)
|
||||
if (!HasSelection()) {
|
||||
auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
|
||||
drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
|
||||
auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + mLineNumberFieldWidth, lineNoStartScreenPos.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(ImVec2(lineNumbersStartPos.x, lineNoStartScreenPos.y), end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
|
||||
drawList->AddRect(ImVec2(lineNumbersStartPos.x, lineNoStartScreenPos.y), end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
|
||||
}
|
||||
}
|
||||
if (mShowLineNumbers && !mIgnoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::BeginChild(aTitle);
|
||||
if (mState.mCursorPosition.mLine == lineNo && mShowCursor) {
|
||||
// Render the cursor
|
||||
if (focused) {
|
||||
auto timeEnd = ImGui::GetTime() * 1000;
|
||||
@ -990,8 +1009,8 @@ void TextEditor::Render() {
|
||||
width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x;
|
||||
}
|
||||
}
|
||||
ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
|
||||
ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y);
|
||||
ImVec2 cstart(lineStartScreenPos.x + cx, lineStartScreenPos.y);
|
||||
ImVec2 cend(lineStartScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y);
|
||||
drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
|
||||
if (elapsed > sCursorBlinkInterval)
|
||||
mStartTime = timeEnd;
|
||||
@ -1135,10 +1154,22 @@ void TextEditor::Render() {
|
||||
|
||||
++lineNo;
|
||||
}
|
||||
|
||||
}
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Dummy(ImVec2(mLongest, (globalLineMax - lineMax - 2) * mCharAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight()));
|
||||
if (mShowLineNumbers && !mIgnoreImGuiChild) {
|
||||
ImGui::BeginChild("##lineNumbers");
|
||||
ImGui::Dummy(ImVec2(mLineNumberFieldWidth, (globalLineMax - lineMax - 1) * mCharAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - mCharAdvance.y));
|
||||
ImGui::EndChild();
|
||||
}
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::BeginChild(aTitle);
|
||||
|
||||
if (mShowLineNumbers)
|
||||
ImGui::Dummy(ImVec2(mLongest, (globalLineMax - lineMax - 2) * mCharAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight()));
|
||||
else
|
||||
ImGui::Dummy(ImVec2(mLongest, (globalLineMax - 1 - lineMax + GetPageSize() - 1) * mCharAdvance.y));
|
||||
|
||||
if (mScrollToCursor)
|
||||
EnsureCursorVisible();
|
||||
@ -1174,39 +1205,75 @@ void TextEditor::Render() {
|
||||
}
|
||||
|
||||
void TextEditor::Render(const char *aTitle, const ImVec2 &aSize, bool aBorder) {
|
||||
mWithinRender = true;
|
||||
mTextChanged = false;
|
||||
mCursorPositionChanged = false;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background]));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||
if (!mIgnoreImGuiChild) {
|
||||
ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove);
|
||||
mWithinRender = true;
|
||||
auto scrollBg = ImGui::GetStyleColorVec4(ImGuiCol_ScrollbarBg);
|
||||
scrollBg.w = 0.0f;
|
||||
auto scrollBarSize = ImGui::GetStyle().ScrollbarSize;
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int) PaletteIndex::Background]));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGui::ColorConvertFloat4ToU32(scrollBg));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding,0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,scrollBarSize);
|
||||
|
||||
auto position = ImGui::GetCursorScreenPos();
|
||||
if (mShowLineNumbers ) {
|
||||
std::string lineNumber = " " + std::to_string(mLines.size()) + " ";
|
||||
mLineNumberFieldWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumber.c_str(), nullptr, nullptr).x + mLeftMargin;
|
||||
ImGui::SetCursorScreenPos(position);
|
||||
auto lineNoSize = ImVec2(mLineNumberFieldWidth, aSize.y);
|
||||
if (!mIgnoreImGuiChild) {
|
||||
ImGui::BeginChild("##lineNumbers", lineNoSize, false, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
} else {
|
||||
mLineNumberFieldWidth = 0;
|
||||
}
|
||||
|
||||
ImVec2 textEditorSize = aSize;
|
||||
textEditorSize.x -= mLineNumberFieldWidth;
|
||||
mLongest = GetLongestLineLength() * mCharAdvance.x;
|
||||
bool scroll_x = mLongest > textEditorSize.x;
|
||||
bool scroll_y = mLines.size() > 1;
|
||||
if (!aBorder && scroll_y)
|
||||
textEditorSize.x -= scrollBarSize;
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x + mLineNumberFieldWidth, position.y));
|
||||
ImGuiChildFlags childFlags = aBorder ? ImGuiChildFlags_Borders : ImGuiChildFlags_None;
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove;
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::BeginChild(aTitle, textEditorSize, childFlags, windowFlags);
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
window->ScrollbarSizes = ImVec2(scrollBarSize * scroll_x, scrollBarSize * scroll_y);
|
||||
ImGui::GetCurrentWindowRead()->ScrollbarSizes = ImVec2(scrollBarSize * scroll_y, scrollBarSize * scroll_x);
|
||||
if (scroll_y) {
|
||||
ImGui::GetCurrentWindow()->ScrollbarY= true;
|
||||
ImGui::Scrollbar(ImGuiAxis_Y);
|
||||
}
|
||||
if (scroll_x) {
|
||||
ImGui::GetCurrentWindow()->ScrollbarX= true;
|
||||
ImGui::Scrollbar(ImGuiAxis_X);
|
||||
}
|
||||
|
||||
if (mHandleKeyboardInputs) {
|
||||
HandleKeyboardInputs();
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, false);
|
||||
}
|
||||
|
||||
if (mHandleMouseInputs)
|
||||
HandleMouseInputs();
|
||||
|
||||
ColorizeInternal();
|
||||
Render();
|
||||
RenderText(aTitle, position, textEditorSize);
|
||||
|
||||
if (mHandleKeyboardInputs)
|
||||
ImGui::PopItemFlag();
|
||||
|
||||
if (!mIgnoreImGuiChild) {
|
||||
mScrollY = ImGui::GetScrollY();
|
||||
mWithinRender = false;
|
||||
if (!mIgnoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
mWithinRender = false;
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x,position.y+aSize.y-1));
|
||||
}
|
||||
|
||||
void TextEditor::SetText(const std::string &aText) {
|
||||
@ -2998,7 +3065,7 @@ void TextEditor::EnsureCursorVisible() {
|
||||
}
|
||||
|
||||
int TextEditor::GetPageSize() const {
|
||||
auto height = ImGui::GetWindowHeight() - 2.0f * ImGui::GetStyle().WindowPadding.y - mTopMargin;
|
||||
auto height = ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - mTopMargin - ImGui::GetStyle().FramePadding.y;
|
||||
return (int)floor(height / mCharAdvance.y);
|
||||
}
|
||||
|
||||
|
@ -322,23 +322,22 @@ namespace hex::plugin::builtin {
|
||||
if (ImHexApi::Provider::isValid() && provider->isAvailable()) {
|
||||
static float height = 0;
|
||||
static bool dragging = false;
|
||||
|
||||
const auto availableSize = ImGui::GetContentRegionAvail();
|
||||
const ImGuiContext& g = *GImGui;
|
||||
if (g.CurrentWindow->Appearing)
|
||||
return;
|
||||
const auto availableSize = g.CurrentWindow->Size;
|
||||
const auto windowPosition = ImGui::GetCursorScreenPos();
|
||||
auto textEditorSize = availableSize;
|
||||
textEditorSize.y *= 3.5 / 5.0;
|
||||
textEditorSize.y -= ImGui::GetTextLineHeightWithSpacing();
|
||||
textEditorSize.y += height;
|
||||
textEditorSize.y = std::clamp(textEditorSize.y + height,200.0F, availableSize.y-200.0F);
|
||||
|
||||
if (availableSize.y > 1)
|
||||
textEditorSize.y = std::clamp(textEditorSize.y, 1.0F, std::max(1.0F, availableSize.y - ImGui::GetTextLineHeightWithSpacing() * 3));
|
||||
const ImGuiContext& g = *GImGui;
|
||||
if (g.NavWindow != nullptr) {
|
||||
std::string name = g.NavWindow->Name;
|
||||
if (name.contains(textEditorView) || name.contains(consoleView))
|
||||
m_focusedSubWindowName = name;
|
||||
}
|
||||
m_textEditor.Render("hex.builtin.view.pattern_editor.name"_lang, textEditorSize, true);
|
||||
m_textEditor.Render("hex.builtin.view.pattern_editor.name"_lang, textEditorSize, false);
|
||||
m_textEditorHoverBox = ImRect(windowPosition,windowPosition+textEditorSize);
|
||||
m_consoleHoverBox = ImRect(ImVec2(windowPosition.x,windowPosition.y+textEditorSize.y),windowPosition+availableSize);
|
||||
TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler();
|
||||
|
Loading…
x
Reference in New Issue
Block a user