mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
DynamicObject: Add virtual functions that are called to indicate a property change
This commit is contained in:
parent
14f663bc0e
commit
67396435e5
2 changed files with 144 additions and 17 deletions
|
|
@ -35,19 +35,13 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
DynamicObject::DynamicObject()
|
||||
{
|
||||
}
|
||||
DynamicObject::DynamicObject() = default;
|
||||
|
||||
DynamicObject::DynamicObject (const DynamicObject& other)
|
||||
: ReferenceCountedObject(), properties (other.properties)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicObject::~DynamicObject()
|
||||
{
|
||||
}
|
||||
|
||||
bool DynamicObject::hasProperty (const Identifier& propertyName) const
|
||||
{
|
||||
const var* const v = properties.getVarPointer (propertyName);
|
||||
|
|
@ -62,11 +56,13 @@ const var& DynamicObject::getProperty (const Identifier& propertyName) const
|
|||
void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue)
|
||||
{
|
||||
properties.set (propertyName, newValue);
|
||||
didModifyProperty (propertyName, newValue);
|
||||
}
|
||||
|
||||
void DynamicObject::removeProperty (const Identifier& propertyName)
|
||||
{
|
||||
properties.remove (propertyName);
|
||||
didModifyProperty (propertyName, std::nullopt);
|
||||
}
|
||||
|
||||
bool DynamicObject::hasMethod (const Identifier& methodName) const
|
||||
|
|
@ -84,12 +80,16 @@ var DynamicObject::invokeMethod (Identifier method, const var::NativeFunctionArg
|
|||
|
||||
void DynamicObject::setMethod (Identifier name, var::NativeFunction function)
|
||||
{
|
||||
properties.set (name, var (function));
|
||||
setProperty (name, var (function));
|
||||
}
|
||||
|
||||
void DynamicObject::clear()
|
||||
{
|
||||
auto copy = properties;
|
||||
properties.clear();
|
||||
|
||||
for (auto& prop : copy)
|
||||
didModifyProperty (prop.name, std::nullopt);
|
||||
}
|
||||
|
||||
void DynamicObject::cloneAllProperties()
|
||||
|
|
@ -151,4 +151,128 @@ void DynamicObject::writeAsJSON (OutputStream& out, const JSON::FormatOptions& f
|
|||
out << '}';
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class DynamicObjectTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
DynamicObjectTests() : UnitTest { "DynamicObject", UnitTestCategories::containers } {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
struct Action
|
||||
{
|
||||
Identifier key;
|
||||
std::optional<var> value;
|
||||
|
||||
bool operator== (const Action& other) const
|
||||
{
|
||||
return other.key == key && other.value == value;
|
||||
}
|
||||
};
|
||||
|
||||
using Actions = std::vector<Action>;
|
||||
|
||||
struct DerivedObject : public DynamicObject
|
||||
{
|
||||
explicit DerivedObject (Actions& a) : actions (a) {}
|
||||
|
||||
void didModifyProperty (const Identifier& key, const std::optional<var>& value) override
|
||||
{
|
||||
actions.push_back (Action { key, value });
|
||||
}
|
||||
|
||||
Actions& actions;
|
||||
};
|
||||
|
||||
Actions actions;
|
||||
DerivedObject object { actions };
|
||||
|
||||
beginTest ("didModifyProperty is emitted on setProperty");
|
||||
{
|
||||
expect (object.getProperties().isEmpty());
|
||||
|
||||
const Identifier key = "foo";
|
||||
const var value = 123;
|
||||
object.setProperty (key, value);
|
||||
|
||||
expect (actions == Actions { Action { key, value } });
|
||||
expect (object.getProperties() == NamedValueSet { { key, value } });
|
||||
}
|
||||
|
||||
object.clear();
|
||||
actions.clear();
|
||||
|
||||
beginTest ("didModifyProperty is emitted on setMethod");
|
||||
{
|
||||
expect (object.getProperties().isEmpty());
|
||||
|
||||
const Identifier key = "foo";
|
||||
const var::NativeFunction value = [] (const var::NativeFunctionArgs&) { return var{}; };
|
||||
object.setMethod (key, value);
|
||||
|
||||
expect (actions.size() == 1);
|
||||
expect (actions.back().key == key);
|
||||
|
||||
expect (object.getProperties().size() == 1);
|
||||
expect (object.hasMethod (key));
|
||||
}
|
||||
|
||||
object.clear();
|
||||
actions.clear();
|
||||
|
||||
beginTest ("didModifyProperty is emitted on removeProperty");
|
||||
{
|
||||
expect (object.getProperties().isEmpty());
|
||||
|
||||
const Identifier key = "bar";
|
||||
object.removeProperty (key);
|
||||
|
||||
expect (actions == Actions { Action { key, std::nullopt } });
|
||||
expect (object.getProperties().isEmpty());
|
||||
}
|
||||
|
||||
object.clear();
|
||||
actions.clear();
|
||||
|
||||
beginTest ("didModifyProperty is emitted on clear");
|
||||
{
|
||||
expect (object.getProperties().isEmpty());
|
||||
|
||||
object.clear();
|
||||
|
||||
expect (actions.empty());
|
||||
|
||||
const Identifier keys[] { "foo", "bar", "baz" };
|
||||
|
||||
for (auto [index, key] : enumerate (keys, int{}))
|
||||
object.setProperty (key, index);
|
||||
|
||||
for (auto& key : keys)
|
||||
expect (object.hasProperty (key));
|
||||
|
||||
actions.clear();
|
||||
|
||||
object.clear();
|
||||
|
||||
expect (actions.size() == std::size (keys));
|
||||
|
||||
for (auto& key : keys)
|
||||
{
|
||||
expect (std::find_if (actions.begin(), actions.end(), [&key] (auto& action)
|
||||
{
|
||||
return action.key == key && action.value == std::nullopt;
|
||||
}) != actions.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static DynamicObjectTests dynamicObjectTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ public:
|
|||
//==============================================================================
|
||||
DynamicObject();
|
||||
DynamicObject (const DynamicObject&);
|
||||
~DynamicObject() override;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<DynamicObject>;
|
||||
|
||||
|
|
@ -76,11 +75,8 @@ public:
|
|||
void removeProperty (const Identifier& propertyName);
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether this object has the specified method.
|
||||
|
||||
The default implementation of this just checks whether there's a property
|
||||
with this name that's actually a method, but this can be overridden for
|
||||
building objects with dynamic invocation.
|
||||
/** Checks whether this object has a property with the given name that has a
|
||||
value of type NativeFunction.
|
||||
*/
|
||||
bool hasMethod (const Identifier& methodName) const;
|
||||
|
||||
|
|
@ -88,9 +84,6 @@ public:
|
|||
|
||||
The default implementation looks up the named property, and if it's a method
|
||||
call, then it invokes it.
|
||||
|
||||
This method is virtual to allow more dynamic invocation to used for objects
|
||||
where the methods may not already be set as properties.
|
||||
*/
|
||||
var invokeMethod (Identifier methodName,
|
||||
const var::NativeFunctionArgs& args);
|
||||
|
|
@ -133,6 +126,16 @@ public:
|
|||
virtual void writeAsJSON (OutputStream&, const JSON::FormatOptions&);
|
||||
|
||||
private:
|
||||
/** Derived classes may override this function to take additional actions after
|
||||
properties are assigned or removed.
|
||||
|
||||
@param name the name of the property that changed
|
||||
@param value if non-null, the value of the property after assignment
|
||||
if null, indicates that the property was removed
|
||||
*/
|
||||
virtual void didModifyProperty ([[maybe_unused]] const Identifier& name,
|
||||
[[maybe_unused]] const std::optional<var>& value) {}
|
||||
|
||||
//==============================================================================
|
||||
NamedValueSet properties;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue