From de53501fcb01f289e7142006a91e988b1e2240b0 Mon Sep 17 00:00:00 2001 From: scheffle Date: Fri, 22 Mar 2024 16:52:23 +0100 Subject: [PATCH] better undo/redo implementation --- vstgui/lib/ctexteditor.cpp | 98 +++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/vstgui/lib/ctexteditor.cpp b/vstgui/lib/ctexteditor.cpp index dd64e8272..e2f4c43ef 100644 --- a/vstgui/lib/ctexteditor.cpp +++ b/vstgui/lib/ctexteditor.cpp @@ -286,8 +286,10 @@ struct TextEditorView : public CView, // undo / redo CharT* createUndoRecord (size_t pos, size_t insertLen, size_t deleteLen) const; + void checkCurrentUndoGroup (bool force) const; void undo () const; void redo () const; + template void doUndoRedo () const; private: @@ -343,7 +345,15 @@ struct TextEditorView : public CView, size_t deleted {0}; String characters; }; - using UndoList = std::vector; + using UndoRecords = std::vector; + + //------------------------------------------------------------------------ + struct UndoGroup + { + uint64_t time {0}; + UndoRecords record; + }; + using UndoList = std::vector; //------------------------------------------------------------------------ struct ModelData @@ -385,8 +395,9 @@ struct TextEditorView : public CView, String findString; CommandKeyArray commandKeys; - UndoList undoList {UndoRecord ()}; + UndoList undoList {UndoGroup ()}; UndoList::iterator undoPos; + UndoGroup currentUndoGroup; }; private: @@ -2709,21 +2720,36 @@ void TextEditorView::setFindString (String&& text) const } } +//------------------------------------------------------------------------ +void TextEditorView::checkCurrentUndoGroup (bool force) const +{ + auto currentTime = getPlatformFactory ().getTicks (); + if (force || md.currentUndoGroup.time < currentTime - 500) + { + if (md.currentUndoGroup.record.empty ()) + return; + md.undoList.emplace_back (std::move (md.currentUndoGroup)); + md.undoPos = md.undoList.end (); + --md.undoPos; + md.currentUndoGroup = {}; + } + md.currentUndoGroup.time = currentTime; +} + //------------------------------------------------------------------------ CharT* TextEditorView::createUndoRecord (size_t pos, size_t insertLen, size_t deleteLen) const { if (md.isInUndoRedo) return nullptr; + checkCurrentUndoGroup (false); if (md.undoPos != md.undoList.end ()) { md.undoPos++; if (md.undoPos != md.undoList.end ()) md.undoList.erase (md.undoPos, md.undoList.end ()); } - md.undoList.emplace_back (UndoRecord {}); - md.undoPos = md.undoList.end (); - --md.undoPos; - auto& record = md.undoList.back (); + md.currentUndoGroup.record.emplace_back (UndoRecord {}); + auto& record = md.currentUndoGroup.record.back (); record.position = pos; record.deleted = deleteLen; if (insertLen) @@ -2735,29 +2761,45 @@ CharT* TextEditorView::createUndoRecord (size_t pos, size_t insertLen, size_t de } //------------------------------------------------------------------------ +template void TextEditorView::doUndoRedo () const { + auto begin = [this] () { + if constexpr (iterateForward) + return md.undoPos->record.begin (); + else + return md.undoPos->record.rbegin (); + }; + auto end = [this] () { + if constexpr (iterateForward) + return md.undoPos->record.end (); + else + return md.undoPos->record.rend (); + }; + md.isInUndoRedo = true; - md.editState.cursor = md.editState.select_start = 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) + for (auto it = begin (); it != end (); ++it) { - 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.editState.cursor = md.editState.select_start = static_cast (it->position); + if (it->deleted > 0) + { + md.editState.select_end = md.editState.select_start + static_cast (it->deleted); + it->characters = md.model.text.substr ( + md.editState.select_start, md.editState.select_end - md.editState.select_start); + it->deleted = 0; + callSTB ([&] () { stb_textedit_cut (this, &md.editState); }); + } + else if (it->characters.empty () == false) + { + md.editState.select_end = + md.editState.select_start + static_cast (it->characters.size ()); + callSTB ([&] () { + stb_textedit_paste (this, &md.editState, it->characters.data (), + static_cast (it->characters.size ())); + }); + it->deleted = it->characters.size (); + it->characters.clear (); + } } md.isInUndoRedo = false; } @@ -2765,21 +2807,23 @@ void TextEditorView::doUndoRedo () const //------------------------------------------------------------------------ void TextEditorView::undo () const { + checkCurrentUndoGroup (true); if (md.undoPos == md.undoList.end () || md.undoPos == md.undoList.begin ()) return; - doUndoRedo (); + doUndoRedo (); --md.undoPos; } //------------------------------------------------------------------------ void TextEditorView::redo () const { + checkCurrentUndoGroup (true); if (std::distance (md.undoPos, md.undoList.end ()) <= 1) return; ++md.undoPos; if (md.undoPos == md.undoList.end ()) return; - doUndoRedo (); + doUndoRedo (); } //------------------------------------------------------------------------