Skip to content

Commit

Permalink
better undo/redo implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
scheffle committed Mar 22, 2024
1 parent e4a9a13 commit de53501
Showing 1 changed file with 71 additions and 27 deletions.
98 changes: 71 additions & 27 deletions vstgui/lib/ctexteditor.cpp
Expand Up @@ -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<bool iterateForward>
void doUndoRedo () const;

private:
Expand Down Expand Up @@ -343,7 +345,15 @@ struct TextEditorView : public CView,
size_t deleted {0};
String characters;
};
using UndoList = std::vector<UndoRecord>;
using UndoRecords = std::vector<UndoRecord>;

//------------------------------------------------------------------------
struct UndoGroup
{
uint64_t time {0};
UndoRecords record;
};
using UndoList = std::vector<UndoGroup>;

//------------------------------------------------------------------------
struct ModelData
Expand Down Expand Up @@ -385,8 +395,9 @@ struct TextEditorView : public CView,
String findString;

CommandKeyArray commandKeys;
UndoList undoList {UndoRecord ()};
UndoList undoList {UndoGroup ()};
UndoList::iterator undoPos;
UndoGroup currentUndoGroup;
};

private:
Expand Down Expand Up @@ -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)
Expand All @@ -2735,51 +2761,69 @@ CharT* TextEditorView::createUndoRecord (size_t pos, size_t insertLen, size_t de
}

//------------------------------------------------------------------------
template<bool iterateForward>
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<int> (md.undoPos->position);
if (md.undoPos->deleted > 0)
{
md.editState.select_end =
md.editState.select_start + static_cast<int> (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<int> (md.undoPos->characters.size ());
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, md.undoPos->characters.data (),
static_cast<int> (md.undoPos->characters.size ()));
});
md.undoPos->deleted = md.undoPos->characters.size ();
md.undoPos->characters.clear ();
md.editState.cursor = md.editState.select_start = static_cast<int> (it->position);
if (it->deleted > 0)
{
md.editState.select_end = md.editState.select_start + static_cast<int> (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<int> (it->characters.size ());
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, it->characters.data (),
static_cast<int> (it->characters.size ()));
});
it->deleted = it->characters.size ();
it->characters.clear ();
}
}
md.isInUndoRedo = false;
}

//------------------------------------------------------------------------
void TextEditorView::undo () const
{
checkCurrentUndoGroup (true);
if (md.undoPos == md.undoList.end () || md.undoPos == md.undoList.begin ())
return;
doUndoRedo ();
doUndoRedo<false> ();
--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<true> ();
}

//------------------------------------------------------------------------
Expand Down

0 comments on commit de53501

Please sign in to comment.