Skip to content

Commit 0cdf9ac

Browse files
Poker-sangArlodotexe
authored andcommitted
WrapLayout Fix
1 parent a4ec2ac commit 0cdf9ac

File tree

5 files changed

+107
-132
lines changed

5 files changed

+107
-132
lines changed

components/Primitives/src/WrapLayout/UvBounds.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using Windows.Foundation;
6+
using Microsoft.UI.Xaml.Controls;
7+
58
namespace CommunityToolkit.WinUI.Controls;
69

710
internal struct UvBounds
811
{
912
public UvBounds(Orientation orientation, Rect rect)
1013
{
11-
if (orientation == Orientation.Horizontal)
14+
if (orientation is Orientation.Horizontal)
1215
{
1316
UMin = rect.Left;
1417
UMax = rect.Right;

components/Primitives/src/WrapLayout/UvMeasure.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,61 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Diagnostics;
6+
using Windows.Foundation;
7+
using Microsoft.UI.Xaml.Controls;
8+
59
namespace CommunityToolkit.WinUI.Controls;
610

711
[DebuggerDisplay("U = {U} V = {V}")]
812
internal struct UvMeasure
913
{
10-
internal static readonly UvMeasure Zero = default(UvMeasure);
11-
1214
internal double U { get; set; }
1315

1416
internal double V { get; set; }
1517

16-
public UvMeasure(Orientation orientation, double width, double height)
18+
public UvMeasure(Orientation orientation, Size size)
1719
{
18-
if (orientation == Orientation.Horizontal)
20+
if (orientation is Orientation.Horizontal)
1921
{
20-
U = width;
21-
V = height;
22+
U = size.Width;
23+
V = size.Height;
2224
}
2325
else
2426
{
25-
U = height;
26-
V = width;
27+
U = size.Height;
28+
V = size.Width;
2729
}
2830
}
2931

32+
public Point GetPoint(Orientation orientation)
33+
{
34+
return orientation is Orientation.Horizontal ? new Point(U, V) : new Point(V, U);
35+
}
36+
37+
public Size GetSize(Orientation orientation)
38+
{
39+
return orientation is Orientation.Horizontal ? new Size(U, V) : new Size(V, U);
40+
}
41+
42+
public static bool operator ==(UvMeasure measure1, UvMeasure measure2)
43+
{
44+
return measure1.U == measure2.U && measure1.V == measure2.V;
45+
}
46+
47+
public static bool operator !=(UvMeasure measure1, UvMeasure measure2)
48+
{
49+
return !(measure1 == measure2);
50+
}
51+
3052
public override bool Equals(object? obj)
3153
{
32-
if (obj is UvMeasure measure)
33-
{
34-
return (measure.U == U) && (measure.V == V);
35-
}
54+
return obj is UvMeasure measure && this == measure;
55+
}
3656

37-
return false;
57+
public bool Equals(UvMeasure value)
58+
{
59+
return this == value;
3860
}
3961

4062
public override int GetHashCode()

components/Primitives/src/WrapLayout/WrapItem.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using Microsoft.UI.Xaml;
6+
57
namespace CommunityToolkit.WinUI.Controls;
68

79
internal class WrapItem
810
{
911
public WrapItem(int index)
1012
{
11-
this.Index = index;
13+
Index = index;
1214
}
1315

1416
public int Index { get; }

components/Primitives/src/WrapLayout/WrapLayout.cs

Lines changed: 46 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using Microsoft.UI.Xaml.Controls;
67
using System.Collections.Specialized;
8+
using Windows.Foundation;
9+
using Microsoft.UI.Xaml;
710

811
namespace CommunityToolkit.WinUI.Controls;
912

@@ -20,8 +23,8 @@ public class WrapLayout : VirtualizingLayout
2023
/// </summary>
2124
public double HorizontalSpacing
2225
{
23-
get { return (double)GetValue(HorizontalSpacingProperty); }
24-
set { SetValue(HorizontalSpacingProperty, value); }
26+
get => (double)GetValue(HorizontalSpacingProperty);
27+
set => SetValue(HorizontalSpacingProperty, value);
2528
}
2629

2730
/// <summary>
@@ -40,8 +43,8 @@ public double HorizontalSpacing
4043
/// </summary>
4144
public double VerticalSpacing
4245
{
43-
get { return (double)GetValue(VerticalSpacingProperty); }
44-
set { SetValue(VerticalSpacingProperty, value); }
46+
get => (double)GetValue(VerticalSpacingProperty);
47+
set => SetValue(VerticalSpacingProperty, value);
4548
}
4649

4750
/// <summary>
@@ -61,8 +64,8 @@ public double VerticalSpacing
6164
/// </summary>
6265
public Orientation Orientation
6366
{
64-
get { return (Orientation)GetValue(OrientationProperty); }
65-
set { SetValue(OrientationProperty, value); }
67+
get => (Orientation)GetValue(OrientationProperty);
68+
set => SetValue(OrientationProperty, value);
6669
}
6770

6871
/// <summary>
@@ -87,8 +90,7 @@ private static void LayoutPropertyChanged(DependencyObject d, DependencyProperty
8790
/// <inheritdoc />
8891
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
8992
{
90-
var state = new WrapLayoutState(context);
91-
context.LayoutState = state;
93+
context.LayoutState = new WrapLayoutState(context);
9294
base.InitializeForContextCore(context);
9395
}
9496

@@ -110,7 +112,7 @@ protected override void OnItemsChangedCore(VirtualizingLayoutContext context, ob
110112
state.RemoveFromIndex(args.NewStartingIndex);
111113
break;
112114
case NotifyCollectionChangedAction.Move:
113-
int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
115+
var minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
114116
state.RemoveFromIndex(minIndex);
115117

116118
state.RecycleElementAt(args.OldStartingIndex);
@@ -134,50 +136,40 @@ protected override void OnItemsChangedCore(VirtualizingLayoutContext context, ob
134136
/// <inheritdoc />
135137
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
136138
{
137-
var totalMeasure = UvMeasure.Zero;
138-
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
139-
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
140-
var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
141-
var position = UvMeasure.Zero;
139+
var parentMeasure = new UvMeasure(Orientation, availableSize);
140+
var spacingMeasure = new UvMeasure(Orientation, new Size(HorizontalSpacing, VerticalSpacing));
142141

143142
var state = (WrapLayoutState)context.LayoutState;
144143
if (state.Orientation != Orientation)
145144
{
146145
state.SetOrientation(Orientation);
147146
}
148147

149-
if (spacingMeasure.Equals(state.Spacing) == false)
148+
if (spacingMeasure != state.Spacing || state.AvailableU != parentMeasure.U)
150149
{
151150
state.ClearPositions();
152151
state.Spacing = spacingMeasure;
153-
}
154-
155-
if (state.AvailableU != parentMeasure.U)
156-
{
157-
state.ClearPositions();
158152
state.AvailableU = parentMeasure.U;
159153
}
160154

161155
double currentV = 0;
162-
for (int i = 0; i < context.ItemCount; i++)
156+
var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
157+
var position = new UvMeasure();
158+
for (var i = 0; i < context.ItemCount; ++i)
163159
{
164-
bool measured = false;
165-
WrapItem item = state.GetItemAt(i);
166-
if (item.Measure == null)
160+
var measured = false;
161+
var item = state.GetItemAt(i);
162+
if (item.Measure is null)
167163
{
168164
item.Element = context.GetOrCreateElementAt(i);
169165
item.Element.Measure(availableSize);
170-
item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height);
166+
item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize);
171167
measured = true;
172168
}
173169

174-
UvMeasure currentMeasure = item.Measure.Value;
175-
if (currentMeasure.U == 0)
176-
{
177-
continue; // ignore collapsed items
178-
}
170+
var currentMeasure = item.Measure.Value;
179171

180-
if (item.Position == null)
172+
if (item.Position is null)
181173
{
182174
if (parentMeasure.U < position.U + currentMeasure.U)
183175
{
@@ -192,20 +184,22 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
192184

193185
position = item.Position.Value;
194186

195-
double vEnd = position.V + currentMeasure.V;
187+
var vEnd = position.V + currentMeasure.V;
196188
if (vEnd < realizationBounds.VMin)
197189
{
198190
// Item is "above" the bounds
199-
if (item.Element != null)
191+
if (item.Element is not null)
200192
{
201193
context.RecycleElement(item.Element);
202194
item.Element = null;
203195
}
196+
197+
continue;
204198
}
205199
else if (position.V > realizationBounds.VMax)
206200
{
207201
// Item is "below" the bounds.
208-
if (item.Element != null)
202+
if (item.Element is not null)
209203
{
210204
context.RecycleElement(item.Element);
211205
item.Element = null;
@@ -214,14 +208,14 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
214208
// We don't need to measure anything below the bounds
215209
break;
216210
}
217-
else if (measured == false)
211+
else if (!measured)
218212
{
219213
// Always measure elements that are within the bounds
220214
item.Element = context.GetOrCreateElementAt(i);
221215
item.Element.Measure(availableSize);
222216

223-
currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height);
224-
if (currentMeasure.Equals(item.Measure) == false)
217+
currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize);
218+
if (currentMeasure != item.Measure)
225219
{
226220
// this item changed size; we need to recalculate layout for everything after this
227221
state.RemoveFromIndex(i + 1);
@@ -249,70 +243,43 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
249243
// if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here
250244
// for the last condition it is zeros so adding it will make no difference
251245
// this way is faster than an if condition in every loop for checking the last item
252-
totalMeasure.U = parentMeasure.U;
253-
254246
// Propagating an infinite size causes a crash. This can happen if the parent is scrollable and infinite in the opposite
255247
// axis to the panel. Clearing to zero prevents the crash.
256-
// This is likely an incorrect use of the control by the developer, however we need stability here so setting a default that wont crash.
257-
if (double.IsInfinity(totalMeasure.U))
258-
{
259-
totalMeasure.U = 0.0;
260-
}
248+
// This is likely an incorrect use of the control by the developer, however we need stability here so setting a default that won't crash.
261249

262-
totalMeasure.V = state.GetHeight();
250+
var totalMeasure = new UvMeasure
251+
{
252+
U = double.IsInfinity(parentMeasure.U) ? 0 : Math.Ceiling(parentMeasure.U),
253+
V = state.GetHeight()
254+
};
263255

264-
totalMeasure.U = Math.Ceiling(totalMeasure.U);
265-
return Orientation == Orientation.Horizontal ? new Size((float)totalMeasure.U, (float)totalMeasure.V) : new Size((float)totalMeasure.V, (float)totalMeasure.U);
256+
return totalMeasure.GetSize(Orientation);
266257
}
267258

268259
/// <inheritdoc />
269260
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
270261
{
271262
if (context.ItemCount > 0)
272263
{
273-
var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height);
274-
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
275264
var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
276265

277266
var state = (WrapLayoutState)context.LayoutState;
278-
bool Arrange(WrapItem item, bool isLast = false)
267+
bool ArrangeItem(WrapItem item)
279268
{
280-
if (item.Measure.HasValue == false)
281-
{
282-
return false;
283-
}
284-
285-
if (item.Position == null)
269+
if (item is { Measure: null } or { Position: null })
286270
{
287271
return false;
288272
}
289273

290274
var desiredMeasure = item.Measure.Value;
291-
if (desiredMeasure.U == 0)
292-
{
293-
return true; // if an item is collapsed, avoid adding the spacing
294-
}
295275

296-
UvMeasure position = item.Position.Value;
276+
var position = item.Position.Value;
297277

298-
// Stretch the last item to fill the available space
299-
if (isLast)
300-
{
301-
desiredMeasure.U = parentMeasure.U - position.U;
302-
}
303-
304-
if (((position.V + desiredMeasure.V) >= realizationBounds.VMin) && (position.V <= realizationBounds.VMax))
278+
if (realizationBounds.VMin <= position.V + desiredMeasure.V && position.V <= realizationBounds.VMax)
305279
{
306280
// place the item
307-
UIElement child = context.GetOrCreateElementAt(item.Index);
308-
if (Orientation == Orientation.Horizontal)
309-
{
310-
child.Arrange(new Rect((float)position.U, (float)position.V, (float)desiredMeasure.U, (float)desiredMeasure.V));
311-
}
312-
else
313-
{
314-
child.Arrange(new Rect((float)position.V, (float)position.U, (float)desiredMeasure.V, (float)desiredMeasure.U));
315-
}
281+
var child = context.GetOrCreateElementAt(item.Index);
282+
child.Arrange(new Rect(position.GetPoint(Orientation), desiredMeasure.GetSize(Orientation)));
316283
}
317284
else if (position.V > realizationBounds.VMax)
318285
{
@@ -322,10 +289,9 @@ bool Arrange(WrapItem item, bool isLast = false)
322289
return true;
323290
}
324291

325-
for (var i = 0; i < context.ItemCount; i++)
292+
for (var i = 0; i < context.ItemCount; ++i)
326293
{
327-
bool continueArranging = Arrange(state.GetItemAt(i));
328-
if (continueArranging == false)
294+
if (!ArrangeItem(state.GetItemAt(i)))
329295
{
330296
break;
331297
}

0 commit comments

Comments
 (0)