Skip to content

Commit

Permalink
Fixes #418
Browse files Browse the repository at this point in the history
  • Loading branch information
rds1983 committed Oct 23, 2023
1 parent 9d6fde5 commit 8184a8e
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 40 deletions.
12 changes: 12 additions & 0 deletions src/Myra/Attributes/SkipSaveAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Myra.Attributes
{
/// <summary>
/// Determines that property shouldn't be saved during MML serialization
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class SkipSaveAttribute: Attribute
{
}
}
12 changes: 8 additions & 4 deletions src/Myra/Graphics2D/UI/Containers/Grid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ public enum GridSelectionMode

public class Grid : MultipleItemsContainerBase
{
public static AttachedPropertyInfo<int> ColumnProperty = AttachedPropertiesRegistry.Create(typeof(Grid), "Column", 0);
public static AttachedPropertyInfo<int> RowProperty = AttachedPropertiesRegistry.Create(typeof(Grid), "Row", 0);
public static AttachedPropertyInfo<int> ColumnSpanProperty = AttachedPropertiesRegistry.Create(typeof(Grid), "ColumnSpan", 1);
public static AttachedPropertyInfo<int> RowSpanProperty = AttachedPropertiesRegistry.Create(typeof(Grid), "RowSpan", 1);
public static readonly AttachedPropertyInfo<int> ColumnProperty =
AttachedPropertiesRegistry.Create(typeof(Grid), "Column", 0, AttachedPropertyOption.AffectsArrange);
public static readonly AttachedPropertyInfo<int> RowProperty =
AttachedPropertiesRegistry.Create(typeof(Grid), "Row", 0, AttachedPropertyOption.AffectsArrange);
public static readonly AttachedPropertyInfo<int> ColumnSpanProperty =
AttachedPropertiesRegistry.Create(typeof(Grid), "ColumnSpan", 1, AttachedPropertyOption.AffectsArrange);
public static readonly AttachedPropertyInfo<int> RowSpanProperty =
AttachedPropertiesRegistry.Create(typeof(Grid), "RowSpan", 1, AttachedPropertyOption.AffectsArrange);

private readonly GridLayout _layout = new GridLayout();

Expand Down
73 changes: 67 additions & 6 deletions src/Myra/Graphics2D/UI/Containers/StackPanel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using System.Collections.ObjectModel;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;

#if MONOGAME || FNA
using Microsoft.Xna.Framework;
using Myra.Attributes;
using Myra.MML;

#elif STRIDE
using Stride.Core.Mathematics;
#else
Expand All @@ -12,9 +17,17 @@

namespace Myra.Graphics2D.UI
{
public abstract class StackPanel: MultipleItemsContainerBase
public abstract class StackPanel : MultipleItemsContainerBase
{
public static readonly AttachedPropertyInfo<ProportionType> ProportionTypeProperty =
AttachedPropertiesRegistry.Create(typeof(StackPanel), "ProportionType", ProportionType.Auto, AttachedPropertyOption.AffectsMeasure);
public static readonly AttachedPropertyInfo<float> ProportionValueProperty =
AttachedPropertiesRegistry.Create(typeof(StackPanel), "ProportionValue", 1.0f, AttachedPropertyOption.AffectsMeasure);


private readonly ObservableCollection<Proportion> _proportions = new ObservableCollection<Proportion>();
private readonly GridLayout _layout = new GridLayout();
private bool _childrenDirty = true;
private int _spacing;

[Browsable(false)]
Expand Down Expand Up @@ -65,21 +78,38 @@ public Proportion DefaultProportion
}
}

[Browsable(false)]
public ObservableCollection<Proportion> Proportions
private ObservableCollection<Proportion> InternalProportions
{
get => Orientation == Orientation.Horizontal ? _layout.ColumnsProportions : _layout.RowsProportions;
}


[Browsable(false)]
[Obsolete("Use StackPanel.GetProportion/StackPanel.SetProportion")]
[SkipSave]
public ObservableCollection<Proportion> Proportions => _proportions;

protected StackPanel()
{
GridLinesColor = Color.White;
DefaultProportion = Proportion.StackPanelDefault;

_proportions.CollectionChanged += (s, e) => InvalidateChildren();
}

private void InvalidateChildren()
{
_childrenDirty = true;
}

protected override void InternalUpdateChildren()
protected void UpdateChildren()
{
base.InternalUpdateChildren();
if (!_childrenDirty)
{
return;
}

InternalProportions.Clear();

var index = 0;
foreach (var widget in Widgets)
Expand All @@ -93,18 +123,49 @@ protected override void InternalUpdateChildren()
Grid.SetRow(widget, index);
}

if (index < _proportions.Count)
{
var prop = _proportions[index];
SetProportionType(widget, prop.Type);
SetProportionValue(widget, prop.Value);
}

InternalProportions.Add(new Proportion(GetProportionType(widget), GetProportionValue(widget)));

++index;
}

