From 9ce64ec6e1e1b89e6c3879a3f7ad4678ac7d8cb2 Mon Sep 17 00:00:00 2001 From: paxcut <53811119+paxcut@users.noreply.github.com> Date: Wed, 25 Dec 2024 10:51:58 -0700 Subject: [PATCH] fix: Pattern Editor console scroll jumping. (#2029) Some issues related to the padding added to scroll past the end for console that has padding added. Added a shortcut to scroll editors one pixel at a time. Fixed whole lines always drawn at the top even if scroll value is chosen so that only a portion of the top line is visible. This caused errors in horizontal scrolling. Fixed Ctrl-F Ctrl-G and Ctrl-H messing the editor display. Fixed the end of the line could not be clicked with mouse Fixed line numbers and their lines could be displayed at different heights. Made numbers that represented lines floats instead of integers to allow partial line display. --- .../ColorTextEditor/include/TextEditor.h | 11 ++- .../ColorTextEditor/source/TextEditor.cpp | 73 +++++++++++++------ plugins/builtin/romfs/lang/en_US.json | 2 + .../content/views/view_pattern_editor.cpp | 10 +++ 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h b/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h index 0896a2c1d..f9cdd0867 100644 --- a/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h +++ b/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h @@ -307,6 +307,7 @@ public: return text.empty() || text == "\n"; } void SetTopLine(); + void SetScrollY(); void SetTextLines(const std::vector& aLines); std::vector GetTextLines() const; @@ -406,7 +407,7 @@ public: void Cut(); void Paste(); void Delete(); - int32_t GetPageSize() const; + float GetPageSize() const; ImVec2 &GetCharAdvance() { return mCharAdvance; } @@ -600,8 +601,8 @@ private: 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; - int mTopLine = 0; + float mLeftMargin = 10.0; + float mTopLine = 0.0F; bool mSetTopLine = false; bool mCursorPositionChanged = false; bool mBreakPointsChanged = false; @@ -631,7 +632,9 @@ private: float mSavedScrollY = 0; float mShiftedScrollY = 0; float mScrollY = 0; - int mNumberOfLinesDisplayed = 0; + float mScrollYIncrement = 0.0F; + bool mSetScrollY = false; + float mNumberOfLinesDisplayed = 0; float mLastClick = -1.0F; bool mShowCursor = true; bool mShowLineNumbers = true; diff --git a/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp b/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp index 465f4d5e1..f9002851c 100644 --- a/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp +++ b/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp @@ -311,6 +311,9 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 &aPositi ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); + if (local.x < mCharAdvance.x) + return Coordinates(lineNo, 0); + local.x -= mCharAdvance.x; int columnCoord = 0; @@ -349,7 +352,7 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 &aPositi } } - return SanitizeCoordinates(Coordinates(lineNo, columnCoord - (columnCoord != 0))); + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); } void TextEditor::DeleteWordLeft() { @@ -891,14 +894,16 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); ImVec2 position = lineNumbersStartPos; auto scrollX = ImGui::GetScrollX(); + if (mSetScrollY) + SetScrollY(); auto scrollY = ImGui::GetScrollY(); if (mSetTopLine) SetTopLine(); else - mTopLine = std::max(0, std::floor((scrollY-mTopMargin) / mCharAdvance.y)); + mTopLine = std::max(0.0F, (scrollY-mTopMargin) / mCharAdvance.y); auto lineNo = mTopLine; - int globalLineMax = mLines.size(); - auto lineMax = std::clamp(lineNo + mNumberOfLinesDisplayed, 0, globalLineMax - 1); + float globalLineMax = mLines.size(); + auto lineMax = std::clamp(lineNo + mNumberOfLinesDisplayed, 0.0F, globalLineMax-1.0F); int totalDigitCount = std::floor(std::log10(globalLineMax)) + 1; mLongest = GetLongestLineLength() * mCharAdvance.x; @@ -915,7 +920,7 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; while (lineNo <= lineMax) { - ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x + mLeftMargin, cursorScreenPos.y + lineNo * mCharAdvance.y); + ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x + mLeftMargin, mTopMargin + cursorScreenPos.y + std::floor(lineNo) * mCharAdvance.y); ImVec2 textScreenPos = lineStartScreenPos; auto &line = mLines[lineNo]; @@ -941,11 +946,8 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo ImVec2 vend(lineStartScreenPos.x + ssend, lineStartScreenPos.y + mCharAdvance.y); drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); } - 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); + ImVec2 lineNoStartScreenPos = ImVec2(position.x, mTopMargin + cursorScreenPos.y + std::floor(lineNo) * mCharAdvance.y); + auto start = ImVec2(lineNoStartScreenPos.x + mLineNumberFieldWidth, lineStartScreenPos.y); bool focused = ImGui::IsWindowFocused(); if (!mIgnoreImGuiChild) ImGui::EndChild(); @@ -960,14 +962,14 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo 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()); + std::string lineNoStr = space + std::to_string((int)(lineNo + 1)); + TextUnformattedColoredAt(ImVec2(mLeftMargin + lineNoStartScreenPos.x, lineStartScreenPos.y), mPalette[(int) PaletteIndex::LineNumber], lineNoStr.c_str()); } // Draw breakpoints if (mBreakpoints.count(lineNo + 1) != 0) { - auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + mLineNumberFieldWidth, lineNoStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(ImVec2(lineNumbersStartPos.x, lineNoStartScreenPos.y), end, mPalette[(int)PaletteIndex::Breakpoint]); + auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + mLineNumberFieldWidth, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(ImVec2(lineNumbersStartPos.x, lineStartScreenPos.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]); @@ -977,9 +979,9 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo // Highlight the current line (where the cursor is) if (!HasSelection()) { - 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); + auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + mLineNumberFieldWidth, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(ImVec2(lineNumbersStartPos.x, lineStartScreenPos.y), end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(ImVec2(lineNumbersStartPos.x, lineStartScreenPos.y), end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f); } } if (mShowLineNumbers && !mIgnoreImGuiChild) @@ -1152,7 +1154,7 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo mLineBuffer.clear(); } - ++lineNo; + lineNo = std::floor(lineNo + 1.0F); } } if (!mIgnoreImGuiChild) @@ -1167,9 +1169,9 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo ImGui::BeginChild(aTitle); if (mShowLineNumbers) - ImGui::Dummy(ImVec2(mLongest, (globalLineMax - lineMax - 2) * mCharAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight())); + ImGui::Dummy(ImVec2(mLongest, (globalLineMax - lineMax - 2.0F) * mCharAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight())); else - ImGui::Dummy(ImVec2(mLongest, (globalLineMax - 1 - lineMax + GetPageSize() - 1) * mCharAdvance.y)); + ImGui::Dummy(ImVec2(mLongest, (globalLineMax - 1.0f - lineMax + GetPageSize() - 1.0f ) * mCharAdvance.y - 2 * ImGuiStyle().WindowPadding.y)); if (mScrollToCursor) EnsureCursorVisible(); @@ -1222,6 +1224,7 @@ void TextEditor::Render(const char *aTitle, const ImVec2 &aSize, bool aBorder) { 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::SetNextWindowPos(position); ImGui::SetCursorScreenPos(position); auto lineNoSize = ImVec2(mLineNumberFieldWidth, aSize.y); if (!mIgnoreImGuiChild) { @@ -1648,6 +1651,11 @@ void TextEditor::JumpToCoords(const Coordinates &aNewPos) { void TextEditor::MoveUp(int aAmount, bool aSelect) { ResetCursorBlinkTime(); auto oldPos = mState.mCursorPosition; + if (aAmount < 0) { + mScrollYIncrement = -1.0; + SetScrollY(); + return; + } mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); if (oldPos != mState.mCursorPosition) { if (aSelect) { @@ -1671,10 +1679,16 @@ void TextEditor::MoveDown(int aAmount, bool aSelect) { IM_ASSERT(mState.mCursorPosition.mColumn >= 0); ResetCursorBlinkTime(); auto oldPos = mState.mCursorPosition; + if (aAmount < 0) { + mScrollYIncrement = 1.0; + SetScrollY(); + return; + } + mState.mCursorPosition.mLine = std::clamp(mState.mCursorPosition.mLine + aAmount, 0, (int)mLines.size() - 1); if (oldPos.mLine == (mLines.size() - 1)) { mTopLine += aAmount; - mTopLine = std::clamp(mTopLine, 0, (int)mLines.size() - 1); + mTopLine = std::clamp(mTopLine, 0.0F, mLines.size() - 1.0F); SetTopLine(); EnsureCursorVisible(); return; @@ -3002,6 +3016,17 @@ float TextEditor::TextDistanceToLineStart(const Coordinates &aFrom) const { return distance; } +void TextEditor::SetScrollY() { + if (!mWithinRender) { + mSetScrollY = true; + return; + } else { + mSetScrollY = false; + auto scrollY = ImGui::GetScrollY(); + ImGui::SetScrollY(std::clamp(scrollY+mScrollYIncrement,0.0f,ImGui::GetScrollMaxY())); + } +} + void TextEditor::SetTopLine() { if (!mWithinRender) { mSetTopLine = true; @@ -3065,9 +3090,9 @@ void TextEditor::EnsureCursorVisible() { mOldTopMargin = mTopMargin; } -int TextEditor::GetPageSize() const { - auto height = ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - mTopMargin - ImGui::GetStyle().FramePadding.y; - return (int)floor(height / mCharAdvance.y); +float TextEditor::GetPageSize() const { + auto height = ImGui::GetCurrentWindow()->InnerClipRect.GetHeight(); + return height / mCharAdvance.y; } void TextEditor::ResetCursorBlinkTime() { diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index d49866fee..019e36ba9 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -1015,6 +1015,8 @@ "hex.builtin.view.pattern_editor.shortcut.move_word_right": "Move Cursor One Word to the Right", "hex.builtin.view.pattern_editor.shortcut.move_up": "Move Cursor One Line Up", "hex.builtin.view.pattern_editor.shortcut.move_down": "Move Cursor One Line Down", + "hex.builtin.view.pattern_editor.shortcut.move_pixel_up": "Move Cursor One Pixel Up", + "hex.builtin.view.pattern_editor.shortcut.move_pixel_down": "Move Cursor One Pixel Down", "hex.builtin.view.pattern_editor.shortcut.move_page_up": "Move Cursor One Page Up", "hex.builtin.view.pattern_editor.shortcut.move_page_down": "Move Cursor One Page Down", "hex.builtin.view.pattern_editor.shortcut.move_home": "Move Cursor to the Start of the Line", diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index d5ba9dcbf..8658c74c7 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -2513,6 +2513,11 @@ namespace hex::plugin::builtin { editor->MoveUp(1, false); }); + ShortcutManager::addShortcut(this, ALT + Keys::Up + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_pixel_up", [this] { + if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) + editor->MoveUp(-1, false); + }); + ShortcutManager::addShortcut(this, Keys::PageUp + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_page_up", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) editor->MoveUp(editor->GetPageSize()-4, false); @@ -2523,6 +2528,11 @@ namespace hex::plugin::builtin { editor->MoveDown(1, false); }); + ShortcutManager::addShortcut(this, ALT+ Keys::Down + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_pixel_down", [this] { + if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) + editor->MoveDown(-1, false); + }); + ShortcutManager::addShortcut(this, Keys::PageDown + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_page_down", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) editor->MoveDown(editor->GetPageSize()-4, false);