From e4a9a131495a10a3d3964ddc0e6407e49c8d21d7 Mon Sep 17 00:00:00 2001 From: scheffle Date: Fri, 22 Mar 2024 16:19:37 +0100 Subject: [PATCH] implement custom undo/redo for text editor (naive version) --- vstgui/lib/ctexteditor.cpp | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/vstgui/lib/ctexteditor.cpp b/vstgui/lib/ctexteditor.cpp index 9ae1f2e17..dd64e8272 100644 --- a/vstgui/lib/ctexteditor.cpp +++ b/vstgui/lib/ctexteditor.cpp @@ -38,6 +38,8 @@ using CharT = char16_t; #define STB_TEXTEDIT_POSITIONTYPE int32_t #define STB_TEXTEDIT_STRING const TextEditorView #define STB_TEXTEDIT_KEYTYPE uint32_t +#define STB_TEXTEDIT_UNDOSTATECOUNT 0 +#define STB_TEXTEDIT_UNDOCHARCOUNT 0 #include "platform/common/stb_textedit.h" @@ -240,6 +242,14 @@ struct TextEditorView : public CView, return static_cast (self->moveToWordNext (pos)); } + static CharT* createUndoRecord (const TextEditorView* self, size_t pos, size_t insert_len, + size_t delete_len) + { + return self->createUndoRecord (pos, insert_len, delete_len); + } + static void undo (const TextEditorView* self) { self->undo (); } + static void redo (const TextEditorView* self) { self->redo (); } + protected: // IFocusDrawing bool drawFocusOnTop () override; @@ -274,6 +284,12 @@ struct TextEditorView : public CView, String::size_type doFindCaseSensitive (bool forward) const; String::size_type doFindIgnoreCase (bool forward) const; + // undo / redo + CharT* createUndoRecord (size_t pos, size_t insertLen, size_t deleteLen) const; + void undo () const; + void redo () const; + void doUndoRedo () const; + private: template bool callSTB (Proc proc) const; @@ -320,6 +336,15 @@ struct TextEditorView : public CView, TextEditorView& mutableThis () const { return *const_cast (this); } public: + //------------------------------------------------------------------------ + struct UndoRecord + { + size_t position {}; + size_t deleted {0}; + String characters; + }; + using UndoList = std::vector; + //------------------------------------------------------------------------ struct ModelData { @@ -351,6 +376,7 @@ struct TextEditorView : public CView, bool mouseIsDown {false}; bool isInsertingText {false}; bool cursorIsVisible {false}; + bool isInUndoRedo {false}; float cursorAlpha {0.f}; Lines::const_iterator stbInternalIterator; @@ -359,6 +385,8 @@ struct TextEditorView : public CView, String findString; CommandKeyArray commandKeys; + UndoList undoList {UndoRecord ()}; + UndoList::iterator undoPos; }; private: @@ -408,6 +436,9 @@ struct TextEditorView : public CView, #define STB_TEXTEDIT_INSERTCHARS TextEditorView::insertChars #define STB_TEXTEDIT_MOVEWORDLEFT TextEditorView::moveToWordPrevious #define STB_TEXTEDIT_MOVEWORDRIGHT TextEditorView::moveToWordNext +#define STB_TEXTEDIT_CUSTOM_CREATEUNDO TextEditorView::createUndoRecord +#define STB_TEXTEDIT_CUSTOM_UNDO TextEditorView::undo +#define STB_TEXTEDIT_CUSTOM_REDO TextEditorView::redo #define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.f #define STB_TEXTEDIT_IMPLEMENTATION @@ -484,6 +515,7 @@ static constexpr size_t Index (ITextEditor::Command cmd) { return static_cast (md.undoPos->position); + if (md.undoPos->deleted > 0) + { + md.editState.select_end = + md.editState.select_start + static_cast (md.undoPos->deleted); + md.undoPos->characters = md.model.text.substr ( + md.editState.select_start, md.editState.select_end - md.editState.select_start); + md.undoPos->deleted = 0; + callSTB ([&] () { stb_textedit_cut (this, &md.editState); }); + } + else if (md.undoPos->characters.empty () == false) + { + md.editState.select_end = + md.editState.select_start + static_cast (md.undoPos->characters.size ()); + callSTB ([&] () { + stb_textedit_paste (this, &md.editState, md.undoPos->characters.data (), + static_cast (md.undoPos->characters.size ())); + }); + md.undoPos->deleted = md.undoPos->characters.size (); + md.undoPos->characters.clear (); + } + md.isInUndoRedo = false; +} + +//------------------------------------------------------------------------ +void TextEditorView::undo () const +{ + if (md.undoPos == md.undoList.end () || md.undoPos == md.undoList.begin ()) + return; + doUndoRedo (); + --md.undoPos; +} + +//------------------------------------------------------------------------ +void TextEditorView::redo () const +{ + if (std::distance (md.undoPos, md.undoList.end ()) <= 1) + return; + ++md.undoPos; + if (md.undoPos == md.undoList.end ()) + return; + doUndoRedo (); +} + //------------------------------------------------------------------------ //------------------------------------------------------------------------ //------------------------------------------------------------------------