diff --git a/src/AtomUI.Controls.DataGrid/Theme/DataGridTheme.cs b/src/AtomUI.Controls.DataGrid/Theme/DataGridTheme.cs new file mode 100644 index 000000000..4b1f6b9b3 --- /dev/null +++ b/src/AtomUI.Controls.DataGrid/Theme/DataGridTheme.cs @@ -0,0 +1,6 @@ +namespace AtomUI.Controls.Theme; + +internal class DataGridTheme +{ + +} \ No newline at end of file diff --git a/src/AtomUI.Controls.XDataGrid/Data/DataGridSortDescription.cs b/src/AtomUI.Controls.XDataGrid/Data/DataGridSortDescription.cs new file mode 100644 index 000000000..2c404ef7b --- /dev/null +++ b/src/AtomUI.Controls.XDataGrid/Data/DataGridSortDescription.cs @@ -0,0 +1,338 @@ +using System.Collections; +using System.ComponentModel; +using System.Globalization; +using AtomUI.Controls.XDataGrid.Utils; +using Avalonia.Collections; + +namespace AtomUI.Controls.XDataGrid.Data; + +public abstract class DataGridSortDescription +{ + public virtual string? PropertyPath => null; + public virtual ListSortDirection Direction => ListSortDirection.Ascending; + public bool HasPropertyPath => !string.IsNullOrEmpty(PropertyPath); + public abstract IComparer Comparer { get; } + + public virtual IOrderedEnumerable OrderBy(IEnumerable seq) + { + return seq.OrderBy(o => o, Comparer); + } + + public virtual IOrderedEnumerable ThenBy(IOrderedEnumerable seq) + { + return seq.ThenBy(o => o, Comparer); + } + + public virtual DataGridSortDescription SwitchSortDirection() + { + return this; + } + + internal virtual void Initialize(Type itemType) + { + } + + private static object? InvokePath(object item, string propertyPath, Type propertyType) + { + object? propertyValue = + TypeHelper.GetNestedPropertyValue(item, propertyPath, propertyType, out Exception? exception); + if (exception != null) + { + throw exception; + } + + return propertyValue; + } + + public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction = ListSortDirection.Ascending, CultureInfo? culture = null) + { + return new DataGridPathSortDescription(propertyPath, direction, null, culture); + } + + public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer) + { + return new DataGridPathSortDescription(propertyPath, direction, comparer, null); + } + + public static DataGridSortDescription FromComparer(IComparer comparer, ListSortDirection direction = ListSortDirection.Ascending) + { + return new DataGridComparerSortDescription(comparer, direction); + } + + /// + /// Creates a comparer class that takes in a CultureInfo as a parameter, + /// which it will use when comparing strings. + /// + private class CultureSensitiveComparer : Comparer + { + /// + /// Private accessor for the CultureInfo of our comparer + /// + private CultureInfo _culture; + + /// + /// Creates a comparer which will respect the CultureInfo + /// that is passed in when comparing strings. + /// + /// The CultureInfo to use in string comparisons + public CultureSensitiveComparer(CultureInfo? culture) + { + _culture = culture ?? CultureInfo.InvariantCulture; + } + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to or greater than the other. + /// + /// first item to compare + /// second item to compare + /// Negative number if x is less than y, zero if equal, and a positive number if x is greater than y + /// + /// Compares the 2 items using the specified CultureInfo for string and using the default object comparer for all other objects. + /// + public override int Compare(object? x, object? y) + { + if (x == null) + { + if (y != null) + { + return -1; + } + + return 0; + } + + if (y == null) + { + return 1; + } + + // at this point x and y are not null + if (x is string stringX && y is string stringY) + { + return _culture.CompareInfo.Compare(stringX, stringY); + } + + return Default.Compare(x, y); + } + } + + private class DataGridPathSortDescription : DataGridSortDescription + { + private readonly ListSortDirection _direction; + private readonly string _propertyPath; + private readonly Lazy _cultureSensitiveComparer; + private readonly Lazy> _comparer; + private Type? _propertyType; + private IComparer? _internalComparer; + private IComparer? _internalComparerTyped; + + private IComparer? InternalComparer + { + get + { + if (_internalComparerTyped == null && _internalComparer != null) + { + if (_internalComparer is IComparer c) + { + _internalComparerTyped = c; + } + else + { + _internalComparerTyped = Comparer.Create((x, y) => _internalComparer.Compare(x, y)); + } + } + + return _internalComparerTyped; + } + } + + public override string PropertyPath => _propertyPath; + public override IComparer Comparer => _comparer.Value; + public override ListSortDirection Direction => _direction; + + public DataGridPathSortDescription(string propertyPath, ListSortDirection direction, IComparer? internalComparer, + CultureInfo? culture) + { + _propertyPath = propertyPath; + _direction = direction; + _cultureSensitiveComparer = new Lazy(() => + new CultureSensitiveComparer(culture ?? CultureInfo.CurrentCulture)); + _internalComparer = internalComparer; + _comparer = new Lazy>(() => Comparer.Create((x, y) => Compare(x, y))); + } + + private DataGridPathSortDescription(DataGridPathSortDescription inner, ListSortDirection direction) + { + _propertyPath = inner._propertyPath; + _direction = direction; + _propertyType = inner._propertyType; + _cultureSensitiveComparer = inner._cultureSensitiveComparer; + _internalComparer = inner._internalComparer; + _internalComparerTyped = inner._internalComparerTyped; + + _comparer = new Lazy>(() => Comparer.Create((x, y) => Compare(x, y))); + } + + private object? GetValue(object? o) + { + if (o == null) + return null; + + if (HasPropertyPath) + { + return InvokePath(o, _propertyPath, _propertyType!); + } + + if (_propertyType == o.GetType()) + { + return o; + } + + return null; + } + + private IComparer? GetComparerForType(Type type) + { + if (type == typeof(string)) + { + return _cultureSensitiveComparer.Value; + } + return GetComparerForNotStringType(type); + } + + internal static IComparer? GetComparerForNotStringType(Type type) + { + if (System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported == false) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && + type.GetGenericArguments()[0].IsAssignableTo(typeof(IComparable))) + { + return Comparer.Create((x, y) => (x as IComparable)!.CompareTo(y)); + } + + if (type.IsAssignableTo(typeof(IComparable))) + { + //enum should be here + return Comparer.Create((x, y) => (x as IComparable)!.CompareTo(y)); + } + return Comparer.Create((x, y) => 0); //avoid using reflection to avoid crash on AOT + } + return (typeof(Comparer<>).MakeGenericType(type).GetProperty("Default"))?.GetValue(null, null) as + IComparer; + } + + private Type? GetPropertyType(object o) + { + return o.GetType().GetNestedPropertyType(_propertyPath); + } + + private int Compare(object? x, object? y) + { + int result = 0; + + if (_propertyType == null) + { + if (x != null) + { + _propertyType = GetPropertyType(x); + } + + if (_propertyType == null && y != null) + { + _propertyType = GetPropertyType(y); + } + } + + var v1 = GetValue(x); + var v2 = GetValue(y); + + if (_propertyType != null) + { + _internalComparer = GetComparerForType(_propertyType); + } + + result = _internalComparer?.Compare(v1, v2) ?? 0; + + if (Direction == ListSortDirection.Descending) + { + return -result; + } + return result; + } + + internal override void Initialize(Type itemType) + { + base.Initialize(itemType); + + if (_propertyType == null) + { + _propertyType = itemType.GetNestedPropertyType(_propertyPath); + } + else + { + _internalComparer = GetComparerForType(_propertyType); + } + } + + public override IOrderedEnumerable OrderBy(IEnumerable seq) + { + if (Direction == ListSortDirection.Descending) + { + return seq.OrderByDescending(o => GetValue(o), InternalComparer); + } + return seq.OrderBy(o => GetValue(o), InternalComparer); + } + + public override IOrderedEnumerable ThenBy(IOrderedEnumerable seq) + { + if (Direction == ListSortDirection.Descending) + { + return seq.ThenByDescending(o => GetValue(o), InternalComparer); + } + return seq.ThenBy(o => GetValue(o), InternalComparer); + } + + public override DataGridSortDescription SwitchSortDirection() + { + var newDirection = _direction == ListSortDirection.Ascending + ? ListSortDirection.Descending + : ListSortDirection.Ascending; + return new DataGridPathSortDescription(this, newDirection); + } + } +} + +public class DataGridComparerSortDescription : DataGridSortDescription +{ + private readonly IComparer _innerComparer; + private readonly ListSortDirection _direction; + private readonly IComparer _comparer; + + public IComparer SourceComparer => _innerComparer; + public override IComparer Comparer => _comparer; + public override ListSortDirection Direction => _direction; + public DataGridComparerSortDescription(IComparer comparer, ListSortDirection direction) + { + _innerComparer = comparer; + _direction = direction; + _comparer = Comparer.Create((x, y) => Compare(x, y)); + } + + private int Compare(object x, object y) + { + int result = _innerComparer.Compare(x, y); + + if (Direction == ListSortDirection.Descending) + return -result; + else + return result; + } + public override DataGridSortDescription SwitchSortDirection() + { + var newDirection = _direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending; + return new DataGridComparerSortDescription(_innerComparer, newDirection); + } +} + +public class DataGridSortDescriptionCollection : AvaloniaList +{ } \ No newline at end of file diff --git a/src/AtomUI.Controls.XDataGrid/Data/IDataGridCollectionView.cs b/src/AtomUI.Controls.XDataGrid/Data/IDataGridCollectionView.cs new file mode 100644 index 000000000..b26fa6422 --- /dev/null +++ b/src/AtomUI.Controls.XDataGrid/Data/IDataGridCollectionView.cs @@ -0,0 +1,38 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections; +using System.Collections.Specialized; +using System.Globalization; + +namespace AtomUI.Controls.XDataGrid.Data; + +public interface IDataGridCollectionView : IEnumerable, INotifyCollectionChanged +{ + /// Gets or sets the cultural information for any operations of the view that may differ by culture, such as sorting. + /// The culture information to use during culture-sensitive operations. + CultureInfo Culture { get; set; } + + /// Indicates whether the specified item belongs to this collection view. + /// true if the item belongs to this collection view; otherwise, false. + /// The object to check. + bool Contains(object item); + + /// Gets the underlying collection. + /// The underlying collection. + IEnumerable SourceCollection { get; } + + /// Gets or sets a callback that is used to determine whether an item is appropriate for inclusion in the view. + /// A method that is used to determine whether an item is appropriate for inclusion in the view. + Func Filter { get; set; } + + /// Gets a value that indicates whether this view supports filtering by way of the property. + /// true if this view supports filtering; otherwise, false. + bool CanFilter { get; } + + /// Gets a collection of instances that describe how the items in the collection are sorted in the view. + /// A collection of values that describe how the items in the collection are sorted in the view. + DataGridSortDescriptionCollection SortDescriptions { get; } +} \ No newline at end of file diff --git a/src/AtomUI.Controls.DataGrid/Theme/DataGridToken.cs b/src/AtomUI.Controls.XDataGrid/Theme/DataGridToken.cs similarity index 99% rename from src/AtomUI.Controls.DataGrid/Theme/DataGridToken.cs rename to src/AtomUI.Controls.XDataGrid/Theme/DataGridToken.cs index 97c8857a9..f6eec7286 100644 --- a/src/AtomUI.Controls.DataGrid/Theme/DataGridToken.cs +++ b/src/AtomUI.Controls.XDataGrid/Theme/DataGridToken.cs @@ -3,7 +3,7 @@ using AtomUI.Theme.TokenSystem; using Avalonia; using Avalonia.Media; -namespace AtomUI.Controls.Theme; +namespace AtomUI.Controls; [ControlDesignToken] internal class DataGridToken : AbstractControlDesignToken