Skip to content

Commit f3c3c52

Browse files
authored
Merge branch 'main' into fix_WrapLayoutSample
2 parents ea1a5f7 + 19fc4de commit f3c3c52

16 files changed

+659
-100
lines changed

components/Collections/samples/AdvancedCollectionViewSample.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private void Setup()
4848
// right list
4949
AdvancedCollectionView acv = new(EmployeeCollection);
5050
acv.Filter = x => !int.TryParse(((Employee)x).Name, out _);
51-
acv.SortDescriptions.Add(new(nameof(Employee.Name), SortDirection.Ascending));
51+
acv.SortDescriptions.Add(new SortDescription<Employee>(nameof(Employee.Name), SortDirection.Ascending));
5252

5353
CollectionView = acv;
5454
}

components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ namespace CommunityToolkit.WinUI.Collections;
1313
/// <summary>
1414
/// A collection view implementation that supports filtering, sorting and incremental loading
1515
/// </summary>
16-
#if NET8_0_OR_GREATER
17-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Item sorting uses reflection to get property types and may not be AOT compatible.")]
18-
#endif
1916
public partial class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<object>
2017
{
2118
private readonly List<object> _view;
@@ -383,7 +380,7 @@ public Predicate<object> Filter
383380
int IComparer<object>.Compare(object x, object y)
384381
#pragma warning restore CA1033 // Interface methods should be callable by child types
385382
{
386-
if (!_sortProperties.Any())
383+
if (_sortProperties.Count == 0)
387384
{
388385
var listType = _source?.GetType();
389386
Type type;
@@ -401,7 +398,7 @@ int IComparer<object>.Compare(object x, object y)
401398
{
402399
if (!string.IsNullOrEmpty(sd.PropertyName))
403400
{
404-
_sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName);
401+
_sortProperties[sd.PropertyName] = sd.GetProperty(type);
405402
}
406403
}
407404
}
@@ -419,8 +416,8 @@ int IComparer<object>.Compare(object x, object y)
419416
{
420417
var pi = _sortProperties[sd.PropertyName];
421418

422-
cx = pi.GetValue(x!);
423-
cy = pi.GetValue(y!);
419+
cx = pi.GetValue(x);
420+
cy = pi.GetValue(y);
424421
}
425422

426423
var cmp = sd.Comparer.Compare(cx, cy);

components/Collections/src/AdvancedCollectionView/SortDescription.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections;
6+
using System.Diagnostics.CodeAnalysis;
67

78
namespace CommunityToolkit.WinUI.Collections;
89

@@ -14,7 +15,7 @@ public class SortDescription
1415
/// <summary>
1516
/// Gets the name of property to sort on
1617
/// </summary>
17-
public string PropertyName { get; }
18+
public string? PropertyName { get; }
1819

1920
/// <summary>
2021
/// Gets the direction of sort
@@ -33,8 +34,10 @@ public class SortDescription
3334
/// <param name="direction">Direction of sort</param>
3435
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
3536
public SortDescription(SortDirection direction, IComparer? comparer = null)
36-
: this(null!, direction, comparer!)
3737
{
38+
PropertyName = null;
39+
Direction = direction;
40+
Comparer = comparer ?? ObjectComparer.Instance;
3841
}
3942

4043
/// <summary>
@@ -43,13 +46,23 @@ public SortDescription(SortDirection direction, IComparer? comparer = null)
4346
/// <param name="propertyName">Name of property to sort on</param>
4447
/// <param name="direction">Direction of sort</param>
4548
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
49+
#if NET8_0_OR_GREATER
50+
[RequiresUnreferencedCode("Item sorting with the property name uses reflection to get the property and is not trim-safe. Either use SortDescription<T> to preserve the required metadata, or use the other constructor without a property name.")]
51+
#endif
4652
public SortDescription(string propertyName, SortDirection direction, IComparer? comparer = null)
4753
{
4854
PropertyName = propertyName;
4955
Direction = direction;
5056
Comparer = comparer ?? ObjectComparer.Instance;
5157
}
5258

59+
#if NET8_0_OR_GREATER
60+
[UnconditionalSuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.",
61+
Justification = "The path which does reflection is only triggered if the user uses the constructor with RequiresUnreferencedCode, which will inform them of the risk.")]
62+
#endif
63+
internal virtual PropertyInfo? GetProperty(Type type)
64+
=> PropertyName != null ? type.GetProperty(PropertyName) : null;
65+
5366
private class ObjectComparer : IComparer
5467
{
5568
public static readonly IComparer Instance = new ObjectComparer();
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections;
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
namespace CommunityToolkit.WinUI.Collections;
9+
10+
/// <summary>
11+
/// A generic version of <see cref="SortDescription"/> which preserves the required metadata for reflection-based sorting.
12+
/// </summary>
13+
/// <typeparam name="T">The type to sort</typeparam>
14+
public sealed class SortDescription<
15+
#if NET8_0_OR_GREATER
16+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
17+
#endif
18+
T> : SortDescription
19+
{
20+
private readonly PropertyInfo _prop;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="SortDescription{T}"/> class.
24+
/// </summary>
25+
/// <param name="propertyName">Name of property to sort on</param>
26+
/// <param name="direction">Direction of sort</param>
27+
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
28+
#if NET8_0_OR_GREATER
29+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
30+
Justification = "This class preserves metadata and ensures at runtime that the received type is compatible.")]
31+
#endif
32+
public SortDescription(string propertyName, SortDirection direction, IComparer? comparer = null) : base(propertyName, direction, comparer)
33+
{
34+
_prop = typeof(T).GetProperty(propertyName) ?? throw new ArgumentException($"Could not find property {propertyName}");
35+
}
36+
37+
internal override PropertyInfo? GetProperty(Type type) =>
38+
(_prop.DeclaringType is not null && _prop.DeclaringType.IsAssignableFrom(type)) ? _prop : throw new ArgumentException("This instance of SortDescription is not compatible with the desired type");
39+
}

