2using System.Collections.Generic;
4using System.Reflection;
5using System.Diagnostics;
6using System.Runtime.CompilerServices;
10using Microsoft.Xna.Framework;
11using Microsoft.Xna.Framework.Input;
12using Microsoft.Xna.Framework.Graphics;
20 public partial class CUIComponent
22 public record CompareResult(
bool equal,
string firstMismatch =
"")
24 public static implicit
operator bool(CompareResult r) => r.equal;
27 public static bool DeepCompareVerbose(CUIComponent a, CUIComponent b)
29 CompareResult result = DeepCompare(a, b);
30 if (result.equal) CUI.Log($
"{a} == {b}");
31 else CUI.Log($
"{result.firstMismatch}");
34 public static CompareResult DeepCompare(CUIComponent a, CUIComponent b)
36 if (a.GetType() != b.GetType())
return new CompareResult(
false, $
"type mismatch: {a} | {b}");
39 CUITypeMetaData meta = CUITypeMetaData.Get(T);
41 foreach (var (key, pi) in meta.Serializable)
43 if (!
object.Equals(pi.GetValue(a), pi.GetValue(b)))
45 return new CompareResult(
false, $
"{pi}: {a}{pi.GetValue(a)} | {b}{pi.GetValue(b)}");
49 if (a.Children.Count != b.Children.Count)
51 return new CompareResult(
false, $
"child count mismatch: {a}{CUI.ArrayToString(a.Children)} | {b}{CUI.ArrayToString(b.Children)}");
54 for (
int i = 0; i < a.Children.Count; i++)
56 CompareResult sub = DeepCompare(a.Children[i], b.Children[i]);
57 if (!sub.equal)
return sub;
60 return new CompareResult(
true);
63 #region State --------------------------------------------------------
68 public Dictionary<string, CUIComponent>
States {
get;
set; } =
new();
76 clone.ApplyState(
this);
80 public void SaveStateAs(
string name) =>
States[name] = this.Clone();
81 public void LoadState(
string name) => ApplyState(
States.GetValueOrDefault(name));
82 public void ForgetState(
string name) =>
States.Remove(name);
85 public void ApplyState(CUIComponent state)
87 Stopwatch sw = Stopwatch.StartNew();
88 if (state ==
null)
return;
91 Type targetType = state.GetType() == GetType() ? GetType() : typeof(CUIComponent);
93 CUITypeMetaData meta = CUITypeMetaData.Get(targetType);
96 foreach (PropertyInfo pi
in meta.Serializable.Values)
98 if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string))
100 pi.SetValue(this, pi.GetValue(state));
104 object value = pi.GetValue(state);
107 pi.SetValue(this, null);
111 if (pi.PropertyType.IsAssignableTo(typeof(ICloneable)))
113 ICloneable cloneable = (ICloneable)pi.GetValue(state);
114 object clone = cloneable.Clone();
115 pi.SetValue(
this, clone);
119 CUI.Info($
"Ekhem, can't copy {pi} prop from {state} to {this} because it's not cloneable");
125 foreach (PropertyInfo pi
in meta.Serializable.Values)
127 if (pi.PropertyType.IsValueType && !object.Equals(pi.GetValue(state), pi.GetValue(this)))
129 pi.SetValue(this, pi.GetValue(state));
136 #region XML --------------------------------------------------------
138 public string SavePath {
get;
set; }
140 public virtual XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
144 if (!Serializable)
return null;
146 Type type = GetType();
148 XElement e =
new XElement(type.Name);
150 PackProps(e, propAttribute);
152 foreach (CUIComponent child
in Children)
154 if (this.SerializeChildren)
156 e.Add(child.ToXML(propAttribute));
165 return new XElement(
"Error", e.Message);
170 public virtual void FromXML(XElement element,
string baseFolder =
null)
174 foreach (XElement childElement
in element.Elements())
176 Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
177 if (childType ==
null)
continue;
179 CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
180 child.FromXML(childElement, baseFolder);
182 if (child.MergeSerialization)
186 if (child.AKA ==
null || !
this.NamedComponents.ContainsKey(child.AKA))
188 CUI.Warning($
"Can't merge {child} into {this}");
189 CUI.Warning($
"Merge deserialization requre matching AKA");
190 this.Append(child, child.AKA);
194 List<CUIComponent> children =
new List<CUIComponent>(child.Children);
195 foreach (CUIComponent c
in children)
197 this[child.AKA].Append(c);
203 CUI.Warning($
"Merge deserialization of {child} into {this}[{child.AKA}] failed");
204 CUI.Warning(e.Message);
209 if (child.AKA !=
null &&
this.NamedComponents.ContainsKey(child.AKA))
211 if (child.ReplaceSerialization)
213 this.RemoveChild(
this[child.AKA]);
217 CUI.Warning($
"{this} already contains {child.AKA}, so deserialization will create a duplicate\nTry using ReplaceSerialization or MergeSerialization");
221 this.Append(child, child.AKA);
227 ExtractProps(element, baseFolder);
230 protected void ExtractProps(XElement element,
string baseFolder =
null)
232 Type type = GetType();
234 CUITypeMetaData meta = CUITypeMetaData.Get(type);
236 foreach (XAttribute attribute
in element.Attributes())
238 if (!meta.Serializable.ContainsKey(attribute.Name.ToString()))
240 CUI.Warning($
"Can't parse prop {attribute.Name} in {type.Name} because type metadata doesn't contain that prop (is it a property? fields aren't supported yet)");
244 PropertyInfo prop = meta.Serializable[attribute.Name.ToString()];
248 if (prop.PropertyType == typeof(CUISprite) && baseFolder !=
null)
250 prop.SetValue(
this, CUISprite.ParseWithContext(attribute.Value, baseFolder));
254 prop.SetValue(
this, CUIParser.Parse(attribute.Value, prop.PropertyType,
false));
259 CUI.Warning($
"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
265 protected void PackProps(XElement element, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
267 Type type = GetType();
268 CUITypeMetaData meta = CUITypeMetaData.Get(type);
270 SortedDictionary<string, PropertyInfo> props = propAttribute
switch
272 CUIAttribute.CUISerializable => meta.Serializable,
273 CUIAttribute.Calculated => meta.Calculated,
274 _ => meta.Serializable,
277 foreach (
string key
in props.Keys)
281 object value = props[key].GetValue(
this);
283 if (!ForceSaveAllProps && meta.Default !=
null && Object.Equals(value, CUIReflection.GetNestedValue(meta.Default, key)))
288 element?.SetAttributeValue(key, CUIParser.Serialize(value,
false));
292 CUI.Warning($
"Failed to serialize prop: {e.Message}");
293 CUI.Warning($
"{key} in {this}");
299 public string Serialize(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
303 XElement e = this.ToXML(propAttribute);
312 public static CUIComponent Deserialize(
string raw,
string baseFolder =
null)
314 return Deserialize(XElement.Parse(raw));
317 public static CUIComponent Deserialize(XElement e,
string baseFolder =
null)
321 Type type = CUIReflection.GetComponentTypeByName(e.Name.ToString());
322 if (type ==
null)
return null;
324 CUIComponent c = (CUIComponent)Activator.CreateInstance(type);
326 c.FromXML(e, baseFolder);
327 CUIComponent.RunRecursiveOn(c, (component) => component.Hydrate());
338 public void LoadSelfFromFile(
string path,
bool searchForSpritesInTheSameFolder =
true,
bool saveAfterLoad =
false)
342 XDocument xdoc = XDocument.Load(path);
345 if (searchForSpritesInTheSameFolder) FromXML(xdoc.Root, Path.GetDirectoryName(path));
346 else FromXML(xdoc.Root);
348 CUIComponent.RunRecursiveOn(
this, (component) => component.Hydrate());
351 if (saveAfterLoad) SaveToTheSamePath();
359 public static CUIComponent LoadFromFile(
string path,
bool searchForSpritesInTheSameFolder =
true,
bool saveAfterLoad =
false)
363 XDocument xdoc = XDocument.Load(path);
365 if (searchForSpritesInTheSameFolder)
367 result = Deserialize(xdoc.Root, Path.GetDirectoryName(path));
369 else result = Deserialize(xdoc.Root);
371 result.SavePath = path;
373 if (saveAfterLoad) result.SaveToTheSamePath();
384 public static T LoadFromFile<T>(
string path,
bool searchForSpritesInTheSameFolder =
true,
bool saveAfterLoad =
false) where T : CUIComponent
388 XDocument xdoc = XDocument.Load(path);
390 if (searchForSpritesInTheSameFolder)
392 result = (T)Deserialize(xdoc.Root, Path.GetDirectoryName(path));
394 else result = (T)Deserialize(xdoc.Root);
396 result.SavePath = path;
398 if (saveAfterLoad) result.SaveToTheSamePath();
409 public void LoadFromTheSameFile()
411 if (SavePath ==
null)
413 CUI.Warning($
"Can't load {this} from The Same Path, SavePath is null");
416 LoadSelfFromFile(SavePath);
419 public void SaveToTheSamePath()
421 if (SavePath ==
null)
423 CUI.Warning($
"Can't save {this} To The Same Path, SavePath is null");
426 SaveToFile(SavePath);
429 public void SaveToFile(
string path, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
433 XDocument xdoc =
new XDocument();
434 xdoc.Add(this.ToXML(propAttribute));
Base class for all components.
virtual void Hydrate()
Experimental method Here you can add data/ callbacks/ save stuff to variables after loading a xml...
Dictionary< string, CUIComponent > States
State is just a clone component with copies of all props.