diff --git a/.github/workflows/clang_format.yml b/.github/workflows/clang_format.yml index 667bb403d..71214737a 100644 --- a/.github/workflows/clang_format.yml +++ b/.github/workflows/clang_format.yml @@ -30,6 +30,7 @@ jobs: uses: tj-actions/changed-files@v44 with: files: | + **/*.c **/*.cpp **/*.h **/*.hpp diff --git a/cmake/yup_modules.cmake b/cmake/yup_modules.cmake index 41c3652ca..1d9e54c78 100644 --- a/cmake/yup_modules.cmake +++ b/cmake/yup_modules.cmake @@ -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) diff --git a/modules/yup_data_model/undo/yup_UndoManager.cpp b/modules/yup_data_model/undo/yup_UndoManager.cpp new file mode 100644 index 000000000..452d3d995 --- /dev/null +++ b/modules/yup_data_model/undo/yup_UndoManager.cpp @@ -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 (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 diff --git a/modules/yup_data_model/undo/yup_UndoManager.h b/modules/yup_data_model/undo/yup_UndoManager.h new file mode 100644 index 000000000..c2d80d96a --- /dev/null +++ b/modules/yup_data_model/undo/yup_UndoManager.h @@ -0,0 +1,334 @@ +/* + ============================================================================== + + 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 +{ + +//============================================================================== +/** + @class UndoManager + + Manages undo and redo functionality for a set of actions. + + The UndoManager class provides a way to manage undo and redo functionality + for a set of actions. It allows you to perform actions, undo them, redo them, + enable or disable the undo manager, and group actions together as a single action. + + To use the UndoManager, create an instance of the class and call the `perform` + method to add actions to the timeline. You can also use the `undo` and `redo` + methods to reverse or perform the action in the current timeline position. + + The UndoManager class also provides a ScopedActionIsolator helper class that + allows you to group certain actions as a single action. This can be useful + when you want to ensure that multiple actions are treated as a single unit + for undo and redo purposes. + + @see UndoableAction +*/ +class YUP_API UndoManager + : public ReferenceCountedObject + , private Timer +{ +public: + using Ptr = ReferenceCountedObjectPtr; + + //============================================================================== + /** + Creates a new UndoManager and starts the timer. + */ + UndoManager(); + + /** + Creates a new UndoManager and starts the timer. + + @param maxHistorySize The maximum number of items to keep in the history. + */ + UndoManager (int maxHistorySize); + + /** + Creates a new UndoManager and starts the timer. + + @param actionGroupThreshold The time used to coalesce actions in the same transaction. + */ + UndoManager (RelativeTime actionGroupThreshold); + + /** + Creates a new UndoManager and starts the timer. + + @param maxHistorySize The maximum number of items to keep in the history. + @param actionGroupThreshold The time used to coalesce actions in the same transaction. + */ + UndoManager (int maxHistorySize, RelativeTime actionGroupThreshold); + + //============================================================================== + /** + Adds a new action to the timeline and performs its `redo` method. + + @param f The action to be performed. + + @return true if the action was added and performed successfully, false otherwise. + */ + bool perform (UndoableAction::Ptr f); + + //============================================================================== + /** + Adds a new action to the timeline and performs its `redo` method. + + This method allows you to create an action using a weak referenceable object and a lambda + that will be performed if the object is still alive. + + @tparam T The type of the object. + + @param object The object to be used in the action. + @param f The lambda function to be performed. + + @return true if the action was added and performed successfully, false otherwise. + */ + template + bool perform (T object, F&& function) + { + static_assert (std::is_base_of_v); + + return perform (new Item (object, std::forward (function))); + } + + //============================================================================== + /** + Begins a new transaction. + */ + void beginNewTransaction(); + + /** + Begins a new transaction with a given name. + + @param transactionName The name of the transaction. + */ + void beginNewTransaction (StringRef transactionName); + + //============================================================================== + /** + Returns the number of transactions in the undo manager. + + @return The number of transactions. + */ + int getNumTransactions() const; + + /** Retrieves the name of a transaction at the specified index. + + @param index The index of the transaction. + + @return The name of the transaction. + */ + String getTransactionName (int index) const; + + //============================================================================== + /** + Returns the name of the current transaction. + + @return The name of the current transaction as a String. + */ + String getCurrentTransactionName() const; + + /** + Sets the name of the current transaction. + + @param newName the new name for the transaction + */ + void setCurrentTransactionName (StringRef newName); + + //============================================================================== + /** + Check if undo action can be performed. + + @return true if an undo action can be performed, false otherwise. + */ + bool canUndo() const; + + /** + Reverses the action in the current timeline position. + + @return true if an action was reversed, false otherwise. + */ + bool undo(); + + //============================================================================== + /** + Check if redo action can be performed. + + @return true if a redo action can be performed, false otherwise. + */ + bool canRedo() const; + + /** + Performs the action in the current timeline position. + + @return true if an action was performed, false otherwise. + */ + bool redo(); + + //============================================================================== + /** + @brief Clears the undo manager. + + This function clears the undo manager, removing all the stored undo/redo actions. + */ + void clear(); + + //============================================================================== + /** + Enables or disables the undo manager. + + @param shouldBeEnabled true to enable the undo manager, false to disable it. + + Disabling the undo manager will clear the history and stop the timer. + */ + void setEnabled (bool shouldBeEnabled); + + /** + Checks if the undo manager is enabled. + + @return true if the undo manager is enabled, false otherwise. + */ + bool isEnabled() const; + + //============================================================================== + /** + Helper class to ensure that certain actions are grouped as a single action. + + By default, the undo manager groups all actions within a 500ms time window + into one action. If you need to have a separate item in the timeline for + certain actions, you can use this class. + + Example usage: + + @code + void doSomething() + { + UndoManager::ScopedTransaction transaction (um); + + um.perform (action1); + um.perform (action2); + } + @endcode + */ + struct ScopedTransaction + { + /** + Constructs a ScopedTransaction object. + + @param undoManager The UndoManager to be used. + */ + ScopedTransaction (UndoManager& undoManager); + + /** + Constructs a ScopedTransaction object. + + @param undoManager The UndoManager to be used. + @param transactionName The name of the transaction. + */ + ScopedTransaction (UndoManager& undoManager, StringRef transactionName); + + /** + Destructs the ScopedTransaction object. + */ + ~ScopedTransaction(); + + private: + UndoManager& undoManager; + }; + +private: + template + struct Item : public UndoableAction + { + using PerformCallback = std::function; + + Item (typename T::Ptr object, PerformCallback function) + : object (object) + , function (std::move (function)) + { + jassert (function != nullptr); + } + + bool perform (UndoableActionState stateToPerform) override + { + if (object.wasObjectDeleted()) + return false; + + return function (*object, stateToPerform); + } + + bool isValid() const override + { + return ! object.wasObjectDeleted(); + } + + private: + WeakReference object; + PerformCallback function; + }; + + struct Transaction : public UndoableAction + { + using Array = ReferenceCountedArray; + using Ptr = ReferenceCountedObjectPtr; + + Transaction() = default; + explicit Transaction (StringRef name); + + void add (UndoableAction::Ptr action); + int size() const; + + String getTransactionName() const; + void setTransactionName (StringRef newName); + + bool perform (UndoableActionState stateToPerform) override; + bool isValid() const override; + + private: + String transactionName; + UndoableAction::Array childItems; + }; + + /** @internal */ + void timerCallback() override; + /** @internal */ + bool internalPerform (UndoableActionState stateToPerform); + /** @internal */ + bool flushCurrentTransaction(); + + int maxHistorySize; + RelativeTime actionGroupThreshold; + + Transaction::Array undoHistory; + Transaction::Ptr currentTransaction; + + // the position in the timeline for the next actions. + int nextUndoAction = -1; + int nextRedoAction = -1; + + bool isUndoEnabled = false; + + YUP_DECLARE_WEAK_REFERENCEABLE (UndoManager) + YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager) +}; + +} // namespace yup diff --git a/modules/yup_data_model/undo/yup_UndoableAction.h b/modules/yup_data_model/undo/yup_UndoableAction.h new file mode 100644 index 000000000..cc5973d91 --- /dev/null +++ b/modules/yup_data_model/undo/yup_UndoableAction.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + 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 +{ + +//============================================================================== +/** + @enum ActionState + + Represents the state of an action in the undo/redo system. + + The ActionState enum is used to indicate whether an action should be undone or redone. + + @see UndoManager +*/ +enum class UndoableActionState +{ + Undo, /**< Indicates that the action should be undone. */ + Redo /**< Indicates that the action should be redone. */ +}; + +//============================================================================== +/** + @class UndoableAction + + The base class for all actions in the timeline. + + You can subclass from this class to define your actions, + but a better way is to just use a lambda with a WeakReferenceable object. +*/ +class UndoableAction : public ReferenceCountedObject +{ +public: + using Array = ReferenceCountedArray; + using Ptr = ReferenceCountedObjectPtr; + + /** + @brief Destructor. + */ + ~UndoableAction() override {} + + /** + @brief Checks if the action is valid. + + This should return true if the action is invalidated (e.g., because the object it operates on was deleted). + + @return True if the action is valid, false otherwise. + */ + virtual bool isValid() const = 0; + + /** + @brief Performs the undo action. + + This function performs the undo action based on the given state. + + @param stateToPerform The state of the action to perform (Undo or Redo). + + @return True if the undo action was successful, false otherwise. + */ + virtual bool perform (UndoableActionState stateToPerform) = 0; +}; + +} // namespace yup diff --git a/modules/yup_data_model/yup_data_model.cpp b/modules/yup_data_model/yup_data_model.cpp new file mode 100644 index 000000000..b92595cb0 --- /dev/null +++ b/modules/yup_data_model/yup_data_model.cpp @@ -0,0 +1,36 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +// clang-format off +#ifdef YUP_AUDIO_PROCESSORS_H_INCLUDED +/* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ +#error "Incorrect use of YUP cpp file" +#endif +// clang-format on + +#include "yup_data_model.h" + +//============================================================================== +#include "undo/yup_UndoManager.cpp" diff --git a/modules/yup_data_model/yup_data_model.h b/modules/yup_data_model/yup_data_model.h new file mode 100644 index 000000000..0d6c5fe7a --- /dev/null +++ b/modules/yup_data_model/yup_data_model.h @@ -0,0 +1,48 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +/******************************************************************************* + + BEGIN_YUP_MODULE_DECLARATION + + ID: yup_data_model + vendor: yup + version: 1.0.0 + name: YUP Data Model + description: The essential set of basic YUP data model classes. + website: https://github.com/kunitoki/yup + license: ISC + + dependencies: yup_events + enableARC: 1 + + END_YUP_MODULE_DECLARATION + +*******************************************************************************/ + +#pragma once +#define YUP_DATA_MODEL_H_INCLUDED + +#include + +//============================================================================== +#include "undo/yup_UndoableAction.h" +#include "undo/yup_UndoManager.h" diff --git a/modules/yup_data_model/yup_data_model.mm b/modules/yup_data_model/yup_data_model.mm new file mode 100644 index 000000000..b362ba23a --- /dev/null +++ b/modules/yup_data_model/yup_data_model.mm @@ -0,0 +1,22 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#include "yup_data_model.cpp" diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.h b/modules/yup_gui/native/yup_Windowing_sdl2.h index 7ebb0ab1c..e5e47484d 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.h +++ b/modules/yup_gui/native/yup_Windowing_sdl2.h @@ -22,6 +22,19 @@ namespace yup { +//============================================================================== +#ifndef YUP_WINDOWING_LOGGING +#define YUP_WINDOWING_LOGGING 1 +#endif + +#if YUP_WINDOWING_LOGGING +#define YUP_WINDOWING_LOG(textToWrite) JUCE_DBG (textToWrite) +#else +#define YUP_WINDOWING_LOG(textToWrite) \ + { \ + } +#endif + //============================================================================== class SDL2ComponentNative final : public ComponentNative diff --git a/modules/yup_gui/yup_gui.h b/modules/yup_gui/yup_gui.h index d03f3613a..407afc29c 100644 --- a/modules/yup_gui/yup_gui.h +++ b/modules/yup_gui/yup_gui.h @@ -32,11 +32,11 @@ website: https://github.com/kunitoki/yup license: ISC - dependencies: yup_events yup_graphics rive + dependencies: yup_events yup_data_model yup_graphics rive appleFrameworks: Metal iosWeakFrameworks: UniformTypeIdentifiers iosSimWeakFrameworks: UniformTypeIdentifiers - enableARC: 1 + enableARC: 1 END_YUP_MODULE_DECLARATION @@ -47,8 +47,11 @@ #define YUP_GUI_H_INCLUDED #include +#include #include +#include + //============================================================================== /** Config: YUP_ENABLE_COMPONENT_REPAINT_DEBUGGING @@ -60,10 +63,6 @@ //============================================================================== -#include -#include -#include - #include //============================================================================== diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 87bbb3953..2aab13e9b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -55,6 +55,7 @@ set (target_modules yup_audio_basics yup_audio_devices yup_events + yup_data_model yup_graphics) yup_standalone_app ( diff --git a/tests/yup_data_model/yup_UndoManager.cpp b/tests/yup_data_model/yup_UndoManager.cpp new file mode 100644 index 000000000..2133832d1 --- /dev/null +++ b/tests/yup_data_model/yup_UndoManager.cpp @@ -0,0 +1,423 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +// A simple UndoableAction class for testing purposes +class TestAction : public UndoableAction +{ +public: + using Ptr = ReferenceCountedObjectPtr; + + TestAction (bool& flag) + : flag (flag) + { + } + + bool perform (UndoableActionState) override + { + flag = ! flag; + return true; + } + + bool isValid() const override + { + return true; + } + +private: + bool& flag; +}; + +// A more complex UndoableAction class for additional testing purposes +class ToggleAction : public UndoableAction +{ +public: + using Ptr = ReferenceCountedObjectPtr; + + ToggleAction (int& counter) + : counter (counter) + { + } + + bool perform (UndoableActionState state) override + { + if (state == UndoableActionState::Redo) + ++counter; + else if (state == UndoableActionState::Undo) + --counter; + + return true; + } + + bool isValid() const override + { + return true; + } + +private: + int& counter; +}; + +class UndoManagerTest : public ::testing::Test +{ +protected: + void SetUp() override + { + actionFlag = false; + counter = 0; + undoManager = std::make_unique (10, RelativeTime::milliseconds (0)); + } + + void TearDown() override + { + } + + bool actionFlag; + int counter; + std::unique_ptr undoManager; +}; + +TEST_F (UndoManagerTest, PerformAction) +{ + TestAction::Ptr action = new TestAction (actionFlag); + EXPECT_TRUE (undoManager->perform (action)); + EXPECT_TRUE (actionFlag); +} + +TEST_F (UndoManagerTest, UndoAction) +{ + TestAction::Ptr action = new TestAction (actionFlag); + undoManager->perform (action); + EXPECT_TRUE (actionFlag); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_FALSE (actionFlag); +} + +TEST_F (UndoManagerTest, TransactionCount) +{ + actionFlag = false; + + EXPECT_EQ (undoManager->getNumTransactions(), 0); + + { + undoManager->beginNewTransaction(); + EXPECT_EQ (undoManager->getNumTransactions(), 1); + + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 1); + } + + { + undoManager->beginNewTransaction(); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + } + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + + EXPECT_FALSE (undoManager->undo()); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + + undoManager->clear(); + EXPECT_EQ (undoManager->getNumTransactions(), 0); +} + +TEST_F (UndoManagerTest, TransactionIteration) +{ + actionFlag = false; + + EXPECT_EQ (undoManager->getNumTransactions(), 0); + + { + undoManager->beginNewTransaction ("1"); + EXPECT_EQ (undoManager->getNumTransactions(), 1); + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 1); + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 1); + } + + { + undoManager->beginNewTransaction ("2"); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + undoManager->perform (new TestAction (actionFlag)); + EXPECT_EQ (undoManager->getNumTransactions(), 2); + } + + EXPECT_EQ (String ("1"), undoManager->getTransactionName (0)); + EXPECT_EQ (String ("2"), undoManager->getTransactionName (1)); +} + +TEST_F (UndoManagerTest, RedoAction) +{ + TestAction::Ptr action = new TestAction (actionFlag); + undoManager->perform (action); + EXPECT_TRUE (actionFlag); + + undoManager->undo(); + EXPECT_FALSE (actionFlag); + + EXPECT_TRUE (undoManager->redo()); + EXPECT_TRUE (actionFlag); +} + +TEST_F (UndoManagerTest, SetEnabled) +{ + undoManager->setEnabled (false); + EXPECT_FALSE (undoManager->isEnabled()); + + TestAction::Ptr action = new TestAction (actionFlag); + EXPECT_FALSE (undoManager->perform (action)); + EXPECT_FALSE (actionFlag); + + undoManager->setEnabled (true); + EXPECT_TRUE (undoManager->isEnabled()); + EXPECT_TRUE (undoManager->perform (action)); + EXPECT_TRUE (actionFlag); +} + +TEST_F (UndoManagerTest, ScopedTransaction) +{ + actionFlag = false; + + { + UndoManager::ScopedTransaction transaction (*undoManager); + + TestAction::Ptr action1 = new TestAction (actionFlag); + undoManager->perform (action1); + EXPECT_TRUE (actionFlag); + + TestAction::Ptr action2 = new TestAction (actionFlag); + undoManager->perform (action2); + EXPECT_FALSE (actionFlag); + } + + EXPECT_TRUE (undoManager->undo()); + EXPECT_FALSE (actionFlag); +} + +TEST_F (UndoManagerTest, ScopedTransactionWithName) +{ + actionFlag = false; + + EXPECT_EQ (undoManager->getCurrentTransactionName(), ""); + + { + UndoManager::ScopedTransaction transaction (*undoManager, "custom name"); + + TestAction::Ptr action1 = new TestAction (actionFlag); + undoManager->perform (action1); + EXPECT_TRUE (actionFlag); + + TestAction::Ptr action2 = new TestAction (actionFlag); + undoManager->perform (action2); + EXPECT_FALSE (actionFlag); + + EXPECT_EQ (undoManager->getCurrentTransactionName(), "custom name"); + } + + EXPECT_TRUE (undoManager->undo()); + EXPECT_FALSE (actionFlag); + + EXPECT_EQ (undoManager->getCurrentTransactionName(), ""); +} + +TEST_F (UndoManagerTest, PerformWithLambda) +{ + struct Object : ReferenceCountedObject + { + public: + using Ptr = ReferenceCountedObjectPtr; + + int counter = 0; + + private: + YUP_DECLARE_WEAK_REFERENCEABLE (Object) + }; + + auto lambdaAction = [] (Object::Ptr x, UndoableActionState s) -> bool + { + x->counter = (s == UndoableActionState::Undo) ? 1 : 2; + return true; + }; + + Object::Ptr x = new Object; + EXPECT_TRUE (undoManager->perform (x, lambdaAction)); + EXPECT_EQ (x->counter, 2); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (x->counter, 1); + + EXPECT_TRUE (undoManager->redo()); + EXPECT_EQ (x->counter, 2); +} + +TEST_F (UndoManagerTest, ComplexPerformUndoRedo) +{ + ToggleAction::Ptr action1 = new ToggleAction (counter); + ToggleAction::Ptr action2 = new ToggleAction (counter); + + undoManager->beginNewTransaction(); + EXPECT_TRUE (undoManager->perform (action1)); + EXPECT_EQ (counter, 1); + + undoManager->beginNewTransaction(); + EXPECT_TRUE (undoManager->perform (action2)); + EXPECT_EQ (counter, 2); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 1); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 0); + + EXPECT_TRUE (undoManager->redo()); + EXPECT_EQ (counter, 1); + + EXPECT_TRUE (undoManager->redo()); + EXPECT_EQ (counter, 2); +} + +TEST_F (UndoManagerTest, RedoWithoutUndo) +{ + ToggleAction::Ptr action = new ToggleAction (counter); + EXPECT_TRUE (undoManager->perform (action)); + EXPECT_EQ (counter, 1); + + EXPECT_FALSE (undoManager->redo()); + EXPECT_EQ (counter, 1); +} + +TEST_F (UndoManagerTest, UndoWithoutPerform) +{ + EXPECT_FALSE (undoManager->undo()); +} + +TEST_F (UndoManagerTest, RedoAfterDisableEnable) +{ + ToggleAction::Ptr action = new ToggleAction (counter); + EXPECT_TRUE (undoManager->perform (action)); + EXPECT_EQ (counter, 1); + + undoManager->undo(); + EXPECT_EQ (counter, 0); + + undoManager->setEnabled (false); + EXPECT_FALSE (undoManager->redo()); + EXPECT_EQ (counter, 0); + + undoManager->setEnabled (true); + EXPECT_FALSE (undoManager->redo()); + EXPECT_EQ (counter, 0); +} + +TEST_F (UndoManagerTest, MaxHistorySize) +{ + undoManager = std::make_unique (2, RelativeTime::milliseconds (0)); + + ToggleAction::Ptr action1 = new ToggleAction (counter); + ToggleAction::Ptr action2 = new ToggleAction (counter); + ToggleAction::Ptr action3 = new ToggleAction (counter); + + undoManager->beginNewTransaction(); + EXPECT_TRUE (undoManager->perform (action1)); + EXPECT_EQ (counter, 1); + + undoManager->beginNewTransaction(); + EXPECT_TRUE (undoManager->perform (action2)); + EXPECT_EQ (counter, 2); + + undoManager->beginNewTransaction(); + EXPECT_TRUE (undoManager->perform (action3)); + EXPECT_EQ (counter, 3); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 2); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 1); + + EXPECT_FALSE (undoManager->undo()); // action1 should be removed due to max history size + EXPECT_EQ (counter, 1); +} + +TEST_F (UndoManagerTest, ScopedTransactionGrouping) +{ + { + UndoManager::ScopedTransaction transaction (*undoManager); + + ToggleAction::Ptr action1 = new ToggleAction (counter); + ToggleAction::Ptr action2 = new ToggleAction (counter); + + undoManager->perform (action1); + EXPECT_EQ (counter, 1); + + undoManager->perform (action2); + EXPECT_EQ (counter, 2); + } + + EXPECT_EQ (counter, 2); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 0); +} + +TEST_F (UndoManagerTest, DISABLED_NestedScopedTransactions) +{ + { + UndoManager::ScopedTransaction transaction (*undoManager); + + ToggleAction::Ptr action1 = new ToggleAction (counter); + EXPECT_TRUE (undoManager->perform (action1)); + EXPECT_EQ (counter, 1); + + { + UndoManager::ScopedTransaction nestedTransaction (*undoManager); + + ToggleAction::Ptr action2 = new ToggleAction (counter); + EXPECT_TRUE (undoManager->perform (action2)); + EXPECT_EQ (counter, 2); + } + + ToggleAction::Ptr action3 = new ToggleAction (counter); + EXPECT_TRUE (undoManager->perform (action3)); + EXPECT_EQ (counter, 3); + } + + EXPECT_EQ (counter, 3); + + EXPECT_TRUE (undoManager->undo()); + EXPECT_EQ (counter, 0); +} diff --git a/thirdparty/rive/rive.h b/thirdparty/rive/rive.h index 498fffef0..d060c91ab 100644 --- a/thirdparty/rive/rive.h +++ b/thirdparty/rive/rive.h @@ -24,18 +24,18 @@ BEGIN_YUP_MODULE_DECLARATION - ID: rive - vendor: rive - version: 1.0 - name: Rive C++ is a runtime library for Rive. - description: Rive C++ is a runtime library for Rive, a real-time interactive design and animation tool. - website: https://github.com/rive-app/rive-runtime - license: MIT - - dependencies: harfbuzz sheenbidi yoga_library - defines: WITH_RIVE_TEXT=1 WITH_RIVE_YOGA=1 WITH_RIVE_LAYOUT=1 - appleFrameworks: CoreText - searchpaths: include + ID: rive + vendor: rive + version: 1.0 + name: Rive C++ is a runtime library for Rive. + description: Rive C++ is a runtime library for Rive, a real-time interactive design and animation tool. + website: https://github.com/rive-app/rive-runtime + license: MIT + + dependencies: harfbuzz sheenbidi yoga_library + defines: WITH_RIVE_TEXT=1 WITH_RIVE_YOGA=1 WITH_RIVE_LAYOUT=1 + appleFrameworks: CoreText + searchpaths: include END_YUP_MODULE_DECLARATION @@ -43,3 +43,14 @@ */ #pragma once + +#include "include/rive/text/utf.hpp" +#include "include/rive/artboard.hpp" +#include "include/rive/file.hpp" +#include "include/rive/static_scene.hpp" +#include "include/rive/layout.hpp" +#include "include/rive/custom_property_number.hpp" +#include "include/rive/custom_property_boolean.hpp" +#include "include/rive/custom_property_string.hpp" +#include "include/rive/animation/state_machine_instance.hpp" +#include "include/rive/animation/state_machine_input_instance.hpp" diff --git a/thirdparty/rive_renderer/rive_renderer.h b/thirdparty/rive_renderer/rive_renderer.h index fb6805749..a2985ad3a 100644 --- a/thirdparty/rive_renderer/rive_renderer.h +++ b/thirdparty/rive_renderer/rive_renderer.h @@ -31,6 +31,7 @@ description: The Rive Renderer is a vector and raster graphics renderer custom-built for Rive content, for animation, and for runtime. website: https://github.com/rive-app/rive-runtime license: MIT + minimumCppStandard: 17 dependencies: rive rive_decoders glad searchpaths: include source source/generated/shaders @@ -122,28 +123,33 @@ #endif #endif +//============================================================================== + #if __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wattributes" #elif _MSC_VER __pragma (warning (push)) __pragma (warning (disable: 4244)) #endif -//============================================================================== // Public API #include #include #include #include -//============================================================================== // Internals #include #include #if __GNUC__ #pragma GCC diagnostic pop +#elif __clang__ + #pragma clang diagnostic pop #elif _MSC_VER __pragma (warning (pop)) #endif