Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
630a5d2
New module for data modelling (undo, value trees, ...)
kunitoki Aug 2, 2024
40625b1
Code formatting
yup-bot Aug 2, 2024
309987d
Improved tests
kunitoki Aug 2, 2024
5745614
Code formatting
yup-bot Aug 2, 2024
b4c98a2
More stuff in undo manager
kunitoki Aug 2, 2024
2fa5707
Improved transaction management
kunitoki Aug 19, 2024
1c35d09
Code formatting
yup-bot Aug 19, 2024
67f67ee
Improved doxygen
kunitoki Aug 19, 2024
88bcf18
Merge branch 'main' into dev/data_model_module
kunitoki Aug 20, 2024
134918a
Fix tests
kunitoki Aug 20, 2024
51b2213
Code formatting
yup-bot Aug 20, 2024
992dc12
Merge branch 'main' into dev/data_model_module
kunitoki Oct 30, 2024
28527c5
Code formatting
yup-bot Oct 30, 2024
56b59c5
Merge branch 'main' into dev/data_model_module
kunitoki Nov 4, 2024
1204ec3
Merge branch 'main' into dev/data_model_module
kunitoki Dec 5, 2024
a1a944f
Merge branch 'main' into dev/data_model_module
kunitoki Mar 22, 2025
ab3d617
Code formatting
yup-bot Mar 22, 2025
9174314
Merge branch 'main' into dev/data_model_module
kunitoki Mar 23, 2025
efd3e24
Merge branch 'main' into dev/data_model_module
kunitoki Apr 6, 2025
8af0533
Code formatting
yup-bot Apr 6, 2025
e82679c
Merge branch 'main' into dev/data_model_module
kunitoki Apr 6, 2025
54d7c52
Code formatting
yup-bot Apr 6, 2025
5f3d852
Format in main
kunitoki Apr 6, 2025
ac4f4e8
Make UndoManager a reference counted and weak referenceable object
kunitoki Apr 6, 2025
de01a75
Merge branch 'main' into dev/data_model_module
kunitoki Apr 9, 2025
00844a2
Code formatting
yup-bot Apr 9, 2025
3a5c650
Merge branch 'main' into dev/data_model_module
kunitoki Apr 20, 2025
5e553b5
Code formatting
yup-bot Apr 20, 2025
cd06f61
Merge branch 'main' into dev/data_model_module
kunitoki Jun 13, 2025
6218cf8
Code formatting
yup-bot Jun 13, 2025
2faf63f
Merge branch 'main' into dev/data_model_module
kunitoki Jun 18, 2025
f429531
Merge branch 'main' into dev/data_model_module
kunitoki Jun 20, 2025
538ae21
Update rive.h
kunitoki Jun 20, 2025
09148f0
More tweaks
kunitoki Jun 20, 2025
0666291
Fix formatting
kunitoki Jun 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/clang_format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
uses: tj-actions/changed-files@v44
with:
files: |
**/*.c
**/*.cpp
**/*.h
**/*.hpp
Expand Down
3 changes: 3 additions & 0 deletions cmake/yup_modules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@ function (_yup_add_default_modules modules_path)
yup_add_module (${modules_path}/modules/yup_events ${modules_group})
add_library (yup::yup_events ALIAS yup_events)

yup_add_module (${modules_path}/modules/yup_data_model ${modules_group})
add_library (yup::yup_data_model ALIAS yup_data_model)

yup_add_module (${modules_path}/modules/yup_audio_basics ${modules_group})
add_library (yup::yup_audio_basics ALIAS yup_audio_basics)

Expand Down
328 changes: 328 additions & 0 deletions modules/yup_data_model/undo/yup_UndoManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
/*
==============================================================================

This file is part of the YUP library.
Copyright (c) 2024 - kunitoki@gmail.com

YUP is an open source library subject to open-source licensing.

The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
to use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.

YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.

==============================================================================
*/