components/RangeSelector/samples/RangeSelector.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ A `RangeSelector` is pretty similar to a regular `Slider`, and shares some of it
1616

1717
> [!Sample RangeSelectorSample]
1818
19+
> [!NOTE]
20+
> Use 'VerticalAlignment="Stretch"' When 'Orientation="Vertical"'
21+
22+
Like this:
23+
24+
```xaml
25+
<controls:RangeSelector x:Name="rangeSelector"
26+
VerticalAlignment="Stretch"
27+
Maximum="100"
28+
Minimum="0"
29+
Orientation="Vertical"
30+
RangeEnd="100"
31+
RangeStart="0"
32+
StepFrequency="1" />
33+
```
34+
1935
> [!NOTE]
2036
> If you are using a RangeSelector within a ScrollViewer you'll need to add some codes. This is because by default, the ScrollViewer will block the thumbs of the RangeSelector to capture the pointer.
2137

components/RangeSelector/samples/RangeSelectorSample.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
MinHeight="86"
1313
MaxWidth="560"
1414
HorizontalAlignment="Stretch">
15+
<!-- Use 'VerticalAlignment="Stretch"' When 'Orientation="Vertical"' -->
1516
<controls:RangeSelector x:Name="rangeSelector"
1617
VerticalAlignment="Center"
1718
IsEnabled="{x:Bind Enable, Mode=OneWay}"
1819
Maximum="{x:Bind Maximum, Mode=OneWay}"
1920
Minimum="{x:Bind Minimum, Mode=OneWay}"
21+
Orientation="{x:Bind OrientationMode, Mode=OneWay}"
2022
RangeEnd="100"
2123
RangeStart="0"
2224
StepFrequency="{x:Bind StepFrequency, Mode=OneWay}" />

components/RangeSelector/samples/RangeSelectorSample.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace RangeSelectorExperiment.Samples;
99
[ToolkitSampleNumericOption("Minimum", 0, 0, 100, 1, false, Title = "Minimum")]
1010
[ToolkitSampleNumericOption("Maximum", 100, 0, 100, 1, false, Title = "Maximum")]
1111
[ToolkitSampleNumericOption("StepFrequency", 1, 0, 10, 1, false, Title = "StepFrequency")]
12+
[ToolkitSampleMultiChoiceOption("OrientationMode", "Horizontal", "Vertical", Title = "Orientation")]
1213
[ToolkitSampleBoolOption("Enable", true, Title = "IsEnabled")]
1314

1415
[ToolkitSample(id: nameof(RangeSelectorSample), "RangeSelector", description: $"A sample for showing how to create and use a {nameof(RangeSelector)} control.")]

components/RangeSelector/src/Dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<!-- WinUI 2 / UWP -->
1313
<ItemGroup Condition="'$(IsUwp)' == 'true'">
1414
<!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
15+
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
1516
</ItemGroup>
1617

