Persisting GUI state across editor instantiations

gui

#1

I’m having some difficulty wrapping my head around one of the unique aspects of audio plugin design; the fact that the GUI (editor) can be destructed and constructed during the lifetime of the processor. Specifically, I’m wondering what solutions/designs people use to maintain the state of the GUI across multiple instantiations of the editor.

I understand how to update the editor from the processor, via one of the following:

  1. a Timer: implement a timer in the editor, and in its timerCallback() determine if any GUI changes must be made;
  2. a ChangeBroadcaster: send a change message from the processor (ChangeBroadcaster) to the editor (ChangeListener), upon which the editor will update itself;
  3. a MessageListener: post a Message from the processor to the editor (MessageListener), informing the editor what needs to be updated.

I prefer the MessageListener solution, as I’m able to send custom messages to the editor. This allows me to be specific about what has changed, as opposed to the more general ChangeBroadcaster solution. I couple this with a view model; the view model maintains the overall state information that the GUI cares about, and the messages inform the GUI what kind of update (from the view model) needs to occur.

So far so good, everything seems to work nicely. However, the problem occurs when the editor is destroyed (which could happen if the user closes the window). When the editor is constructed again, I have obviously lost the complex state of the GUI, which has evolved over time.

Two solutions come to mind, though I’m not sure how ideal or possible they are:

  1. I could continue using my view model approach, but force all GUI updates to be complete refreshes. This way, when an editor is destructed and constructed again, it will be able to recreate its state from the view model. However, this forfeits the granular-update approach offered by the MessageListener solution (as each update would become a full “refresh” of the UI).
  2. When the editor is constructed for the first time, I could maintain a reference to that and return it each time createEditor() is called. However I see that this is not advised, due to the possibility of dangling pointers.

What is the preferred solution? I should add that I’m not dealing with parameters; most of the UI changes I’m working with are results of user interaction, component position/size changes, histrograms, etc.

Thanks in advance for any guidance!


#2

I’m also interested in seeing what the best practice is for GUI state/updates! I’m doing something similar to what tsherr is doing in solution 1. So far it functions well but it feels very verbose when coding everything out


#3

#4

I expect your editor to have some initialization phase (probably the constructor) where it creates all the widgets it contains.
During that phase, you could query the underlying processor to set the state of each created widget (slider, button, etc) so that it represents the current state of the corresponding aspect or parameter of the processor.

By doing so, at the end of this initialization/construction phase, the editor will exactly represent the state of the underlying processor, and the subsequent granular notifications will only need to update the corresponding aspect as expected


#5

Note, that you can happily use the public state variable of your AudioProcessorValueTreeState to add things like “Last opened tab”, “last zoom factor” etc. as child nodes.
By doing so, your getCurrentStateInformation will still be only one line.

Just in your setCurrentStateInformation you have to check if an editor exists and maybe update that from the state (so not doing in the constructor but in a separate function, that you can call from the constructor and from the loading procedure)

And to connect some GUI elements to ValueTrees that are not AudioParameters, I created a module called ffGuiAttachments, if that helps…


#6

Thanks everyone for your answers. I don’t know how I completely overlooked the ValueTree. I’ve been reading up on it and I can see how it will help hold my app’s model, and propagate changes in a granular way. However, There are still two issues that remain unresolved, even when using a ValueTree:

  1. When an editor is opened some time after the processor (the plugin is created, some changes occur and the ValueTree’s properties are modified, then the editor is opened), the editor doesn’t pick up these changes. Which is fair, since from the newly constructed editor’s point of view, these aren’t really changes - but the current state of the ValueTree represents the model that the view should display, and there doesn’t seem like an easy way to force a “refresh”. @yfede, your point about initializing the view in its constructor makes sense; and I could do this - by iterating through the various properties of the ValueTree - but since my view’s state is largely defined by the implementation of its valueTreePropertyChanged() function, this would be a lot of duplicated code. If there was a way for a view to tell its ValueTree to “re-fire” all property changed messages, then the UI would be able to initialize itself automatically (though I would assume this would interfere with some other things, such as the undo manager).
    TL;DR: Since the view’s state is a product of successive calls to valueTreePropertyChanged(), is there a convenient way to force a “refresh” from the ValueTree upon the view’s construction?

  2. The above point aside, everything fits together nicely. But how does this work with properties that are purely view-oriented, and don’t really belong in the app’s model? For example, a window’s scroll position or some text that a user has entered into a search box. I anticipate that the response to this is that these values do in fact belong in the model (as per @daniel’s response). In this case would it make sense to have a separate ValueTree that holds all the view-only properties?


#7

That’s what the AudioProcessorValueTreeState::XXXAttachment classes are for. For buttons, that don’t affect the parameters, there are the mentioned classes. For anything else, that might be too specific. But if you find some generic properties, like scroll positions, it makes sense to write some reusable code. Maybe your first own module… :slight_smile:

Yes, that was exactly my point. The AudioProcessorValueTreeState manages the parameter values. But it offers in a public variable called “AudioProcessorValueTreeState::state”, where you can add arbitrary additional properties. These won’t be seen by the host obviously (which is desired in this case.

My set and getStateInformation implementation looks like this:

void MyAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    MemoryOutputStream stream(destData, false);
    state.state.writeToStream (stream);
}

void MyAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    ValueTree tree = ValueTree::readFromData (data, sizeInBytes);
    if (tree.isValid()) {
        state.state = tree;
        if (auto* editor = dynamic_cast<MyAudioProcessorEditor*> (getActiveEditor())
            editor->sendUpdate (state.state);  // this could be a way to send a new state
    }
}

There are three situations, when an editor needs update (in my view):

  1. On construction: the processor already exists and should have a proper state
  2. When setCurrentStateInformation is called (aka load): use the method above
  3. When the GUI changes: that shouldn’t propagate back anyway…

HTH