namespace yup
{

//==============================================================================

UndoManager::Transaction::Transaction (StringRef name)
: transactionName (name)
{
}

void UndoManager::Transaction::add (UndoableAction::Ptr action)
{
if (action != nullptr)
childItems.add (action);
}

int UndoManager::Transaction::size() const
{
return childItems.size();
}

String UndoManager::Transaction::getTransactionName() const
{
return transactionName;
}

void UndoManager::Transaction::setTransactionName (StringRef newName)
{
transactionName = newName;
}

bool UndoManager::Transaction::perform (UndoableActionState stateToPerform)
{
if (stateToPerform == UndoableActionState::Undo)
{
for (int i = childItems.size() - 1; i >= 0; i--)
{
if (! childItems[i]->perform (stateToPerform))
childItems.remove (i);
}
}
else
{
for (int i = 0; i < childItems.size(); i++)
{
if (! childItems[i]->perform (stateToPerform))
childItems.remove (i--);
}
}

return isValid();
}

bool UndoManager::Transaction::isValid() const
{
return ! childItems.isEmpty();
}

//==============================================================================

UndoManager::ScopedTransaction::ScopedTransaction (UndoManager& undoManager)
: undoManager (undoManager)
{
undoManager.beginNewTransaction();
}

UndoManager::ScopedTransaction::ScopedTransaction (UndoManager& undoManager, StringRef transactionName)
: undoManager (undoManager)
{
undoManager.beginNewTransaction (transactionName);
}

UndoManager::ScopedTransaction::~ScopedTransaction()
{
undoManager.flushCurrentTransaction();
}

//==============================================================================

UndoManager::UndoManager()
: maxHistorySize (100)
, actionGroupThreshold (RelativeTime::milliseconds (500))
{
setEnabled (true);
}

UndoManager::UndoManager (int maxHistorySize)
: maxHistorySize (maxHistorySize)
, actionGroupThreshold (RelativeTime::milliseconds (500))
{
setEnabled (true);
}

UndoManager::UndoManager (RelativeTime actionGroupThreshold)
: maxHistorySize (100)
, actionGroupThreshold (actionGroupThreshold)
{
setEnabled (true);
}

UndoManager::UndoManager (int maxHistorySize, RelativeTime actionGroupThreshold)
: maxHistorySize (maxHistorySize)
, actionGroupThreshold (actionGroupThreshold)
{
setEnabled (true);
}

//==============================================================================

bool UndoManager::perform (UndoableAction::Ptr action)
{
jassert (action != nullptr);

if (! isEnabled())
return false;

if (action->perform (UndoableActionState::Redo))
{
if (currentTransaction == nullptr)
beginNewTransaction();

currentTransaction->add (action);

return true;
}

return false;
}

//==============================================================================

void UndoManager::beginNewTransaction()
{
beginNewTransaction ({});
}

void UndoManager::beginNewTransaction (StringRef transactionName)
{
flushCurrentTransaction();

if (currentTransaction == nullptr)
currentTransaction = new Transaction (transactionName);

else if (currentTransaction->isValid())
currentTransaction->setTransactionName (transactionName);
}

//==============================================================================

int UndoManager::getNumTransactions() const
{
return undoHistory.size() + (currentTransaction != nullptr ? 1 : 0);
}

String UndoManager::getTransactionName (int index) const
{
if (isPositiveAndBelow (index, getNumTransactions()))
{
if (isPositiveAndBelow (index, undoHistory.size()))
return undoHistory.getUnchecked (index)->getTransactionName();

else if (currentTransaction != nullptr)
return currentTransaction->getTransactionName();
}

return {};
}

//==============================================================================

String UndoManager::getCurrentTransactionName() const
{
if (currentTransaction != nullptr)
return currentTransaction->getTransactionName();

return {};
}

void UndoManager::setCurrentTransactionName (StringRef newName)
{
if (currentTransaction != nullptr)
currentTransaction->setTransactionName (newName);
}

//==============================================================================

bool UndoManager::canUndo() const
{
return (currentTransaction != nullptr && currentTransaction->isValid())
|| isPositiveAndBelow (nextUndoAction, undoHistory.size());
}

bool UndoManager::undo()
{
return internalPerform (UndoableActionState::Undo);
}

bool UndoManager::canRedo() const
{
return (currentTransaction != nullptr && currentTransaction->isValid())
|| isPositiveAndBelow (nextRedoAction, undoHistory.size());
}

bool UndoManager::redo()
{
return internalPerform (UndoableActionState::Redo);
}

//==============================================================================

void UndoManager::clear()
{
nextUndoAction = -1;
nextRedoAction = -1;

undoHistory.clearQuick();
currentTransaction.reset();
}

//==============================================================================

void UndoManager::setEnabled (bool shouldBeEnabled)
{
if (isEnabled() != shouldBeEnabled)
{
isUndoEnabled = shouldBeEnabled;

if (shouldBeEnabled)
{
if (actionGroupThreshold > RelativeTime())
startTimer (static_cast<int> (actionGroupThreshold.inMilliseconds()));
}
else
{
if (actionGroupThreshold > RelativeTime())
stopTimer();

flushCurrentTransaction();

undoHistory.clear();
}
}
}

bool UndoManager::isEnabled() const
{
return isUndoEnabled;
}

//==============================================================================

void UndoManager::timerCallback()
{
beginNewTransaction();
}

//==============================================================================

bool UndoManager::internalPerform (UndoableActionState stateToPerform)
{
flushCurrentTransaction();

auto actionIndex = (stateToPerform == UndoableActionState::Undo) ? nextUndoAction : nextRedoAction;
if (! isPositiveAndBelow (actionIndex, undoHistory.size()))
return false;

auto current = undoHistory[actionIndex];
if (current == nullptr)
return false;

auto result = current->perform (stateToPerform);
if (result)
{
const auto delta = (stateToPerform == UndoableActionState::Undo) ? -1 : 1;
nextUndoAction += delta;
nextRedoAction += delta;
}

return result;
}

//==============================================================================

bool UndoManager::flushCurrentTransaction()
{
if (currentTransaction == nullptr || ! currentTransaction->isValid())
return false;

// Remove all future actions
if (nextRedoAction < undoHistory.size())
undoHistory.removeRange (nextRedoAction, undoHistory.size() - nextRedoAction);

undoHistory.add (currentTransaction.get());
currentTransaction = nullptr;

// Clean up to keep the undo history in check
const int numToRemove = jmax (0, undoHistory.size() - maxHistorySize);
if (numToRemove > 0)
undoHistory.removeRange (0, numToRemove);

nextUndoAction = undoHistory.size() - 1;
nextRedoAction = undoHistory.size();

return true;
}

} // namespace yup
Loading