AspectEditor

This is something that will probably look at first like a convoluted way to do something simple.
However, I think it’s actually quite a simple way of doing something that is otherwise quite complicated.

[A quick overview]

An EditableAspect defines a collection of properties related to a specific feature of an object type. It also defines how PropertyComponents should be created for these properties.

EditableAspect::Container is a base for objects wishing to expose EditableAspects. It is an interface for providing a list of supported aspects, as well as Value objects wrapping the data the properties represent.

An AspectEditor wraps a PropertyPanel with the ability to display properties for objects derived from EditableAspect::Container.

The main unique feature this system provides is the ability to edit properties of multiple target objects at once, even if they are not of the same type. The AspectEditor can be given an array of EditableAspect::Container objects to show properties for. As each container provides a list of supported aspects, the editor is able to determine which aspects are common to all containers, and show their properties. Also, as the properties are provided in the form of Value objects, it is able to automatically bundle matching properties of multiple targets into proxy Values.

There’s a lot more to it than that; have a look at the comments in the header for more information.

If nothing else, it also provides a ‘pattern’ of sorts for dealing with a PropertyPanel; though the basic PropertyPanel is very useful, you’re very much on your own when it comes to figuring out how to make it work with your data model.

Download EditableAspect_v0.zip

[Haven’t got any shareable example code yet I’m afraid, but hopefully the comments are reasonably clear]
When i have a bit more time, I’ll try to put together a more comprehensive description of how it all slots together. However, I’ll first wait to see if anyone even cares!

Interesting stuff. At some point I’m going to have to get the jucer to show property components for multiple selections, so I guess I’m going to have to come up with some kind of scheme like this too, I’m struggling a bit to follow your design, but it looks like you’ve thought it through pretty deeply!

This is probably the order to approach it in:

[ AspectEditor ]

This doesn’t need much explanation, other than the fact that it needs an EditableAspect::Source [just as a ListBox requires a ListBoxModel].
The source is used by the editor purely to retrieve EditableAspect instances by their ID. The EditableAspectLibrary is handy for this.
*** Key thing to know: the AspectEditor is the only thing that ever uses the EditableAspect instances directly.

[ EditableAspect ]

Each aspect has a unique ID. These IDs are how the various container types specify their composition.
An aspect has two main jobs.

  1. Hold a list of property descriptors.
  2. Provide a list of PropertyComponents for the AspectEditor.

Each property descriptor has an ID, a label, and a hint as to which type of PropertyComponent it needs.
They’re stored as a ValueTree, so you can add any other properties you need to them [e.g. for a slider-based property, you’d want to specify min/max/interval values].

All you need to override is createEditor(), which gives you a Property descriptor along with an already-proxied Value. If you’re okay with text editors for all properties, you don’t even need to do that.

[ EditableAspect::Container ]

This has two jobs:

  1. Provide a list of IDs indicating which EditableAspects are supported.
  2. Given a Property descriptor, return a suitable Value object.

That’s about all you need to know to use it, really. You can do more with it, but that’s the fundamentals.

Thanks a lot for this Haydxn, it pretty much looks like exactly what I need for some software I am just starting to write. Before diving in, I am looking at the actual code and your notes to plan/conceptualize how to correctly implement AspectEditor. Looking at an example of it in the wild would be fantastic, but perhaps you could clarify a few questions?

Goal: Have an 8x8 grid of instances of a custom “button” class which have various properties that can be set through a common PropertyPanel / editor. Since all buttons have the same set of properties, selecting multiple buttons should allow setting the common properties in all selected buttons simultaneously (well, all properties besides their unique IDs). I attached a screenshot of my program, showing the grid of buttons, with one selected (yellow-green border); the space to its left is where the PropertyPanel/AspectEditor will go…

I’m a bit confused about how everything fits together. AspectEditor seems pretty straightforward, I replace my current PropertyPanel with AspectEditor to get a PropertyPanel with the new functionality. Then it seems like I need to make my custom button class extend EditableAspect? Or is it that I need to create an instance of EditableAspect, and somehow define which Properties I want to create for the button (e.g. text editors, sliders, etc)?.. and then i use EditableAspect::getEditorComponents() to actually go through and create the PropertyComponents? Or should I have already done this task using createEditor()? Different properties[PropertyComponents] will require different gui interfaces (slider, text box… color picker, etc)

Sorry about all question marks! Maybe I’m way off here, and/or perhaps the correct usage is staring me right in the face… I am really excited about this however, and these look like extremely useful classes. Thanks for your work on these classes and your help.

Regards,
Jordan

I’m thinking of rejigging the structure of it all a little bit, which will make it clearer :slight_smile: but if I get time tonight I’ll write up an example implementation using it

Fantastic! Your efforts are much appreciated…

J

Okeydokey, I’ve renamed a lot of stuff here, and rejigged it a bit.

PropertyGroup

Some of the names here are those I’d originally chosen; however, I originally thought ‘PropertyGroup’ was a little vague, so chose the more specific ‘aspect’ idiom instead. In retrospect, that made the whole thing seem a little cryptic despite being more precise, so I’ve gone back to using more immediately recognisable terms.

Here’s the gist:

PropertyDescriptor describes a single property.

PropertyGroup holds a bunch of PropertyDescriptors, and has a unique id/name. All it does is define the composition of a given group of related properties.

PropertyGroup::Container is what your objects should derive from. They must be able to provide a list of IDs for all the PropertyGroups they support. They must also be able to return a Value objects suitable for a given PropertyDescriptor.

