mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
1 Commits
feat/sopho
...
fix/1609
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afbebe4222 |
23
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapItem.cs
Normal file
23
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapItem.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Fou// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class WrapItem
|
||||
{
|
||||
public WrapItem(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public int Index { get; }
|
||||
|
||||
public Size? Size { get; set; }
|
||||
|
||||
public Point? Position { get; set; }
|
||||
|
||||
public UIElement? Element { get; set; }
|
||||
}
|
||||
213
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapLayout.cs
Normal file
213
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapLayout.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections.Specialized;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
[DependencyProperty("HorizontalSpacing", typeof(double), 0D, nameof(LayoutPropertyChanged))]
|
||||
[DependencyProperty("VerticalSpacing", typeof(double), 0D, nameof(LayoutPropertyChanged))]
|
||||
internal sealed partial class WrapLayout : VirtualizingLayout
|
||||
{
|
||||
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
|
||||
{
|
||||
context.LayoutState = new WrapLayoutState(context);
|
||||
base.InitializeForContextCore(context);
|
||||
}
|
||||
|
||||
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
|
||||
{
|
||||
context.LayoutState = default;
|
||||
base.UninitializeForContextCore(context);
|
||||
}
|
||||
|
||||
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
WrapLayoutState state = (WrapLayoutState)context.LayoutState;
|
||||
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
state.RemoveFromIndex(args.NewStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
|
||||
state.RemoveFromIndex(minIndex);
|
||||
state.RecycleElementAt(args.OldStartingIndex);
|
||||
state.RecycleElementAt(args.NewStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
state.RemoveFromIndex(args.OldStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
state.RemoveFromIndex(args.NewStartingIndex);
|
||||
state.RecycleElementAt(args.NewStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
state.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
base.OnItemsChangedCore(context, source, args);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
|
||||
{
|
||||
Size spacing = new(HorizontalSpacing, VerticalSpacing);
|
||||
|
||||
WrapLayoutState state = (WrapLayoutState)context.LayoutState;
|
||||
|
||||
if (spacing != state.Spacing || state.AvailableWidth != availableSize.Width)
|
||||
{
|
||||
state.ClearPositions();
|
||||
state.Spacing = spacing;
|
||||
state.AvailableWidth = availableSize.Height;
|
||||
}
|
||||
|
||||
double currentHeight = 0;
|
||||
Point position = default;
|
||||
for (int i = 0; i < context.ItemCount; ++i)
|
||||
{
|
||||
bool measured = false;
|
||||
WrapItem item = state.GetItemAt(i);
|
||||
if (item.Size is null)
|
||||
{
|
||||
item.Element = context.GetOrCreateElementAt(i);
|
||||
item.Element.Measure(availableSize);
|
||||
item.Size = item.Element.DesiredSize;
|
||||
measured = true;
|
||||
}
|
||||
|
||||
Size currentSize = item.Size.Value;
|
||||
|
||||
if (item.Position is null)
|
||||
{
|
||||
if (availableSize.Width < position.X + currentSize.Height)
|
||||
{
|
||||
// New Row
|
||||
position.X = 0;
|
||||
position.Y += currentHeight + spacing.Height;
|
||||
currentHeight = 0;
|
||||
}
|
||||
|
||||
item.Position = position;
|
||||
}
|
||||
|
||||
position = item.Position.Value;
|
||||
|
||||
double vEnd = position.Y + currentSize.Width;
|
||||
if (vEnd < context.RealizationRect.Top)
|
||||
{
|
||||
// Item is "above" the bounds
|
||||
if (item.Element is not null)
|
||||
{
|
||||
context.RecycleElement(item.Element);
|
||||
item.Element = default;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (position.Y > context.RealizationRect.Bottom)
|
||||
{
|
||||
// Item is "below" the bounds.
|
||||
if (item.Element is not null)
|
||||
{
|
||||
context.RecycleElement(item.Element);
|
||||
item.Element = default;
|
||||
}
|
||||
|
||||
// We don't need to measure anything below the bounds
|
||||
break;
|
||||
}
|
||||
else if (!measured)
|
||||
{
|
||||
// Always measure elements that are within the bounds
|
||||
item.Element = context.GetOrCreateElementAt(i);
|
||||
item.Element.Measure(availableSize);
|
||||
|
||||
currentSize = item.Element.DesiredSize;
|
||||
if (currentSize != item.Size)
|
||||
{
|
||||
// this item changed size; we need to recalculate layout for everything after this
|
||||
state.RemoveFromIndex(i + 1);
|
||||
item.Size = currentSize;
|
||||
|
||||
// did the change make it go into the new row?
|
||||
if (availableSize.Width < position.X + currentSize.Width)
|
||||
{
|
||||
// New Row
|
||||
position.X = 0;
|
||||
position.Y += currentHeight + spacing.Height;
|
||||
currentHeight = 0;
|
||||
}
|
||||
|
||||
item.Position = position;
|
||||
}
|
||||
}
|
||||
|
||||
position.X += currentSize.Width + spacing.Width;
|
||||
currentHeight = Math.Max(currentSize.Height, currentHeight);
|
||||
}
|
||||
|
||||
return new Size(double.IsInfinity(availableSize.Width) ? 0 : Math.Ceiling(availableSize.Width), state.GetHeight());
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
|
||||
{
|
||||
if (context.ItemCount > 0)
|
||||
{
|
||||
WrapLayoutState state = (WrapLayoutState)context.LayoutState;
|
||||
|
||||
bool ArrangeItem(WrapItem item)
|
||||
{
|
||||
if (item is { Size: null } or { Position: null })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Size desiredSize = item.Size.Value;
|
||||
|
||||
Point position = item.Position.Value;
|
||||
|
||||
if (context.RealizationRect.Top <= position.Y + desiredSize.Height && position.Y <= context.RealizationRect.Bottom)
|
||||
{
|
||||
// place the item
|
||||
UIElement child = context.GetOrCreateElementAt(item.Index);
|
||||
child.Arrange(new Rect(position, desiredSize));
|
||||
}
|
||||
else if (position.Y > context.RealizationRect.Bottom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.ItemCount; ++i)
|
||||
{
|
||||
if (!ArrangeItem(state.GetItemAt(i)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private static void LayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is WrapLayout wp)
|
||||
{
|
||||
wp.InvalidateMeasure();
|
||||
wp.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
110
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapLayoutState.cs
Normal file
110
src/Snap.Hutao/Snap.Hutao/Control/Layout/WrapLayoutState.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class WrapLayoutState
|
||||
{
|
||||
private readonly List<WrapItem> items = [];
|
||||
private readonly VirtualizingLayoutContext context;
|
||||
|
||||
public WrapLayoutState(VirtualizingLayoutContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Orientation Orientation { get; private set; }
|
||||
|
||||
public Size Spacing { get; set; }
|
||||
|
||||
public double AvailableWidth { get; set; }
|
||||
|
||||
public WrapItem GetItemAt(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
if (index <= (items.Count - 1))
|
||||
{
|
||||
return items[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
WrapItem item = new(index);
|
||||
items.Add(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
RecycleElementAt(i);
|
||||
}
|
||||
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
public void RemoveFromIndex(int index)
|
||||
{
|
||||
if (index >= items.Count)
|
||||
{
|
||||
// Item was added/removed but we haven't realized that far yet
|
||||
return;
|
||||
}
|
||||
|
||||
int numToRemove = items.Count - index;
|
||||
items.RemoveRange(index, numToRemove);
|
||||
}
|
||||
|
||||
public void ClearPositions()
|
||||
{
|
||||
foreach (ref readonly WrapItem item in CollectionsMarshal.AsSpan(items))
|
||||
{
|
||||
item.Position = default;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetHeight()
|
||||
{
|
||||
if (items.Count is 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Point? lastPosition = default;
|
||||
double maxHeight = 0;
|
||||
|
||||
for (int i = items.Count - 1; i >= 0; --i)
|
||||
{
|
||||
WrapItem item = items[i];
|
||||
|
||||
if (item.Position is null || item.Size is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastPosition is not null && lastPosition.Value.Y > item.Position.Value.Y)
|
||||
{
|
||||
// This is a row above the last item.
|
||||
break;
|
||||
}
|
||||
|
||||
lastPosition = item.Position;
|
||||
maxHeight = Math.Max(maxHeight, item.Size.Value.Height);
|
||||
}
|
||||
|
||||
return lastPosition?.Y + maxHeight ?? 0;
|
||||
}
|
||||
|
||||
public void RecycleElementAt(int index)
|
||||
{
|
||||
UIElement element = context.GetOrCreateElementAt(index);
|
||||
context.RecycleElement(element);
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,7 @@
|
||||
ItemTemplate="{StaticResource InventoryItemTemplate}"
|
||||
ItemsSource="{Binding InventoryItems}">
|
||||
<ItemsRepeater.Layout>
|
||||
<cwcont:WrapLayout HorizontalSpacing="12" VerticalSpacing="12"/>
|
||||
<shcl:WrapLayout HorizontalSpacing="12" VerticalSpacing="12"/>
|
||||
</ItemsRepeater.Layout>
|
||||
</ItemsRepeater>
|
||||
</ScrollView>
|
||||
|
||||
Reference in New Issue
Block a user