dlxjj/imradv3
0263
1#include <algorithm>2#include <chrono>3#include <string>4#include <regex>5#include <cmath>6#include <vector>7#include <ranges> // 需支持 C++208 9#include "TextEditor.h"10 11#define IMGUI_DEFINE_MATH_OPERATORS12#include "imgui.h" // for imGui::GetCurrentWindow()13 14 15//#include <opencv2/core.hpp>16//#include <opencv2/highgui.hpp>17//#include <opencv2/imgcodecs.hpp>18 19#include <opencv2/opencv.hpp>20#include <GLFW/glfw3.h> 21#include "../imrad.h"22 23//#pragma comment(lib, "E:\\huggingface\\ColorTextEditorV2\\build\\bin\\opencv_core4120.lib")24 25#include "../nlohmann_json/json.hpp"26#include "../global.h"27 28 29 30using json = nlohmann::json;31 32//#include "../MessageQueue.h"33 34// TODO35// - multiline comments vs single-line: latter is blocking start of a ML36 37template<class InputIt1, class InputIt2, class BinaryPredicate>38bool equals(InputIt1 first1, InputIt1 last1,39 InputIt2 first2, InputIt2 last2, BinaryPredicate p)40{41 for (; first1 != last1 && first2 != last2; ++first1, ++first2)42 {43 if (!p(*first1, *first2))44 return false;45 }46 return first1 == last1 && first2 == last2;47}48 49TextEditor::TextEditor(ImTextureID texture)50 : mLineSpacing(1.0f)51 , mUndoIndex(0)52 , mTabSize(4)53 , mOverwrite(false)54 , mReadOnly(false)55 , mWithinRender(false)56 , mScrollToCursor(false)57 , mScrollToTop(false)58 , mTextChanged(false)59 , mColorizerEnabled(true)60 , mTextStart(20.0f)61 , mLeftMargin(10)62 , mCursorPositionChanged(false)63 , mColorRangeMin(0)64 , mColorRangeMax(0)65 , mSelectionMode(SelectionMode::Normal)66 , mCheckComments(true)67 , mLastClick(-1.0f)68 , mHandleKeyboardInputs(true)69 , mHandleMouseInputs(true)70 , mIgnoreImGuiChild(false)71 , mShowWhitespaces(true)72 , mStartTime(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count())73{74 SetPalette(GetDarkPalette());75 SetLanguageDefinition(LanguageDefinition::HLSL());76 mLines.push_back(Line());77 mRect.SetTexture(texture);78}79 80TextEditor::~TextEditor()81{82}83 84void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef)85{86 mLanguageDefinition = aLanguageDef;87 mRegexList.clear();88 89 for (auto& r : mLanguageDefinition.mTokenRegexStrings)90 mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second));91 92 Colorize();93}94 95void TextEditor::SetPalette(const Palette & aValue)96{97 mPaletteBase = aValue;98}99 100std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const101{102 std::string result;103 104 auto lstart = aStart.mLine;105 auto lend = aEnd.mLine;106 auto istart = GetCharacterIndex(aStart);107 auto iend = GetCharacterIndex(aEnd);108 size_t s = 0;109 110 for (size_t i = lstart; i < lend; i++)111 s += mLines[i].size();112 113 result.reserve(s + s / 8);114 115 while (istart < iend || lstart < lend)116 {117 if (lstart >= (int)mLines.size())118 break;119 120 auto& line = mLines[lstart];121 if (istart < (int)line.size())122 {123 result += line[istart].mChar;124 istart++;125 }126 else127 {128 istart = 0;129 ++lstart;130 result += '\n';131 }132 }133 134 return result;135}136 137TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const138{139 return SanitizeCoordinates(mState.mCursorPosition);140}141 142TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const143{144 auto line = aValue.mLine;145 auto column = aValue.mColumn;146 if (line >= (int)mLines.size())147 {148 if (mLines.empty())149 {150 line = 0;151 column = 0;152 }153 else154 {155 line = (int)mLines.size() - 1;156 column = GetLineMaxColumn(line);157 }158 return Coordinates(line, column);159 }160 else161 {162 column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line));163 return Coordinates(line, column);164 }165}166 167// https://en.wikipedia.org/wiki/UTF-8168// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)169static int UTF8CharLength(TextEditor::Char c)170{171 if ((c & 0xFE) == 0xFC)172 return 6;173 if ((c & 0xFC) == 0xF8)174 return 5;175 if ((c & 0xF8) == 0xF0)176 return 4;177 else if ((c & 0xF0) == 0xE0)178 return 3;179 else if ((c & 0xE0) == 0xC0)180 return 2;181 return 1;182}183 184// "Borrowed" from ImGui source185static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)186{187 if (c < 0x80)188 {189 buf[0] = (char)c;190 return 1;191 }192 if (c < 0x800)193 {194 if (buf_size < 2) return 0;195 buf[0] = (char)(0xc0 + (c >> 6));196 buf[1] = (char)(0x80 + (c & 0x3f));197 return 2;198 }199 if (c >= 0xdc00 && c < 0xe000)200 {201 return 0;202 }203 if (c >= 0xd800 && c < 0xdc00)204 {205 if (buf_size < 4) return 0;206 buf[0] = (char)(0xf0 + (c >> 18));207 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));208 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));209 buf[3] = (char)(0x80 + ((c) & 0x3f));210 return 4;211 }212 //else if (c < 0x10000)213 {214 if (buf_size < 3) return 0;215 buf[0] = (char)(0xe0 + (c >> 12));216 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));217 buf[2] = (char)(0x80 + ((c) & 0x3f));218 return 3;219 }220}221 222void TextEditor::Advance(Coordinates & aCoordinates) const223{224 if (aCoordinates.mLine < (int)mLines.size())225 {226 auto& line = mLines[aCoordinates.mLine];227 auto cindex = GetCharacterIndex(aCoordinates);228 229 if (cindex + 1 < (int)line.size())230 {231 auto delta = UTF8CharLength(line[cindex].mChar);232 cindex = std::min(cindex + delta, (int)line.size() - 1);233 }234 else235 {236 ++aCoordinates.mLine;237 cindex = 0;238 }239 aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex);240 }241}242 243void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)244{245 assert(aEnd >= aStart);246 assert(!mReadOnly);247 248 //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn);249 250 if (aEnd == aStart)251 return;252 253 auto start = GetCharacterIndex(aStart);254 auto end = GetCharacterIndex(aEnd);255 256 if (aStart.mLine == aEnd.mLine)257 {258 auto& line = mLines[aStart.mLine];259 auto n = GetLineMaxColumn(aStart.mLine);260 if (aEnd.mColumn >= n)261 line.erase(line.begin() + start, line.end());262 else263 line.erase(line.begin() + start, line.begin() + end);264 }265 else266 {267 auto& firstLine = mLines[aStart.mLine];268 auto& lastLine = mLines[aEnd.mLine];269 270 firstLine.erase(firstLine.begin() + start, firstLine.end());271 lastLine.erase(lastLine.begin(), lastLine.begin() + end);272 273 if (aStart.mLine < aEnd.mLine)274 firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());275 276 if (aStart.mLine < aEnd.mLine)277 RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);278 }279 280 mTextChanged = true;281}282 283int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue)284{285 assert(!mReadOnly);286 287 int cindex = GetCharacterIndex(aWhere);288 int totalLines = 0;289 while (*aValue != '\0')290 {291 assert(!mLines.empty());292 293 if (*aValue == '\r')294 {295 // skip296 ++aValue;297 }298 else if (*aValue == '\n')299 {300 if (cindex < (int)mLines[aWhere.mLine].size())301 {302 auto& newLine = InsertLine(aWhere.mLine + 1);303 auto& line = mLines[aWhere.mLine];304 newLine.insert(newLine.begin(), line.begin() + cindex, line.end());305 line.erase(line.begin() + cindex, line.end());306 }307 else308 {309 InsertLine(aWhere.mLine + 1);310 }311 ++aWhere.mLine;312 aWhere.mColumn = 0;313 cindex = 0;314 ++totalLines;315 ++aValue;316 }317 else318 {319 auto& line = mLines[aWhere.mLine];320 auto d = UTF8CharLength(*aValue);321 while (d-- > 0 && *aValue != '\0')322 line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default));323 ++aWhere.mColumn;324 }325 326 mTextChanged = true;327 }328 329 return totalLines;330}331 332void TextEditor::AddUndo(UndoRecord& aValue)333{334 assert(!mReadOnly);335 //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n",336 // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn,337 // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn,338 // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn,339 // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn340 // );341 342 mUndoBuffer.resize((size_t)(mUndoIndex + 1));343 mUndoBuffer.back() = aValue;344 ++mUndoIndex;345}346 347TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const348{349 ImVec2 origin = ImGui::GetCursorScreenPos();350 ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);351 352 int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y));353 lineNo = std::min(lineNo, (int)mLines.size() - 1);354 355 int columnCoord = 0;356 357 if (lineNo >= 0 && lineNo < (int)mLines.size())358 {359 auto& line = mLines.at(lineNo);360 361 int columnIndex = 0;362 float columnX = 0.0f;363 364 while ((size_t)columnIndex < line.size())365 {366 float columnWidth = 0.0f;367 368 if (line[columnIndex].mChar == '\t')369 {370 float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x;371 float oldX = columnX;372 float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);373 columnWidth = newColumnX - oldX;374 if (mTextStart + columnX + columnWidth * 0.5f > local.x)375 break;376 columnX = newColumnX;377 columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize;378 columnIndex++;379 }380 else381 {382 char buf[7];383 auto d = UTF8CharLength(line[columnIndex].mChar);384 int i = 0;385 while (i < 6 && d-- > 0)386 buf[i++] = line[columnIndex++].mChar;387 buf[i] = '\0';388 columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x;389 if (mTextStart + columnX + columnWidth * 0.5f > local.x)390 break;391 columnX += columnWidth;392 columnCoord++;393 }394 }395 }396 397 return SanitizeCoordinates(Coordinates(lineNo, columnCoord));398}399 400TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const401{402 Coordinates at = aFrom;403 if (at.mLine >= (int)mLines.size())404 return at;405 406 auto& line = mLines[at.mLine];407 auto cindex = GetCharacterIndex(at);408 409 if (cindex >= (int)line.size())410 return at;411 412 while (cindex > 0 && isspace(line[cindex].mChar))413 --cindex;414 415 auto cstart = (PaletteIndex)line[cindex].mColorIndex;416 while (cindex > 0)417 {418 auto c = line[cindex].mChar;419 if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx420 {421 if (c <= 32 && isspace(c))422 {423 cindex++;424 break;425 }426 if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex)427 break;428 }429 --cindex;430 }431 return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));432}433 434TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const435{436 Coordinates at = aFrom;437 if (at.mLine >= (int)mLines.size())438 return at;439 440 auto& line = mLines[at.mLine];441 auto cindex = GetCharacterIndex(at);442 443 if (cindex >= (int)line.size())444 return at;445 446 bool prevspace = (bool)isspace(line[cindex].mChar);447 auto cstart = (PaletteIndex)line[cindex].mColorIndex;448 while (cindex < (int)line.size())449 {450 auto c = line[cindex].mChar;451 auto d = UTF8CharLength(c);452 if (cstart != (PaletteIndex)line[cindex].mColorIndex)453 break;454 455 if (prevspace != !!isspace(c))456 {457 if (isspace(c))458 while (cindex < (int)line.size() && isspace(line[cindex].mChar))459 ++cindex;460 break;461 }462 cindex += d;463 }464 return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));465}466 467TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const468{469 Coordinates at = aFrom;470 if (at.mLine >= (int)mLines.size())471 return at;472 473 // skip to the next non-word character474 auto cindex = GetCharacterIndex(aFrom);475 bool isword = false;476 bool skip = false;477 if (cindex < (int)mLines[at.mLine].size())478 {479 auto& line = mLines[at.mLine];480 isword = isalnum(line[cindex].mChar);481 skip = isword;482 }483 484 while (!isword || skip)485 {486 if (at.mLine >= mLines.size())487 {488 auto l = std::max(0, (int) mLines.size() - 1);489 return Coordinates(l, GetLineMaxColumn(l));490 }491 492 auto& line = mLines[at.mLine];493 if (cindex < (int)line.size())494 {495 isword = isalnum(line[cindex].mChar);496 497 if (isword && !skip)498 return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));499 500 if (!isword)501 skip = false;502 503 cindex++;504 }505 else506 {507 cindex = 0;508 ++at.mLine;509 skip = false;510 isword = false;511 }512 }513 514 return at;515}516 517int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const518{519 if (aCoordinates.mLine >= mLines.size())520 return -1;521 auto& line = mLines[aCoordinates.mLine];522 int c = 0;523 int i = 0;524 for (; i < line.size() && c < aCoordinates.mColumn;)525 {526 if (line[i].mChar == '\t')527 c = (c / mTabSize) * mTabSize + mTabSize;528 else529 ++c;530 i += UTF8CharLength(line[i].mChar);531 }532 return i;533}534 535int TextEditor::GetCharacterColumn(int aLine, int aIndex) const536{537 if (aLine >= mLines.size())538 return 0;539 auto& line = mLines[aLine];540 int col = 0;541 int i = 0;542 while (i < aIndex && i < (int)line.size())543 {544 auto c = line[i].mChar;545 i += UTF8CharLength(c);546 if (c == '\t')547 col = (col / mTabSize) * mTabSize + mTabSize;548 else549 col++;550 }551 return col;552}553 554int TextEditor::GetLineCharacterCount(int aLine) const555{556 if (aLine >= mLines.size())557 return 0;558 auto& line = mLines[aLine];559 int c = 0;560 for (unsigned i = 0; i < line.size(); c++)561 i += UTF8CharLength(line[i].mChar);562 return c;563}564 565int TextEditor::GetLineMaxColumn(int aLine) const566{567 if (aLine >= mLines.size())568 return 0;569 auto& line = mLines[aLine];570 int col = 0;571 for (unsigned i = 0; i < line.size(); )572 {573 auto c = line[i].mChar;574 if (c == '\t')575 col = (col / mTabSize) * mTabSize + mTabSize;576 else577 col++;578 i += UTF8CharLength(c);579 }580 return col;581}582 583bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const584{585 if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)586 return true;587 588 auto& line = mLines[aAt.mLine];589 auto cindex = GetCharacterIndex(aAt);590 if (cindex >= (int)line.size())591 return true;592 593 if (mColorizerEnabled)594 return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex;595 596 return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar);597}598 599void TextEditor::RemoveLine(int aStart, int aEnd)600{601 assert(!mReadOnly);602 assert(aEnd >= aStart);603 assert(mLines.size() > (size_t)(aEnd - aStart));604 605 ErrorMarkers etmp;606 for (auto& i : mErrorMarkers)607 {608 ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second);609 if (e.first >= aStart && e.first <= aEnd)610 continue;611 etmp.insert(e);612 }613 mErrorMarkers = std::move(etmp);614 615 Breakpoints btmp;616 for (auto i : mBreakpoints)617 {618 if (i >= aStart && i <= aEnd)619 continue;620 btmp.insert(i >= aStart ? i - 1 : i);621 }622 mBreakpoints = std::move(btmp);623 624 mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);625 assert(!mLines.empty());626 627 mTextChanged = true;628}629 630void TextEditor::RemoveLine(int aIndex)631{632 assert(!mReadOnly);633 assert(mLines.size() > 1);634 635 ErrorMarkers etmp;636 for (auto& i : mErrorMarkers)637 {638 ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second);639 if (e.first - 1 == aIndex)640 continue;641 etmp.insert(e);642 }643 mErrorMarkers = std::move(etmp);644 645 Breakpoints btmp;646 for (auto i : mBreakpoints)647 {648 if (i == aIndex)649 continue;650 btmp.insert(i >= aIndex ? i - 1 : i);651 }652 mBreakpoints = std::move(btmp);653 654 mLines.erase(mLines.begin() + aIndex);655 assert(!mLines.empty());656 657 mTextChanged = true;658}659 660TextEditor::Line& TextEditor::InsertLine(int aIndex)661{662 assert(!mReadOnly);663 664 auto& result = *mLines.insert(mLines.begin() + aIndex, Line());665 666 ErrorMarkers etmp;667 for (auto& i : mErrorMarkers)668 etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second));669 mErrorMarkers = std::move(etmp);670 671 Breakpoints btmp;672 for (auto i : mBreakpoints)673 btmp.insert(i >= aIndex ? i + 1 : i);674 mBreakpoints = std::move(btmp);675 676 return result;677}678 679std::string TextEditor::GetWordUnderCursor() const680{681 auto c = GetCursorPosition();682 return GetWordAt(c);683}684 685std::string TextEditor::GetWordAt(const Coordinates & aCoords) const686{687 auto start = FindWordStart(aCoords);688 auto end = FindWordEnd(aCoords);689 690 std::string r;691 692 auto istart = GetCharacterIndex(start);693 auto iend = GetCharacterIndex(end);694 695 for (auto it = istart; it < iend; ++it)696 r.push_back(mLines[aCoords.mLine][it].mChar);697 698 return r;699}700 701TextEditor::Glyph TextEditor::GetCharactersAroundCursor() const702{703 // 获取当前光标位置704 auto cursor = GetActualCursorCoordinates();705 706 // 检查行是否有效707 if (cursor.mLine >= 0 && cursor.mLine < (int)mLines.size())708 {709 const auto& line = mLines[cursor.mLine];710 711 char buf[7];712 int columnIndex = GetCharacterIndex({ cursor.mLine, cursor.mColumn });713 if (columnIndex >= 0 && columnIndex < line.size()) {714 Glyph gh = line[columnIndex];715 auto d = UTF8CharLength(gh.mChar);716 int i = 0;717 while (i < 6 && d-- > 0)718 buf[i++] = line[columnIndex++].mChar;719 buf[i] = '\0';720 721 sprintf(gh.mUtf8Char, "%s", buf);722 723 int utf8CharWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x;724 725 return gh;726 }727 728 //return Glyph();729 730 //// 获取光标前后的字符731 //char prevChar = (cursor.mColumn > 0) ? line[GetCharacterIndex({ cursor.mLine, cursor.mColumn - 1 })].mChar : '\0';732 //char nextChar = (cursor.mColumn < GetLineMaxColumn(cursor.mLine)) ? line[GetCharacterIndex({ cursor.mLine, cursor.mColumn })].mChar : '\0';733 734 //return { prevChar, nextChar };735 }736 737 return Glyph();738 739}740 741//void TextEditor::SetMessageQueue(MessageQueue* queue) { mMessageQueue = queue; }742 743ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const744{745 if (!mColorizerEnabled)746 return mPalette[(int)PaletteIndex::Default];747 if (aGlyph.mComment)748 return mPalette[(int)PaletteIndex::Comment];749 if (aGlyph.mMultiLineComment)750 return mPalette[(int)PaletteIndex::MultiLineComment];751 auto const color = mPalette[(int)aGlyph.mColorIndex];752 if (aGlyph.mPreprocessor)753 {754 const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor];755 const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2;756 const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2;757 const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2;758 const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2;759 return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24));760 }761 return color;762}763 764void TextEditor::HandleKeyboardInputs()765{766 ImGuiIO& io = ImGui::GetIO();767 auto shift = io.KeyShift;768 auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;769 auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;770 771 if (ImGui::IsWindowFocused())772 {773 if (ImGui::IsWindowHovered())774 ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);775 776 io.WantCaptureKeyboard = true;777 io.WantTextInput = true;778 779 if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Z))780 Undo();781 else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace))782 Undo();783 else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y))784 Redo();785 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow))786 MoveUp(1, shift);787 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow))788 MoveDown(1, shift);789 else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow))790 MoveLeft(1, shift, ctrl);791 else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow))792 MoveRight(1, shift, ctrl);793 else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp))794 MoveUp(GetPageSize() - 4, shift);795 else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown))796 MoveDown(GetPageSize() - 4, shift);797 else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home))798 MoveTop(shift);799 else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))800 MoveBottom(shift);801 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home))802 MoveHome(shift);803 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))804 MoveEnd(shift);805 else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete))806 Delete();807 else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Backspace))808 Backspace();809 else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))810 mOverwrite ^= true;811 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))812 Copy();813 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C))814 Copy();815 else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))816 Paste();817 else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V))818 Paste();819 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X))820 Cut();821 else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete))822 Cut();823 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A))824 SelectAll();825 else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter))826 EnterCharacter('\n', false);827 else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab))828 EnterCharacter('\t', shift);829 830 if (!IsReadOnly() && !io.InputQueueCharacters.empty())831 {832 for (int i = 0; i < io.InputQueueCharacters.Size; i++)833 {834 auto c = io.InputQueueCharacters[i];835 if (c != 0 && (c == '\n' || c >= 32))836 EnterCharacter(c, shift);837 }838 io.InputQueueCharacters.resize(0);839 }840 }841}842 843void TextEditor::HandleMouseInputs()844{845 ImGuiIO& io = ImGui::GetIO();846 auto shift = io.KeyShift;847 auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;848 auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;849 850 if (ImGui::IsWindowHovered())851 {852 if (!shift && !alt)853 {854 auto click = ImGui::IsMouseClicked(0);855 auto doubleClick = ImGui::IsMouseDoubleClicked(0);856 auto t = ImGui::GetTime();857 auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime);858 859 /*860 Left mouse button triple click861 */862 863 if (tripleClick)864 {865 if (!ctrl)866 {867 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());868 mSelectionMode = SelectionMode::Line;869 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);870 }871 872 mLastClick = -1.0f;873 }874 875 /*876 Left mouse button double click877 */878 879 else if (doubleClick)880 {881 if (!ctrl)882 {883 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());884 if (mSelectionMode == SelectionMode::Line)885 mSelectionMode = SelectionMode::Normal;886 else887 mSelectionMode = SelectionMode::Word;888 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);889 }890 891 mLastClick = (float)ImGui::GetTime();892 }893 894 /*895 Left mouse button click896 */897 else if (click)898 {899 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());900 if (ctrl)901 mSelectionMode = SelectionMode::Word;902 else903 mSelectionMode = SelectionMode::Normal;904 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);905 906 if (!mRect.isHovered) {907 Glyph gh = GetCharactersAroundCursor(); // gh.mUtf8Char 就是当前光标后面的一个完整 utf8 字符908 if (click ) { // && mMessageQueue909 910 const char* m5 = gh.mImageMD5;911 if (m5 && *m5) {912 auto [pth_img, pth_json] = get_img_json_pth(std::string(m5));913 914 std::ifstream f(pth_json);915 if (!f.is_open()) {916 }917 json jsn = json::parse(f);918 919 int width = jsn["width"];920 int height = jsn["height"];921 922 json prism_wordsInfo = jsn["prism_wordsInfo"];923 924 std::string word_line;925 json line_pos;926 927 bool found = false;928 929 for (const auto& elem : prism_wordsInfo) {930 word_line = elem["word"]; // 图片上的一行文字931 line_pos = elem["pos"]; // 这一行在图片上的坐标932 json charInfo = elem["charInfo"];933 934 for (const auto& elem2 : charInfo) {935 const std::string& utf8_char = elem2["word"];936 int x = elem2["x"];937 int y = elem2["y"];938 int w = elem2["w"];939 int h = elem2["h"];940 941 if (gh.x == x && gh.y == y && gh.w == w && gh.h == h) {942 found = true;943 break;944 }945 946 //for (auto chr : utf8_char) // utf8_char 可能是好几个字节,emplace_back 进去的很可能只是一部分947 //{948 // //if (chr == '\r')949 // //{950 // // // 理论上不应该有951 // //}952 // //else if (chr == '\n') {953 // // // 理论上不应该有954 // //}955 // //else956 // //{957 // // mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default, x, y, w, h));958 // //}959 960 // //mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default, x, y, w, h, md5.c_str()));961 //}962 }963 964 if (found) {965 break;966 }967 }968 969 if (found) {970 // 左上 右上 右下 左下971 int x = line_pos[0]["x"];972 int y = line_pos[0]["y"];973 974 int x_min = std::min(line_pos[0]["x"], line_pos[3]["x"]);975 int x_max = std::max(line_pos[1]["x"], line_pos[2]["x"]);976 977 int y_min = std::min(line_pos[0]["y"], line_pos[1]["y"]);978 int y_max = std::max(line_pos[2]["y"], line_pos[3]["y"]);979 980 int w = x_max - x_min;981 int h = y_max - y_min;982 983 auto b64_str = readFileToString(pth_img.string());984 auto b64_buf = base64_decode(b64_str);985 986 //cv::Mat srcImage = cv::imread("E:\\er.jpeg", cv::IMREAD_COLOR_BGR);987 auto srcImage = cv::imdecode(b64_buf, -1);988 989 switch (srcImage.channels()) {990 case 1: // 单通道灰度图 → 扩展为 RGBA991 cv::cvtColor(srcImage, srcImage, cv::COLOR_GRAY2RGBA);992 break;993 case 3: // 三通道BGR → 转换为 RGBA994 cv::cvtColor(srcImage, srcImage, cv::COLOR_BGR2RGBA);995 break;996 case 4: // 四通道直接使用(需确认OpenCV为BGRA)997 //convertedImage = srcImage.clone();998 break;999 }1000 1001 cv::rectangle(srcImage, cv::Point(gh.x, gh.y), cv::Point(gh.x + gh.w, gh.y + gh.h), cv::Scalar(255, 0, 0, 255), 2);1002 // 框出选中字符1003 1004 // 剪裁图片1005 cv::Mat cut = srcImage(cv::Rect(x, y, w, h)).clone(); // cv::Rect(0, 0, img_orgin.cols, 500)1006 1007 ImRad::Texture tex2;1008 unsigned char* image_data2 = cut.data;1009 tex2.w = cut.cols;1010 tex2.h = cut.rows;1011 1012 GLuint image_texture;1013 glGenTextures(1, &image_texture);1014 tex2.id = (ImTextureID)(intptr_t)image_texture;1015 glBindTexture(GL_TEXTURE_2D, image_texture);1016 1017 1018 int minFilter = GL_LINEAR;1019 int magFilter = GL_LINEAR;1020 int wrapS = GL_CLAMP_TO_EDGE; // This is required on WebGL for non power-of-two textures1021 int wrapT = GL_CLAMP_TO_EDGE; // Same1022 1023 // Setup filtering parameters for display1024 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);1025 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);1026 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);1027 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);1028 1029 // Upload pixels into texture1030 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);1031 1032 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex2.w, tex2.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data2);1033 //stbi_image_free(image_data2);1034 1035 mRect.size = ImVec2((float)tex2.w, (float)tex2.h);1036 mRect.SetTexture(tex2.id);1037 1038 //cv::imshow("原图", cut);1039 //cv::waitKey(0);1040 1041 }1042 1043 1044 1045 }1046 1047 1048 1049 //Message msg;1050 //msg.type = MessageType::EditorClick;1051 //auto mousePos = ImGui::GetMousePos();1052 //msg.x = (int)mousePos.x;1053 //msg.y = (int)mousePos.y;1054 1055 // 启动异步线程加载图片1056 //std::thread([this, msg]() {1057 1058 // // 发消息通知弹窗显示图片1059 // this->mMessageQueue->push(msg);1060 1061 //}).detach();1062 1063 }1064 }1065 1066 mLastClick = (float)ImGui::GetTime();1067 }1068 // Mouse left button dragging (=> update selection)1069 else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))1070 {1071 // 只有在非图片拖动状态下才进行文本选择1072 if (!mRect.isHovered)1073 {1074 io.WantCaptureMouse = true;1075 mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());1076 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);1077 }1078 }1079 }1080 }1081}1082 1083void TextEditor::Render()1084{1085 /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/1086 const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;1087 mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing);1088 1089 /* Update palette with the current alpha from style */1090 for (int i = 0; i < (int)PaletteIndex::Max; ++i)1091 {1092 auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]);1093 color.w *= ImGui::GetStyle().Alpha;1094 mPalette[i] = ImGui::ColorConvertFloat4ToU32(color);1095 }1096 1097 assert(mLineBuffer.empty());1098 1099 auto contentSize = ImGui::GetWindowContentRegionMax();1100 auto drawList = ImGui::GetWindowDrawList();1101 float longest(mTextStart);1102 1103 if (mScrollToTop)1104 {1105 mScrollToTop = false;1106 ImGui::SetScrollY(0.f);1107 }1108 1109 ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();1110 auto scrollX = ImGui::GetScrollX();1111 auto scrollY = ImGui::GetScrollY();1112 1113 auto lineNo = (int)floor(scrollY / mCharAdvance.y);1114 auto globalLineMax = (int)mLines.size();1115 auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y)));1116 1117 // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width1118 char buf[16];1119 snprintf(buf, 16, " %d ", globalLineMax);1120 mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;1121 1122 if (!mLines.empty())1123 {1124 float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;1125 1126 while (lineNo <= lineMax)1127 {1128 ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);1129 ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);1130 1131 auto& line = mLines[lineNo];1132 longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);1133 auto columnNo = 0;1134 Coordinates lineStartCoord(lineNo, 0);1135 Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo));1136 1137 // Draw selection for the current line1138 float sstart = -1.0f;1139 float ssend = -1.0f;1140 1141 assert(mState.mSelectionStart <= mState.mSelectionEnd);1142 if (mState.mSelectionStart <= lineEndCoord)1143 sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f;1144 if (mState.mSelectionEnd > lineStartCoord)1145 ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord);1146 1147 if (mState.mSelectionEnd.mLine > lineNo)1148 ssend += mCharAdvance.x;1149 1150 if (sstart != -1 && ssend != -1 && sstart < ssend)1151 {1152 ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y);1153 ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y);1154 drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]);1155 }1156 1157 // Draw breakpoints1158 auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);1159 1160 if (mBreakpoints.count(lineNo + 1) != 0)1161 {1162 auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);1163 drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]);1164 }1165 1166 // Draw error markers1167 auto errorIt = mErrorMarkers.find(lineNo + 1);1168 if (errorIt != mErrorMarkers.end())1169 {1170 auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);1171 drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]);1172 1173 if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end))1174 {1175 ImGui::BeginTooltip();1176 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));1177 ImGui::Text("Error at line %d:", errorIt->first);1178 ImGui::PopStyleColor();1179 ImGui::Separator();1180 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f));1181 ImGui::Text("%s", errorIt->second.c_str());1182 ImGui::PopStyleColor();1183 ImGui::EndTooltip();1184 }1185 }1186 1187 // Draw line number (right aligned)1188 snprintf(buf, 16, "%d ", lineNo + 1);1189 1190 auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x;1191 drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf);1192 1193 if (mState.mCursorPosition.mLine == lineNo)1194 {1195 auto focused = ImGui::IsWindowFocused();1196 1197 // Highlight the current line (where the cursor is)1198 if (!HasSelection())1199 {1200 auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y);