diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 30a7b9100..571cd66e3 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -58,6 +58,7 @@ #include "app/util/tile_flags_utils.h" #include "base/chrono.h" #include "base/convert_to.h" +#include "base/scoped_value.h" #include "doc/doc.h" #include "doc/mask_boundaries.h" #include "doc/slice.h" @@ -266,6 +267,23 @@ void Editor::setStateInternal(const EditorStatePtr& newState) { m_brushPreview.hide(); + // Some onLeaveState impls (like the ones from MovingPixelsState, + // WritingTextState, MovingSelectionState) might generate a + // Tx/Transaction::commit(), which will add a new undo state, + // triggering a sprite change scripting event + // (SpriteEvents::onAddUndoState). This event could be handled by an + // extension and that extension might want to save the current + // sprite (e.g. calling Sprite_saveCopyAs, the kind of extension + // that takes snapshots after each sprite change). That will be a + // new Context::executeCommand() for the save command, generating a + // BeforeCommandExecution signal, getting back to onLeaveState + // again. In that case, we just ignore the reentry as the first + // onLeaveState should handle everything (to avoid an stack + // overflow/infinite recursion). + if (m_leavingState) + return; + base::ScopedValue leaving(m_leavingState, true); + // Fire before change state event, set the state, and fire after // change state event. EditorState::LeaveAction leaveAction = m_state->onLeaveState(this, newState.get()); diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 038fa5361..d79c63a31 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2024 Igara Studio S.A. +// Copyright (C) 2018-2025 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -458,6 +458,13 @@ private: DocView* m_docView; + // Special flag to avoid re-entering a new state when we are leaving + // the current one. This avoids an infinite onLeaveState() recursion + // in some special cases when an extension (third-party code) + // creates a new sprite change in the same sprite change scripting + // event. + bool m_leavingState = false; + // Last known mouse position received by this editor when the // mouse button was pressed. Used for auto-scrolling. To get the // current mouse position on the editor you can use