In a previous blog posting I rambled on about how plugins need to be easier, both for users and developers. I haven’t had a lot of time lately to work on Paint.NET, but I have managed to get started on a new system for developing effect plugins that provide configuration UI for the user. These types of plugins are the most popular: you click on them in the Effects menu and it pops up a dialog with sliders and buttons and whatnot which lets you configure properties that determine how the effect is rendered.
The problem with developing these configuration dialogs is that they are extremely tiresome to write. You can write rendering code for a simple plugin very quickly using CodeLab, but you are limited to a “1, 2, or 3 amount sliders” configuration dialog. If you need 4 sliders, then you have to write a brand new dialog from scratch complete with layout and data binding. It’s easy to get the implementation wrong, or to at least skirt the line of what constitutes a “correct” implementation. My experience has been that if it’s possible to do things “the wrong way,” then your framework needs to be changed: it should only ever be possible to do things the right way, or at least make it extremely obvious when you’re breaking the rules. Right now that distinction isn’t really clear.
I have been working on a new system that will allow plugin developers to easily get intricate configuration dialogs from just a description of the properties that govern rendering. For example, adding this code in an Effect:
protected override PropertyCollection CreatePropertyCollection()
{
List<Property> props = new List<Property>();
props.Add(new Int32Property(PropertyNames.Factor, 1, 1, 10));
props.Add(new DoubleProperty(PropertyNames.Zoom, 10, 0, 100));
props.Add(new AngleProperty(PropertyNames.Angle));
props.Add(new DoubleVectorProperty(
PropertyNames.Offset,
Pair.MakePair(0.0, 0.0),
Pair.MakePair(-1.0, -1.0),
Pair.MakePair(1.0, 1.0)));
props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 4));
props.Add(new BooleanProperty(PropertyNames.InvertColors));
return new PropertyCollection(props, new PropertyCollectionRule[0]);
}
… generates this type of dialog:
(click for full-size)
All of the layout and data binding is handled automatically, making the plugin author’s life much, much easier. The dialog is resizable, and if there are too many properties to fit on-screen it will automatically provide a scrollbar. The UI itself is still rough draft: the “offset” is still just two sliders right now, for instance, and needs something akin to what we have in the Rotate/Zoom dialog. This system and the UI generation should only improve with time. Eventually I want to have the Rotate/Zoom, Curves, and even Levels configuration dialogs written using this. So far I have an enhanced version of Frosted Glass using this, and have re-done the UI for Clouds up to feature parity with v3.10.
But the great thing about being able to easily get this UI is that it is also very easy to change it. What if you want to change the ordering? Add a property? Remove two properties? It’s easy. Just change the code in your CreatePropertyCollection() function.
Once this system is mostly finished for Effect development, I will also be extending it to the UI generation for file type plugins. This will allow me to provide rich configuration for things like HDPhoto, but without having to write a lot of the complicated logic that governs its data binding and property value dependencies (“property Z may only be configured if property M is enabled” … barf).
The other thing some of you may have picked up on is that the code above could easily be auto-generated from, for instance, an XML description of the same properties. This system is being designed to make code generation scenarios simple to implement, such as for CodeLab.
Lastly, once the system is well understood, I will rewrite the “Colors” floater window using the same UI generation and property system. That thing is about 5,000 lines of code and is very fragile. It has grown organically since 2004 and is in very sad shape. It works, but only because it has been thoroughly debugged. I much prefer code that works because it was meant to work (i.o.w. it works by design, not by accident).