From 79f602636f34e886a6c11ae5349f822952a12fa4 Mon Sep 17 00:00:00 2001 From: James J Date: Wed, 7 Feb 2024 20:13:49 +0000 Subject: [PATCH 1/6] Add variant converter for tuples --- .../jive_MiscVariantConverters.h | 15 +++++++++ .../jive_VariantConvertion.cpp | 30 +++++++++++++++++ .../jive_VariantConvertion.h | 33 +++++++++++++++---- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/jive_core/values/variant-converters/jive_MiscVariantConverters.h b/jive_core/values/variant-converters/jive_MiscVariantConverters.h index 1ab360eb..f89e9f07 100644 --- a/jive_core/values/variant-converters/jive_MiscVariantConverters.h +++ b/jive_core/values/variant-converters/jive_MiscVariantConverters.h @@ -192,4 +192,19 @@ namespace juce static Rectangle fromVar(const var& v); static var toVar(const Rectangle& rectangle); }; + + template + class VariantConverter + { + public: + static juce::String fromVar(const var& v) + { + return v.toString(); + } + + static var toVar(const char cString[N]) + { + return var{ String{ cString } }; + } + }; } // namespace juce diff --git a/jive_core/values/variant-converters/jive_VariantConvertion.cpp b/jive_core/values/variant-converters/jive_VariantConvertion.cpp index 2f495eea..d6cdfb72 100644 --- a/jive_core/values/variant-converters/jive_VariantConvertion.cpp +++ b/jive_core/values/variant-converters/jive_VariantConvertion.cpp @@ -14,6 +14,7 @@ class VarConversionUnitTest : public juce::UnitTest testToVar(); testFromVar(); testOptionalParsing(); + testMultiValueParsing(); } private: @@ -41,5 +42,34 @@ class VarConversionUnitTest : public juce::UnitTest expectEquals(jive::toVar(std::optional{}), juce::var{}); expectEquals(jive::toVar(std::make_optional(123)), juce::var{ 123 }); } + + void testMultiValueParsing() + { + beginTest("multiple values - toVar"); + { + expectEquals(jive::toVar(1, "foo", 66.6), + juce::var{ + juce::Array{ + juce::var{ 1 }, + juce::var{ "foo" }, + juce::var{ 66.6 }, + }, + }); + } + + beginTest("multiple values - fromVar"); + { + const auto [actualString, actualInt] = jive::fromVar(juce::var{ + juce::Array{ + juce::var{ "bar" }, + juce::var{ 7531 }, + }, + }); + const juce::String expectedString = "bar"; + const auto expectedInt = 7531; + expectEquals(actualString, expectedString); + expectEquals(actualInt, expectedInt); + } + } }; #endif diff --git a/jive_core/values/variant-converters/jive_VariantConvertion.h b/jive_core/values/variant-converters/jive_VariantConvertion.h index d03c931c..7e43fae5 100644 --- a/jive_core/values/variant-converters/jive_VariantConvertion.h +++ b/jive_core/values/variant-converters/jive_VariantConvertion.h @@ -2,14 +2,22 @@ namespace jive { - template - [[nodiscard]] auto toVar(const T& value) + template + [[nodiscard]] auto toVar(const Value& value) { - return juce::VariantConverter::toVar(value); + return juce::VariantConverter::toVar(value); } - template - [[nodiscard]] auto toVar(const std::optional& value) + template + [[nodiscard]] auto toVar(const Values&... values) + { + return juce::var{ + juce::Array{ toVar(values)... }, + }; + } + + template + [[nodiscard]] auto toVar(const std::optional& value) { if (value.has_value()) return toVar(*value); @@ -17,9 +25,20 @@ namespace jive return juce::var{}; } - template + template + [[nodiscard]] auto fromVar(const juce::var& value) + { + return juce::VariantConverter::fromVar(value); + } + + template [[nodiscard]] auto fromVar(const juce::var& value) { - return juce::VariantConverter::fromVar(value); + auto next = [i = 0](const juce::Array& values) mutable { + return values.getReference(i++); + }; + return std::make_tuple(fromVar(next(*value.getArray())), + fromVar(next(*value.getArray())), + fromVar(next(*value.getArray()))...); } } // namespace jive From decd89d1eba4af6f3e99022641bd3752066e0c3e Mon Sep 17 00:00:00 2001 From: James J Date: Sat, 2 Dec 2023 01:31:41 +0000 Subject: [PATCH 2/6] Add `jive::Visitor` --- jive_core/algorithms/jive_Visitor.h | 13 +++++++++++++ jive_core/jive_core.h | 1 + 2 files changed, 14 insertions(+) create mode 100644 jive_core/algorithms/jive_Visitor.h diff --git a/jive_core/algorithms/jive_Visitor.h b/jive_core/algorithms/jive_Visitor.h new file mode 100644 index 00000000..f0188e71 --- /dev/null +++ b/jive_core/algorithms/jive_Visitor.h @@ -0,0 +1,13 @@ +#pragma once + +namespace jive +{ + template + struct Visitor : Variants... + { + using Variants::operator()...; + }; + + template + Visitor(Variants...) -> Visitor; +} // namespace jive diff --git a/jive_core/jive_core.h b/jive_core/jive_core.h index 874789c7..880cc720 100644 --- a/jive_core/jive_core.h +++ b/jive_core/jive_core.h @@ -17,6 +17,7 @@ END_JUCE_MODULE_DECLARATION */ #include "logging/jive_StringStreams.h" #include "algorithms/jive_Find.h" +#include "algorithms/jive_Visitor.h" #include "values/jive_Colours.h" #include "values/jive_Event.h" From 6acf64786083b82c0e8b5aedff5b35a3bd0f7853 Mon Sep 17 00:00:00 2001 From: James J Date: Tue, 12 Dec 2023 22:33:20 +0000 Subject: [PATCH 3/6] Add support for `Object` sources in `jive::Property` --- jive_core/geometry/jive_Length.cpp | 12 +- jive_core/values/jive_Object.cpp | 29 +++ jive_core/values/jive_Object.h | 6 + jive_core/values/jive_Property.cpp | 115 +++++++++ jive_core/values/jive_Property.h | 363 ++++++++++++++++++++++++----- 5 files changed, 456 insertions(+), 69 deletions(-) diff --git a/jive_core/geometry/jive_Length.cpp b/jive_core/geometry/jive_Length.cpp index af17ba05..90ddc926 100644 --- a/jive_core/geometry/jive_Length.cpp +++ b/jive_core/geometry/jive_Length.cpp @@ -42,7 +42,7 @@ namespace jive [[nodiscard]] double Length::getRelativeParentLength(const juce::Rectangle& parentBounds) const { - jassert(tree.getParent().isValid()); + jassert(isValid(getParent(source))); if (id.toString().containsIgnoreCase("width") || id.toString().containsIgnoreCase("x")) return parentBounds.getWidth(); @@ -52,11 +52,11 @@ namespace jive [[nodiscard]] float Length::getFontSize() const { - for (auto toSearch = tree; - toSearch.isValid(); - toSearch = toSearch.getParent()) + for (auto toSearch = source; + isValid(source); + toSearch = getParent(source)) { - if (const auto style = toSearch["style"]; + if (const auto style = getVar(toSearch, "style"); style.isObject()) { if (const auto fontSize = style["font-size"]; @@ -72,7 +72,7 @@ namespace jive [[nodiscard]] float Length::getRootFontSize() const { - if (const auto style = tree.getRoot()["style"]; + if (const auto style = getVar(getRoot(source), "style"); style.isObject()) { if (const auto fontSize = style["font-size"]; diff --git a/jive_core/values/jive_Object.cpp b/jive_core/values/jive_Object.cpp index e1d4f668..f5f64112 100644 --- a/jive_core/values/jive_Object.cpp +++ b/jive_core/values/jive_Object.cpp @@ -82,6 +82,9 @@ namespace jive void Object::setProperty(const juce::Identifier& propertyName, const juce::var& newValue) { + if (auto* childObject = dynamic_cast(newValue.getDynamicObject())) + childObject->parent = this; + const auto propertyChanged = DynamicObject::getProperties() .set(propertyName, newValue); @@ -94,6 +97,32 @@ namespace jive return dynamic_cast(const_cast(this))->getProperties(); } + Object* Object::getParent() noexcept + { + return parent; + } + + const Object* Object::getParent() const noexcept + { + return parent; + } + + Object* Object::getRoot() noexcept + { + if (parent == nullptr) + return this; + + return parent->getRoot(); + } + + const Object* Object::getRoot() const noexcept + { + if (parent == nullptr) + return this; + + return parent->getRoot(); + } + void Object::addListener(Listener& listener) const { listeners.add(&listener); diff --git a/jive_core/values/jive_Object.h b/jive_core/values/jive_Object.h index 21a64155..e5e34968 100644 --- a/jive_core/values/jive_Object.h +++ b/jive_core/values/jive_Object.h @@ -25,6 +25,11 @@ namespace jive const juce::var& newValue) override; const juce::NamedValueSet& getProperties() const; + Object* getParent() noexcept; + const Object* getParent() const noexcept; + Object* getRoot() noexcept; + const Object* getRoot() const noexcept; + void addListener(Listener& listener) const; void removeListener(Listener& listener) const; @@ -35,6 +40,7 @@ namespace jive mutable juce::ListenerList listeners; const std::unique_ptr internalListener; + Object* parent = nullptr; JUCE_LEAK_DETECTOR(Object) }; diff --git a/jive_core/values/jive_Property.cpp b/jive_core/values/jive_Property.cpp index 758cca6d..4c01e77a 100644 --- a/jive_core/values/jive_Property.cpp +++ b/jive_core/values/jive_Property.cpp @@ -17,6 +17,7 @@ class PropertyUnitTest : public juce::UnitTest testHereditaryValues(); testObservations(); testFunctionalProperties(); + testDynamicObjectSource(); } private: @@ -261,6 +262,120 @@ class PropertyUnitTest : public juce::UnitTest expect(value.isFunctional()); expectEquals(value.get(), 300); } + + void testDynamicObjectSource() + { + juce::ValueTree state{ + "State", + { + { + "object", + new jive::Object{ + { "value", 12345 }, + { + "inner", + new jive::Object{ + { "leaf", new jive::Object{} }, + }, + }, + }, + }, + }, + }; + + beginTest("DynamicObject source - standard template args"); + { + jive::Property value{ + dynamic_cast(state["object"].getObject()), + "value", + }; + expectEquals(value.get(), 12345); + + value.set(98765); + expectEquals(value.get(), 98765); + + expect(!value.isAuto()); + value.setAuto(); + expect(value.isAuto()); + + expect(value.exists()); + value.clear(); + expect(!value.exists()); + + expect(!value.isFunctional()); + value = []() { + return 999; + }; + expect(value.isFunctional()); + expectEquals(value.get(), 999); + + value = 7648; + expectEquals(value.toString(), "7648"); + + bool receivedCallback = false; + value.onValueChange = [&receivedCallback]() { + receivedCallback = true; + }; + state["object"].getDynamicObject()->setProperty("foo", 111); + expect(!receivedCallback); + state["object"].getDynamicObject()->setProperty("value", 11358); + expect(receivedCallback); + } + + beginTest("DynamicObject source - inheritFromParent"); + { + jive::Property value{ + dynamic_cast(state["object"]["inner"].getObject()), + "value", + }; + expect(!value.exists()); + expectEquals(value.get(), static_cast(state["object"]["value"])); + + value = 24816; + expectEquals(value.get(), 24816); + + value.clear(); + expectEquals(value.get(), static_cast(state["object"]["value"])); + + bool receivedCallback = false; + value.onValueChange = [&receivedCallback]() { + receivedCallback = true; + }; + state["object"]["inner"].getDynamicObject()->setProperty("value", 11358); + expect(receivedCallback); + receivedCallback = false; + state["object"].getDynamicObject()->setProperty("value", 34544); + expect(receivedCallback); + } + + beginTest("DynamicObject source - inheritFromAncestors"); + { + state["object"]["inner"].getDynamicObject()->removeProperty("value"); + jive::Property value{ + dynamic_cast(state["object"]["inner"]["leaf"].getObject()), + "value", + }; + expect(!value.exists()); + expectEquals(value.get(), static_cast(state["object"]["value"])); + } + + beginTest("DynamicObject source - accumulate"); + { + state["object"].getDynamicObject()->setProperty("value", 123); + state["object"]["inner"].getDynamicObject()->setProperty("value", 456); + state["object"]["inner"]["leaf"].getDynamicObject()->setProperty("value", 789); + + jive::Property value{ + dynamic_cast(state["object"].getObject()), + "value", + }; + expect(value.exists()); + expectEquals(value.get(), + static_cast(state["object"]["value"]) + + static_cast(state["object"]["inner"]["value"]) + + static_cast(state["object"]["inner"]["leaf"]["value"])); + } + } }; static PropertyUnitTest propertyUnitTest; diff --git a/jive_core/values/jive_Property.h b/jive_core/values/jive_Property.h index a47b5c9d..3a23bc1f 100644 --- a/jive_core/values/jive_Property.h +++ b/jive_core/values/jive_Property.h @@ -20,37 +20,33 @@ namespace jive template - class Property : protected juce::ValueTree::Listener + class Property + : protected juce::ValueTree::Listener + , protected Object::Listener { public: - using Converter = juce::VariantConverter; + using VariantConverter = juce::VariantConverter; + using Source = std::variant; - Property(juce::ValueTree sourceTree, + Property(Source propertySource, const juce::Identifier& propertyID) : id{ propertyID } - , tree{ sourceTree } + , source{ propertySource } { - switch (inheritance) - { - case Inheritance::inheritFromParent: - treeToListenTo = tree.getParent(); - break; - case Inheritance::inheritFromAncestors: - treeToListenTo = tree.getRoot(); - break; - case Inheritance::doNotInherit: - treeToListenTo = tree; - } + listenerTarget = findListenerTarget(propertySource); - if (!treeToListenTo.isValid()) - treeToListenTo = tree; + if (!isValid(listenerTarget)) + listenerTarget = source; - treeToListenTo.addListener(this); + addThisAsListener(listenerTarget); if constexpr (std::is_same()) { - if (tree[id].isString()) - tree.setProperty(id, parseJSON(tree[id].toString()), nullptr); + if (auto value = getVar(source, id); value.isString()) + { + if (auto json = parseJSON(value.toString()); json != juce::var{}) + set(source, json); + } } } @@ -58,7 +54,12 @@ namespace jive Property(Property&& other) = delete; Property& operator=(const Property& other) = delete; Property& operator=(Property&& other) = delete; - ~Property() override = default; + + ~Property() override + { + removeThisAsListener(source); + removeThisAsListener(listenerTarget); + } [[nodiscard]] virtual ValueType get() const { @@ -68,17 +69,17 @@ namespace jive [[nodiscard]] auto getOr(const ValueType& valueIfNoneSpecified) const { if (auto root = getRootOfInheritance(); - root.isValid()) + isValid(root)) { return getFrom(root); } if constexpr (accumulation == Accumulation::accumulate) { - if (auto source = getFirstAncestorWithProperty(tree); - source.isValid()) + if (auto src = getFirstDescendantWithProperty(source); + src.isValid()) { - return getFrom(tree); + return getFrom(src); } } @@ -87,40 +88,60 @@ namespace jive void set(const ValueType& newValue) { - tree.setProperty(id, Converter::toVar(newValue), nullptr); + set(source, VariantConverter::toVar(newValue)); } void set(std::function function) { - const auto nativeFunction = [function](const auto&) { - return Converter::toVar(function()); - }; - tree.setProperty(id, juce::var{ nativeFunction }, nullptr); + set(source, juce::var{ + [function](const auto&) { + return VariantConverter::toVar(function()); + }, + }); } void setAuto() { - tree.setProperty(id, "auto", nullptr); + set(source, "auto"); } void clear() { - tree.removeProperty(id, nullptr); + std::visit(Visitor{ + [this](juce::ValueTree& sourceTree) { + sourceTree.removeProperty(id, nullptr); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + sourceObject->removeProperty(id); + }, + }, + source); } [[nodiscard]] auto exists() const { - return tree.hasProperty(id); + return !getVar(source, id).isVoid(); } [[nodiscard]] auto isAuto() const { - return (!exists()) || tree[id].toString().trim().equalsIgnoreCase("auto"); + return (!exists()) || toString().trim().equalsIgnoreCase("auto"); } [[nodiscard]] auto isFunctional() const { - return exists() && tree[id].isMethod(); + if (!exists()) + return false; + + return std::visit(Visitor{ + [this](const juce::ValueTree& sourceTree) { + return sourceTree[id].isMethod(); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + return sourceObject->getProperty(id).isMethod(); + }, + }, + source); } [[nodiscard]] auto toString() const @@ -128,7 +149,15 @@ namespace jive if (!exists()) return juce::String{}; - return tree[id].toString(); + return std::visit(Visitor{ + [this](const juce::ValueTree& sourceTree) { + return sourceTree[id].toString(); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + return sourceObject->getProperty(id).toString(); + }, + }, + source); } [[nodiscard]] operator ValueType() const @@ -172,17 +201,33 @@ namespace jive onValueChange(); } + void propertyChanged(Object& objectWhosePropertyChanged, + const juce::Identifier& property) override + { + if (property != id) + return; + if (!objectWhosePropertyChanged.hasProperty(property)) + return; + + if (onValueChange != nullptr) + onValueChange(); + } + + Source source; + Source listenerTarget; + + // private: [[nodiscard]] auto respondToPropertyChanges(juce::ValueTree& treeWhosePropertyChanged) const { - if (treeWhosePropertyChanged == tree) + if (treeWhosePropertyChanged == std::get(source)) return true; switch (inheritance) { case Inheritance::inheritFromParent: - return treeWhosePropertyChanged == tree.getParent(); + return treeWhosePropertyChanged == std::get(source).getParent(); case Inheritance::inheritFromAncestors: - return tree.isAChildOf(treeWhosePropertyChanged); + return std::get(source).isAChildOf(treeWhosePropertyChanged); case Inheritance::doNotInherit: break; } @@ -190,7 +235,7 @@ namespace jive switch (accumulation) { case Accumulation::accumulate: - return treeWhosePropertyChanged.isAChildOf(tree); + return treeWhosePropertyChanged.isAChildOf(std::get(source)); case Accumulation::doNotAccumulate: break; } @@ -198,67 +243,75 @@ namespace jive return false; } + [[nodiscard]] auto respondToPropertyChanges(Object& objectWhosePropertyChanged) const + { + return &objectWhosePropertyChanged == static_cast(std::get(source)); + } + [[nodiscard]] auto getRootOfInheritance() const { if (exists() || accumulation == Accumulation::accumulate) - return tree; + return source; if constexpr (inheritance == Inheritance::inheritFromParent) { - if (auto parent = tree.getParent(); - parent.hasProperty(id)) + if (auto parent = getParent(source); + !getVar(parent, id).isVoid()) { return parent; } } if constexpr (inheritance == Inheritance::inheritFromAncestors) { - for (auto ancestor = tree.getParent(); - ancestor.isValid(); - ancestor = ancestor.getParent()) + for (auto ancestor = getParent(source); + isValid(ancestor); + ancestor = getParent(ancestor)) { - if (ancestor.hasProperty(id)) + if (!getVar(ancestor, id).isVoid()) return ancestor; } } - return juce::ValueTree{}; + return Source{}; } - [[nodiscard]] auto getFirstAncestorWithProperty(const juce::ValueTree& root) const + [[nodiscard]] auto getFirstDescendantWithProperty(const Source& root) const { - for (const auto& child : root) + for (auto i = 0; i < getNumChildren(root); i++) { - if (child.hasProperty(id)) + if (auto child = getChild(root, i); !getVar(child, id).isVoid()) return child; } - for (const auto& child : root) + for (auto i = 0; i < getNumChildren(root); i++) { - if (auto ancestor = getFirstAncestorWithProperty(child); - ancestor.isValid()) + if (auto descendant = getFirstDescendantWithProperty(getChild(root, i)); + isValid(descendant)) { - return ancestor; + return descendant; } } - return juce::ValueTree{}; + return Source{}; } [[nodiscard]] ValueType getFrom(const juce::ValueTree& root) const { + if (!isValid(root)) + return VariantConverter::fromVar(juce::var{}); + if constexpr (accumulation == Accumulation::accumulate) { - auto result = Converter::fromVar(root[id]); + auto result = VariantConverter::fromVar(getVar(root, id)); - for (const auto& child : root) - result += getFrom(child); + for (auto i = 0; i < getNumChildren(root); i++) + result += getFrom(getChild(root, i)); return result; } else { - auto var = root.hasProperty(id) ? root[id] : getFirstAncestorWithProperty(root).getProperty(id, juce::var{}); + auto var = root.hasProperty(id) ? root[id] : getVar(getFirstDescendantWithProperty(root), id); if (var.isMethod()) { @@ -266,11 +319,195 @@ namespace jive var = var.getNativeFunction()(args); } - return Converter::fromVar(var); + return VariantConverter::fromVar(var); } } - juce::ValueTree treeToListenTo; - juce::ValueTree tree; + [[nodiscard]] ValueType getFrom(Object* root) const + { + if (!isValid(root)) + return VariantConverter::fromVar(juce::var{}); + + if constexpr (accumulation == Accumulation::accumulate) + { + auto result = VariantConverter::fromVar(getVar(root, id)); + + for (auto i = 0; i < getNumChildren(root); i++) + result += getFrom(getChild(root, i)); + + return result; + } + else + { + if (root->hasMethod(id)) + { + juce::var::NativeFunctionArgs args{ juce::var{}, nullptr, 0 }; + return VariantConverter::fromVar(root->invokeMethod(id, args)); + } + + return VariantConverter::fromVar(root->getProperty(id)); + } + } + + [[nodiscard]] auto getFrom(const Source& root) const + { + return std::visit(Visitor{ + [this](const juce::ValueTree& sourceTree) { + return getFrom(sourceTree); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + return getFrom(sourceObject.get()); + }, + }, + root); + } + + [[nodiscard]] auto isValid(const Source& src) const + { + return std::visit(Visitor{ + [](const juce::ValueTree& sourceTree) { + return sourceTree.isValid(); + }, + [](Object::ReferenceCountedPointer sourceObject) { + return sourceObject != nullptr; + }, + }, + src); + } + + [[nodiscard]] auto getVar(const Source& src, const juce::Identifier& name) const + { + return std::visit(Visitor{ + [name](const juce::ValueTree& sourceTree) { + return sourceTree[name]; + }, + [name](Object::ReferenceCountedPointer sourceObject) { + return sourceObject->getProperty(name); + }, + }, + src); + } + + [[nodiscard]] auto getParent(const Source& src) const + { + return std::visit(Visitor{ + [](const juce::ValueTree& sourceTree) -> Source { + return sourceTree.getParent(); + }, + [](Object::ReferenceCountedPointer sourceObject) -> Source { + return sourceObject->getParent(); + }, + }, + src); + } + + [[nodiscard]] auto getNumChildren(const Source& src) const + { + return std::visit(Visitor{ + [](const juce::ValueTree& sourceTree) { + return sourceTree.getNumChildren(); + }, + [](Object::ReferenceCountedPointer sourceObject) { + return static_cast(std::count_if(std::begin(sourceObject->getProperties()), + std::end(sourceObject->getProperties()), + [](const auto& property) { + return dynamic_cast(property.value.getDynamicObject()) != nullptr; + })); + }, + }, + src); + } + + [[nodiscard]] auto getChild(const Source& src, int index) const + { + return std::visit(Visitor{ + [index](const juce::ValueTree& sourceTree) -> Source { + return sourceTree.getChild(index); + }, + [index](Object::ReferenceCountedPointer sourceObject) -> Source { + auto current = 0; + + for (const auto& [_, value] : sourceObject->getProperties()) + { + if (auto* child = dynamic_cast(value.getDynamicObject())) + { + if (current++ == index) + return child; + } + } + + return nullptr; + }, + }, + src); + } + + [[nodiscard]] auto findListenerTarget(const Source& src) const + { + switch (inheritance) + { + case Inheritance::inheritFromParent: + return getParent(src); + case Inheritance::inheritFromAncestors: + return getRoot(src); + case Inheritance::doNotInherit: + return src; + } + + jassertfalse; + return src; + } + + [[nodiscard]] auto getRoot(const Source& src) const + { + return std::visit(Visitor{ + [](const juce::ValueTree& sourceTree) -> Source { + return sourceTree.getRoot(); + }, + [](Object::ReferenceCountedPointer sourceObject) -> Source { + return sourceObject->getRoot(); + }, + }, + src); + } + + void addThisAsListener(Source& src) + { + std::visit(Visitor{ + [this](juce::ValueTree& sourceTree) { + sourceTree.addListener(this); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + sourceObject->addListener(*this); + }, + }, + src); + } + + void removeThisAsListener(Source& src) + { + std::visit(Visitor{ + [this](juce::ValueTree& sourceTree) { + sourceTree.removeListener(this); + }, + [this](Object::ReferenceCountedPointer sourceObject) { + sourceObject->removeListener(*this); + }, + }, + src); + } + + void set(Source& src, const juce::var& value) + { + std::visit(Visitor{ + [this, &value](juce::ValueTree& sourceTree) { + sourceTree.setProperty(id, value, nullptr); + }, + [this, &value](Object::ReferenceCountedPointer sourceObject) { + sourceObject->setProperty(id, value); + }, + }, + src); + } }; } // namespace jive From 4a39ee4bc8418cd469f0b6974a0d333f13e5bc73 Mon Sep 17 00:00:00 2001 From: James J Date: Wed, 13 Dec 2023 23:11:00 +0000 Subject: [PATCH 4/6] Add `jive::Timer` --- jive_core/jive_core.cpp | 2 + jive_core/jive_core.h | 2 + jive_core/logging/jive_StringStreams.cpp | 5 + jive_core/logging/jive_StringStreams.h | 1 + jive_core/time/jive_Timer.cpp | 121 +++++++++++++++++++++++ jive_core/time/jive_Timer.h | 68 +++++++++++++ 6 files changed, 199 insertions(+) create mode 100644 jive_core/time/jive_Timer.cpp create mode 100644 jive_core/time/jive_Timer.h diff --git a/jive_core/jive_core.cpp b/jive_core/jive_core.cpp index a546cc2c..c82dbaea 100644 --- a/jive_core/jive_core.cpp +++ b/jive_core/jive_core.cpp @@ -27,3 +27,5 @@ #include "graphics/jive_LookAndFeel.cpp" #include "interface/jive_ComponentInteractionState.cpp" + +#include "time/jive_Timer.cpp" diff --git a/jive_core/jive_core.h b/jive_core/jive_core.h index 880cc720..94532258 100644 --- a/jive_core/jive_core.h +++ b/jive_core/jive_core.h @@ -43,3 +43,5 @@ END_JUCE_MODULE_DECLARATION */ #include "graphics/jive_Fill.h" #include "interface/jive_ComponentInteractionState.h" + +#include "time/jive_Timer.h" diff --git a/jive_core/logging/jive_StringStreams.cpp b/jive_core/logging/jive_StringStreams.cpp index b1d7a5e7..eed41417 100644 --- a/jive_core/logging/jive_StringStreams.cpp +++ b/jive_core/logging/jive_StringStreams.cpp @@ -41,4 +41,9 @@ namespace juce { return str << juce::String{ static_cast(value) }; } + + String& operator<<(String& str, RelativeTime relativeTime) + { + return str << juce::String{ relativeTime.inSeconds() } << "s"; + } } // namespace juce diff --git a/jive_core/logging/jive_StringStreams.h b/jive_core/logging/jive_StringStreams.h index 2483ab20..120e755e 100644 --- a/jive_core/logging/jive_StringStreams.h +++ b/jive_core/logging/jive_StringStreams.h @@ -8,6 +8,7 @@ namespace juce String& operator<<(String& str, const var& value); String& operator<<(String& str, const Time& time); String& operator<<(String& str, long double value); + String& operator<<(String& str, RelativeTime relativeTime); template String& operator<<(String& str, const Point& point) diff --git a/jive_core/time/jive_Timer.cpp b/jive_core/time/jive_Timer.cpp new file mode 100644 index 00000000..0f28a604 --- /dev/null +++ b/jive_core/time/jive_Timer.cpp @@ -0,0 +1,121 @@ +#include "jive_Timer.h" + +namespace jive +{ +#if JIVE_UNIT_TESTS + FakeTime::~FakeTime() + { + clearSingletonInstance(); + } + + void FakeTime::addListener(Listener& listener) + { + listeners.add(&listener); + } + + void FakeTime::removeListener(Listener& listener) + { + listeners.remove(&listener); + } + + void FakeTime::incrementTime(juce::RelativeTime increment) + { + getInstance()->incrementTimeInternal(increment); + } + + juce::Time FakeTime::now() noexcept + { + return getInstance()->currentTime; + } + + void FakeTime::incrementTimeInternal(juce::RelativeTime increment) + { + currentTime += increment; + listeners.call(&Listener::timeChanged); + } + + JUCE_IMPLEMENT_SINGLETON(FakeTime) +#endif + + Timer::Timer(Timer::Callback timerCallback, + juce::RelativeTime callbackInterval) + : callback{ timerCallback } + , interval{ callbackInterval } + { +#if JIVE_UNIT_TESTS + timeLastCallbackInvoked = FakeTime::now(); + FakeTime::getInstance()->addListener(*this); +#else + timeLastCallbackInvoked = juce::Time::getCurrentTime(); + startTimer(static_cast(interval.inMilliseconds())); +#endif + } + + Timer::~Timer() + { +#if JIVE_UNIT_TESTS + FakeTime::getInstance()->removeListener(*this); +#endif + } + +#if JIVE_UNIT_TESTS + void Timer::timeChanged() + { + for (auto elapsed = FakeTime::now() - timeLastCallbackInvoked; + elapsed.inMilliseconds() > interval.inMilliseconds(); + elapsed -= interval) + { + timeLastCallbackInvoked = FakeTime::now() - elapsed + interval; + callback(timeLastCallbackInvoked); + } + } +#else + void Timer::timerCallback() + { + callback(juce::Time::getCurrentTime()); + } +#endif +} // namespace jive + +#if JIVE_UNIT_TESTS +class TimerUnitTest : public juce::UnitTest +{ +public: + TimerUnitTest() + : juce::UnitTest{ "jive::Timer", "jive" } + { + } + + void runTest() final + { + const auto initialTime = jive::FakeTime::now(); + + beginTest("faking the passing of time"); + jive::FakeTime::incrementTime(juce::RelativeTime::seconds(5.3)); + expectEquals(jive::FakeTime::now(), initialTime + juce::RelativeTime::seconds(5.3)); + + beginTest("getting callbacks"); + juce::Array invokeTimes; + jive::Timer timer{ + [&invokeTimes](juce::Time now) mutable { + invokeTimes.add(now); + }, + juce::RelativeTime::seconds(0.1), + }; + jive::FakeTime::incrementTime(juce::RelativeTime::seconds(0.25)); + expectEquals(invokeTimes.size(), 2); + expectEquals(invokeTimes[0], initialTime + juce::RelativeTime::seconds(5.4)); + expectEquals(invokeTimes[1], initialTime + juce::RelativeTime::seconds(5.5)); + + jive::FakeTime::incrementTime(juce::RelativeTime::seconds(0.3333)); + expectEquals(invokeTimes.size(), 5); + expectEquals(invokeTimes[0], initialTime + juce::RelativeTime::seconds(5.4)); + expectEquals(invokeTimes[1], initialTime + juce::RelativeTime::seconds(5.5)); + expectEquals(invokeTimes[2], initialTime + juce::RelativeTime::seconds(5.6)); + expectEquals(invokeTimes[3], initialTime + juce::RelativeTime::seconds(5.7)); + expectEquals(invokeTimes[4], initialTime + juce::RelativeTime::seconds(5.8)); + } +}; + +static TimerUnitTest timerUnitTest; +#endif diff --git a/jive_core/time/jive_Timer.h b/jive_core/time/jive_Timer.h new file mode 100644 index 00000000..416af9d4 --- /dev/null +++ b/jive_core/time/jive_Timer.h @@ -0,0 +1,68 @@ +#pragma once + +namespace jive +{ +#if JIVE_UNIT_TESTS + class FakeTime : public juce::DeletedAtShutdown + { + public: + struct Listener + { + virtual ~Listener() = default; + + virtual void timeChanged() = 0; + }; + + FakeTime() = default; + ~FakeTime(); + + void addListener(Listener& listener); + void removeListener(Listener& listener); + + static void incrementTime(juce::RelativeTime); + static juce::Time now() noexcept; + + JUCE_DECLARE_SINGLETON(FakeTime, false) + + private: + void incrementTimeInternal(juce::RelativeTime); + + juce::Time currentTime; + juce::ListenerList listeners; + }; +#endif + + class Timer +#if JIVE_UNIT_TESTS + : private FakeTime::Listener +#else + : private juce::Timer +#endif + { + public: + using Callback = std::function; + + Timer(Callback timerCallback, + juce::RelativeTime callbackInterval); + ~Timer(); + + private: +#if JIVE_UNIT_TESTS + void timeChanged() final; +#else + void timerCallback() final; +#endif + Callback callback; + juce::RelativeTime interval; + juce::Time timeLastCallbackInvoked; + }; + + [[nodiscard]] static inline juce::Time now() noexcept + { +#if JIVE_UNIT_TESTS + return FakeTime::now(); +#else + return juce::Time::getCurrentTime(); +#endif + } +} // namespace jive From 97493620a9b5f816ab421e48e8600149da397370 Mon Sep 17 00:00:00 2001 From: James J Date: Mon, 15 Jan 2024 20:28:35 +0000 Subject: [PATCH 5/6] Add string conversion for optional types --- jive_core/logging/jive_StringStreams.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jive_core/logging/jive_StringStreams.h b/jive_core/logging/jive_StringStreams.h index 120e755e..bd3edc68 100644 --- a/jive_core/logging/jive_StringStreams.h +++ b/jive_core/logging/jive_StringStreams.h @@ -30,4 +30,17 @@ namespace juce << ", " << rect.getHeight() << " }"; } + + template + String& operator<<(String& str, const std::optional& optional) + { + str << "std::optional<" << typeid(T).name() << "> { "; + + if (optional.has_value()) + str << *optional; + else + str << "NULL"; + + return str << " }"; + } } // namespace juce From da003a81ae9b4cebe12850923ee38d0dbc4c74de Mon Sep 17 00:00:00 2001 From: James J Date: Wed, 7 Feb 2024 20:18:26 +0000 Subject: [PATCH 6/6] Fix `juce::Point` variant converter --- .../jive_MiscVariantConverters.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/jive_core/values/variant-converters/jive_MiscVariantConverters.cpp b/jive_core/values/variant-converters/jive_MiscVariantConverters.cpp index ea9334cc..77bb764b 100644 --- a/jive_core/values/variant-converters/jive_MiscVariantConverters.cpp +++ b/jive_core/values/variant-converters/jive_MiscVariantConverters.cpp @@ -237,10 +237,20 @@ namespace juce const auto tokens = StringArray::fromTokens(value.toString(), ",", ""); jassert(tokens.size() == 2); - return Point{ - static_cast(tokens[0].getDoubleValue()), - static_cast(tokens[1].getDoubleValue()), - }; + if constexpr (std::is_integral()) + { + return { + static_cast(std::round(tokens[0].getDoubleValue())), + static_cast(std::round(tokens[1].getDoubleValue())), + }; + } + else + { + return { + static_cast(tokens[0].getDoubleValue()), + static_cast(tokens[1].getDoubleValue()), + }; + } } template