After weeks of putting more working hours into Duality than I could reasonably afford it’s finally done: The big user experience update for Dualitor.
(Download links below)
As most things in life, it started with something small. Wouldn’t it be nice, I thought to myself working on the recent lighting techdemo,_ if that Propertygrid of yours updated a little faster?_ It sure would, so I began tinkering with that old code again. A little polishing here, some bugfixes there.. and while I’m at it, why not redesign this and that. I ended up completely rewriting the whole Propertygrid, then introducing Resource previews and intelligent Dragdrop, followed by a complete redesign of the whole editor GUI. But I’m getting ahead of myself. Here’s what happened, feature by feature:
The new Propertygrid
As you might remember, I already did write a complete Propertygrid before. The main concept was that each property editor was an individual Windows.Forms Control, allowing utmost flexibility and a better workflow due to highly specialized editing features. The design and implementation fulfilled most of what I wanted to achieve but I ran into a lot of problems related to layout events, redraw and most of all, performance. It was about time to wave the multi-Control concept goodbye.
My second attempt to solve the property editing problem is to write a custom Control for the Propertygrid itself and handle layout, rendering and event handling manually for all child editors. It works surprisingly well, allows a similar degree of flexibility and runs so fast that I’m starting to wonder what Windows.Forms actually does in its Control code. Also, you can control the new Propertygrid entirely by keyboard – I thought, this might improve the workflow a little.
Duality knows a lot of Resource types: ShaderPrograms, DrawTechniques, Textures, AudioData and so on. It is a waste of usability if all that distinguishes them for the user is their name and a tiny icon. To fix that, the three major Resource types (Pixmap, AudioData, Font) now generate a preview image that is shown wherever they are displayed or referenced.
This feature is to editing what syntactical sugar is to programming: While not adding any core functionality, its an elegant shortcut for something that improves your workflow and “feels” good in matters of user experience. Dragdrop has already been a core mechanic to Dualitor: You use it to establish links between Resources, to create Prefabs from GameObjects or vice versa. The difference is that it is now combined with an extendable data conversion API. It is easier to explain with an example in mind:
Lets say you want to create a sprite object from an image somewhere on your hard disk.
- You would first do a dragdrop operation of that file into Dualitor which would import it and add a Pixmap Resource to your project.
- The next step is to create a new Texture from that Pixmap by right-clicking it and selecting the “Create Texture” action (which is itself a shortcut for doing it manually)
- Then click on that new Texture, select “Create Material”. So far so good. Now we need a GameObject displaying it, so:
- Rightclick, select “New / GameObject”
- Rightclick, select “New / Component / Transform”
- Rightclick, select “New / Component / SpriteRenderer”
- Select the object and dragdrop the Material to the SpriteRenderers SharedMaterial property.
Seems unnecessary? Well, it was. Lets look at how this looks with intelligent dragdrop:
- Import the image file, the same as #1 from above. You’ll get a Pixmap
- Dragdrop that Pixmap to your main CamView. Note that a suitable Texture and Material have been automatically created, as well as a GameObject, the Transform Component and the SpriteRenderer Component. Which has been set up correctly to fit Pixmap bounds.
Whoa! What the hell happened to all those other steps? Quite easy in theory: The CamView, as each other Control, accepts certain types of data when hovering it while doing a dragdrop operation. One of these data types are GameObjects, so it hands over the dragdrop data object to the new API and requests a GameObject. It doesn’t care what kind of data is dragged as long as there is a way to convert that data to a type that is useful to it. This is one side of the API. On the other side is where plugins register DataConverter objects, each one able to convert one type of data to another one. What intelligent dragdrop does is figure out a way how to use the available set of DataConverter objects most effective in order to generate data of a requested type. You might think of it as some kind of pathfinding. Looking at the internals of the above example, this is what you’d see:
- Target type: GameObject.
- Convert from Pixmap to Texture by creating a new Texture using it.
- Convert from Texture to Material by creating a new Material using it.
- Convert from Material to Component by creating a new SpriteRenderer Component.
- Convert from Component to GameObject by creating a new GameObject and adding the specified Component as well as all of its required Components (here: Transform)
- Return the new GameObject
And there it is.