_childrenDirty = false;
}

protected override Point InternalMeasure(Point availableSize)
{
UpdateChildren();

return _layout.Measure(Widgets, availableSize);
}

protected override void InternalArrange()
{
UpdateChildren();

_layout.Arrange(Widgets, ActualBounds);
}

public override void OnAttachedPropertyChanged(BaseAttachedPropertyInfo propertyInfo)
{
base.OnAttachedPropertyChanged(propertyInfo);

if (propertyInfo.Id == ProportionTypeProperty.Id ||
propertyInfo.Id == ProportionValueProperty.Id)
{
InvalidateChildren();
}
}

public static ProportionType GetProportionType(Widget widget) => ProportionTypeProperty.GetValue(widget);
public static void SetProportionType(Widget widget, ProportionType value) => ProportionTypeProperty.SetValue(widget, value);
public static float GetProportionValue(Widget widget) => ProportionValueProperty.GetValue(widget);
public static void SetProportionValue(Widget widget, float value) => ProportionValueProperty.SetValue(widget, value);
}
}
4 changes: 2 additions & 2 deletions src/Myra/Graphics2D/UI/Properties/PropertyGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,6 @@ public void Rebuild()
hasSetter = false;
}


var record = new FieldRecord(field)
{
HasSetter = hasSetter,
Expand All @@ -1357,8 +1356,9 @@ public void Rebuild()
{
var record = new AttachedPropertyRecord(attachedProperty)
{
Category = ParentType.Name
Category = attachedProperty.OwnerType.Name
};

records.Add(record);
}
}
Expand Down
8 changes: 1 addition & 7 deletions src/Myra/Graphics2D/UI/Widget.Children.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,15 @@ private void UpdateChildren()

_childrenCopy.SortWidgetsByZIndex();

InternalUpdateChildren();

_childrenDirty = false;
}

protected void InvalidateChildren()
private void InvalidateChildren()
{
InvalidateMeasure();
_childrenDirty = true;
}

protected virtual void InternalUpdateChildren()
{
}

