This blog is no longer updated. Feel free to copy any useful information to other blogs or wikis. Thanks.


Generic UI for parameter editing

February 7th, 2010 | Comments Off

I’ve spent a good amount of the weekend thinking up designs that would allow me to quickly add new UI types into Shader Toy to support all sorts of parameter types. I think I’ve finally got something that works fairly well. What I’ve done is create a very simple interface that represents what a given UI must support:

public interface IParameterControl
{
	EffectParameter Parameter { get; set; }

	void CopyValue();
	void PasteValue();
}

Like I said, very simple. Each UI control must allow me to get and set the EffectParameter it is editing as well as supporting copy/paste functionality. Next I created a mapping for matching a parameter type to a control type, taking into account annotations. Right now I don’t have much but this is the part that will fill up as time goes on:

private readonly Dictionary<Type, Dictionary<string, Type>> parameterEditorTypes =
	new Dictionary<Type, Dictionary<string, Type>>
	{
		{
			typeof(Vector4),
			new Dictionary<string, Type>
			{
				{ "color", typeof(ColorChooser) }
			}
		}
	};

Basically what that means is that we first look up what type the parameter is. In this case a float4/Vector4. Next we look up the “UIType” annotation on the parameter and use that to find what Type of control we need. In this case that’s a ColorChooser UI. Obviously we’d also want some defaults and lots more in here, but this is just where I’m starting.

Finally the meat of the operation. I have this method wired up for when I select a new item in my parameter list:

private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
	groupBox1.Controls.Clear();

	if (listBox1.SelectedIndex < 0)
	{
		currentParameter = null;
		currentControl = null;
		groupBox1.Text = "{No Parameter Selected}";
		return;
	}

	currentParameter = parameters[listBox1.SelectedIndex];
	groupBox1.Text = currentParameter.Name;

	bool isArray = currentParameter.Elements.Count > 1;
	int arraySize = currentParameter.Elements.Count;

	Type currentParameterType;

	switch (currentParameter.ParameterType)
	{
		case EffectParameterType.Bool:
			currentParameterType = isArray ? typeof(bool[]) : typeof(bool);
			break;
		case EffectParameterType.Int32:
			currentParameterType = isArray ? typeof(int[]) : typeof(int);
			break;
		case EffectParameterType.Single:
		{
			if (currentParameter.RowCount == 1)
			{
				switch (currentParameter.ColumnCount)
				{
					case 1:
						currentParameterType = isArray ? typeof(float[]) : typeof(float);
						break;
					case 2:
						currentParameterType = isArray ? typeof(Vector2[]) : typeof(Vector2);
						break;
					case 3:
						currentParameterType = isArray ? typeof(Vector3[]) : typeof(Vector3);
						break;
					case 4:
						currentParameterType = isArray ? typeof(Vector4[]) : typeof(Vector4);
						break;
					default:
						currentParameterType = null;
						break;
				}
			}
			else
			{
				currentParameterType = isArray ? typeof(Matrix[]) : typeof(Matrix);
			}
			break;
		}
		case EffectParameterType.String:
		currentParameterType = typeof(string);
			break;
		case EffectParameterType.Texture:
		case EffectParameterType.Texture2D:
			currentParameterType = typeof(Texture2D);
			break;
		default:
			currentParameterType = null;
			break;
	}

	if (currentParameterType == null)
		return;

	string uiType = string.Empty;

	foreach (var annotation in currentParameter.Annotations)
	{
		if (annotation.Name == "UIType")
		{
			uiType = annotation.GetValueString().ToLower();
			break;
		}
	}

	Type editorType = null;
	Dictionary<string, Type> editorMatch = null;
	if (parameterEditorTypes.TryGetValue(currentParameterType, out editorMatch) &&
		editorMatch.TryGetValue(uiType, out editorType))
	{
		currentControl = Activator.CreateInstance(editorType) as IParameterControl;
		currentControl.Parameter = currentParameter;
		Control c = currentControl as Control;
		c.Location = new Point(12, 24);
		groupBox1.Controls.Add(c);
	}
}

That goes and figures out exactly what type of parameter we have, then we find the UIType annotation, and lastly we use those two pieces of information to figure out what type of control we want to use. We create an instance, set the parameter, position it, and add it to the group box. I’ll have to see if this holds up as I go, but for just colors right now it works quite well.

The idea behind all of this is to reduce the effort for implementing more UI for other parameter types. Before I had all sorts of hard coded types and different code paths based on type. With this new system, I just have to implement the new UI types and add them to my big dictionary mapping and this method will automatically work for the new UI. Should allow me to finally get down to making more UI for editing parameters.

And of course, what post on Shader Toy would be complete without yet another video. You’ll see the new color UI as well as me using copy/paste. Otherwise it’s nothing all that amazing. I just like making videos. :)

httpvh://www.youtube.com/watch?v=SHRvpOY0ERU

Comments are closed.