PropertyGroup::ComponentFactory must be able to create a PropertyComponent for a given Value and PropertyDescriptor. It’s capable of further customisation than that, but that’s all you need to worry about. For testing purposes, you can skip subclassing it and use it directly; it will default to a text editor for all properties.

PropertyGroupLibrary is a simple PropertyGroup::Source, which holds your property groups. So, you’d define your various PropertyGroups (adding PropertyDescriptors), and register each one with a PropertyGroupLibrary. It takes ownership, so they’ll just live there and be deleted automatically.

PropertyGroupEditor takes a PropertyGroup::Source [e.g. a PropertyGroupLibrary] and a PropertyGroup::ComponentFactory. And that’s about all you need to know. Just ask it to show properties for an object (or array of objects) derived from PropertyGroup::Container, and it’ll do the fiddly stuff for you.

Sorry if some of the comments in the code are out of date; my brain is a bit frazzled from working all day in a different codebase [plus, at work I have much nicer refactoring tools and a bigger monitor! coding at home is far more laborious!]. I just put in the minimum amount of effort I could allow myself to get away with :wink:

Hope this helps

Thanks Haydxn!

Does the following look correct to you? Im trying my best to understand the flow of things here! :wink: Lets say I have my button class, NomeButton, which I create 64 instances of, and want to set certain properties from each instance…

  1. NomeButton should subclass PropertyGroup::Container
  2. NomeButton should be responsible for creating a PropertyGroup for itself
  3. NomeButton should next create all PropertDescriptors it needs (each of its properties which needs to be user editable), and then add them to the PropertyGroup
  4. Must then call PropertyGroup::ComponentFactory to create the actual PropertyComponents. For testing, I can use this directly and see text boxes for properties, or I can subclass and use custom PropertyComponents (for eg. if I need a ColorSelector property component?)
  5. PropertyGroupLibrary will manage all PropertyGroups-- so each instance of my NomeButton should pass its PropertyGroup* to PropertyGroupLibrary using PropertyGroupLibrary::registerPropertyGroup (NomeButton->getPropertyGroupPtr())?
  6. PropertyGroupEditor acts as a liaison to a PropertyPanel. I should pass in my PropertyGroupLibrary upon instantiation and PropertyGroup::ComponentFactory (can use default for testing)… I’m still a little confused as to the correct way to have it know which instance of my NomeButton properties to change? Do I just call PropertyGroupEditor::showPropertiesFor(the NomeButton (which subclasses Container) whose properties I want to edit…) or ::showPropertiesFor(an Array of NomeButtons I want to edit at the same time)

Thanks again for your help, hope I’m not too far off…

You’re close, but there are a few errors :slight_smile:

One important thing to understand is that there is only ever one instance of any given PropertyGroup definition. Your containers do not create them, they simply refer to them. Each container uses the same group information, so there’s no need to recreate it for every object.
These groups should be created entirely outside of the data that will be using them (e.g. during your app’s initialisation). You’d create any you need (filling them with appropriate PropertyDescriptors), and store them in a PropertyGroupLibrary. The minimum you’d need to do is make a single PropertyGroup defining all the properties of your NomeButton class [but you can of course divide those properties into multiple groups if you want].

You do not call anything in PropertyGroup::ComponentFactory. You just need to make sure that one exists for the PropertyGroupEditor.

Once you have a PropertyGroupLibrary (or other custom-made PropertyGroup::Source) and a PropertyGroup::ComponentFactory, you have all you need to create a PropertyGroupEditor.

Then of course you have your data…

The container has two functions to override:

getPropertyGroups(groupIds)

This just needs to add the IDs of supported groups. You don’t use any group objects directly here, you’re just referring to the instances in your library. So, if you were just using a single group called “NomeButtonProperties”, you’d just call groupIds.add(“NomeButtonProperties”) in here.

getGroupProperty(groupId,propInfo)

This part can either be VERY EASY or QUITE FIDDLY, depending on how your data is stored and your familiarity with the Value class.
All you have to do is look at the group/property parameters, and return a corresponding Value wrapping the data they describe. If your data is stored in a ValueTree this is SO EASY. If not, you’ll need to create your own Value::ValueSource subclasses to handle your data. You’ll have to look at the docs and juce library code for more info on that, as I can’t really cover that here!

If you’ve not dabbled with such things before (and you’re not already using a ValueTree), I would recommend trying out a ValueTree for now.
If you give NomeButton a member ValueTree (let’s say it’s called “myTreeNode”), you’d just need to change functions like this:
“NomeButton::setNumSpiders(x)” should set a tree property (e.g. myTreeNode.setProperty(“numSpiders”,x))
“NomeButton::getNumSpiders()” should pull a named value from the tree (e.g. return myTreeNode[“numSpiders”]).
etc…

Then, for getGroupProperty(groupId,propInfo), if propInfo indicated the number-of-spiders property, you could say
return myTreeNode.getPropertyAsValue(“numSpiders”,NULL);

Once you’ve tackled those things, you use the editor like this:

myGroupEditor->showPropertiesFor (thisParticularNomeButton);

or

myGroupEditor->showPropertiesFor (anArrayOfNomeButtonPointersIJustMade);

Hey Haydxn,

Was using this fantastic editor in one of my projects, and I opened the project up to recompile with the latest git tip tonight and its throwing this error now, in MultiValueProxysource, line 64 (inside the setValue funciton)

“passing ‘const juce::Value’ as ‘this’ argument of ‘void juce::Value::setValue(const juce::var&)’ discards qualifiers”

Are you getting this error with the latest git tip as well?
cheers

Any word on how this is working with the new juce modules? Any updates?

Best,
Owen