public int CalculateTotalChildCount(bool visibleOnly)
{
var result = ChildrenCopy.Count;
Expand Down
57 changes: 46 additions & 11 deletions src/Myra/MML/AttachedPropertiesRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,28 @@

namespace Myra.MML
{
public interface INotifyAttachedPropertyChanged
{
void OnAttachedPropertyChanged(BaseAttachedPropertyInfo propertyInfo);
}

public enum AttachedPropertyOption
{
None,
AffectsArrange,
AffectsMeasure,
}

public abstract class BaseAttachedPropertyInfo
{
public Type OwnerType { get; private set; }
public int Id { get; private set; }
public string Name { get; private set; }
public AttachedPropertyOption Option { get; private set; }
public abstract Type PropertyType { get; }
public abstract object DefaultValueObject { get; }

protected BaseAttachedPropertyInfo(int id, string name, Type ownerType)
protected BaseAttachedPropertyInfo(int id, string name, Type ownerType, AttachedPropertyOption option)
{
if (string.IsNullOrEmpty(name))
{
Expand All @@ -23,19 +36,21 @@ protected BaseAttachedPropertyInfo(int id, string name, Type ownerType)
OwnerType = ownerType ?? throw new ArgumentNullException(nameof(ownerType));
Name = name;
Id = id;
Option = option;
}

public abstract object GetValueObject(BaseObject obj);
public abstract void SetValueObject(BaseObject obj, object value);
}

public class AttachedPropertyInfo<T>: BaseAttachedPropertyInfo
public class AttachedPropertyInfo<T> : BaseAttachedPropertyInfo
{
public T DefaultValue { get; private set; }
public override Type PropertyType => typeof(T);
public override object DefaultValueObject => DefaultValue;

public AttachedPropertyInfo(int id, string name, Type ownerType, T defaultValue): base(id, name, ownerType)
public AttachedPropertyInfo(int id, string name, Type ownerType, T defaultValue, AttachedPropertyOption option) :
base(id, name, ownerType, option)
{
DefaultValue = defaultValue;
}
Expand All @@ -62,8 +77,20 @@ public void SetValue(BaseObject obj, T value)
var asWidget = obj as Widget;
if (asWidget != null)
{
asWidget.InvalidateMeasure();
switch (Option)
{
case AttachedPropertyOption.None:
break;
case AttachedPropertyOption.AffectsArrange:
asWidget.InvalidateArrange();
break;
case AttachedPropertyOption.AffectsMeasure:
asWidget.InvalidateMeasure();
break;
}
}

obj.OnAttachedPropertyChanged(this);
}

public override object GetValueObject(BaseObject widget) => GetValue(widget);
Expand All @@ -76,9 +103,9 @@ public static class AttachedPropertiesRegistry
private static readonly Dictionary<int, BaseAttachedPropertyInfo> _properties = new Dictionary<int, BaseAttachedPropertyInfo>();
private static readonly Dictionary<Type, BaseAttachedPropertyInfo[]> _propertiesByType = new Dictionary<Type, BaseAttachedPropertyInfo[]>();

public static AttachedPropertyInfo<T> Create<T>(Type type, string name, T defaultValue)
public static AttachedPropertyInfo<T> Create<T>(Type type, string name, T defaultValue, AttachedPropertyOption option)
{
var result = new AttachedPropertyInfo<T>(_properties.Count, name, type, defaultValue);
var result = new AttachedPropertyInfo<T>(_properties.Count, name, type, defaultValue, option);
_properties[result.Id] = result;

return result;
Expand All @@ -92,15 +119,23 @@ public static BaseAttachedPropertyInfo[] GetPropertiesOfType(Type type)
return result;
}

// Make sure all static fields of type are initialized
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
var propertiesList = new List<BaseAttachedPropertyInfo>();
foreach(var pair in _properties)

// Build list of all attached properties
var currentType = type;
while (currentType != null && currentType != typeof(object))
{
if (pair.Value.OwnerType == type)
// Make sure all static fields of type are initialized
RuntimeHelpers.RunClassConstructor(currentType.TypeHandle);
foreach (var pair in _properties)
{
propertiesList.Add(pair.Value);
if (pair.Value.OwnerType == currentType)
{
propertiesList.Add(pair.Value);
}
}

currentType = currentType.BaseType;
}

result = propertiesList.ToArray();
Expand Down
14 changes: 12 additions & 2 deletions src/Myra/MML/BaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Xml.Serialization;
using FontStashSharp;
using Myra.Utility;
using Myra.Attributes;

#if MONOGAME || FNA
using Microsoft.Xna.Framework;
Expand Down Expand Up @@ -37,7 +38,7 @@ public static ITypeSerializer FindSerializer(Type type)
return null;
}

protected static void ParseProperties(Type type,
protected static void ParseProperties(Type type, bool checkSkipSave,
out List<PropertyInfo> complexProperties,
out List<PropertyInfo> simpleProperties)
{
Expand All @@ -54,12 +55,21 @@ protected static void ParseProperties(Type type,
continue;
}

var attr = property.FindAttribute<XmlIgnoreAttribute>();
Attribute attr = property.FindAttribute<XmlIgnoreAttribute>();
if (attr != null)
{
continue;
}

if (checkSkipSave)
{
attr = property.FindAttribute<SkipSaveAttribute>();
if (attr != null)
{
continue;
}
}

var propertyType = property.PropertyType;
if (propertyType.IsPrimitive ||
propertyType.IsNullablePrimitive() ||
Expand Down
6 changes: 5 additions & 1 deletion src/Myra/MML/BaseObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Myra.MML
{
public class BaseObject: IItemWithId
public class BaseObject: IItemWithId, INotifyAttachedPropertyChanged
{
private string _id = null;

Expand Down Expand Up @@ -54,5 +54,9 @@ protected internal virtual void OnIdChanged()
{
IdChanged.Invoke(this);
}

public virtual void OnAttachedPropertyChanged(BaseAttachedPropertyInfo propertyInfo)
{
}
}
}
2 changes: 1 addition & 1 deletion src/Myra/MML/LoadContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void Load<T>(object obj, XElement el, T handler) where T : class
var baseObject = obj as BaseObject;

List<PropertyInfo> complexProperties, simpleProperties;
ParseProperties(type, out complexProperties, out simpleProperties);
ParseProperties(type, false, out complexProperties, out simpleProperties);

string newName;
foreach (var attr in el.Attributes())
Expand Down
Loading

0 comments on commit 8184a8e

Please sign in to comment.