[FR] ValueTree::Listener lambdas

Can we get some lambdas added to the ValueTree::Listener class?

struct ValueTreeListener : private ValueTree::Listener
{
    ValueTreeListener(const ValueTree& treeToListenTo) : tree(treeToListenTo)
    {
        tree.addListener(this);
    }
    ~ValueTreeListener()
    {
        tree.removeListener(this);
    }
    
    std::function<void(ValueTree&, const Identifier&)> onValueTreePropertyChanged;
    std::function<void(ValueTree&, ValueTree&)> onValueTreeChildAdded;
    std::function<void(ValueTree&, ValueTree&, int)> onValueTreeChildRemoved;
    std::function<void(ValueTree&, int, int)> onValueTreeChildOrderChanged;
    std::function<void(ValueTree&)> onValueTreeParentChanged;
private:
    void valueTreePropertyChanged (ValueTree& treeThatChanged, const Identifier& identifier) override
    {
        if( onValueTreePropertyChanged )
            onValueTreePropertyChanged(treeThatChanged, identifier);
    }
    void valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree) override
    {
        if( onValueTreeChildAdded )
            onValueTreeChildAdded(parentTree, childTree);
    }
    void valueTreeChildRemoved (ValueTree& parentTree, ValueTree& childTree, int i) override
    {
        if( onValueTreeChildRemoved )
            onValueTreeChildRemoved(parentTree, childTree, i);
    }
    void valueTreeChildOrderChanged (ValueTree& parentTree, int a, int b) override
    {
        if( onValueTreeChildOrderChanged )
            onValueTreeChildOrderChanged(parentTree, a, b);
    }
    void valueTreeParentChanged (ValueTree& tree) override
    {
        if( onValueTreeParentChanged )
            onValueTreeParentChanged(tree);
    }
    ValueTree tree;
};

I much prefer using Composition over inheritance for my code, and having this little lambda class lets me do that with classes that have ValueTrees as members.

It also removes the need to implement all 5 ValueTree::Listener functions in whatever class I need to listen to changes on a value tree. I just add an instance of this class as a member alongside the ValueTree class member, and set up the lambda accordingly.

struct Foo : Component
{
    ValueTree tree{"tree"};
    ValueTreeListener listener{tree};

    Foo()
    {
        listener.onValueTreePropertyChanged = [this](ValueTree& tree, const Identifier& id) 
        {
             if( id == "someCoolID" ) 
             { 
                 DBG("someCoolID changed" );
                 this->someSetting = tree.getProperty(id);
                 repaint();
             }
        };
    }

    void paint(Graphics& g) override { /* etc */ }
    int someSetting = 0;
};

I don’t know, maybe std::function<> is too slow/expensive for this. I just abhor massive if/else if/else if blocks that accompany the listener pattern. thoughts?

3 Likes

FYI, that need has been eliminated from the develop branch in https://github.com/WeAreROLI/JUCE/commit/20864a2f5f9e4305490209e9a304f2718e4d909a.

it doesn’t eliminate the if (id == a) { } else if( id == b ) though
for

void valueTreePropertyChanged (ValueTree& treeThatChanged, const Identifier& id) override
{
    if( id == someID ) { ... }
    else if (id == someOtherID ) { ... }
}

I made my own class to do this that looks very similar, but additionally, I added a per property callback system for the main node using a map. This is also why I’d like to be able to use a HashMap with Identifiers which is currently not possible in Juce.

void addPropertyChangeCallback(Identifier propId, std::function<void(var& newValue)> fnc) {
	propCallbacks[propId] = fnc;
}

void valueTreePropertyChanged(ValueTree& vt, const Identifier& propID) override {
	if (onPropertyChanged != nullptr) {
		onPropertyChanged(vt, propID);
	}
	if (vt == tree) { // restrict to parent vt!
		auto it = propCallbacks.find(propID);
		if (it != propCallbacks.end()) {
			var newVal = vt[propID];
			it->second(newVal);
		}
	}
}

private:
std::map<Identifier, std::function<void(var& newValue)>> propCallbacks; 

This has allowed me to get rid of these “else if”-chains that usually happened when listening to multiple properties on the same ValueTree. It has cleaned up my sources a lot.

1 Like

does that come at the cost of a map lookup?

obviously yes. But I’m not using it for time-critical things. In most cases just using a vector instead of a map would probably be faster, but I’m hoping for a HashMap in the future.