mirror of
https://github.com/riperiperi/FreeSO
synced 2026-08-13 20:26:13 -04:00
684 lines
21 KiB
C#
684 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.IO;
|
|
using System.Globalization;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System.Reflection;
|
|
using Microsoft.Xna.Framework;
|
|
using FSO.Client.Utils;
|
|
using FSO.Client.UI.Controls;
|
|
|
|
namespace FSO.Client.UI.Framework.Parser
|
|
{
|
|
public class UIScript
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
/// Nodes which represent functions
|
|
/// </summary>
|
|
private static string[] FUNCTIONS = new string[] { "DefineString", "DefineImage", "AddButton", "SetControlProperties", "AddText", "AddTextEdit", "AddSlider", "AddListBox", "AddFormatedText", "AddProgressBar" };
|
|
|
|
private Dictionary<string, string> Strings;
|
|
private Dictionary<string, Texture2D> Textures;
|
|
private Dictionary<string, UIElement> Components;
|
|
private Dictionary<string, object> NamedObjects;
|
|
private Dictionary<string, UINode> NodesByID;
|
|
private Dictionary<string, UINode> ControlSettings;
|
|
private GraphicsDevice gd;
|
|
|
|
private UIContainer target;
|
|
private Type targetType;
|
|
|
|
public UIScript(GraphicsDevice gd, UIContainer target)
|
|
{
|
|
this.gd = gd;
|
|
this.target = target;
|
|
this.targetType = target.GetType();
|
|
Strings = new Dictionary<string, string>();
|
|
Textures = new Dictionary<string, Texture2D>();
|
|
Components = new Dictionary<string, UIElement>();
|
|
NodesByID = new Dictionary<string, UINode>();
|
|
NamedObjects = new Dictionary<string, object>();
|
|
ControlSettings = new Dictionary<string, UINode>();
|
|
}
|
|
|
|
|
|
public void LinkMembers(UIContainer target, bool addUIElements)
|
|
{
|
|
var fields = target.GetType().GetProperties();
|
|
foreach (var field in fields)
|
|
{
|
|
if (NamedObjects.ContainsKey(field.Name))
|
|
{
|
|
var value = NamedObjects[field.Name];
|
|
field.SetValue(target, value, new object[]{});
|
|
|
|
if (value is UIElement && addUIElements)
|
|
{
|
|
var uiValue = (UIElement)value;
|
|
if (uiValue.Parent != null)
|
|
{
|
|
uiValue.Parent.Remove(uiValue);
|
|
}
|
|
target.Add(uiValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public object this[string id]
|
|
{
|
|
get {
|
|
if (NamedObjects.ContainsKey(id)) return NamedObjects[id];
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void ApplyControlProperties(object instance, string id)
|
|
{
|
|
if (ControlSettings.ContainsKey(id))
|
|
{
|
|
DoSetControlProperties(instance, ControlSettings[id]);
|
|
}
|
|
else if (NodesByID.ContainsKey(id))
|
|
{
|
|
DoSetControlProperties(instance, NodesByID[id]);
|
|
}
|
|
}
|
|
|
|
public T Create<T>(string id)
|
|
{
|
|
var instance = Activator.CreateInstance<T>();
|
|
if (ControlSettings.ContainsKey(id))
|
|
{
|
|
DoSetControlProperties(instance, ControlSettings[id]);
|
|
} else if (NodesByID.ContainsKey(id)) {
|
|
DoSetControlProperties(instance, NodesByID[id]);
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
public T Create<T>(string id, object param)
|
|
{
|
|
var instance = Activator.CreateInstance(typeof(T), new object[] { param });
|
|
if (ControlSettings.ContainsKey(id))
|
|
{
|
|
DoSetControlProperties(instance, ControlSettings[id]);
|
|
}
|
|
return (T)instance;
|
|
}
|
|
|
|
public object GetControlProperty(string id, string field)
|
|
{
|
|
var result = GetControlProperty(target, id);
|
|
if (result is Dictionary<string, object>)
|
|
{
|
|
return ((Dictionary<string, object>)result)[field];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public object GetControlProperty(string id)
|
|
{
|
|
return GetControlProperty(target, id);
|
|
}
|
|
|
|
public object GetControlProperty(object target, string id)
|
|
{
|
|
if (ControlSettings.ContainsKey(id))
|
|
{
|
|
var props = DoGetControlProperties(target, ControlSettings[id]);
|
|
if (props.Keys.Count == 1)
|
|
{
|
|
return props[props.Keys.First()];
|
|
}
|
|
return props;
|
|
}else if (NodesByID.ContainsKey(id))
|
|
{
|
|
var node = NodesByID[id];
|
|
var props = DoGetControlProperties(target, node);
|
|
if (props.Keys.Count == 1)
|
|
{
|
|
return props[props.Keys.First()];
|
|
}
|
|
return props;
|
|
}
|
|
return null;
|
|
//DoGetControlProperties
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles AddText nodes in a UIScript
|
|
/// This method will:
|
|
/// * Create a label
|
|
/// * Assign all the properties from the UIScript
|
|
/// * Add to the display list
|
|
/// * Wire up against any members with the same name in the target class
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
public void AddText(UINode node)
|
|
{
|
|
var label = new UILabel();
|
|
DoSetControlProperties(label, node);
|
|
target.Add(label);
|
|
WireUp(node.ID, label);
|
|
}
|
|
|
|
private Type GetPropertyType(string id, Type defaultType)
|
|
{
|
|
var prop = targetType.GetProperty(id);
|
|
if (prop != null)
|
|
{
|
|
return prop.PropertyType;
|
|
}
|
|
|
|
return defaultType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles AddButton nodes in a UIScript.
|
|
/// This method will:
|
|
/// * Create a button,
|
|
/// * Assign all the properties from the UIScript
|
|
/// * Add to the display list
|
|
/// * Wire up against any members with the same name in the target class
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
public void AddButton(UINode node)
|
|
{
|
|
var type = GetPropertyType(node.ID, typeof(UIButton));
|
|
|
|
UIButton btn = null;
|
|
if (node.Attributes.ContainsKey("image"))
|
|
{
|
|
var txKey = node.Attributes["image"];
|
|
if (Textures.ContainsKey(txKey))
|
|
{
|
|
btn = (UIButton)Activator.CreateInstance(type, new object[] { Textures[txKey.ToLowerInvariant()] });
|
|
}
|
|
else
|
|
{
|
|
btn = (UIButton)Activator.CreateInstance(type);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
btn = (UIButton)Activator.CreateInstance(type);
|
|
}
|
|
Components.Add(node.ID, btn);
|
|
btn.ID = node.ID;
|
|
|
|
DoSetControlProperties(btn, node);
|
|
target.Add(btn);
|
|
WireUp(node.ID, btn);
|
|
}
|
|
|
|
|
|
public void AddSlider(UINode node)
|
|
{
|
|
UISlider slider = new UISlider();
|
|
Components.Add(node.ID, slider);
|
|
slider.ID = node.ID;
|
|
|
|
DoSetControlProperties(slider, node);
|
|
target.Add(slider);
|
|
WireUp(node.ID, slider);
|
|
}
|
|
|
|
public void AddListBox(UINode node)
|
|
{
|
|
UIListBox list = new UIListBox();
|
|
Components.Add(node.ID, list);
|
|
list.ID = node.ID;
|
|
|
|
DoSetControlProperties(list, node);
|
|
target.Add(list);
|
|
WireUp(node.ID, list);
|
|
}
|
|
|
|
|
|
|
|
public void AddTextEdit(UINode node)
|
|
{
|
|
UITextEdit textEdit = new UITextEdit();
|
|
Components.Add(node.ID, textEdit);
|
|
textEdit.ID = node.ID;
|
|
|
|
DoSetControlProperties(textEdit, node);
|
|
target.Add(textEdit);
|
|
WireUp(node.ID, textEdit);
|
|
}
|
|
|
|
public void AddFormatedText(UINode node)
|
|
{
|
|
UITextEdit textEdit = new UITextEdit();
|
|
Components.Add(node.ID, textEdit);
|
|
textEdit.ID = node.ID;
|
|
|
|
DoSetControlProperties(textEdit, node);
|
|
|
|
textEdit.Mode = UITextEditMode.ReadOnly;
|
|
|
|
target.Add(textEdit);
|
|
WireUp(node.ID, textEdit);
|
|
}
|
|
|
|
public void AddProgressBar(UINode node)
|
|
{
|
|
UIProgressBar pb = new UIProgressBar();
|
|
Components.Add(node.ID, pb);
|
|
pb.ID = node.ID;
|
|
|
|
DoSetControlProperties(pb, node);
|
|
|
|
target.Add(pb);
|
|
WireUp(node.ID, pb);
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetControlProperties(UINode node)
|
|
{
|
|
|
|
/** If the component is already setup, change the settings **/
|
|
if (Components.ContainsKey(node.ID))
|
|
{
|
|
DoSetControlProperties(Components[node.ID], node);
|
|
}
|
|
else if (NodesByID.ContainsKey(node.ID))
|
|
{
|
|
/** It could be another node in this UIScript, try find and merge the settings **/
|
|
var childNode = NodesByID[node.ID];
|
|
childNode.AddAtts(node.Attributes);
|
|
}
|
|
else
|
|
{
|
|
/** Might be a field already created on the UI container **/
|
|
var prop = targetType.GetProperty(node.ID);
|
|
if (prop != null)
|
|
{
|
|
var propValue = prop.GetValue(target, new object[] { });
|
|
if (propValue != null && propValue is UIElement)
|
|
{
|
|
DoSetControlProperties(propValue, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
ControlSettings[node.ID] = node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applys the various settings in the UINode to the control
|
|
/// provided
|
|
/// </summary>
|
|
/// <param name="control"></param>
|
|
/// <param name="node"></param>
|
|
private void DoSetControlProperties(object control, UINode node)
|
|
{
|
|
if (control == null) { return; }
|
|
|
|
/** Get UIAttribute decorations **/
|
|
var atts = GetTypeFields(control.GetType());
|
|
|
|
foreach (var att in node.Attributes)
|
|
{
|
|
var controlField = atts.Keys.FirstOrDefault(x => string.Equals(x, att.Key, StringComparison.CurrentCultureIgnoreCase));
|
|
|
|
if (controlField != null)
|
|
{
|
|
var uiAtt = atts[controlField];
|
|
var value = GetAtt(node, att.Key, uiAtt);
|
|
if (value != null) uiAtt.Field.SetValue(control, value, new object[] { });
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Applys the various settings in the UINode to the control
|
|
/// provided
|
|
/// </summary>
|
|
/// <param name="control"></param>
|
|
/// <param name="node"></param>
|
|
private Dictionary<string, object> DoGetControlProperties(object control, UINode node)
|
|
{
|
|
if (control == null) { return null; }
|
|
|
|
/** Get UIAttribute decorations **/
|
|
var atts = GetTypeFields(control.GetType());
|
|
var result = new Dictionary<string, object>();
|
|
|
|
foreach (var att in node.Attributes)
|
|
{
|
|
if (atts.ContainsKey(att.Key))
|
|
{
|
|
var uiAtt = atts[att.Key];
|
|
var value = GetAtt(node, att.Key, uiAtt);
|
|
|
|
result.Add(att.Key, value);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
/// <param name="name"></param>
|
|
/// <param name="converter"></param>
|
|
/// <returns></returns>
|
|
private object GetAtt(UINode node, string name, UIAttField field)
|
|
{
|
|
if (field.Parser != null)
|
|
{
|
|
var instance = (UIAttributeParser)Activator.CreateInstance(field.Parser);
|
|
instance.ParseAttribute(node);
|
|
return instance;
|
|
}
|
|
|
|
switch (field.Converter)
|
|
{
|
|
case UIAttributeType.Color:
|
|
return node.GetColor(name);
|
|
case UIAttributeType.Point:
|
|
return node.GetPoint(name);
|
|
case UIAttributeType.Texture:
|
|
if (Textures.ContainsKey(node[name].ToLowerInvariant())) return Textures[node[name].ToLowerInvariant()];
|
|
else return null;
|
|
case UIAttributeType.Vector2:
|
|
return node.GetVector2(name);
|
|
case UIAttributeType.StringTable:
|
|
if (Strings.ContainsKey(node[name].ToLowerInvariant())) return Strings[node[name].ToLowerInvariant()];
|
|
else return "";
|
|
case UIAttributeType.String:
|
|
return node[name];
|
|
case UIAttributeType.Integer:
|
|
return Int32.Parse(node[name]);
|
|
case UIAttributeType.Boolean:
|
|
return node[name] == "1";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
public void DefineString(UINode node)
|
|
{
|
|
var stringValue = GameFacade.Strings[node["stringDir"], node["stringTable"], node["stringIndex"] ?? node["StringIndex"]];
|
|
Strings.Add(node.ID.ToLowerInvariant(), stringValue);
|
|
WireUp(node.ID, stringValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the DefineImage nodes in the UIScript
|
|
/// </summary>
|
|
/// <param name="node"></param>
|
|
public void DefineImage(UINode node)
|
|
{
|
|
var assetID = node["assetID"];
|
|
|
|
try
|
|
{
|
|
var assetNum = ulong.Parse(assetID.Substring(2), NumberStyles.HexNumber);
|
|
var texture = UIElement.GetTexture(assetNum);
|
|
|
|
Textures.Add(node.ID.ToLowerInvariant(), texture);
|
|
WireUp(node.ID, texture);
|
|
}
|
|
catch
|
|
{
|
|
//might be a string. check custom
|
|
var texture = UIElement.GetTexture(assetID);
|
|
|
|
Textures.Add(node.ID.ToLowerInvariant(), texture);
|
|
WireUp(node.ID, texture);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
public string GetString(string id)
|
|
{
|
|
id = id.ToLowerInvariant();
|
|
if (Strings.ContainsKey(id)) return Strings[id];
|
|
else return "";
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Tries to assign a value created by the UIScript to a
|
|
/// property in the target class
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="value"></param>
|
|
private void WireUp(string id, object value)
|
|
{
|
|
NamedObjects.Add(id, value);
|
|
|
|
id = id.Replace("-", "");
|
|
var prop = targetType.GetProperty(id);
|
|
if (prop != null)
|
|
{
|
|
prop.SetValue(target, value, new object[] { });
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void Parse(string path)
|
|
{
|
|
var parser = new UIScriptParser();
|
|
parser.Setup();
|
|
using (var reader = File.OpenText(path))
|
|
{
|
|
var didParse = parser.Parse(reader);
|
|
if (didParse)
|
|
{
|
|
var children = (List<UINode>)parser.program;
|
|
this.Process((UIGroup)children.First());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void Process(UIGroup content)
|
|
{
|
|
/** Pre-Process **/
|
|
PreProcessUIGroup(content, new Dictionary<string, string>());
|
|
ProcessUIGroup(content);
|
|
}
|
|
|
|
|
|
private void PreProcessUIGroup(UIGroup content, Dictionary<string, string> shared)
|
|
{
|
|
var sharedNode = content.SharedProperties;
|
|
if (sharedNode != null)
|
|
{
|
|
foreach (var att in sharedNode.Attributes)
|
|
{
|
|
shared.Add(att.Key, att.Value);
|
|
}
|
|
}
|
|
|
|
var type = typeof(UIScript);
|
|
var funcSig = new Type[] { typeof(UINode) };
|
|
|
|
foreach (var child in content.Children)
|
|
{
|
|
child.AddAtts(shared);
|
|
if (child is UIGroup)
|
|
{
|
|
PreProcessUIGroup((UIGroup)child, CollectionUtils.Clone(shared));
|
|
}
|
|
|
|
if(child.ID != null && child.Name != "SetControlProperties"){
|
|
NodesByID[child.ID] = child;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ProcessUIGroup(UIGroup content)
|
|
{
|
|
|
|
var type = typeof(UIScript);
|
|
var funcSig = new Type[] { typeof(UINode) };
|
|
|
|
foreach (var child in content.Children)
|
|
{
|
|
//child.AddAtts(shared);
|
|
if (child is UIGroup)
|
|
{
|
|
ProcessUIGroup((UIGroup)child);
|
|
}
|
|
else
|
|
{
|
|
if (child.Name != null && FUNCTIONS.Contains(child.Name))
|
|
{
|
|
var m = type.GetMethod(child.Name, funcSig);
|
|
m.Invoke(this, new object[] { child });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private Dictionary<Type, Dictionary<string, UIAttField>> FieldCache
|
|
= new Dictionary<Type, Dictionary<string, UIAttField>>();
|
|
|
|
/// <summary>
|
|
/// Gets a map of string => UIAttField.
|
|
/// The map indicates what UIScript attribute name e.g. "position"
|
|
/// maps to in the target type via a [UIAttribute] decoration
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, UIAttField> GetTypeFields(Type type)
|
|
{
|
|
if (FieldCache.ContainsKey(type))
|
|
{
|
|
return FieldCache[type];
|
|
}
|
|
|
|
|
|
PropertyInfo[] infos =
|
|
type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
var idMap = new Dictionary<string, UIAttField>();
|
|
|
|
foreach (var field in infos)
|
|
{
|
|
var atts = field.GetCustomAttributes(typeof(UIAttribute), true);
|
|
if (atts.Length > 0)
|
|
{
|
|
var convertType = UIAttributeType.Unknown;
|
|
var fieldType = field.PropertyType;
|
|
|
|
if (fieldType.IsAssignableFrom(typeof(Texture2D)))
|
|
{
|
|
convertType = UIAttributeType.Texture;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(Point)))
|
|
{
|
|
convertType = UIAttributeType.Point;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(Vector2)))
|
|
{
|
|
convertType = UIAttributeType.Vector2;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(Int32)))
|
|
{
|
|
convertType = UIAttributeType.Integer;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(Boolean)))
|
|
{
|
|
convertType = UIAttributeType.Boolean;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(Color)))
|
|
{
|
|
convertType = UIAttributeType.Color;
|
|
}
|
|
else if (fieldType.IsAssignableFrom(typeof(string)))
|
|
{
|
|
convertType = UIAttributeType.String;
|
|
}
|
|
|
|
foreach (UIAttribute att in atts)
|
|
{
|
|
idMap.Add(att.Name, new UIAttField
|
|
{
|
|
Field = field,
|
|
Converter = att.DataType == UIAttributeType.Unknown ? convertType : att.DataType,
|
|
Parser = att.Parser
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
FieldCache.Add(type, idMap);
|
|
return idMap;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static Color ParseRGB(string rgb)
|
|
{
|
|
if (rgb.StartsWith("("))
|
|
{
|
|
rgb = rgb.Substring(1);
|
|
}
|
|
if (rgb.EndsWith(")"))
|
|
{
|
|
rgb = rgb.Substring(0, rgb.Length - 1);
|
|
}
|
|
|
|
var split = rgb.Split(new char[] { ',' });
|
|
return new Color(
|
|
byte.Parse(split[0]),
|
|
byte.Parse(split[1]),
|
|
byte.Parse(split[2])
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
public enum UIAttributeType
|
|
{
|
|
Point,
|
|
Texture,
|
|
Vector2,
|
|
Unknown,
|
|
StringTable,
|
|
String,
|
|
Integer,
|
|
Boolean,
|
|
Color
|
|
}
|
|
|
|
public class UIAttField
|
|
{
|
|
public PropertyInfo Field;
|
|
public UIAttributeType Converter;
|
|
public Type Parser;
|
|
}
|
|
|
|
}
|