From cb3b3df034b892d22147b55bda3ebc47169e1618 Mon Sep 17 00:00:00 2001 From: TechPizza Date: Sat, 20 Feb 2021 17:01:03 +0100 Subject: [PATCH 1/6] Added simple markup diagnostics --- src/Myra/Graphics2D/UI/Project.cs | 786 ++-- src/Myra/Graphics2D/UI/Styles/Stylesheet.cs | 17 +- .../Graphics2D/UI/Styles/StylesheetLoader.cs | 95 +- src/Myra/MML/LoadContext.cs | 501 +-- src/Myra/MML/MMLDiagnostic.cs | 24 + src/Myra/MML/MMLDiagnosticSeverity.cs | 10 + src/Myra/MML/SaveContext.cs | 2 +- src/MyraPad/MyraPad.csproj | 1 + src/MyraPad/Studio.cs | 3219 +++++++++-------- 9 files changed, 2391 insertions(+), 2264 deletions(-) create mode 100644 src/Myra/MML/MMLDiagnostic.cs create mode 100644 src/Myra/MML/MMLDiagnosticSeverity.cs diff --git a/src/Myra/Graphics2D/UI/Project.cs b/src/Myra/Graphics2D/UI/Project.cs index e2161ab1..f394c5aa 100644 --- a/src/Myra/Graphics2D/UI/Project.cs +++ b/src/Myra/Graphics2D/UI/Project.cs @@ -18,391 +18,403 @@ namespace Myra.Graphics2D.UI { - public class ExportOptions - { - public string Namespace { get; set; } - public string Class { get; set; } - public string OutputPath { get; set; } - public string TemplateDesigner { get; set; } - public string TemplateMain { get; set; } - } - - public class Project - { - public const string ProportionName = "Proportion"; - public const string DefaultProportionName = "DefaultProportion"; - public const string DefaultColumnProportionName = "DefaultColumnProportion"; - public const string DefaultRowProportionName = "DefaultRowProportion"; - - private static readonly Dictionary LegacyClassNames = new Dictionary(); - - private readonly ExportOptions _exportOptions = new ExportOptions(); - - [Browsable(false)] - public ExportOptions ExportOptions - { - get { return _exportOptions; } - } - - [Browsable(false)] - [Content] - public Widget Root { get; set; } - - [Browsable(false)] - public string StylesheetPath - { - get; set; - } - - [Browsable(false)] - [XmlIgnore] - public Stylesheet Stylesheet { get; set; } - - static Project() - { - LegacyClassNames["Button"] = "ImageTextButton"; - LegacyClassNames["VerticalBox"] = "VerticalStackPanel"; - LegacyClassNames["HorizontalBox"] = "HorizontalStackPanel"; - LegacyClassNames["TextField"] = "TextBox"; - LegacyClassNames["TextBlock"] = "Label"; - LegacyClassNames["ScrollPane"] = "ScrollViewer"; - } - - public Project() - { - Stylesheet = Stylesheet.Current; - } - - public static bool IsProportionName(string s) - { - return s.EndsWith(ProportionName) || - s.EndsWith(DefaultProportionName) || - s.EndsWith(DefaultColumnProportionName) || - s.EndsWith(DefaultRowProportionName); - } - - public static bool ShouldSerializeProperty(Stylesheet stylesheet, object o, PropertyInfo p) - { - var asWidget = o as Widget; - if (asWidget != null && asWidget.Parent != null && asWidget.Parent is Grid) - { - var container = asWidget.Parent.Parent; - if (container != null && - (container is StackPanel || container is SplitPane) && - (p.Name == "GridRow" || p.Name == "GridColumn")) - { - // Skip serializing auto-assigned GridRow/GridColumn for SplitPane and Box containers - return false; - } - } - - var asGrid = o as Grid; - if (asGrid != null) - { - var value = p.GetValue(o); - if ((p.Name == DefaultColumnProportionName || p.Name == DefaultRowProportionName) && - value == Proportion.GridDefault) - { - return false; - } - } - - var asBox = o as StackPanel; - if (asBox != null) - { - var value = p.GetValue(o); - if (p.Name == DefaultProportionName && value == Proportion.StackPanelDefault) - { - return false; - } - } - - if (SaveContext.HasDefaultValue(o, p)) - { - return false; - } - - if(asWidget != null && HasStylesheetValue(asWidget, p, stylesheet)) - { - return false; - } - - return true; - } - - public bool ShouldSerializeProperty(object o, PropertyInfo p) - { - return ShouldSerializeProperty(Stylesheet, o, p); - } - - internal static SaveContext CreateSaveContext(Stylesheet stylesheet) - { - return new SaveContext - { - ShouldSerializeProperty = (o, p) => ShouldSerializeProperty(stylesheet, o, p) - }; - } - - internal SaveContext CreateSaveContext() - { - return CreateSaveContext(Stylesheet); - } - - internal static LoadContext CreateLoadContext(IAssetManager assetManager, Stylesheet stylesheet) - { - Func resourceGetter = (t, name) => - { - if (t == typeof(IBrush)) - { - return new SolidBrush(name); - } - else if (t == typeof(IImage)) - { - return assetManager.Load(name); - } - else if (t == typeof(SpriteFontBase)) - { - return assetManager.Load(name); - } - - throw new Exception(string.Format("Type {0} isn't supported", t.Name)); - }; - - return new LoadContext - { - Namespaces = new[] - { - typeof(Widget).Namespace, - typeof(PropertyGrid).Namespace, - }, - LegacyClassNames = LegacyClassNames, - ObjectCreator = (t, el) => CreateItem(t, el, stylesheet), - ResourceGetter = resourceGetter - }; - } - - internal LoadContext CreateLoadContext(IAssetManager assetManager) - { - return CreateLoadContext(assetManager, Stylesheet); - } - - public string Save() - { - var saveContext = CreateSaveContext(); - var root = saveContext.Save(this); - - var xDoc = new XDocument(root); - - return xDoc.ToString(); - } - - public static Project LoadFromXml(XDocument xDoc, IAssetManager assetManager, Stylesheet stylesheet) - { - var result = new Project - { - Stylesheet = stylesheet - }; - - var loadContext = result.CreateLoadContext(assetManager); - loadContext.Load(result, xDoc.Root); - - return result; - } - - public static Project LoadFromXml(string data, IAssetManager assetManager, Stylesheet stylesheet) - { - return LoadFromXml(XDocument.Parse(data), assetManager, stylesheet); - } - - public static Project LoadFromXml(string data, IAssetManager assetManager = null) - { - return LoadFromXml(data, assetManager, Stylesheet.Current); - } - - public static object LoadObjectFromXml(string data, IAssetManager assetManager, Stylesheet stylesheet) - { - XDocument xDoc = XDocument.Parse(data); - - var name = xDoc.Root.Name.ToString(); - Type itemType; - - if (name == "PropertyGrid") - { - itemType = typeof(PropertyGrid); - } else if (!IsProportionName(name)) - { - string newName; - if (LegacyClassNames.TryGetValue(name, out newName)) - { - name = newName; - } - - var itemNamespace = typeof(Widget).Namespace; - itemType = typeof(Widget).Assembly.GetType(itemNamespace + "." + name); - } - else - { - itemType = typeof(Proportion); - } - - if (itemType == null) - { - return null; - } - - var item = CreateItem(itemType, xDoc.Root, stylesheet); - var loadContext = CreateLoadContext(assetManager, stylesheet); - loadContext.Load(item, xDoc.Root); - - return item; - } - - public object LoadObjectFromXml(string data, IAssetManager assetManager) - { - return LoadObjectFromXml(data, assetManager, Stylesheet); - } - - public string SaveObjectToXml(object obj, string tagName) - { - var saveContext = CreateSaveContext(Stylesheet); - return saveContext.Save(obj, true, tagName).ToString(); - } - - private static object CreateItem(Type type, XElement element, Stylesheet stylesheet) - { - if (typeof(Widget).IsAssignableFrom(type)) - { - // Check whether it accepts style name parameter - var acceptsStyleName = false; - foreach (var c in type.GetConstructors()) - { - var p = c.GetParameters(); - if (p != null && p.Length == 1) - { - if (p[0].ParameterType == typeof(string)) - { - acceptsStyleName = true; - break; - } - } - } - - if (acceptsStyleName) - { - var result = (Widget)Activator.CreateInstance(type, (string)null); - - // Determine style name - var styleName = Stylesheet.DefaultStyleName; - var styleNameAttr = element.Attribute("StyleName"); - if (styleNameAttr != null) - { - var stylesNames = stylesheet.GetStylesByWidgetName(type.Name); - if (stylesNames != null && stylesNames.Contains(styleNameAttr.Value)) - { - styleName = styleNameAttr.Value; - } - else - { - // Remove property with absent value - styleNameAttr.Remove(); - } - } - - // Set style - result.SetStyle(stylesheet, styleName); - - return result; - } - } - - return Activator.CreateInstance(type); - } - - private static bool HasStylesheetValue(Widget w, PropertyInfo property, Stylesheet stylesheet) - { - if (stylesheet == null) - { - return false; - } - - var styleName = w.StyleName; - if (string.IsNullOrEmpty(styleName)) - { - styleName = Stylesheet.DefaultStyleName; - } - - // Find styles dict of that widget - var typeName = w.GetType().Name; - var styleTypeNameAttribute = w.GetType().FindAttribute(); - if (styleTypeNameAttribute != null) - { - typeName = styleTypeNameAttribute.Name; - } - - var stylesDictPropertyName = typeName + "Styles"; - var stylesDictProperty = stylesheet.GetType().GetRuntimeProperty(stylesDictPropertyName); - if (stylesDictProperty == null) - { - return false; - } - - var stylesDict = (IDictionary)stylesDictProperty.GetValue(stylesheet); - if (stylesDict == null) - { - return false; - } - - // Fetch style from the dict - if (!stylesDict.Contains(styleName)) - { - styleName = Stylesheet.DefaultStyleName; - } - - object obj = stylesDict[styleName]; - - // Now find corresponding property - PropertyInfo styleProperty = null; - - var stylePropertyPathAttribute = property.FindAttribute(); - if (stylePropertyPathAttribute != null) - { - var path = stylePropertyPathAttribute.Name; - if (path.StartsWith("/")) - { - obj = stylesheet; - path = path.Substring(1); - } - - var parts = path.Split('/'); - for (var i = 0; i < parts.Length; ++i) - { - styleProperty = obj.GetType().GetRuntimeProperty(parts[i]); - - if (i < parts.Length - 1) - { - obj = styleProperty.GetValue(obj); - } - } - } - else - { - styleProperty = obj.GetType().GetRuntimeProperty(property.Name); - } - - if (styleProperty == null) - { - return false; - } - - // Compare values - var styleValue = styleProperty.GetValue(obj); - var value = property.GetValue(w); - if (!Equals(styleValue, value)) - { - return false; - } - - return true; - } - } + public class ExportOptions + { + public string Namespace { get; set; } + public string Class { get; set; } + public string OutputPath { get; set; } + public string TemplateDesigner { get; set; } + public string TemplateMain { get; set; } + } + + public class Project + { + public const string ProportionName = "Proportion"; + public const string DefaultProportionName = "DefaultProportion"; + public const string DefaultColumnProportionName = "DefaultColumnProportion"; + public const string DefaultRowProportionName = "DefaultRowProportion"; + + private static readonly Dictionary LegacyClassNames = new Dictionary(); + + private readonly ExportOptions _exportOptions = new ExportOptions(); + private readonly List _diagnostics = new List(); + + [Browsable(false)] + public ExportOptions ExportOptions + { + get { return _exportOptions; } + } + + [Browsable(false)] + [Content] + public Widget Root { get; set; } + + [Browsable(false)] + public string StylesheetPath + { + get; set; + } + + [Browsable(false)] + [XmlIgnore] + public Stylesheet Stylesheet { get; set; } + + [Browsable(false)] + [XmlIgnore] + public List Diagnostics + { + get { return _diagnostics; } + } + + static Project() + { + LegacyClassNames["Button"] = "ImageTextButton"; + LegacyClassNames["VerticalBox"] = "VerticalStackPanel"; + LegacyClassNames["HorizontalBox"] = "HorizontalStackPanel"; + LegacyClassNames["TextField"] = "TextBox"; + LegacyClassNames["TextBlock"] = "Label"; + LegacyClassNames["ScrollPane"] = "ScrollViewer"; + } + + public Project() + { + Stylesheet = Stylesheet.Current; + } + + public static bool IsProportionName(string s) + { + return s.EndsWith(ProportionName) || + s.EndsWith(DefaultProportionName) || + s.EndsWith(DefaultColumnProportionName) || + s.EndsWith(DefaultRowProportionName); + } + + public static bool ShouldSerializeProperty(Stylesheet stylesheet, object o, PropertyInfo p) + { + var asWidget = o as Widget; + if (asWidget != null && asWidget.Parent != null && asWidget.Parent is Grid) + { + var container = asWidget.Parent.Parent; + if (container != null && + (container is StackPanel || container is SplitPane) && + (p.Name == "GridRow" || p.Name == "GridColumn")) + { + // Skip serializing auto-assigned GridRow/GridColumn for SplitPane and Box containers + return false; + } + } + + var asGrid = o as Grid; + if (asGrid != null) + { + var value = p.GetValue(o); + if ((p.Name == DefaultColumnProportionName || p.Name == DefaultRowProportionName) && + value == Proportion.GridDefault) + { + return false; + } + } + + var asBox = o as StackPanel; + if (asBox != null) + { + var value = p.GetValue(o); + if (p.Name == DefaultProportionName && value == Proportion.StackPanelDefault) + { + return false; + } + } + + if (SaveContext.HasDefaultValue(o, p)) + { + return false; + } + + if (asWidget != null && HasStylesheetValue(asWidget, p, stylesheet)) + { + return false; + } + + return true; + } + + public bool ShouldSerializeProperty(object o, PropertyInfo p) + { + return ShouldSerializeProperty(Stylesheet, o, p); + } + + internal static SaveContext CreateSaveContext(Stylesheet stylesheet) + { + return new SaveContext + { + ShouldSerializeProperty = (o, p) => ShouldSerializeProperty(stylesheet, o, p) + }; + } + + internal SaveContext CreateSaveContext() + { + return CreateSaveContext(Stylesheet); + } + + internal static LoadContext CreateLoadContext(IAssetManager assetManager, Stylesheet stylesheet) + { + Func resourceGetter = (t, name) => + { + if (t == typeof(IBrush)) + { + return new SolidBrush(name); + } + else if (t == typeof(IImage)) + { + return assetManager.Load(name); + } + else if (t == typeof(SpriteFontBase)) + { + return assetManager.Load(name); + } + + throw new Exception(string.Format("Type {0} isn't supported", t.Name)); + }; + + return new LoadContext + { + Namespaces = new[] + { + typeof(Widget).Namespace, + typeof(PropertyGrid).Namespace, + }, + LegacyClassNames = LegacyClassNames, + ObjectCreator = (t, el) => CreateItem(t, el, stylesheet), + ResourceGetter = resourceGetter + }; + } + + internal LoadContext CreateLoadContext(IAssetManager assetManager) + { + return CreateLoadContext(assetManager, Stylesheet); + } + + public string Save() + { + var saveContext = CreateSaveContext(); + var root = saveContext.Save(this); + + var xDoc = new XDocument(root); + + return xDoc.ToString(); + } + + public static Project LoadFromXml( + XDocument xDoc, IAssetManager assetManager, Stylesheet stylesheet) + { + var result = new Project + { + Stylesheet = stylesheet + }; + + var loadContext = result.CreateLoadContext(assetManager); + + loadContext.Load(result, xDoc.Root, (d) => result.Diagnostics.Add(d)); + + return result; + } + + public static Project LoadFromXml(string data, IAssetManager assetManager, Stylesheet stylesheet) + { + return LoadFromXml(XDocument.Parse(data), assetManager, stylesheet); + } + + public static Project LoadFromXml(string data, IAssetManager assetManager = null) + { + return LoadFromXml(data, assetManager, Stylesheet.Current); + } + + public static object LoadObjectFromXml( + string data, IAssetManager assetManager, Stylesheet stylesheet, MMLDiagnosticAction onDiagnostic = null) + { + XDocument xDoc = XDocument.Parse(data); + + var name = xDoc.Root.Name.ToString(); + Type itemType; + + if (name == "PropertyGrid") + { + itemType = typeof(PropertyGrid); + } + else if (!IsProportionName(name)) + { + string newName; + if (LegacyClassNames.TryGetValue(name, out newName)) + { + name = newName; + } + + var itemNamespace = typeof(Widget).Namespace; + itemType = typeof(Widget).Assembly.GetType(itemNamespace + "." + name); + } + else + { + itemType = typeof(Proportion); + } + + if (itemType == null) + { + return null; + } + + var item = CreateItem(itemType, xDoc.Root, stylesheet); + var loadContext = CreateLoadContext(assetManager, stylesheet); + loadContext.Load(item, xDoc.Root, onDiagnostic); + + return item; + } + + public object LoadObjectFromXml(string data, IAssetManager assetManager) + { + return LoadObjectFromXml(data, assetManager, Stylesheet); + } + + public string SaveObjectToXml(object obj, string tagName) + { + var saveContext = CreateSaveContext(Stylesheet); + return saveContext.Save(obj, true, tagName).ToString(); + } + + private static object CreateItem(Type type, XElement element, Stylesheet stylesheet) + { + if (typeof(Widget).IsAssignableFrom(type)) + { + // Check whether it accepts style name parameter + var acceptsStyleName = false; + foreach (var c in type.GetConstructors()) + { + var p = c.GetParameters(); + if (p != null && p.Length == 1) + { + if (p[0].ParameterType == typeof(string)) + { + acceptsStyleName = true; + break; + } + } + } + + if (acceptsStyleName) + { + var result = (Widget)Activator.CreateInstance(type, (string)null); + + // Determine style name + var styleName = Stylesheet.DefaultStyleName; + var styleNameAttr = element.Attribute("StyleName"); + if (styleNameAttr != null) + { + var stylesNames = stylesheet.GetStylesByWidgetName(type.Name); + if (stylesNames != null && stylesNames.Contains(styleNameAttr.Value)) + { + styleName = styleNameAttr.Value; + } + else + { + // Remove property with absent value + styleNameAttr.Remove(); + } + } + + // Set style + result.SetStyle(stylesheet, styleName); + + return result; + } + } + + return Activator.CreateInstance(type); + } + + private static bool HasStylesheetValue(Widget w, PropertyInfo property, Stylesheet stylesheet) + { + if (stylesheet == null) + { + return false; + } + + var styleName = w.StyleName; + if (string.IsNullOrEmpty(styleName)) + { + styleName = Stylesheet.DefaultStyleName; + } + + // Find styles dict of that widget + var typeName = w.GetType().Name; + var styleTypeNameAttribute = w.GetType().FindAttribute(); + if (styleTypeNameAttribute != null) + { + typeName = styleTypeNameAttribute.Name; + } + + var stylesDictPropertyName = typeName + "Styles"; + var stylesDictProperty = stylesheet.GetType().GetRuntimeProperty(stylesDictPropertyName); + if (stylesDictProperty == null) + { + return false; + } + + var stylesDict = (IDictionary)stylesDictProperty.GetValue(stylesheet); + if (stylesDict == null) + { + return false; + } + + // Fetch style from the dict + if (!stylesDict.Contains(styleName)) + { + styleName = Stylesheet.DefaultStyleName; + } + + object obj = stylesDict[styleName]; + + // Now find corresponding property + PropertyInfo styleProperty = null; + + var stylePropertyPathAttribute = property.FindAttribute(); + if (stylePropertyPathAttribute != null) + { + var path = stylePropertyPathAttribute.Name; + if (path.StartsWith("/")) + { + obj = stylesheet; + path = path.Substring(1); + } + + var parts = path.Split('/'); + for (var i = 0; i < parts.Length; ++i) + { + styleProperty = obj.GetType().GetRuntimeProperty(parts[i]); + + if (i < parts.Length - 1) + { + obj = styleProperty.GetValue(obj); + } + } + } + else + { + styleProperty = obj.GetType().GetRuntimeProperty(property.Name); + } + + if (styleProperty == null) + { + return false; + } + + // Compare values + var styleValue = styleProperty.GetValue(obj); + var value = property.GetValue(w); + if (!Equals(styleValue, value)) + { + return false; + } + + return true; + } + } } \ No newline at end of file diff --git a/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs b/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs index c9398c62..bcb00641 100644 --- a/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs +++ b/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs @@ -573,9 +573,12 @@ private static void SetDefaultStyle(Dictionary styles, T value) wh styles[DefaultStyleName] = value; } - public static Stylesheet LoadFromSource(string stylesheetXml, + + public static Stylesheet LoadFromSource( + string stylesheetXml, TextureRegionAtlas textureRegionAtlas, - Dictionary fonts) + Dictionary fonts, + MMLDiagnosticAction onDiagnostic) { var xDoc = XDocument.Parse(stylesheetXml); @@ -641,11 +644,19 @@ public static Stylesheet LoadFromSource(string stylesheetXml, Colors = colors }; - loadContext.Load(result, xDoc.Root); + loadContext.Load(result, xDoc.Root, onDiagnostic); return result; } + public static Stylesheet LoadFromSource( + string stylesheetXml, + TextureRegionAtlas textureRegionAtlas, + Dictionary fonts) + { + return LoadFromSource(stylesheetXml, textureRegionAtlas, fonts, null); + } + public string[] GetStylesByWidgetName(string name) { // Special case diff --git a/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs b/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs index 3d3bc24a..cf9386c8 100644 --- a/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs +++ b/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs @@ -8,57 +8,62 @@ namespace Myra.Graphics2D.UI.Styles { - public class StylesheetLoader : IAssetLoader - { - public Stylesheet Load(AssetLoaderContext context, string assetName) - { - var xml = context.Load(assetName); + public class StylesheetLoader : IAssetLoader + { + public Stylesheet Load(AssetLoaderContext context, string assetName) + { + return Load(context, assetName, null); + } - var xDoc = XDocument.Parse(xml); - var attr = xDoc.Root.Attribute("TextureRegionAtlas"); - if (attr == null) - { - throw new Exception("Mandatory attribute 'TextureRegionAtlas' doesnt exist"); - } + public Stylesheet Load(AssetLoaderContext context, string assetName, MMLDiagnosticAction onDiagnostic) + { + var xml = context.Load(assetName); - var textureRegionAtlas = context.Load(attr.Value); + var xDoc = XDocument.Parse(xml); + var attr = xDoc.Root.Attribute("TextureRegionAtlas"); + if (attr == null) + { + throw new Exception("Mandatory attribute 'TextureRegionAtlas' doesnt exist"); + } - // Load fonts - var fonts = new Dictionary(); - var fontsNode = xDoc.Root.Element("Fonts"); - foreach (var el in fontsNode.Elements()) - { - SpriteFontBase font = null; + var textureRegionAtlas = context.Load(attr.Value); - var fontFile = el.Attribute("File").Value; - if (fontFile.EndsWith(".ttf")) - { - var parts = new List(); - parts.Add(fontFile); - - var typeAttribute = el.Attribute("Type"); - if (typeAttribute != null) - { - parts.Add(typeAttribute.Value); + // Load fonts + var fonts = new Dictionary(); + var fontsNode = xDoc.Root.Element("Fonts"); + foreach (var el in fontsNode.Elements()) + { + SpriteFontBase font = null; - var amountAttribute = el.Attribute("Amount"); - parts.Add(amountAttribute.Value); - } + var fontFile = el.Attribute("File").Value; + if (fontFile.EndsWith(".ttf")) + { + var parts = new List(); + parts.Add(fontFile); + + var typeAttribute = el.Attribute("Type"); + if (typeAttribute != null) + { + parts.Add(typeAttribute.Value); - parts.Add(el.Attribute("Size").Value); - font = context.Load(string.Join(":", parts)); - } else if (fontFile.EndsWith(".fnt")) - { - font = context.Load(fontFile); - } else - { - throw new Exception(string.Format("Font '{0}' isn't supported", fontFile)); - } + var amountAttribute = el.Attribute("Amount"); + parts.Add(amountAttribute.Value); + } - fonts[el.Attribute(BaseContext.IdName).Value] = font; - } + parts.Add(el.Attribute("Size").Value); + font = context.Load(string.Join(":", parts)); + } else if (fontFile.EndsWith(".fnt")) + { + font = context.Load(fontFile); + } else + { + throw new Exception(string.Format("Font '{0}' isn't supported", fontFile)); + } - return Stylesheet.LoadFromSource(xml, textureRegionAtlas, fonts); - } - } + fonts[el.Attribute(BaseContext.IdName).Value] = font; + } + + return Stylesheet.LoadFromSource(xml, textureRegionAtlas, fonts, onDiagnostic); + } + } } \ No newline at end of file diff --git a/src/Myra/MML/LoadContext.cs b/src/Myra/MML/LoadContext.cs index 9920afc6..7560c0a3 100644 --- a/src/Myra/MML/LoadContext.cs +++ b/src/Myra/MML/LoadContext.cs @@ -21,253 +21,306 @@ namespace Myra.MML { - internal class LoadContext: BaseContext - { - public Dictionary LegacyClassNames = null; - public Dictionary LegacyPropertyNames = null; - public Dictionary Colors; - public HashSet NodesToIgnore = null; - public Func ObjectCreator = (type, el) => Activator.CreateInstance(type); - public string[] Namespaces; - public Assembly Assembly = typeof(Widget).Assembly; - public Func ResourceGetter = null; + internal class LoadContext : BaseContext + { + public Dictionary LegacyClassNames = null; + public Dictionary LegacyPropertyNames = null; + public Dictionary Colors; + public HashSet NodesToIgnore = null; + public Func ObjectCreator = (type, el) => Activator.CreateInstance(type); + public string[] Namespaces; + public Assembly Assembly = typeof(Widget).Assembly; + public Func ResourceGetter = null; - private const string UserDataAttributePrefix = "_"; + private const string UserDataAttributePrefix = "_"; - public void Load(object obj, XElement el) - { - var type = obj.GetType(); + public void Load(object obj, XElement el, MMLDiagnosticAction onDiagnostic) + { + if (onDiagnostic == null) + onDiagnostic = (d) => throw new Exception(d.Message); - var baseObject = obj as BaseObject; + try + { + var type = obj.GetType(); - List complexProperties, simpleProperties; - ParseProperties(type, out complexProperties, out simpleProperties); + var baseObject = obj as BaseObject; - string newName; - foreach (var attr in el.Attributes()) - { - var propertyName = attr.Name.ToString(); - if (LegacyPropertyNames != null && LegacyPropertyNames.TryGetValue(propertyName, out newName)) - { - propertyName = newName; - } + List complexProperties, simpleProperties; + ParseProperties(type, out complexProperties, out simpleProperties); - var property = (from p in simpleProperties where p.Name == propertyName select p).FirstOrDefault(); + string newName; + foreach (XAttribute attr in el.Attributes()) + { + var propertyName = attr.Name.ToString(); + if (LegacyPropertyNames != null && LegacyPropertyNames.TryGetValue(propertyName, out newName)) + { + propertyName = newName; + } - if (property != null) - { - object value = null; + PropertyInfo property = + (from p in simpleProperties where p.Name == propertyName select p).FirstOrDefault(); - var propertyType = property.PropertyType; - if (propertyType.IsEnum) - { - value = Enum.Parse(propertyType, attr.Value); - } - else if (propertyType == typeof(Color) || propertyType == typeof(Color?)) - { - Color color; - if (Colors != null && Colors.TryGetValue(attr.Value, out color)) - { - value = color; - } - else - { - value = ColorStorage.FromName(attr.Value); - if (value == null) - { - throw new Exception(string.Format("Could not find parse color '{0}'", attr.Value)); - } - } - } - else if ((typeof(IBrush).IsAssignableFrom(propertyType) || - propertyType == typeof(SpriteFontBase)) && - !string.IsNullOrEmpty(attr.Value) && - ResourceGetter != null) - { - try - { - var texture = ResourceGetter(propertyType, attr.Value); - if (texture == null) - { - throw new Exception(string.Format("Could not find resource '{0}'", attr.Value)); - } - value = texture; + if (property != null) + { + object value = null; + MMLDiagnostic pdiagnostic = null; - if (baseObject != null) - { - baseObject.Resources[property.Name] = attr.Value; - } - } - catch (Exception) - { - } - } - else if (propertyType == typeof(Thickness)) - { - try - { - value = Thickness.FromString(attr.Value); - } - catch (Exception) - { - } - } - else - { - if (propertyType.IsNullablePrimitive()) - { - propertyType = propertyType.GetNullableType(); - } + var propertyType = property.PropertyType; + if (propertyType.IsEnum) + { + try + { + value = Enum.Parse(propertyType, attr.Value); + } + catch + { + pdiagnostic = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Failed to parse {0}: '{1}'", propertyType.Name, attr.Value)); + continue; + } + } + else if (propertyType == typeof(Color) || propertyType == typeof(Color?)) + { + Color color; + if (Colors != null && Colors.TryGetValue(attr.Value, out color)) + { + value = color; + } + else + { + value = ColorStorage.FromName(attr.Value); + if (value == null) + { + pdiagnostic = (new MMLDiagnostic(MMLDiagnosticSeverity.Warning, "", "", string.Format( + "Failed to parse Color: '{0}'", attr.Value))); + } + } + } + else if ((typeof(IBrush).IsAssignableFrom(propertyType) || + typeof(SpriteFontBase).IsAssignableFrom(propertyType)) && + !string.IsNullOrEmpty(attr.Value) && + ResourceGetter != null) + { + var texture = ResourceGetter(propertyType, attr.Value); + if (texture == null) + { + pdiagnostic = (new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Could not find resource '{0}'", attr.Value))); + continue; + } + value = texture; - value = Convert.ChangeType(attr.Value, propertyType, CultureInfo.InvariantCulture); - } + if (baseObject != null) + { + baseObject.Resources[property.Name] = attr.Value; + } + } + else if (propertyType == typeof(Thickness)) + { + try + { + value = Thickness.FromString(attr.Value); + } + catch (Exception ex) + { + pdiagnostic = (new MMLDiagnostic(MMLDiagnosticSeverity.Warning, "", "", string.Format( + "Failed to parse Thickness: {0}", ex.Message))); + } + } + else + { + if (propertyType.IsNullablePrimitive()) + { + propertyType = propertyType.GetNullableType(); + } - property.SetValue(obj, value); - } - else - { - // Stow away custom user attributes - if (propertyName.StartsWith(UserDataAttributePrefix) && baseObject != null) - { - baseObject.UserData.Add(propertyName, attr.Value); - } - } - } + try + { + value = Convert.ChangeType(attr.Value, propertyType, CultureInfo.InvariantCulture); + } + catch (Exception ex) + { + pdiagnostic = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Failed to convert '{0}' to type '{1}': {2}", attr.Value, propertyType, ex.Message)); + } + } - var contentProperty = (from p in complexProperties - where p.FindAttribute() - != null select p).FirstOrDefault(); + if (pdiagnostic != null) + { + pdiagnostic.TargetElements.Add(attr); + onDiagnostic.Invoke(pdiagnostic); + } + else + { + property.SetValue(obj, value); + } + } + else + { + // Stow away custom user attributes + if (propertyName.StartsWith(UserDataAttributePrefix) && baseObject != null) + { + baseObject.UserData.Add(propertyName, attr.Value); + } + } + } - foreach (var child in el.Elements()) - { - var childName = child.Name.ToString(); - if (NodesToIgnore != null && NodesToIgnore.Contains(childName)) - { - continue; - } + PropertyInfo contentProperty = + (from p in complexProperties + where p.FindAttribute() != null + select p).FirstOrDefault(); - var isProperty = false; - if (childName.Contains(".")) - { - // Property name - var parts = childName.Split('.'); - childName = parts[1]; - isProperty = true; - } + foreach (XElement child in el.Elements()) + { + var childName = child.Name.ToString(); + if (NodesToIgnore != null && NodesToIgnore.Contains(childName)) + { + continue; + } - if (LegacyPropertyNames != null && LegacyPropertyNames.TryGetValue(childName, out newName)) - { - childName = newName; - } + var isProperty = false; + if (childName.Contains(".")) + { + // Property name + var parts = childName.Split('.'); + childName = parts[1]; + isProperty = true; + } - // Find property - var property = (from p in complexProperties where p.Name == childName select p).FirstOrDefault(); - if (property != null) - { - do - { - var value = property.GetValue(obj); - var asList = value as IList; - if (asList != null) - { - // List - foreach (var child2 in child.Elements()) - { - var item = ObjectCreator(property.PropertyType.GenericTypeArguments[0], child2); - Load(item, child2); - asList.Add(item); - } + if (LegacyPropertyNames != null && LegacyPropertyNames.TryGetValue(childName, out newName)) + { + childName = newName; + } - break; - } + // Find property + var property = (from p in complexProperties where p.Name == childName select p).FirstOrDefault(); + if (property != null) + { + do + { + var value = property.GetValue(obj); + var asList = value as IList; + if (asList != null) + { + // List + foreach (XElement child2 in child.Elements()) + { + var item = ObjectCreator(property.PropertyType.GenericTypeArguments[0], child2); + Load(item, child2, onDiagnostic); + asList.Add(item); + } + break; + } - var asDict = value as IDictionary; - if (asDict != null) - { - // Dict - foreach (var child2 in child.Elements()) - { - var item = ObjectCreator(property.PropertyType.GenericTypeArguments[1], child2); - Load(item, child2); + var asDict = value as IDictionary; + if (asDict != null) + { + // Dict + foreach (XElement child2 in child.Elements()) + { + var item = ObjectCreator(property.PropertyType.GenericTypeArguments[1], child2); + Load(item, child2, onDiagnostic); - var id = string.Empty; - if (child2.Attribute(IdName) != null) - { - id = child2.Attribute(IdName).Value; - } + var id = string.Empty; + if (child2.Attribute(IdName) != null) + { + id = child2.Attribute(IdName).Value; + } - asDict[id] = item; - } + asDict[id] = item; + } + break; + } - break; - } + if (property.SetMethod == null) + { + // Readonly + Load(value, child, onDiagnostic); + } + else + { + var newValue = ObjectCreator(property.PropertyType, child); + Load(newValue, child, onDiagnostic); + property.SetValue(obj, newValue); + } + break; + } + while (true); + } + else + { + // Property not found + if (isProperty) + { + var diagnostic = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Class '{0}' doesnt have property '{1}'", type.Name, childName)); + diagnostic.TargetElements.Add(child); + onDiagnostic.Invoke(diagnostic); + continue; + } - if (property.SetMethod == null) - { - // Readonly - Load(value, child); - } - else - { - var newValue = ObjectCreator(property.PropertyType, child); - Load(newValue, child); - property.SetValue(obj, newValue); - } - break; - } while (true); - } - else - { - // Property not found - if (isProperty) - { - throw new Exception(string.Format("Class {0} doesnt have property {1}", type.Name, childName)); - } + // Should be widget class name then + var widgetName = childName; + if (LegacyClassNames != null && LegacyClassNames.TryGetValue(widgetName, out newName)) + { + widgetName = newName; + } - // Should be widget class name then - var widgetName = childName; - if (LegacyClassNames != null && LegacyClassNames.TryGetValue(widgetName, out newName)) - { - widgetName = newName; - } + Type itemType = null; + foreach (string ns in Namespaces) + { + itemType = Assembly.GetType(ns + "." + widgetName); + if (itemType != null) + { + break; + } + } + if (itemType != null) + { + var item = ObjectCreator(itemType, child); + Load(item, child, onDiagnostic); - Type itemType = null; - foreach(var ns in Namespaces) - { - itemType = Assembly.GetType(ns + "." + widgetName); - if (itemType != null) - { - break; - } - } - if (itemType != null) - { - var item = ObjectCreator(itemType, child); - Load(item, child); + if (contentProperty == null) + { + var diagnostic = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Class '{0}' lacks property marked with ContentAttribute", type.Name)); + diagnostic.TargetElements.Add(child); + onDiagnostic.Invoke(diagnostic); + } + else + { + var containerValue = contentProperty.GetValue(obj); + var asList = containerValue as IList; + if (asList != null) + { + // List + asList.Add(item); + } + else + { + // Simple + contentProperty.SetValue(obj, item); + } + } + } + else + { + var diagnostic = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Could not resolve tag '{0}'", widgetName)); + diagnostic.TargetElements.Add(child); + onDiagnostic.Invoke(diagnostic); + } + } + } + } + catch (Exception ex) + { + var d = new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Failed to load: '{0}'", ex.Message)); + d.TargetElements.Add(el); - if (contentProperty == null) - { - throw new Exception(string.Format("Class {0} lacks property marked with ContentAttribute", type.Name)); - } - - var containerValue = contentProperty.GetValue(obj); - var asList = containerValue as IList; - if (asList != null) - { - // List - asList.Add(item); - } else - { - // Simple - contentProperty.SetValue(obj, item); - } - } - else - { - throw new Exception(string.Format("Could not resolve tag '{0}'", widgetName)); - } - } - } - } - } + onDiagnostic.Invoke(d); + } + } + } } diff --git a/src/Myra/MML/MMLDiagnostic.cs b/src/Myra/MML/MMLDiagnostic.cs new file mode 100644 index 00000000..6f39a730 --- /dev/null +++ b/src/Myra/MML/MMLDiagnostic.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Myra.MML +{ + public delegate void MMLDiagnosticAction(MMLDiagnostic diagnostic); + + public class MMLDiagnostic + { + public List TargetElements { get; } = new List(); + public MMLDiagnosticSeverity Severity { get; } + public string Group { get; } + public string ID { get; } + public string Message { get; } + + public MMLDiagnostic(MMLDiagnosticSeverity severity, string group, string id, string message) + { + Severity = severity; + Group = group; + ID = id; + Message = message; + } + } +} diff --git a/src/Myra/MML/MMLDiagnosticSeverity.cs b/src/Myra/MML/MMLDiagnosticSeverity.cs new file mode 100644 index 00000000..cd71d97a --- /dev/null +++ b/src/Myra/MML/MMLDiagnosticSeverity.cs @@ -0,0 +1,10 @@ + +namespace Myra.MML +{ + public enum MMLDiagnosticSeverity + { + Info, + Warning, + Error, + } +} diff --git a/src/Myra/MML/SaveContext.cs b/src/Myra/MML/SaveContext.cs index d05d1e90..c880c704 100644 --- a/src/Myra/MML/SaveContext.cs +++ b/src/Myra/MML/SaveContext.cs @@ -21,7 +21,7 @@ namespace Myra.MML { - internal class SaveContext: BaseContext + internal class SaveContext : BaseContext { public Func ShouldSerializeProperty = HasDefaultValue; diff --git a/src/MyraPad/MyraPad.csproj b/src/MyraPad/MyraPad.csproj index 6facb8b4..2e166be9 100644 --- a/src/MyraPad/MyraPad.csproj +++ b/src/MyraPad/MyraPad.csproj @@ -6,6 +6,7 @@ MyraPad MyraPad + MONOGAME diff --git a/src/MyraPad/Studio.cs b/src/MyraPad/Studio.cs index a4aed45e..2cd2c3a0 100644 --- a/src/MyraPad/Studio.cs +++ b/src/MyraPad/Studio.cs @@ -20,1612 +20,1623 @@ using Myra.Graphics2D; using AssetManagementBase; using AssetManagementBase.Utility; +using Myra.MML; namespace MyraPad { - public class Studio : Game - { - private const string RowsProportionsName = "RowsProportions"; - private const string ColumnsProportionsName = "ColumnsProportions"; - private const string ProportionsName = "Proportions"; - private const string MenuItemName = "MenuItem"; - private const string ListItemName = "ListItem"; - - private static readonly string[] SimpleWidgets = new[] - { - "ImageTextButton", - "TextButton", - "ImageButton", - "RadioButton", - "SpinButton", - "CheckBox", - "HorizontalProgressBar", - "VerticalProgressBar", - "HorizontalSeparator", - "VerticalSeparator", - "HorizontalSlider", - "VerticalSlider", - "Image", - "Label", - "TextBox", - "PropertyGrid", - }; - - private static readonly string[] Containers = new[] - { - "Window", - "Grid", - "Panel", - "ScrollViewer", - "VerticalSplitPane", - "HorizontalSplitPane", - "VerticalStackPanel", - "HorizontalStackPanel" - }; - - private static readonly string[] SpecialContainers = new[] + public class Studio : Game + { + private const string RowsProportionsName = "RowsProportions"; + private const string ColumnsProportionsName = "ColumnsProportions"; + private const string ProportionsName = "Proportions"; + private const string MenuItemName = "MenuItem"; + private const string ListItemName = "ListItem"; + + private static readonly string[] SimpleWidgets = new[] + { + "ImageTextButton", + "TextButton", + "ImageButton", + "RadioButton", + "SpinButton", + "CheckBox", + "HorizontalProgressBar", + "VerticalProgressBar", + "HorizontalSeparator", + "VerticalSeparator", + "HorizontalSlider", + "VerticalSlider", + "Image", + "Label", + "TextBox", + "PropertyGrid", + }; + + private static readonly string[] Containers = new[] + { + "Window", + "Grid", + "Panel", + "ScrollViewer", + "VerticalSplitPane", + "HorizontalSplitPane", + "VerticalStackPanel", + "HorizontalStackPanel" + }; + + private static readonly string[] SpecialContainers = new[] { - "HorizontalMenu", - "VerticalMenu", - "ComboBox", - "ListBox", - "TabControl", - }; - - private static Studio _instance; - - private readonly ConcurrentQueue _loadQueue = new ConcurrentQueue(); - private readonly ConcurrentQueue _newProjectsQueue = new ConcurrentQueue(); - private readonly AutoResetEvent _refreshProjectEvent = new AutoResetEvent(false); - - private bool _suppressProjectRefresh = false; - private readonly GraphicsDeviceManager _graphicsDeviceManager; - private readonly State _state; - private StudioWidget _ui; - private PropertyGrid _propertyGrid; - private string _filePath; - private string _lastFolder; - private bool _isDirty; - private Project _project; - private bool _needsCloseTag; - private string _parentTag; - private int? _currentTagStart, _currentTagEnd; - private int _line, _col, _indentLevel; - private bool _applyAutoIndent = false; - private bool _applyAutoClose = false; - private object _newObject; - private DateTime? _refreshInitiated; - - private VerticalMenu _autoCompleteMenu = null; - private readonly Options _options = null; - private Desktop _desktop; - - public static Studio Instance - { - get - { - return _instance; - } - } - - public string FilePath - { - get - { - return _filePath; - } - - set - { - if (value == _filePath) - { - return; - } - - _filePath = value; - - if (!string.IsNullOrEmpty(_filePath)) - { - var folder = Path.GetDirectoryName(_filePath); - PropertyGridSettings.BasePath = folder; - PropertyGridSettings.AssetManager = new AssetManager(new FileAssetResolver(folder)); - _lastFolder = folder; - } else - { - PropertyGridSettings.BasePath = string.Empty; - PropertyGridSettings.AssetManager = MyraEnvironment.DefaultAssetManager; - PropertyGridSettings.AssetManager.ClearCache(); - } - - UpdateTitle(); - UpdateMenuFile(); - } - } - - public bool IsDirty - { - get - { - return _isDirty; - } - - set - { - if (value == _isDirty) - { - return; - } - - _isDirty = value; - UpdateTitle(); - } - } - - public Project Project - { - get - { - return _project; - } - - set - { - if (value == _project) - { - return; - } - - _project = value; - - _ui._projectHolder.Widgets.Clear(); - - if (_project != null && _project.Root != null) - { - _ui._projectHolder.Widgets.Add(_project.Root); - } - - UpdateMenuFile(); - } - } - - private string CurrentTag - { - get - { - if (_currentTagStart == null || _currentTagEnd == null || _currentTagEnd.Value <= _currentTagStart.Value) - { - return null; - } - - return _ui._textSource.Text.Substring(_currentTagStart.Value, _currentTagEnd.Value - _currentTagStart.Value + 1); - } - } - - private PropertyGridSettings PropertyGridSettings - { - get - { - return _propertyGrid.Settings; - } - } - - private IAssetManager AssetManager - { - get - { - return PropertyGridSettings.AssetManager; - } - } - - public Studio(string[] args) - { - _instance = this; - - // Restore state - _state = State.Load(); - - //Load via program argument - if (args.Length > 0) - { - string filePathArg = args[0]; - if (!string.IsNullOrEmpty(filePathArg)) - { - _state.EditedFile = filePathArg; - _state.LastFolder = Path.GetDirectoryName(filePathArg); - } - } - - _graphicsDeviceManager = new GraphicsDeviceManager(this); - - if (_state != null) - { - _graphicsDeviceManager.PreferredBackBufferWidth = _state.Size.X; - _graphicsDeviceManager.PreferredBackBufferHeight = _state.Size.Y; - - if (_state.UserColors != null) - { - for (var i = 0; i < Math.Min(ColorPickerPanel.UserColors.Length, _state.UserColors.Length); ++i) - { - ColorPickerPanel.UserColors[i] = _state.UserColors[i]; - } - } - - _lastFolder = _state.LastFolder; - _options = _state.Options; - } - else - { - _graphicsDeviceManager.PreferredBackBufferWidth = 1280; - _graphicsDeviceManager.PreferredBackBufferHeight = 800; - _options = new Options(); - } - - ThreadPool.QueueUserWorkItem(RefreshProjectProc); - } - - protected override void Initialize() - { - base.Initialize(); - - IsMouseVisible = true; - Window.AllowUserResizing = true; - } - - protected override void LoadContent() - { - base.LoadContent(); - - MyraEnvironment.Game = this; - - _desktop = new Desktop(); - - BuildUI(); - - #if MONOGAME - - // Inform Myra that external text input is available - // So it stops translating Keys to chars - _desktop.HasExternalTextInput = true; - - // Provide that text input - Window.TextInput += (s, a) => - { - _desktop.OnChar(a.Character); - }; - - #endif - - if (_state != null && !string.IsNullOrEmpty(_state.EditedFile)) - { - Load(_state.EditedFile); - } - } - - public void ClosingFunction(object sender, System.ComponentModel.CancelEventArgs e) - { - if (_isDirty) - { - OnExiting(); - e.Cancel = true; - } - } - - public void OnExiting() - { - var mb = Dialog.CreateMessageBox("Quit", "There are unsaved changes. Do you want to exit without saving?"); - - mb.Closed += (o, args) => - { - if (mb.Result) - { - Exit(); - } - }; - - mb.ShowModal(_desktop); - } - - private void BuildUI() - { - _desktop.ContextMenuClosed += Desktop_ContextMenuClosed; - _desktop.KeyDownHandler = key => - { - if (_autoCompleteMenu != null && - (key == Keys.Up || key == Keys.Down || key == Keys.Enter)) - { - _autoCompleteMenu.OnKeyDown(key); - } - else - { - _desktop.OnKeyDown(key); - } - }; - - _desktop.KeyDown += (s, a) => - { - if (_desktop.HasModalWidget || _ui._mainMenu.IsOpen) - { - return; - } - - if (_desktop.IsKeyDown(Keys.LeftControl) || _desktop.IsKeyDown(Keys.RightControl)) - { - if (_desktop.IsKeyDown(Keys.N)) - { - NewItemOnClicked(this, EventArgs.Empty); - } - else if (_desktop.IsKeyDown(Keys.O)) - { - OpenItemOnClicked(this, EventArgs.Empty); - } - else if (_desktop.IsKeyDown(Keys.R)) - { - OnMenuFileReloadSelected(this, EventArgs.Empty); - } - else if (_desktop.IsKeyDown(Keys.S)) - { - SaveItemOnClicked(this, EventArgs.Empty); - } - else if (_desktop.IsKeyDown(Keys.E)) - { - ExportCsItemOnSelected(this, EventArgs.Empty); - } - else if (_desktop.IsKeyDown(Keys.Q)) - { - Exit(); - } - else if (_desktop.IsKeyDown(Keys.F)) - { - _menuEditUpdateSource_Selected(this, EventArgs.Empty); - } - } - }; - - _ui = new StudioWidget(); - - _ui._menuFileNew.Selected += NewItemOnClicked; - _ui._menuFileOpen.Selected += OpenItemOnClicked; - _ui._menuFileReload.Selected += OnMenuFileReloadSelected; - _ui._menuFileSave.Selected += SaveItemOnClicked; - _ui._menuFileSaveAs.Selected += SaveAsItemOnClicked; - _ui._menuFileExportToCS.Selected += ExportCsItemOnSelected; - _ui._menuFileLoadStylesheet.Selected += OnMenuFileLoadStylesheet; - _ui._menuFileResetStylesheet.Selected += OnMenuFileResetStylesheetSelected; - _ui._menuFileDebugOptions.Selected += DebugOptionsItemOnSelected; - _ui._menuFileQuit.Selected += QuitItemOnDown; - - _ui._menuItemSelectAll.Selected += (s, a) => { _ui._textSource.SelectAll(); }; - _ui._menuEditFormatSource.Selected += _menuEditUpdateSource_Selected; - - _ui._menuHelpAbout.Selected += AboutItemOnClicked; - - _ui._textSource.CursorPositionChanged += _textSource_CursorPositionChanged; - _ui._textSource.TextChanged += _textSource_TextChanged; - _ui._textSource.KeyDown += _textSource_KeyDown; - _ui._textSource.Char += _textSource_Char; - - _ui._textStatus.Text = string.Empty; - _ui._textLocation.Text = "Line: 0, Column: 0, Indent: 0"; - - _propertyGrid = new PropertyGrid - { - IgnoreCollections = true - }; - _propertyGrid.PropertyChanged += PropertyGridOnPropertyChanged; - _propertyGrid.CustomValuesProvider = RecordValuesProvider; - _propertyGrid.CustomSetter = RecordSetter; - _propertyGrid.Settings.AssetManager = MyraEnvironment.DefaultAssetManager; - - _ui._propertyGridPane.Content = _propertyGrid; - - _ui._topSplitPane.SetSplitterPosition(0, _state != null ? _state.TopSplitterPosition : 0.75f); - _ui._leftSplitPane.SetSplitterPosition(0, _state != null ? _state.LeftSplitterPosition : 0.5f); - - _desktop.Root = _ui; - - UpdateMenuFile(); - } - - private object[] RecordValuesProvider(Record record) - { - if (record.Name != "StyleName") - { - // Default processing - return null; - } - - var widget = _propertyGrid.Object as Widget; - if (widget == null) - { - return null; - } - - var styleNames = Project.Stylesheet.GetStylesByWidgetName(widget.GetType().Name); - if (styleNames == null || styleNames.Length < 2) - { - // Dont show this property if there's only one style(Default) or less - styleNames = new string[0]; - } - - return (from s in styleNames select (object)s).ToArray(); - } - - private bool RecordSetter(Record record, object obj, object value) - { - if (record.Name != "StyleName") - { - // Default processing - return false; - } - - var widget = obj as Widget; - if (widget == null) - { - return false; - } - - widget.SetStyle(Project.Stylesheet, (string)value); - - return true; - } - - private void Desktop_ContextMenuClosed(object sender, GenericEventArgs e) - { - if (e.Data != _autoCompleteMenu) - { - return; - } - - _autoCompleteMenu = null; - } - - private void UpdateSource() - { - var data = Project != null ? Project.Save() : string.Empty; - if (data == _ui._textSource.Text) - { - return; - } - - _ui._textSource.ReplaceAll(data); - } - - private void _menuEditUpdateSource_Selected(object sender, EventArgs e) - { - try - { - var project = Project.LoadFromXml(_ui._textSource.Text, AssetManager, _project.Stylesheet); - _ui._textSource.Text = _project.Save(); - } - catch (Exception ex) - { - var messageBox = Dialog.CreateMessageBox("Error", ex.Message); - messageBox.ShowModal(_desktop); - } - } - - private void _textSource_Char(object sender, GenericEventArgs e) - { - _applyAutoClose = e.Data == '>'; - } - - private void _textSource_KeyDown(object sender, GenericEventArgs e) - { - _applyAutoIndent = e.Data == Keys.Enter; - } - - private static void ProcessResourcesPaths(Widget w, Func resourceProcessor) - { - var type = w.GetType(); - foreach (var res in w.Resources) - { - var propertyInfo = type.GetProperty(res.Key); - if (propertyInfo == null) - { - continue; - } - - // Skip brushes for now - if (propertyInfo.PropertyType == typeof(IBrush)) - { - continue; - } - - var result = resourceProcessor(res.Key); - if (!result) - { - break; - } - } - } - - private void UpdateResourcesPaths(string oldPath, string newPath, Action onFinished) - { - try - { - // For now only empty old path is allowed - if (!string.IsNullOrEmpty(oldPath)) - { - onFinished(false); - return; - } - - // Check whether project has external assets - var hasExternalResources = false; - - UIUtils.ProcessWidgets(Project.Root, w => - { - ProcessResourcesPaths(w, k => - { - // Found - hasExternalResources = true; - return false; - }); - - // Continue iteration depending whether hasExternalResources had been set - return !hasExternalResources; - }); - - if (!hasExternalResources) - { - onFinished(false); - return; - } - - var dialog = Dialog.CreateMessageBox("Resources Paths Update", "Would you like to update resources paths so it become relative to the project location?"); - dialog.Closed += (s, a) => - { - if (dialog.Result) - { - var updated = false; - - var folder = Path.GetDirectoryName(newPath); - UIUtils.ProcessWidgets(Project.Root, widget => - { - var newResources = new Dictionary(); - - ProcessResourcesPaths(widget, key => - { - try - { - var path = widget.Resources[key]; - - if (Path.IsPathRooted(path)) - { - path = PathUtils.TryToMakePathRelativeTo(path, folder); - newResources[key] = path; - } - } - catch (Exception) - { - } - - // Continue iteration - return true; - }); - - // Update resources - foreach(var pair in newResources) - { - if (widget.Resources[pair.Key] != pair.Value) - { - updated = true; - widget.Resources[pair.Key] = pair.Value; - } - } - - // Continue iteration - return true; - }); - - if (updated) - { - try - { - _suppressProjectRefresh = true; - UpdateSource(); - } - finally - { - _suppressProjectRefresh = false; - } - } - } - - onFinished(true); - }; - - dialog.ShowModal(_desktop); - } - catch (Exception) - { - onFinished(false); - } - } - - private void ApplyAutoIndent() - { - if (!_options.AutoIndent || _options.IndentSpacesSize <= 0 || !_applyAutoIndent) - { - return; - } - - _applyAutoIndent = false; - - var text = _ui._textSource.Text; - var pos = _ui._textSource.CursorPosition; - - if (string.IsNullOrEmpty(text) || pos == 0 || pos >= text.Length) - { - return; - } - - var il = _indentLevel; - if (pos < text.Length - 2 && text[pos] == '<' && text[pos + 1] == '/') - { - --il; - } - - if (il <= 0) - { - return; - } - - // Insert indent - var indent = new string(' ', il * _options.IndentSpacesSize); - _ui._textSource.Insert(pos, indent); - } - - private void ApplyAutoClose() - { - if (!_options.AutoClose || !_applyAutoClose) - { - return; - } - - _applyAutoClose = false; - - var text = _ui._textSource.Text; - var pos = _ui._textSource.CursorPosition; - - var currentTag = CurrentTag; - if (string.IsNullOrEmpty(currentTag) || !_needsCloseTag) - { - return; - } - - var close = ""; - _ui._textSource.Insert(pos, close); - } - - private void _textSource_TextChanged(object sender, ValueChangedEventArgs e) - { - try - { - IsDirty = true; - - if (_suppressProjectRefresh) - { - return; - } - - var newLength = string.IsNullOrEmpty(e.NewValue) ? 0 : e.NewValue.Length; - var oldLength = string.IsNullOrEmpty(e.OldValue) ? 0 : e.OldValue.Length; - if (Math.Abs(newLength - oldLength) > 1 || _applyAutoClose) - { - // Refresh now - QueueRefreshProject(); - } - else - { - // Refresh after delay - _refreshInitiated = DateTime.Now; - } - } - catch (Exception) - { - } - } - - private void QueueRefreshProject() - { - _refreshInitiated = null; - - _loadQueue.Enqueue(_ui._textSource.Text); - _refreshProjectEvent.Set(); - } - - private void RefreshProjectProc(object state) - { - while (true) - { - _refreshProjectEvent.WaitOne(); - - string data; - - // We're interested only in the last data - while (_loadQueue.Count > 1) - { - _loadQueue.TryDequeue(out data); - } - - if (_loadQueue.TryDequeue(out data)) - { - try - { - _ui._textStatus.Text = "Reloading..."; - - var xDoc = XDocument.Parse(data); - - var stylesheet = Stylesheet.Current; - var stylesheetPathAttr = xDoc.Root.Attribute("StylesheetPath"); - if (stylesheetPathAttr != null) - { - try - { - stylesheet = StylesheetFromFile(stylesheetPathAttr.Value); - } - catch (Exception ex) - { - var dialog = Dialog.CreateMessageBox("Stylesheet Error", ex.ToString()); - dialog.ShowModal(_desktop); - } - } - - var newProject = Project.LoadFromXml(xDoc, AssetManager, stylesheet); - _newProjectsQueue.Enqueue(newProject); - - _ui._textStatus.Text = string.Empty; - } - catch (Exception ex) - { - _ui._textStatus.Text = ex.Message; - } - } - } - } - - private static readonly Regex TagResolver = new Regex("<([A-Za-z0-9\\.]+)"); - - private static string ExtractTag(string source) - { - if (string.IsNullOrEmpty(source)) - { - return null; - } - - return TagResolver.Match(source).Groups[1].Value; - } - - private void UpdatePositions() - { - var lastStart = _currentTagStart; - var lastEnd = _currentTagEnd; - - _line = _col = _indentLevel = 0; - _parentTag = null; - _currentTagStart = null; - _currentTagEnd = null; - _needsCloseTag = false; - - if (string.IsNullOrEmpty(_ui._textSource.Text)) - { - return; - } - - var cursorPos = _ui._textSource.CursorPosition; - var text = _ui._textSource.Text; - - int? tagOpen = null; - var isOpenTag = true; - var length = text.Length; - - string currentTag = null; - Stack parentStack = new Stack(); - for (var i = 0; i < length; ++i) - { - if (tagOpen == null) - { - if (i >= cursorPos) - { - break; - } - - currentTag = null; - _currentTagStart = null; - _currentTagEnd = null; - } - - if (i < cursorPos) - { - ++_col; - } - - var c = text[i]; - if (c == '\n') - { - ++_line; - _col = 0; - } - - if (c == '<') - { - if (tagOpen != null && isOpenTag && i >= cursorPos + 1) - { - // tag is not closed - _currentTagStart = tagOpen; - _currentTagEnd = null; - break; - } - - if (i < length - 1 && text[i + 1] != '?') - { - tagOpen = i; - isOpenTag = text[i + 1] != '/'; - } - } - - if (tagOpen != null && i > tagOpen.Value && c == '>') - { - if (isOpenTag) - { - var needsCloseTag = text[i - 1] != '/'; - _needsCloseTag = needsCloseTag; - - currentTag = text.Substring(tagOpen.Value, i - tagOpen.Value + 1); - _currentTagStart = tagOpen; - _currentTagEnd = i; - - if (needsCloseTag && i <= cursorPos) - { - parentStack.Push(currentTag); - } - } - else - { - if (parentStack.Count > 0) - { - parentStack.Pop(); - } - } - - tagOpen = null; - } - } - - _indentLevel = parentStack.Count; - if (parentStack.Count > 0) - { - _parentTag = parentStack.Pop(); - } - - _ui._textLocation.Text = string.Format("Line: {0}, Col: {1}, Indent: {2}", _line + 1, _col + 1, _indentLevel); - - if (!string.IsNullOrEmpty(_parentTag)) - { - _parentTag = ExtractTag(_parentTag); - - _ui._textLocation.Text += ", Parent: " + _parentTag; - } - - if ((lastStart != _currentTagStart || lastEnd != _currentTagEnd)) - { - _propertyGrid.Object = null; - if (!string.IsNullOrEmpty(currentTag)) - { - var xml = currentTag; - - if (_needsCloseTag) - { - var tag = ExtractTag(currentTag); - xml += ""; - } - - ThreadPool.QueueUserWorkItem(LoadObjectAsync, xml); - } - } - - HandleAutoComplete(); - } - - private void HandleAutoComplete() - { - if (_desktop.ContextMenu == _autoCompleteMenu) - { - _desktop.HideContextMenu(); - } - - if (_currentTagStart == null || _currentTagEnd != null || string.IsNullOrEmpty(_parentTag)) - { - return; - } - - var cursorPos = _ui._textSource.CursorPosition; - var text = _ui._textSource.Text; - - // Tag isn't closed - var typed = text.Substring(_currentTagStart.Value, cursorPos - _currentTagStart.Value); - if (typed.StartsWith("<")) - { - typed = typed.Substring(1); - - var all = BuildAutoCompleteVariants(); - - // Filter typed - if (!string.IsNullOrEmpty(typed)) - { - all = (from a in all where a.StartsWith(typed, StringComparison.OrdinalIgnoreCase) select a).ToList(); - } - - if (all.Count > 0) - { - var lastStartPos = _currentTagStart.Value; - var lastEndPos = cursorPos; - // Build context menu - _autoCompleteMenu = new VerticalMenu(); - foreach (var a in all) - { - var menuItem = new MenuItem - { - Text = a - }; - - menuItem.Selected += (s, args) => - { - var result = "<" + menuItem.Text; - var skip = result.Length; - var needsClose = false; - - if (SimpleWidgets.Contains(menuItem.Text) || - Project.IsProportionName(menuItem.Text) || - menuItem.Text == MenuItemName || - menuItem.Text == ListItemName) - { - result += "/>"; - skip += 2; - } - else - { - result += ">"; - ++skip; - - if (_options.AutoIndent && _options.IndentSpacesSize > 0) - { - // Indent before cursor pos - result += "\n"; - var indentSize = _options.IndentSpacesSize * (_indentLevel + 1); - result += new string(' ', indentSize); - skip += indentSize; - - // Indent before closing tag - result += "\n"; - indentSize = _options.IndentSpacesSize * _indentLevel; - result += new string(' ', indentSize); - } - result += ""; - ++skip; - needsClose = true; - } - - _ui._textSource.Replace(lastStartPos, lastEndPos - lastStartPos, result); - _ui._textSource.CursorPosition = lastStartPos + skip; - if (needsClose) - { - // _ui._textSource.OnKeyDown(Keys.Enter); - } - }; - - _autoCompleteMenu.Items.Add(menuItem); - } - - var screen = _ui._textSource.CursorScreenPosition; - screen.Y += _ui._textSource.Font.FontSize; - - if (_autoCompleteMenu.Items.Count > 0) - { - _autoCompleteMenu.HoverIndex = 0; - } - - _desktop.ShowContextMenu(_autoCompleteMenu, screen); - // Keep focus at text field - _desktop.FocusedKeyboardWidget = _ui._textSource; - - _refreshInitiated = null; - } - } - } - - private List BuildAutoCompleteVariants() - { - var result = new List(); - - if (string.IsNullOrEmpty(_parentTag)) - { - return result; - } - - if (_parentTag == "Project") - { - result.AddRange(Containers); - result.Add("Dialog"); - } - else if (Containers.Contains(_parentTag) || _parentTag == "Dialog") - { - result.AddRange(SimpleWidgets); - result.AddRange(Containers); - result.AddRange(SpecialContainers); - } - else if (_parentTag.EndsWith(RowsProportionsName) || _parentTag.EndsWith(ColumnsProportionsName) || _parentTag.EndsWith(ProportionsName)) - { - result.Add(Project.ProportionName); - } - else if (_parentTag.EndsWith("Menu")) - { - result.Add("MenuItem"); - } - else if (_parentTag == "ListBox" || _parentTag == "ComboBox") - { - result.Add("ListItem"); - } - else if (_parentTag == "TabControl") - { - result.Add("TabItem"); - } - - if (_parentTag == "Grid") - { - result.Add(_parentTag + "." + ColumnsProportionsName); - result.Add(_parentTag + "." + RowsProportionsName); - result.Add(_parentTag + "." + Project.DefaultColumnProportionName); - result.Add(_parentTag + "." + Project.DefaultRowProportionName); - } - - if (_parentTag == "VerticalStackPanel" || _parentTag == "HorizontalStackPanel") - { - result.Add(_parentTag + "." + Project.DefaultProportionName); - result.Add(_parentTag + "." + ProportionsName); - } - - result = result.OrderBy(s => !s.Contains('.')).ThenBy(s => s).ToList(); - - return result; - } - - private void LoadObjectAsync(object state) - { - try - { - var xml = (string)state; - _newObject = Project.LoadObjectFromXml(xml, AssetManager); - } - catch (Exception) - { - } - } - - private void UpdateCursor() - { - try - { - UpdatePositions(); - ApplyAutoIndent(); - ApplyAutoClose(); - } - catch (Exception) - { - } - } - - private void _textSource_CursorPositionChanged(object sender, EventArgs e) - { - UpdateCursor(); - } - - private void OnMenuFileReloadSelected(object sender, EventArgs e) - { - AssetManager.ClearCache(); - Load(FilePath); - } - - private Stylesheet StylesheetFromFile(string path) - { - if (!Path.IsPathRooted(path)) - { - path = Path.Combine(Path.GetDirectoryName(FilePath), path); - } - - return AssetManager.Load(path); - } - - private void LoadStylesheet(string filePath) - { - if (string.IsNullOrEmpty(filePath)) - { - return; - } - - try - { - if (!Path.IsPathRooted(filePath)) - { - filePath = Path.Combine(Path.GetDirectoryName(FilePath), filePath); - } - - var stylesheet = StylesheetFromFile(filePath); - - Project.StylesheetPath = filePath; - UpdateSource(); - } - catch (Exception ex) - { - var dialog = Dialog.CreateMessageBox("Error", ex.ToString()); - dialog.ShowModal(_desktop); - } - } - - private void OnMenuFileLoadStylesheet(object sender, EventArgs e) - { - AssetManager.ClearCache(); - - var dlg = new FileDialog(FileDialogMode.OpenFile) - { - Filter = "*.xmms|*.xml" - }; - - try - { - if (!string.IsNullOrEmpty(Project.StylesheetPath)) - { - var stylesheetPath = Project.StylesheetPath; - if (!Path.IsPathRooted(stylesheetPath)) - { - // Prepend folder path - stylesheetPath = Path.Combine(Path.GetDirectoryName(FilePath), stylesheetPath); - } - - dlg.Folder = Path.GetDirectoryName(stylesheetPath); - } - else if (!string.IsNullOrEmpty(FilePath)) - { - dlg.Folder = Path.GetDirectoryName(FilePath); - } - } - catch (Exception) - { - } - - dlg.Closed += (s, a) => - { - if (!dlg.Result) - { - return; - } - - var filePath = dlg.FilePath; - - // Check whether stylesheet could be loaded - try - { - var stylesheet = StylesheetFromFile(filePath); - } - catch(Exception ex) - { - var msg = Dialog.CreateMessageBox("Stylesheet Error", ex.Message); - msg.ShowModal(_desktop); - return; - } - - // Try to make stylesheet path relative to project folder - filePath = PathUtils.TryToMakePathRelativeTo(filePath, Path.GetDirectoryName(FilePath)); - - Project.StylesheetPath = filePath; - UpdateSource(); - UpdateMenuFile(); - }; - - dlg.ShowModal(_desktop); - } - - private void OnMenuFileResetStylesheetSelected(object sender, EventArgs e) - { - AssetManager.ClearCache(); - Project.StylesheetPath = null; - UpdateSource(); - UpdateMenuFile(); - } - - private void DebugOptionsItemOnSelected(object sender1, EventArgs eventArgs) - { - var debugOptions = new DebugOptionsWindow(); - debugOptions.ShowModal(_desktop); - } - - private void ExportCsItemOnSelected(object sender1, EventArgs eventArgs) - { - var dlg = new ExportOptionsDialog(); - dlg.ShowModal(_desktop); - - dlg.Closed += (s, a) => - { - if (!dlg.Result) - { - return; - } - - try - { - Project.ExportOptions.Namespace = dlg._textNamespace.Text; - Project.ExportOptions.OutputPath = dlg._textOutputPath.Text; - Project.ExportOptions.Class = dlg._textClassName.Text; - - UpdateSource(); - - using (var export = new ExporterCS(Instance.Project)) - { - var strings = new List - { - "Success. Following files had been written:" - }; - strings.AddRange(export.Export()); - - var msg = Dialog.CreateMessageBox("Export To C#", string.Join("\n", strings)); - msg.ShowModal(_desktop); - } - } - catch (Exception ex) - { - var msg = Dialog.CreateMessageBox("Error", ex.Message); - msg.ShowModal(_desktop); - } - }; - } - - private void PropertyGridOnPropertyChanged(object sender, GenericEventArgs eventArgs) - { - IsDirty = true; - - var xml = _project.SaveObjectToXml(_propertyGrid.Object, ExtractTag(CurrentTag)); - - if (_needsCloseTag) - { - xml = xml.Replace("/>", ">"); - } - - if (_currentTagStart != null && _currentTagEnd != null) - { - try - { - _suppressProjectRefresh = true; - _ui._textSource.Replace(_currentTagStart.Value, - _currentTagEnd.Value - _currentTagStart.Value + 1, - xml); - QueueRefreshProject(); - } - finally - { - _suppressProjectRefresh = false; - } - - _currentTagEnd = _currentTagStart.Value + xml.Length - 1; - } - } - - private void QuitItemOnDown(object sender, EventArgs eventArgs) - { - var mb = Dialog.CreateMessageBox("Quit", "Are you sure?"); - - mb.Closed += (o, args) => - { - if (mb.Result) - { - Exit(); - } - }; - - mb.ShowModal(_desktop); - } - - private void AboutItemOnClicked(object sender, EventArgs eventArgs) - { - var messageBox = Dialog.CreateMessageBox("About", "MyraPad " + MyraEnvironment.Version); - messageBox.ShowModal(_desktop); - } - - private void SaveAsItemOnClicked(object sender, EventArgs eventArgs) - { - Save(true); - } - - private void SaveItemOnClicked(object sender, EventArgs eventArgs) - { - Save(false); - } - - private void NewItemOnClicked(object sender, EventArgs eventArgs) - { - var dlg = new NewProjectWizard(); - - dlg.Closed += (s, a) => - { - if (!dlg.Result) - { - return; - } - - var rootType = "Grid"; - - if (dlg._radioButtonHorizontalStackPanel.IsPressed) - { - rootType = "HorizontalStackPanel"; - } - else - if (dlg._radioButtonVerticalStackPanel.IsPressed) - { - rootType = "VerticalStackPanel"; - } - else - if (dlg._radioButtonPanel.IsPressed) - { - rootType = "Panel"; - } - else - if (dlg._radioButtonScrollViewer.IsPressed) - { - rootType = "ScrollViewer"; - } - else - if (dlg._radioButtonHorizontalSplitPane.IsPressed) - { - rootType = "HorizontalSplitPane"; - } - else - if (dlg._radioButtonVerticalSplitPane.IsPressed) - { - rootType = "VerticalSplitPane"; - } - else - if (dlg._radioButtonWindow.IsPressed) - { - rootType = "Window"; - } - else - if (dlg._radioButtonDialog.IsPressed) - { - rootType = "Dialog"; - } - - New(rootType); - }; - - dlg.ShowModal(_desktop); - } - - private void OpenItemOnClicked(object sender, EventArgs eventArgs) - { - var dlg = new FileDialog(FileDialogMode.OpenFile) - { - Filter = "*.xmmp|*.xml" - }; - - if (!string.IsNullOrEmpty(FilePath)) - { - dlg.Folder = Path.GetDirectoryName(FilePath); - } - else if (!string.IsNullOrEmpty(_lastFolder)) - { - dlg.Folder = _lastFolder; - } - - dlg.Closed += (s, a) => - { - if (!dlg.Result) - { - return; - } - - var filePath = dlg.FilePath; - if (string.IsNullOrEmpty(filePath)) - { - return; - } - - Load(filePath); - }; - - dlg.ShowModal(_desktop); - } - - protected override void Update(GameTime gameTime) - { - base.Update(gameTime); - - if (_refreshInitiated != null && (DateTime.Now - _refreshInitiated.Value).TotalSeconds >= 0.75f) - { - QueueRefreshProject(); - } - - if (_newObject != null) - { - _propertyGrid.Object = _newObject; - _newObject = null; - } - - Project newProject; - while (_newProjectsQueue.Count > 1) - { - _newProjectsQueue.TryDequeue(out newProject); - } - - if (_newProjectsQueue.TryDequeue(out newProject)) - { - Project = newProject; - - if (Project.Stylesheet != null && Project.Stylesheet.DesktopStyle != null) - { - _ui._projectHolder.Background = Project.Stylesheet.DesktopStyle.Background; - } - else - { - _ui._projectHolder.Background = null; - } - } - } - - protected override void Draw(GameTime gameTime) - { - base.Draw(gameTime); - - GraphicsDevice.Clear(Color.Black); - - _desktop.Render(); - } - - protected override void EndRun() - { - base.EndRun(); - - var state = new State - { - Size = new Point(GraphicsDevice.PresentationParameters.BackBufferWidth, - GraphicsDevice.PresentationParameters.BackBufferHeight), - TopSplitterPosition = _ui._topSplitPane.GetSplitterPosition(0), - LeftSplitterPosition = _ui._leftSplitPane.GetSplitterPosition(0), - EditedFile = FilePath, - LastFolder = _lastFolder, - UserColors = (from c in ColorPickerPanel.UserColors select c).ToArray() - }; - - state.Save(); - } - - private void New(string rootType) - { - var source = Resources.NewProjectTemplate.Replace("$containerType", rootType); - - _ui._textSource.Text = source; - - var newLineCount = 0; - var pos = 0; - while (pos < _ui._textSource.Text.Length && newLineCount < 3) - { - ++pos; - - if (_ui._textSource.Text[pos] == '\n') - { - ++newLineCount; - } - } - - _ui._textSource.CursorPosition = pos; - _desktop.FocusedKeyboardWidget = _ui._textSource; - - FilePath = string.Empty; - IsDirty = false; - _ui._projectHolder.Background = null; - } - - private void ProcessSave(string filePath) - { - if (string.IsNullOrEmpty(filePath)) - { - return; - } - - UpdateResourcesPaths(FilePath, filePath, updated => - { - File.WriteAllText(filePath, _ui._textSource.Text); - - FilePath = filePath; - IsDirty = false; - - if (updated) - { - QueueRefreshProject(); - } - }); - } - - private void Save(bool setFileName) - { - if (string.IsNullOrEmpty(FilePath) || setFileName) - { - var dlg = new FileDialog(FileDialogMode.SaveFile) - { - Filter = "*.xmmp" - }; - - if (!string.IsNullOrEmpty(FilePath)) - { - dlg.FilePath = FilePath; - } - else if (!string.IsNullOrEmpty(_lastFolder)) - { - dlg.Folder = _lastFolder; - } - - dlg.ShowModal(_desktop); - - dlg.Closed += (s, a) => - { - if (dlg.Result) - { - ProcessSave(dlg.FilePath); - } - }; - } - else - { - ProcessSave(FilePath); - } - } - - private void Load(string filePath) - { - try - { - var data = File.ReadAllText(filePath); - - FilePath = filePath; - - try - { - _suppressProjectRefresh = true; - _ui._textSource.Text = data; - _ui._textSource.CursorPosition = 0; - } - finally - { - _suppressProjectRefresh = false; - } - - QueueRefreshProject(); - UpdateCursor(); - _desktop.FocusedKeyboardWidget = _ui._textSource; - - IsDirty = false; - } - catch (Exception ex) - { - var dialog = Dialog.CreateMessageBox("Error", ex.ToString()); - dialog.ShowModal(_desktop); - } - } - - private void UpdateTitle() - { - var title = string.IsNullOrEmpty(_filePath) ? "MyraPad" : _filePath; - - if (_isDirty) - { - title += " *"; - } - - Window.Title = title; - } - - private void UpdateMenuFile() - { - _ui._menuFileReload.Enabled = !string.IsNullOrEmpty(FilePath); - } - } + "HorizontalMenu", + "VerticalMenu", + "ComboBox", + "ListBox", + "TabControl", + }; + + private static Studio _instance; + + private readonly ConcurrentQueue _loadQueue = new ConcurrentQueue(); + private readonly ConcurrentQueue _newProjectsQueue = new ConcurrentQueue(); + private readonly AutoResetEvent _refreshProjectEvent = new AutoResetEvent(false); + + private bool _suppressProjectRefresh = false; + private readonly GraphicsDeviceManager _graphicsDeviceManager; + private readonly State _state; + private StudioWidget _ui; + private PropertyGrid _propertyGrid; + private string _filePath; + private string _lastFolder; + private bool _isDirty; + private Project _project; + private bool _needsCloseTag; + private string _parentTag; + private int? _currentTagStart, _currentTagEnd; + private int _line, _col, _indentLevel; + private bool _applyAutoIndent = false; + private bool _applyAutoClose = false; + private object _newObject; + private DateTime? _refreshInitiated; + + private VerticalMenu _autoCompleteMenu = null; + private readonly Options _options = null; + private Desktop _desktop; + + public static Studio Instance + { + get + { + return _instance; + } + } + + public string FilePath + { + get + { + return _filePath; + } + + set + { + if (value == _filePath) + { + return; + } + + _filePath = value; + + if (!string.IsNullOrEmpty(_filePath)) + { + var folder = Path.GetDirectoryName(_filePath); + PropertyGridSettings.BasePath = folder; + PropertyGridSettings.AssetManager = new AssetManager(new FileAssetResolver(folder)); + _lastFolder = folder; + } + else + { + PropertyGridSettings.BasePath = string.Empty; + PropertyGridSettings.AssetManager = MyraEnvironment.DefaultAssetManager; + PropertyGridSettings.AssetManager.ClearCache(); + } + + UpdateTitle(); + UpdateMenuFile(); + } + } + + public bool IsDirty + { + get + { + return _isDirty; + } + + set + { + if (value == _isDirty) + { + return; + } + + _isDirty = value; + UpdateTitle(); + } + } + + public Project Project + { + get + { + return _project; + } + + set + { + if (value == _project) + { + return; + } + + _project = value; + + _ui._projectHolder.Widgets.Clear(); + + if (_project != null && _project.Root != null) + { + _ui._projectHolder.Widgets.Add(_project.Root); + } + + UpdateMenuFile(); + } + } + + private string CurrentTag + { + get + { + if (_currentTagStart == null || _currentTagEnd == null || _currentTagEnd.Value <= _currentTagStart.Value) + { + return null; + } + + return _ui._textSource.Text.Substring(_currentTagStart.Value, _currentTagEnd.Value - _currentTagStart.Value + 1); + } + } + + private PropertyGridSettings PropertyGridSettings + { + get + { + return _propertyGrid.Settings; + } + } + + private IAssetManager AssetManager + { + get + { + return PropertyGridSettings.AssetManager; + } + } + + public Studio(string[] args) + { + _instance = this; + + // Restore state + _state = State.Load(); + + //Load via program argument + if (args.Length > 0) + { + string filePathArg = args[0]; + if (!string.IsNullOrEmpty(filePathArg)) + { + _state.EditedFile = filePathArg; + _state.LastFolder = Path.GetDirectoryName(filePathArg); + } + } + + _graphicsDeviceManager = new GraphicsDeviceManager(this); + + if (_state != null) + { + _graphicsDeviceManager.PreferredBackBufferWidth = _state.Size.X; + _graphicsDeviceManager.PreferredBackBufferHeight = _state.Size.Y; + + if (_state.UserColors != null) + { + for (var i = 0; i < Math.Min(ColorPickerPanel.UserColors.Length, _state.UserColors.Length); ++i) + { + ColorPickerPanel.UserColors[i] = _state.UserColors[i]; + } + } + + _lastFolder = _state.LastFolder; + _options = _state.Options; + } + else + { + _graphicsDeviceManager.PreferredBackBufferWidth = 1280; + _graphicsDeviceManager.PreferredBackBufferHeight = 800; + _options = new Options(); + } + + ThreadPool.QueueUserWorkItem(RefreshProjectProc); + } + + protected override void Initialize() + { + base.Initialize(); + + IsMouseVisible = true; + Window.AllowUserResizing = true; + } + + protected override void LoadContent() + { + base.LoadContent(); + + MyraEnvironment.Game = this; + + _desktop = new Desktop(); + + BuildUI(); + +#if MONOGAME + + // Inform Myra that external text input is available + // So it stops translating Keys to chars + _desktop.HasExternalTextInput = true; + + // Provide that text input + Window.TextInput += (s, a) => + { + _desktop.OnChar(a.Character); + }; + +#endif + + if (_state != null && !string.IsNullOrEmpty(_state.EditedFile)) + { + Load(_state.EditedFile); + } + } + + public void ClosingFunction(object sender, System.ComponentModel.CancelEventArgs e) + { + if (_isDirty) + { + OnExiting(); + e.Cancel = true; + } + } + + public void OnExiting() + { + var mb = Dialog.CreateMessageBox("Quit", "There are unsaved changes. Do you want to exit without saving?"); + + mb.Closed += (o, args) => + { + if (mb.Result) + { + Exit(); + } + }; + + mb.ShowModal(_desktop); + } + + private void BuildUI() + { + _desktop.ContextMenuClosed += Desktop_ContextMenuClosed; + _desktop.KeyDownHandler = key => + { + if (_autoCompleteMenu != null && + (key == Keys.Up || key == Keys.Down || key == Keys.Enter)) + { + _autoCompleteMenu.OnKeyDown(key); + } + else + { + _desktop.OnKeyDown(key); + } + }; + + _desktop.KeyDown += (s, a) => + { + if (_desktop.HasModalWidget || _ui._mainMenu.IsOpen) + { + return; + } + + if (_desktop.IsKeyDown(Keys.LeftControl) || _desktop.IsKeyDown(Keys.RightControl)) + { + if (_desktop.IsKeyDown(Keys.N)) + { + NewItemOnClicked(this, EventArgs.Empty); + } + else if (_desktop.IsKeyDown(Keys.O)) + { + OpenItemOnClicked(this, EventArgs.Empty); + } + else if (_desktop.IsKeyDown(Keys.R)) + { + OnMenuFileReloadSelected(this, EventArgs.Empty); + } + else if (_desktop.IsKeyDown(Keys.S)) + { + SaveItemOnClicked(this, EventArgs.Empty); + } + else if (_desktop.IsKeyDown(Keys.E)) + { + ExportCsItemOnSelected(this, EventArgs.Empty); + } + else if (_desktop.IsKeyDown(Keys.Q)) + { + Exit(); + } + else if (_desktop.IsKeyDown(Keys.F)) + { + _menuEditUpdateSource_Selected(this, EventArgs.Empty); + } + } + }; + + _ui = new StudioWidget(); + + _ui._menuFileNew.Selected += NewItemOnClicked; + _ui._menuFileOpen.Selected += OpenItemOnClicked; + _ui._menuFileReload.Selected += OnMenuFileReloadSelected; + _ui._menuFileSave.Selected += SaveItemOnClicked; + _ui._menuFileSaveAs.Selected += SaveAsItemOnClicked; + _ui._menuFileExportToCS.Selected += ExportCsItemOnSelected; + _ui._menuFileLoadStylesheet.Selected += OnMenuFileLoadStylesheet; + _ui._menuFileResetStylesheet.Selected += OnMenuFileResetStylesheetSelected; + _ui._menuFileDebugOptions.Selected += DebugOptionsItemOnSelected; + _ui._menuFileQuit.Selected += QuitItemOnDown; + + _ui._menuItemSelectAll.Selected += (s, a) => { _ui._textSource.SelectAll(); }; + _ui._menuEditFormatSource.Selected += _menuEditUpdateSource_Selected; + + _ui._menuHelpAbout.Selected += AboutItemOnClicked; + + _ui._textSource.CursorPositionChanged += _textSource_CursorPositionChanged; + _ui._textSource.TextChanged += _textSource_TextChanged; + _ui._textSource.KeyDown += _textSource_KeyDown; + _ui._textSource.Char += _textSource_Char; + + _ui._textStatus.Text = string.Empty; + _ui._textLocation.Text = "Line: 0, Column: 0, Indent: 0"; + + _propertyGrid = new PropertyGrid + { + IgnoreCollections = true + }; + _propertyGrid.PropertyChanged += PropertyGridOnPropertyChanged; + _propertyGrid.CustomValuesProvider = RecordValuesProvider; + _propertyGrid.CustomSetter = RecordSetter; + _propertyGrid.Settings.AssetManager = MyraEnvironment.DefaultAssetManager; + + _ui._propertyGridPane.Content = _propertyGrid; + + _ui._topSplitPane.SetSplitterPosition(0, _state != null ? _state.TopSplitterPosition : 0.75f); + _ui._leftSplitPane.SetSplitterPosition(0, _state != null ? _state.LeftSplitterPosition : 0.5f); + + _desktop.Root = _ui; + + UpdateMenuFile(); + } + + private object[] RecordValuesProvider(Record record) + { + if (record.Name != "StyleName") + { + // Default processing + return null; + } + + var widget = _propertyGrid.Object as Widget; + if (widget == null) + { + return null; + } + + var styleNames = Project.Stylesheet.GetStylesByWidgetName(widget.GetType().Name); + if (styleNames == null || styleNames.Length < 2) + { + // Dont show this property if there's only one style(Default) or less + styleNames = new string[0]; + } + + return (from s in styleNames select (object)s).ToArray(); + } + + private bool RecordSetter(Record record, object obj, object value) + { + if (record.Name != "StyleName") + { + // Default processing + return false; + } + + var widget = obj as Widget; + if (widget == null) + { + return false; + } + + widget.SetStyle(Project.Stylesheet, (string)value); + + return true; + } + + private void Desktop_ContextMenuClosed(object sender, GenericEventArgs e) + { + if (e.Data != _autoCompleteMenu) + { + return; + } + + _autoCompleteMenu = null; + } + + private void UpdateSource() + { + var data = Project != null ? Project.Save() : string.Empty; + if (data == _ui._textSource.Text) + { + return; + } + + _ui._textSource.ReplaceAll(data); + } + + private void _menuEditUpdateSource_Selected(object sender, EventArgs e) + { + try + { + var project = Project.LoadFromXml(_ui._textSource.Text, AssetManager, _project.Stylesheet); + _ui._textSource.Text = _project.Save(); + } + catch (Exception ex) + { + var messageBox = Dialog.CreateMessageBox("Error", ex.Message); + messageBox.ShowModal(_desktop); + } + } + + private void _textSource_Char(object sender, GenericEventArgs e) + { + _applyAutoClose = e.Data == '>'; + } + + private void _textSource_KeyDown(object sender, GenericEventArgs e) + { + _applyAutoIndent = e.Data == Keys.Enter; + } + + private static void ProcessResourcesPaths(Widget w, Func resourceProcessor) + { + var type = w.GetType(); + foreach (var res in w.Resources) + { + var propertyInfo = type.GetProperty(res.Key); + if (propertyInfo == null) + { + continue; + } + + // Skip brushes for now + if (propertyInfo.PropertyType == typeof(IBrush)) + { + continue; + } + + var result = resourceProcessor(res.Key); + if (!result) + { + break; + } + } + } + + private void UpdateResourcesPaths(string oldPath, string newPath, Action onFinished) + { + try + { + // For now only empty old path is allowed + if (!string.IsNullOrEmpty(oldPath)) + { + onFinished(false); + return; + } + + // Check whether project has external assets + var hasExternalResources = false; + + UIUtils.ProcessWidgets(Project.Root, w => + { + ProcessResourcesPaths(w, k => + { + // Found + hasExternalResources = true; + return false; + }); + + // Continue iteration depending whether hasExternalResources had been set + return !hasExternalResources; + }); + + if (!hasExternalResources) + { + onFinished(false); + return; + } + + var dialog = Dialog.CreateMessageBox("Resources Paths Update", "Would you like to update resources paths so it become relative to the project location?"); + dialog.Closed += (s, a) => + { + if (dialog.Result) + { + var updated = false; + + var folder = Path.GetDirectoryName(newPath); + UIUtils.ProcessWidgets(Project.Root, widget => + { + var newResources = new Dictionary(); + + ProcessResourcesPaths(widget, key => + { + try + { + var path = widget.Resources[key]; + + if (Path.IsPathRooted(path)) + { + path = PathUtils.TryToMakePathRelativeTo(path, folder); + newResources[key] = path; + } + } + catch (Exception) + { + } + + // Continue iteration + return true; + }); + + // Update resources + foreach (var pair in newResources) + { + if (widget.Resources[pair.Key] != pair.Value) + { + updated = true; + widget.Resources[pair.Key] = pair.Value; + } + } + + // Continue iteration + return true; + }); + + if (updated) + { + try + { + _suppressProjectRefresh = true; + UpdateSource(); + } + finally + { + _suppressProjectRefresh = false; + } + } + } + + onFinished(true); + }; + + dialog.ShowModal(_desktop); + } + catch (Exception) + { + onFinished(false); + } + } + + private void ApplyAutoIndent() + { + if (!_options.AutoIndent || _options.IndentSpacesSize <= 0 || !_applyAutoIndent) + { + return; + } + + _applyAutoIndent = false; + + var text = _ui._textSource.Text; + var pos = _ui._textSource.CursorPosition; + + if (string.IsNullOrEmpty(text) || pos == 0 || pos >= text.Length) + { + return; + } + + var il = _indentLevel; + if (pos < text.Length - 2 && text[pos] == '<' && text[pos + 1] == '/') + { + --il; + } + + if (il <= 0) + { + return; + } + + // Insert indent + var indent = new string(' ', il * _options.IndentSpacesSize); + _ui._textSource.Insert(pos, indent); + } + + private void ApplyAutoClose() + { + if (!_options.AutoClose || !_applyAutoClose) + { + return; + } + + _applyAutoClose = false; + + var text = _ui._textSource.Text; + var pos = _ui._textSource.CursorPosition; + + var currentTag = CurrentTag; + if (string.IsNullOrEmpty(currentTag) || !_needsCloseTag) + { + return; + } + + var close = ""; + _ui._textSource.Insert(pos, close); + } + + private void _textSource_TextChanged(object sender, ValueChangedEventArgs e) + { + try + { + IsDirty = true; + + if (_suppressProjectRefresh) + { + return; + } + + var newLength = string.IsNullOrEmpty(e.NewValue) ? 0 : e.NewValue.Length; + var oldLength = string.IsNullOrEmpty(e.OldValue) ? 0 : e.OldValue.Length; + if (Math.Abs(newLength - oldLength) > 1 || _applyAutoClose) + { + // Refresh now + QueueRefreshProject(); + } + else + { + // Refresh after delay + _refreshInitiated = DateTime.Now; + } + } + catch (Exception) + { + } + } + + private void QueueRefreshProject() + { + _refreshInitiated = null; + + _loadQueue.Enqueue(_ui._textSource.Text); + _refreshProjectEvent.Set(); + } + + private void RefreshProjectProc(object state) + { + while (true) + { + _refreshProjectEvent.WaitOne(); + + string data; + + // We're interested only in the last data + while (_loadQueue.Count > 1) + { + _loadQueue.TryDequeue(out data); + } + + if (_loadQueue.TryDequeue(out data)) + { + try + { + _ui._textStatus.Text = "Reloading..."; + + var xDoc = XDocument.Parse(data, LoadOptions.SetLineInfo); + + var stylesheet = Stylesheet.Current; + var stylesheetPathAttr = xDoc.Root.Attribute("StylesheetPath"); + Exception styleException = null; + if (stylesheetPathAttr != null) + { + try + { + stylesheet = StylesheetFromFile(stylesheetPathAttr.Value); + } + catch (Exception ex) + { + var dialog = Dialog.CreateMessageBox("Stylesheet Error", ex.ToString()); + dialog.ShowModal(_desktop); + styleException = ex; + } + } + + var newProject = Project.LoadFromXml(xDoc, AssetManager, stylesheet); + + if (styleException != null) + { + newProject.Diagnostics.Add(new MMLDiagnostic(MMLDiagnosticSeverity.Error, "", "", string.Format( + "Failed to load Stylesheet: '{0}'", styleException.Message))); + } + + _newProjectsQueue.Enqueue(newProject); + + _ui._textStatus.Text = string.Empty; + } + catch (Exception ex) + { + _ui._textStatus.Text = ex.Message; + } + } + } + } + + private static readonly Regex TagResolver = new Regex("<([A-Za-z0-9\\.]+)"); + + private static string ExtractTag(string source) + { + if (string.IsNullOrEmpty(source)) + { + return null; + } + + return TagResolver.Match(source).Groups[1].Value; + } + + private void UpdatePositions() + { + var lastStart = _currentTagStart; + var lastEnd = _currentTagEnd; + + _line = _col = _indentLevel = 0; + _parentTag = null; + _currentTagStart = null; + _currentTagEnd = null; + _needsCloseTag = false; + + if (string.IsNullOrEmpty(_ui._textSource.Text)) + { + return; + } + + var cursorPos = _ui._textSource.CursorPosition; + var text = _ui._textSource.Text; + + int? tagOpen = null; + var isOpenTag = true; + var length = text.Length; + + string currentTag = null; + Stack parentStack = new Stack(); + for (var i = 0; i < length; ++i) + { + if (tagOpen == null) + { + if (i >= cursorPos) + { + break; + } + + currentTag = null; + _currentTagStart = null; + _currentTagEnd = null; + } + + if (i < cursorPos) + { + ++_col; + } + + char c = text[i]; + if (c == '\n') + { + ++_line; + _col = 0; + } + + if (c == '<') + { + if (tagOpen != null && isOpenTag && i >= cursorPos + 1) + { + // tag is not closed + _currentTagStart = tagOpen; + _currentTagEnd = null; + break; + } + + if (i < length - 1 && text[i + 1] != '?') + { + tagOpen = i; + isOpenTag = text[i + 1] != '/'; + } + } + + if (tagOpen != null && i > tagOpen.Value && c == '>') + { + if (isOpenTag) + { + bool needsCloseTag = text[i - 1] != '/'; + _needsCloseTag = needsCloseTag; + + currentTag = text.Substring(tagOpen.Value, i - tagOpen.Value + 1); + _currentTagStart = tagOpen; + _currentTagEnd = i; + + if (needsCloseTag && i <= cursorPos) + { + parentStack.Push(currentTag); + } + } + else + { + if (parentStack.Count > 0) + { + parentStack.Pop(); + } + } + + tagOpen = null; + } + } + + _indentLevel = parentStack.Count; + if (parentStack.Count > 0) + { + _parentTag = parentStack.Pop(); + } + + _ui._textLocation.Text = string.Format("Line: {0}, Col: {1}, Indent: {2}", _line + 1, _col + 1, _indentLevel); + + if (!string.IsNullOrEmpty(_parentTag)) + { + _parentTag = ExtractTag(_parentTag); + + _ui._textLocation.Text += ", Parent: " + _parentTag; + } + + if ((lastStart != _currentTagStart || lastEnd != _currentTagEnd)) + { + _propertyGrid.Object = null; + if (!string.IsNullOrEmpty(currentTag)) + { + var xml = currentTag; + + if (_needsCloseTag) + { + var tag = ExtractTag(currentTag); + xml += ""; + } + + ThreadPool.QueueUserWorkItem(LoadObjectAsync, xml); + } + } + + HandleAutoComplete(); + } + + private void HandleAutoComplete() + { + if (_desktop.ContextMenu == _autoCompleteMenu) + { + _desktop.HideContextMenu(); + } + + if (_currentTagStart == null || _currentTagEnd != null || string.IsNullOrEmpty(_parentTag)) + { + return; + } + + var cursorPos = _ui._textSource.CursorPosition; + var text = _ui._textSource.Text; + + // Tag isn't closed + var typed = text.Substring(_currentTagStart.Value, cursorPos - _currentTagStart.Value); + if (typed.StartsWith("<")) + { + typed = typed.Substring(1); + + var all = BuildAutoCompleteVariants(); + + // Filter typed + if (!string.IsNullOrEmpty(typed)) + { + all = (from a in all where a.StartsWith(typed, StringComparison.OrdinalIgnoreCase) select a).ToList(); + } + + if (all.Count > 0) + { + var lastStartPos = _currentTagStart.Value; + var lastEndPos = cursorPos; + // Build context menu + _autoCompleteMenu = new VerticalMenu(); + foreach (var a in all) + { + var menuItem = new MenuItem + { + Text = a + }; + + menuItem.Selected += (s, args) => + { + var result = "<" + menuItem.Text; + var skip = result.Length; + var needsClose = false; + + if (SimpleWidgets.Contains(menuItem.Text) || + Project.IsProportionName(menuItem.Text) || + menuItem.Text == MenuItemName || + menuItem.Text == ListItemName) + { + result += "/>"; + skip += 2; + } + else + { + result += ">"; + ++skip; + + if (_options.AutoIndent && _options.IndentSpacesSize > 0) + { + // Indent before cursor pos + result += "\n"; + var indentSize = _options.IndentSpacesSize * (_indentLevel + 1); + result += new string(' ', indentSize); + skip += indentSize; + + // Indent before closing tag + result += "\n"; + indentSize = _options.IndentSpacesSize * _indentLevel; + result += new string(' ', indentSize); + } + result += ""; + ++skip; + needsClose = true; + } + + _ui._textSource.Replace(lastStartPos, lastEndPos - lastStartPos, result); + _ui._textSource.CursorPosition = lastStartPos + skip; + if (needsClose) + { + // _ui._textSource.OnKeyDown(Keys.Enter); + } + }; + + _autoCompleteMenu.Items.Add(menuItem); + } + + var screen = _ui._textSource.CursorScreenPosition; + screen.Y += _ui._textSource.Font.FontSize; + + if (_autoCompleteMenu.Items.Count > 0) + { + _autoCompleteMenu.HoverIndex = 0; + } + + _desktop.ShowContextMenu(_autoCompleteMenu, screen); + // Keep focus at text field + _desktop.FocusedKeyboardWidget = _ui._textSource; + + _refreshInitiated = null; + } + } + } + + private List BuildAutoCompleteVariants() + { + var result = new List(); + + if (string.IsNullOrEmpty(_parentTag)) + { + return result; + } + + if (_parentTag == "Project") + { + result.AddRange(Containers); + result.Add("Dialog"); + } + else if (Containers.Contains(_parentTag) || _parentTag == "Dialog") + { + result.AddRange(SimpleWidgets); + result.AddRange(Containers); + result.AddRange(SpecialContainers); + } + else if (_parentTag.EndsWith(RowsProportionsName) || _parentTag.EndsWith(ColumnsProportionsName) || _parentTag.EndsWith(ProportionsName)) + { + result.Add(Project.ProportionName); + } + else if (_parentTag.EndsWith("Menu")) + { + result.Add("MenuItem"); + } + else if (_parentTag == "ListBox" || _parentTag == "ComboBox") + { + result.Add("ListItem"); + } + else if (_parentTag == "TabControl") + { + result.Add("TabItem"); + } + + if (_parentTag == "Grid") + { + result.Add(_parentTag + "." + ColumnsProportionsName); + result.Add(_parentTag + "." + RowsProportionsName); + result.Add(_parentTag + "." + Project.DefaultColumnProportionName); + result.Add(_parentTag + "." + Project.DefaultRowProportionName); + } + + if (_parentTag == "VerticalStackPanel" || _parentTag == "HorizontalStackPanel") + { + result.Add(_parentTag + "." + Project.DefaultProportionName); + result.Add(_parentTag + "." + ProportionsName); + } + + result = result.OrderBy(s => !s.Contains('.')).ThenBy(s => s).ToList(); + + return result; + } + + private void LoadObjectAsync(object state) + { + try + { + var xml = (string)state; + _newObject = Project.LoadObjectFromXml(xml, AssetManager); + } + catch (Exception) + { + } + } + + private void UpdateCursor() + { + try + { + UpdatePositions(); + ApplyAutoIndent(); + ApplyAutoClose(); + } + catch (Exception) + { + } + } + + private void _textSource_CursorPositionChanged(object sender, EventArgs e) + { + UpdateCursor(); + } + + private void OnMenuFileReloadSelected(object sender, EventArgs e) + { + AssetManager.ClearCache(); + Load(FilePath); + } + + private Stylesheet StylesheetFromFile(string path) + { + if (!Path.IsPathRooted(path)) + { + path = Path.Combine(Path.GetDirectoryName(FilePath), path); + } + + return AssetManager.Load(path); + } + + private void LoadStylesheet(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + try + { + if (!Path.IsPathRooted(filePath)) + { + filePath = Path.Combine(Path.GetDirectoryName(FilePath), filePath); + } + + var stylesheet = StylesheetFromFile(filePath); + + Project.StylesheetPath = filePath; + UpdateSource(); + } + catch (Exception ex) + { + var dialog = Dialog.CreateMessageBox("Error", ex.ToString()); + dialog.ShowModal(_desktop); + } + } + + private void OnMenuFileLoadStylesheet(object sender, EventArgs e) + { + AssetManager.ClearCache(); + + var dlg = new FileDialog(FileDialogMode.OpenFile) + { + Filter = "*.xmms|*.xml" + }; + + try + { + if (!string.IsNullOrEmpty(Project.StylesheetPath)) + { + var stylesheetPath = Project.StylesheetPath; + if (!Path.IsPathRooted(stylesheetPath)) + { + // Prepend folder path + stylesheetPath = Path.Combine(Path.GetDirectoryName(FilePath), stylesheetPath); + } + + dlg.Folder = Path.GetDirectoryName(stylesheetPath); + } + else if (!string.IsNullOrEmpty(FilePath)) + { + dlg.Folder = Path.GetDirectoryName(FilePath); + } + } + catch (Exception) + { + } + + dlg.Closed += (s, a) => + { + if (!dlg.Result) + { + return; + } + + var filePath = dlg.FilePath; + + // Check whether stylesheet could be loaded + try + { + var stylesheet = StylesheetFromFile(filePath); + } + catch (Exception ex) + { + var msg = Dialog.CreateMessageBox("Stylesheet Error", ex.Message); + msg.ShowModal(_desktop); + return; + } + + // Try to make stylesheet path relative to project folder + filePath = PathUtils.TryToMakePathRelativeTo(filePath, Path.GetDirectoryName(FilePath)); + + Project.StylesheetPath = filePath; + UpdateSource(); + UpdateMenuFile(); + }; + + dlg.ShowModal(_desktop); + } + + private void OnMenuFileResetStylesheetSelected(object sender, EventArgs e) + { + AssetManager.ClearCache(); + Project.StylesheetPath = null; + UpdateSource(); + UpdateMenuFile(); + } + + private void DebugOptionsItemOnSelected(object sender1, EventArgs eventArgs) + { + var debugOptions = new DebugOptionsWindow(); + debugOptions.ShowModal(_desktop); + } + + private void ExportCsItemOnSelected(object sender1, EventArgs eventArgs) + { + var dlg = new ExportOptionsDialog(); + dlg.ShowModal(_desktop); + + dlg.Closed += (s, a) => + { + if (!dlg.Result) + { + return; + } + + try + { + Project.ExportOptions.Namespace = dlg._textNamespace.Text; + Project.ExportOptions.OutputPath = dlg._textOutputPath.Text; + Project.ExportOptions.Class = dlg._textClassName.Text; + + UpdateSource(); + + using (var export = new ExporterCS(Instance.Project)) + { + var strings = new List + { + "Success. Following files had been written:" + }; + strings.AddRange(export.Export()); + + var msg = Dialog.CreateMessageBox("Export To C#", string.Join("\n", strings)); + msg.ShowModal(_desktop); + } + } + catch (Exception ex) + { + var msg = Dialog.CreateMessageBox("Error", ex.Message); + msg.ShowModal(_desktop); + } + }; + } + + private void PropertyGridOnPropertyChanged(object sender, GenericEventArgs eventArgs) + { + IsDirty = true; + + var xml = _project.SaveObjectToXml(_propertyGrid.Object, ExtractTag(CurrentTag)); + + if (_needsCloseTag) + { + xml = xml.Replace("/>", ">"); + } + + if (_currentTagStart != null && _currentTagEnd != null) + { + try + { + _suppressProjectRefresh = true; + _ui._textSource.Replace(_currentTagStart.Value, + _currentTagEnd.Value - _currentTagStart.Value + 1, + xml); + QueueRefreshProject(); + } + finally + { + _suppressProjectRefresh = false; + } + + _currentTagEnd = _currentTagStart.Value + xml.Length - 1; + } + } + + private void QuitItemOnDown(object sender, EventArgs eventArgs) + { + var mb = Dialog.CreateMessageBox("Quit", "Are you sure?"); + + mb.Closed += (o, args) => + { + if (mb.Result) + { + Exit(); + } + }; + + mb.ShowModal(_desktop); + } + + private void AboutItemOnClicked(object sender, EventArgs eventArgs) + { + var messageBox = Dialog.CreateMessageBox("About", "MyraPad " + MyraEnvironment.Version); + messageBox.ShowModal(_desktop); + } + + private void SaveAsItemOnClicked(object sender, EventArgs eventArgs) + { + Save(true); + } + + private void SaveItemOnClicked(object sender, EventArgs eventArgs) + { + Save(false); + } + + private void NewItemOnClicked(object sender, EventArgs eventArgs) + { + var dlg = new NewProjectWizard(); + + dlg.Closed += (s, a) => + { + if (!dlg.Result) + { + return; + } + + var rootType = "Grid"; + + if (dlg._radioButtonHorizontalStackPanel.IsPressed) + { + rootType = "HorizontalStackPanel"; + } + else + if (dlg._radioButtonVerticalStackPanel.IsPressed) + { + rootType = "VerticalStackPanel"; + } + else + if (dlg._radioButtonPanel.IsPressed) + { + rootType = "Panel"; + } + else + if (dlg._radioButtonScrollViewer.IsPressed) + { + rootType = "ScrollViewer"; + } + else + if (dlg._radioButtonHorizontalSplitPane.IsPressed) + { + rootType = "HorizontalSplitPane"; + } + else + if (dlg._radioButtonVerticalSplitPane.IsPressed) + { + rootType = "VerticalSplitPane"; + } + else + if (dlg._radioButtonWindow.IsPressed) + { + rootType = "Window"; + } + else + if (dlg._radioButtonDialog.IsPressed) + { + rootType = "Dialog"; + } + + New(rootType); + }; + + dlg.ShowModal(_desktop); + } + + private void OpenItemOnClicked(object sender, EventArgs eventArgs) + { + var dlg = new FileDialog(FileDialogMode.OpenFile) + { + Filter = "*.xmmp|*.xml" + }; + + if (!string.IsNullOrEmpty(FilePath)) + { + dlg.Folder = Path.GetDirectoryName(FilePath); + } + else if (!string.IsNullOrEmpty(_lastFolder)) + { + dlg.Folder = _lastFolder; + } + + dlg.Closed += (s, a) => + { + if (!dlg.Result) + { + return; + } + + var filePath = dlg.FilePath; + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + Load(filePath); + }; + + dlg.ShowModal(_desktop); + } + + protected override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (_refreshInitiated != null && (DateTime.Now - _refreshInitiated.Value).TotalSeconds >= 0.75f) + { + QueueRefreshProject(); + } + + if (_newObject != null) + { + _propertyGrid.Object = _newObject; + _newObject = null; + } + + Project newProject; + while (_newProjectsQueue.Count > 1) + { + _newProjectsQueue.TryDequeue(out newProject); + } + + if (_newProjectsQueue.TryDequeue(out newProject)) + { + Project = newProject; + + if (Project.Stylesheet != null && Project.Stylesheet.DesktopStyle != null) + { + _ui._projectHolder.Background = Project.Stylesheet.DesktopStyle.Background; + } + else + { + _ui._projectHolder.Background = null; + } + } + } + + protected override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + + GraphicsDevice.Clear(Color.Black); + + _desktop.Render(); + } + + protected override void EndRun() + { + base.EndRun(); + + var state = new State + { + Size = new Point(GraphicsDevice.PresentationParameters.BackBufferWidth, + GraphicsDevice.PresentationParameters.BackBufferHeight), + TopSplitterPosition = _ui._topSplitPane.GetSplitterPosition(0), + LeftSplitterPosition = _ui._leftSplitPane.GetSplitterPosition(0), + EditedFile = FilePath, + LastFolder = _lastFolder, + UserColors = (from c in ColorPickerPanel.UserColors select c).ToArray() + }; + + state.Save(); + } + + private void New(string rootType) + { + var source = Resources.NewProjectTemplate.Replace("$containerType", rootType); + + _ui._textSource.Text = source; + + var newLineCount = 0; + var pos = 0; + while (pos < _ui._textSource.Text.Length && newLineCount < 3) + { + ++pos; + + if (_ui._textSource.Text[pos] == '\n') + { + ++newLineCount; + } + } + + _ui._textSource.CursorPosition = pos; + _desktop.FocusedKeyboardWidget = _ui._textSource; + + FilePath = string.Empty; + IsDirty = false; + _ui._projectHolder.Background = null; + } + + private void ProcessSave(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + UpdateResourcesPaths(FilePath, filePath, updated => + { + File.WriteAllText(filePath, _ui._textSource.Text); + + FilePath = filePath; + IsDirty = false; + + if (updated) + { + QueueRefreshProject(); + } + }); + } + + private void Save(bool setFileName) + { + if (string.IsNullOrEmpty(FilePath) || setFileName) + { + var dlg = new FileDialog(FileDialogMode.SaveFile) + { + Filter = "*.xmmp" + }; + + if (!string.IsNullOrEmpty(FilePath)) + { + dlg.FilePath = FilePath; + } + else if (!string.IsNullOrEmpty(_lastFolder)) + { + dlg.Folder = _lastFolder; + } + + dlg.ShowModal(_desktop); + + dlg.Closed += (s, a) => + { + if (dlg.Result) + { + ProcessSave(dlg.FilePath); + } + }; + } + else + { + ProcessSave(FilePath); + } + } + + private void Load(string filePath) + { + try + { + var data = File.ReadAllText(filePath); + + FilePath = filePath; + + try + { + _suppressProjectRefresh = true; + _ui._textSource.Text = data; + _ui._textSource.CursorPosition = 0; + } + finally + { + _suppressProjectRefresh = false; + } + + QueueRefreshProject(); + UpdateCursor(); + _desktop.FocusedKeyboardWidget = _ui._textSource; + + IsDirty = false; + } + catch (Exception ex) + { + var dialog = Dialog.CreateMessageBox("Error", ex.ToString()); + dialog.ShowModal(_desktop); + } + } + + private void UpdateTitle() + { + var title = string.IsNullOrEmpty(_filePath) ? "MyraPad" : _filePath; + + if (_isDirty) + { + title += " *"; + } + + Window.Title = title; + } + + private void UpdateMenuFile() + { + _ui._menuFileReload.Enabled = !string.IsNullOrEmpty(FilePath); + } + } } From 2c9f6083008fa37fe5601d3744bb9ea57f2c282c Mon Sep 17 00:00:00 2001 From: LuckyScamper Date: Sun, 21 Feb 2021 01:20:53 +0100 Subject: [PATCH 2/6] multiple bugfixes and stack error changes --- src/Myra/Graphics2D/UI/HorizontalSplitPane.cs | 5 +- src/Myra/Graphics2D/UI/Project.cs | 15 +-- src/Myra/Graphics2D/UI/Styles/Stylesheet.cs | 22 +++- .../Graphics2D/UI/Styles/StylesheetLoader.cs | 51 +++++---- src/Myra/MML/LoadContext.cs | 106 +++++++----------- src/MyraPad/CannotBuildException.cs | 30 +++++ 6 files changed, 128 insertions(+), 101 deletions(-) create mode 100644 src/MyraPad/CannotBuildException.cs diff --git a/src/Myra/Graphics2D/UI/HorizontalSplitPane.cs b/src/Myra/Graphics2D/UI/HorizontalSplitPane.cs index 7d659fee..bcde4d59 100644 --- a/src/Myra/Graphics2D/UI/HorizontalSplitPane.cs +++ b/src/Myra/Graphics2D/UI/HorizontalSplitPane.cs @@ -19,7 +19,10 @@ public HorizontalSplitPane(string styleName = Stylesheet.DefaultStyleName) : bas protected override void InternalSetStyle(Stylesheet stylesheet, string name) { - ApplySplitPaneStyle(stylesheet.HorizontalSplitPaneStyles[name]); + if (stylesheet.HorizontalSplitPaneStyles.ContainsKey(name)) + { + ApplySplitPaneStyle(stylesheet.HorizontalSplitPaneStyles[name]); + } } } } \ No newline at end of file diff --git a/src/Myra/Graphics2D/UI/Project.cs b/src/Myra/Graphics2D/UI/Project.cs index f24ad6f3..a9389177 100644 --- a/src/Myra/Graphics2D/UI/Project.cs +++ b/src/Myra/Graphics2D/UI/Project.cs @@ -215,7 +215,7 @@ public static Project LoadFromXml(XDocument xDoc, IAssetManager assetManager, var loadContext = result.CreateLoadContext(assetManager); - loadContext.Load(result, xDoc.Root, (d) => result.Diagnostics.Add(d)); + loadContext.Load(result, xDoc.Root, (d) => result.Diagnostics.Add(d), handler); return result; } @@ -245,12 +245,8 @@ public static Project LoadFromXml(string data, IAssetManager assetManager = null return LoadFromXml(data, assetManager, Stylesheet.Current, null); } - public static object LoadObjectFromXml(string data, IAssetManager assetManager, Stylesheet stylesheet, T handler) where T : class + public static object LoadObjectFromXml(string data, IAssetManager assetManager, Stylesheet stylesheet, T handler, MMLDiagnosticAction onDiagnostic = null) where T : class { - XDocument xDoc = XDocument.Parse(data); - public static object LoadObjectFromXml( - string data, IAssetManager assetManager, Stylesheet stylesheet, MMLDiagnosticAction onDiagnostic = null) - { XDocument xDoc = XDocument.Parse(data); var name = xDoc.Root.Name.ToString(); @@ -283,7 +279,7 @@ public static object LoadObjectFromXml( var item = CreateItem(itemType, xDoc.Root, stylesheet); var loadContext = CreateLoadContext(assetManager, stylesheet); - loadContext.Load(item, xDoc.Root, handler); + loadContext.Load(item, xDoc.Root, onDiagnostic, handler); return item; } @@ -297,10 +293,7 @@ public object LoadObjectFromXml(string data, IAssetManager assetManager) { return LoadObjectFromXml(data, assetManager, Stylesheet); } - public object LoadObjectFromXml(string data, IAssetManager assetManager) - { - return LoadObjectFromXml(data, assetManager, Stylesheet); - } + public string SaveObjectToXml(object obj, string tagName) { diff --git a/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs b/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs index 280cae5b..b8624981 100644 --- a/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs +++ b/src/Myra/Graphics2D/UI/Styles/Stylesheet.cs @@ -615,11 +615,27 @@ public static Stylesheet LoadFromSource( return region; } - throw new Exception(string.Format("Could not find parse IBrush '{0}'", name)); + onDiagnostic?.Invoke(new MMLDiagnostic(MMLDiagnosticSeverity.Error, "Assets", "Brush", $"Could not find parse IBrush '{name}'")); + return null; } else if (t == typeof(SpriteFontBase)) { - return fonts[name]; + if (fonts.ContainsKey(name)) + { + return fonts[name]; + } + + if (fonts == null || fonts.Count == 0) + { + onDiagnostic?.Invoke(new MMLDiagnostic(MMLDiagnosticSeverity.Error, "Assets", "Font", "There are no fonts registered.")); + + } + else + { + onDiagnostic?.Invoke(new MMLDiagnostic(MMLDiagnosticSeverity.Error, "Assets", "Font", $"Font '{name}' has not been registered.")); + } + + return null; } throw new Exception(string.Format("Type {0} isn't supported", t.Name)); @@ -644,7 +660,7 @@ public static Stylesheet LoadFromSource( Colors = colors }; - loadContext.Load(result, xDoc.Root, null); + loadContext.Load(result, xDoc.Root, onDiagnostic, null); return result; } diff --git a/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs b/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs index cf9386c8..a6154682 100644 --- a/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs +++ b/src/Myra/Graphics2D/UI/Styles/StylesheetLoader.cs @@ -31,36 +31,41 @@ public Stylesheet Load(AssetLoaderContext context, string assetName, MMLDiagnost // Load fonts var fonts = new Dictionary(); var fontsNode = xDoc.Root.Element("Fonts"); - foreach (var el in fontsNode.Elements()) + if (fontsNode != null) { - SpriteFontBase font = null; - - var fontFile = el.Attribute("File").Value; - if (fontFile.EndsWith(".ttf")) + foreach (var el in fontsNode.Elements()) { - var parts = new List(); - parts.Add(fontFile); - - var typeAttribute = el.Attribute("Type"); - if (typeAttribute != null) + SpriteFontBase font = null; + + var fontFile = el.Attribute("File").Value; + if (fontFile.EndsWith(".ttf")) { - parts.Add(typeAttribute.Value); + var parts = new List(); + parts.Add(fontFile); + + var typeAttribute = el.Attribute("Type"); + if (typeAttribute != null) + { + parts.Add(typeAttribute.Value); - var amountAttribute = el.Attribute("Amount"); - parts.Add(amountAttribute.Value); + var amountAttribute = el.Attribute("Amount"); + parts.Add(amountAttribute.Value); + } + + parts.Add(el.Attribute("Size").Value); + font = context.Load(string.Join(":", parts)); + } + else if (fontFile.EndsWith(".fnt")) + { + font = context.Load(fontFile); + } + else + { + throw new Exception(string.Format("Font '{0}' isn't supported", fontFile)); } - parts.Add(el.Attribute("Size").Value); - font = context.Load(string.Join(":", parts)); - } else if (fontFile.EndsWith(".fnt")) - { - font = context.Load(fontFile); - } else - { - throw new Exception(string.Format("Font '{0}' isn't supported", fontFile)); + fonts[el.Attribute(BaseContext.IdName).Value] = font; } - - fonts[el.Attribute(BaseContext.IdName).Value] = font; } return Stylesheet.LoadFromSource(xml, textureRegionAtlas, fonts, onDiagnostic); diff --git a/src/Myra/MML/LoadContext.cs b/src/Myra/MML/LoadContext.cs index b54c3264..a3155169 100644 --- a/src/Myra/MML/LoadContext.cs +++ b/src/Myra/MML/LoadContext.cs @@ -34,18 +34,15 @@ internal class LoadContext : BaseContext private const string UserDataAttributePrefix = "_"; - public void Load(object obj, XElement el, MMLDiagnosticAction onDiagnostic) + public void Load(object obj, XElement el, MMLDiagnosticAction onDiagnostic, T handler) where T : class { if (onDiagnostic == null) onDiagnostic = (d) => throw new Exception(d.Message); try { - var type = obj.GetType(); - public void Load(object obj, XElement el, T handler) where T : class - { - var type = obj.GetType(); - var handlerType = typeof(T); + var type = obj.GetType(); + var handlerType = typeof(T); var baseObject = obj as BaseObject; @@ -159,6 +156,18 @@ public void Load(object obj, XElement el, T handler) where T : class property.SetValue(obj, value); } } + else if (handler != null && type.GetEvent(attr.Name.LocalName) != null) + { + var method = handlerType.GetMethod(attr.Value, BindingFlags.Public | BindingFlags.Instance); + var eventHandler = type.GetEvent(attr.Name.LocalName); + if (method == null) + { + throw new InvalidOperationException($"Handler of type '{handlerType}' does not contain method '{attr.Value}'. If it does, ensure the method is both public and non-static."); + } + + var delegateMethod = method.CreateDelegate(eventHandler.EventHandlerType, handler); + eventHandler.AddEventHandler(obj, delegateMethod); + } else { // Stow away custom user attributes @@ -169,38 +178,9 @@ public void Load(object obj, XElement el, T handler) where T : class } } - PropertyInfo contentProperty = - (from p in complexProperties - where p.FindAttribute() != null - select p).FirstOrDefault(); - property.SetValue(obj, value); - } - else if (handler != null && type.GetEvent(attr.Name.LocalName) != null) - { - var method = handlerType.GetMethod(attr.Value, BindingFlags.Public | BindingFlags.Instance); - var eventHandler = type.GetEvent(attr.Name.LocalName); - if (method == null) - { - throw new InvalidOperationException($"Handler of type '{handlerType}' does not contain method '{attr.Value}'. If it does, ensure the method is both public and non-static."); - } - - var delegateMethod = method.CreateDelegate(eventHandler.EventHandlerType, handler); - eventHandler.AddEventHandler(obj, delegateMethod); - } - else - { - // Stow away custom user attributes - if (propertyName.StartsWith(UserDataAttributePrefix) && baseObject != null) - { - baseObject.UserData.Add(propertyName, attr.Value); - } - } - } - - - var contentProperty = (from p in complexProperties - where p.FindAttribute() - != null select p).FirstOrDefault(); + var contentProperty = (from p in complexProperties + where p.FindAttribute() + != null select p).FirstOrDefault(); foreach (XElement child in el.Elements()) { @@ -238,7 +218,7 @@ where p.FindAttribute() foreach (var child2 in child.Elements()) { var item = ObjectCreator(property.PropertyType.GenericTypeArguments[0], child2); - Load(item, child2, handler); + Load(item, child2, onDiagnostic, handler); asList.Add(item); } @@ -252,28 +232,28 @@ where p.FindAttribute() foreach (var child2 in child.Elements()) { var item = ObjectCreator(property.PropertyType.GenericTypeArguments[1], child2); - Load(item, child2, handler); - - var id = string.Empty; - if (child2.Attribute(IdName) != null) - { - id = child2.Attribute(IdName).Value; - } + Load(item, child2, onDiagnostic, handler); - asDict[id] = item; + var id = string.Empty; + if (child2.Attribute(IdName) != null) + { + id = child2.Attribute(IdName).Value; } - break; + + asDict[id] = item; } + break; + } if (property.SetMethod == null) { // Readonly - Load(value, child, handler); + Load(value, child, onDiagnostic, handler); } else { var newValue = ObjectCreator(property.PropertyType, child); - Load(newValue, child, handler); + Load(newValue, child, onDiagnostic, handler); property.SetValue(obj, newValue); } break; @@ -294,19 +274,19 @@ where p.FindAttribute() widgetName = newName; } - Type itemType = null; - foreach(var ns in Namespaces) - { - itemType = Assembly.GetType(ns + "." + widgetName); - if (itemType != null) - { - break; - } - } - if (itemType != null) - { - var item = ObjectCreator(itemType, child); - Load(item, child, handler); + Type itemType = null; + foreach(var ns in Namespaces) + { + itemType = Assembly.GetType(ns + "." + widgetName); + if (itemType != null) + { + break; + } + } + if (itemType != null) + { + var item = ObjectCreator(itemType, child); + Load(item, child, onDiagnostic, handler); if (contentProperty == null) { diff --git a/src/MyraPad/CannotBuildException.cs b/src/MyraPad/CannotBuildException.cs new file mode 100644 index 00000000..8db7f119 --- /dev/null +++ b/src/MyraPad/CannotBuildException.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MyraPad +{ + /// + /// throw this exception whenever the pad lacks sufficient information to build the UI. + /// + public class CannotBuildException : Exception + { + public CannotBuildErrorType ErrorType { get; } + + public CannotBuildException(CannotBuildErrorType errorType, string error) : base(error) + { + if (String.IsNullOrWhiteSpace(error)) + throw new ArgumentException("Eror message cannot be null or empty!", nameof(error)); + + ErrorType = errorType; + } + } + + public enum CannotBuildErrorType + { + FONT_MISSING, + XML_SYNTAX_ERROR + } +} From 6bcd1d8fe0dcae04050899549c10fd9f312e829d Mon Sep 17 00:00:00 2001 From: LuckyScamper Date: Sun, 21 Feb 2021 14:21:55 +0100 Subject: [PATCH 3/6] Prevented unnessary exception TextureRegionAtlas: why throw an exception when you can check if something is xml? --- src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs b/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs index 6c966bf9..38bd227c 100644 --- a/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs +++ b/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs @@ -103,16 +103,12 @@ public string ToXml() /// public static TextureRegionAtlas Load(string data, Func textureGetter) { - bool isXml; - try + bool isXml = false; + if (data.StartsWith("<") && data.EndsWith(">")) { var xDoc = XDocument.Parse(data); isXml = true; } - catch (Exception) - { - isXml = false; - } if (isXml) { From 837b23421c10c2fa0433a79dcd065b221b299c6a Mon Sep 17 00:00:00 2001 From: LuckyScamper Date: Thu, 25 Feb 2021 09:19:26 +0100 Subject: [PATCH 4/6] diagnostics are now entered in project constructor multiple bugfixes --- src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs | 7 ------- src/Myra/Graphics2D/UI/ComboBox.cs | 5 ++++- src/Myra/Graphics2D/UI/Project.cs | 1 + src/Myra/Myra.MonoGame.csproj | 7 +++++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs b/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs index 38bd227c..2729e86d 100644 --- a/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs +++ b/src/Myra/Graphics2D/TextureAtlases/TextureRegionAtlas.cs @@ -103,14 +103,7 @@ public string ToXml() /// public static TextureRegionAtlas Load(string data, Func textureGetter) { - bool isXml = false; if (data.StartsWith("<") && data.EndsWith(">")) - { - var xDoc = XDocument.Parse(data); - isXml = true; - } - - if (isXml) { return FromXml(data, textureGetter); } diff --git a/src/Myra/Graphics2D/UI/ComboBox.cs b/src/Myra/Graphics2D/UI/ComboBox.cs index e630c928..16e94506 100644 --- a/src/Myra/Graphics2D/UI/ComboBox.cs +++ b/src/Myra/Graphics2D/UI/ComboBox.cs @@ -196,7 +196,10 @@ internal void UpdateSelectedItem() { InternalChild.Text = item.Text; InternalChild.TextColor = item.Color ?? _listBox.ListBoxStyle.ListItemStyle.LabelStyle.TextColor; - ((ImageTextButton)item.Widget).IsPressed = true; + if (item.Widget != null) + { + ((ImageTextButton)item.Widget).IsPressed = true; + } } else { diff --git a/src/Myra/Graphics2D/UI/Project.cs b/src/Myra/Graphics2D/UI/Project.cs index a9389177..971a77e3 100644 --- a/src/Myra/Graphics2D/UI/Project.cs +++ b/src/Myra/Graphics2D/UI/Project.cs @@ -81,6 +81,7 @@ static Project() public Project() { Stylesheet = Stylesheet.Current; + _diagnostics = new List(); } public static bool IsProportionName(string s) diff --git a/src/Myra/Myra.MonoGame.csproj b/src/Myra/Myra.MonoGame.csproj index 26c9d0b1..0b6fb746 100644 --- a/src/Myra/Myra.MonoGame.csproj +++ b/src/Myra/Myra.MonoGame.csproj @@ -6,6 +6,9 @@ Myra bin\MonoGame\$(Configuration) true + 1.2.2.7 + 1.2.2.7 + 1.2.2.7 @@ -18,8 +21,8 @@ - - + + \ No newline at end of file From 2e2c4c58103dc6ddd7c82cf7c5bb3196a871c6b2 Mon Sep 17 00:00:00 2001 From: LuckyScamper Date: Thu, 25 Feb 2021 09:37:11 +0100 Subject: [PATCH 5/6] ColorHSV now uses proper equals function Small edit in Widget for sake of readability. --- src/Myra/Graphics2D/UI/Widget.cs | 27 +++++++++++++------------- src/Myra/Utility/ColorHSV.cs | 33 +++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Myra/Graphics2D/UI/Widget.cs b/src/Myra/Graphics2D/UI/Widget.cs index 7d921549..55213081 100644 --- a/src/Myra/Graphics2D/UI/Widget.cs +++ b/src/Myra/Graphics2D/UI/Widget.cs @@ -1292,20 +1292,19 @@ private Widget FindWidgetBy(Func finder) return this; } - var asContainer = this as Container; - if (asContainer != null) - { - foreach (var widget in asContainer.ChildrenCopy) - { - var result = widget.FindWidgetBy(finder); - if (result != null) - { - return result; - } - } - } - - return null; + if (this is Container asContainer) + { + foreach (var widget in asContainer.ChildrenCopy) + { + var result = widget.FindWidgetBy(finder); + if (result != null) + { + return result; + } + } + } + + return null; } /// diff --git a/src/Myra/Utility/ColorHSV.cs b/src/Myra/Utility/ColorHSV.cs index 1cc7d916..cd16877a 100644 --- a/src/Myra/Utility/ColorHSV.cs +++ b/src/Myra/Utility/ColorHSV.cs @@ -1,12 +1,14 @@ -namespace Myra.Utility +using System; + +namespace Myra.Utility { - internal struct ColorHSV - { + internal struct ColorHSV : IEquatable + { public int H { get; set; } public int S { get; set; } public int V { get; set; } - public static bool operator ==(ColorHSV a, ColorHSV b) + public static bool operator ==(ColorHSV a, ColorHSV b) { return a.H == b.H && a.V == b.V && a.S == b.V; } @@ -15,5 +17,26 @@ internal struct ColorHSV { return !(a == b); } - } + + public override bool Equals(object obj) + { + return obj is ColorHSV hSV && Equals(hSV); + } + + public bool Equals(ColorHSV other) + { + return H == other.H && + S == other.S && + V == other.V; + } + + public override int GetHashCode() + { + int hashCode = -1397884734; + hashCode *= -1521134295 + H.GetHashCode(); + hashCode *= -1521134295 + S.GetHashCode(); + hashCode *= -1521134295 + V.GetHashCode(); + return hashCode; + } + } } From 4cbd9f07f4f54fc01dc851ac9bc680a16bf1c62e Mon Sep 17 00:00:00 2001 From: LuckyScamper Date: Thu, 4 Mar 2021 14:35:24 +0100 Subject: [PATCH 6/6] new versioning --- src/Myra/Myra.MonoGame.csproj | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Myra/Myra.MonoGame.csproj b/src/Myra/Myra.MonoGame.csproj index 1b66fe47..4614ef1e 100644 --- a/src/Myra/Myra.MonoGame.csproj +++ b/src/Myra/Myra.MonoGame.csproj @@ -5,13 +5,8 @@ Myra Myra bin\MonoGame\$(Configuration) - - - + 1.2.3.2 true - 1.2.2.7 - 1.2.2.7 - 1.2.2.7