1718
<!-- WinUI 2 / Uno -->
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.WinUI.Controls;
6+
7+
/// <summary>
8+
/// A struct representing a coordinate in UV adjusted space.
9+
/// </summary>
10+
[DebuggerDisplay("({U}u,{V}v)")]
11+
public struct UVCoord: IEquatable<UVCoord>
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="UVCoord"/> struct.
15+
/// </summary>
16+
public UVCoord(Orientation orientation)
17+
{
18+
Orientation = orientation;
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="UVCoord"/> struct.
23+
/// </summary>
24+
public UVCoord(double x, double y, Orientation orientation)
25+
{
26+
X = x;
27+
Y = y;
28+
Orientation = orientation;
29+
}
30+
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="UVCoord"/> struct.
33+
/// </summary>
34+
public UVCoord(Point point, Orientation orientation) : this(point.X, point.Y, orientation)
35+
{
36+
}
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="UVCoord"/> struct.
40+
/// </summary>
41+
public UVCoord(Size size, Orientation orientation) : this(size.Width, size.Height, orientation)
42+
{
43+
}
44+
45+
/// <summary>
46+
/// Gets or sets the X coordinate.
47+
/// </summary>
48+
public double X { readonly get; set; }
49+
50+
/// <summary>
51+
/// Gets or sets the Y coordinate.
52+
/// </summary>
53+
public double Y { readonly get; set; }
54+
55+
/// <summary>
56+
/// Gets or sets the orientation for translation between the XY and UV coordinate systems.
57+
/// </summary>
58+
public Orientation Orientation { get; set; }
59+
60+
/// <summary>
61+
/// Gets or sets the U coordinate.
62+
/// </summary>
63+
public double U
64+
{
65+
readonly get => Orientation is Orientation.Horizontal ? X : Y;
66+
set
67+
{
68+
if (Orientation is Orientation.Horizontal)
69+
{
70+
X = value;
71+
}
72+
else
73+
{
74+
Y = value;
75+
}
76+
}
77+
}
78+
79+
/// <summary>
80+
/// Gets or sets the V coordinate.
81+
/// </summary>
82+
public double V
83+
{
84+
readonly get => Orientation is Orientation.Vertical ? X : Y;
85+
set
86+
{
87+
if (Orientation is Orientation.Vertical)
88+
{
89+
X = value;
90+
}
91+
else
92+
{
93+
Y = value;
94+
}
95+
}
96+
}
97+
98+
/// <summary>
99+
/// Implicitly casts a <see cref="UVCoord"/> to a <see cref="Point"/>.
100+
/// </summary>
101+
public static implicit operator Point(UVCoord uv) => new(uv.X, uv.Y);
102+
103+
/// <summary>
104+
/// Implicitly casts a <see cref="UVCoord"/> to a <see cref="Size"/>.
105+
/// </summary>
106+
public static implicit operator Size(UVCoord uv) => new(uv.X, uv.Y);
107+
108+
/// <inheritdoc/>
109+
public static UVCoord operator +(UVCoord addend1, UVCoord addend2)
110+
{
111+
if (addend1.Orientation != addend2.Orientation)
112+
{
113+
throw new InvalidOperationException($"Cannot add {nameof(UVCoord)} with mismatched {nameof(Orientation)}.");
114+
}
115+
116+
var xSum = addend1.X + addend2.X;
117+
var ySum = addend1.Y + addend2.Y;
118+
var orientation = addend1.Orientation;
119+
return new UVCoord(xSum, ySum, orientation);
120+
}
121+
122+
/// <inheritdoc/>
123+
public static bool operator ==(UVCoord coord1, UVCoord coord2)
124+
{
125+
return coord1.U == coord2.U && coord1.V == coord2.V;
126+
}
127+
128+
129+
/// <inheritdoc/>
130+
public static bool operator !=(UVCoord measure1, UVCoord measure2)
131+
{
132+
return !(measure1 == measure2);
133+
}
134+
135+
/// <inheritdoc/>
136+
public override bool Equals(object? obj)
137+
{
138+
return obj is UVCoord other && Equals(other);
139+
}
140+
141+
/// <inheritdoc/>
142+
public bool Equals(UVCoord other)
143+
{
144+
return this == other;
145+
}
146+
147+
/// <inheritdoc/>
148+
public override int GetHashCode()
149+
{
150+
return HashCode.Combine(U, V);
151+
}
152+
}

0 commit comments

Comments
 (0)