A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

112 lines
4.2 KiB

// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls
{
public class RecyclePool
{
internal static readonly AttachedProperty<IDataTemplate> OriginTemplateProperty =
AvaloniaProperty.RegisterAttached<RecyclePool, Control, IDataTemplate>("OriginTemplate");
internal static readonly AttachedProperty<string> ReuseKeyProperty =
AvaloniaProperty.RegisterAttached<RecyclePool, Control, string>("ReuseKey", string.Empty);
private static ConditionalWeakTable<IDataTemplate, RecyclePool> s_pools = new ConditionalWeakTable<IDataTemplate, RecyclePool>();
private readonly Dictionary<string, List<ElementInfo>> _elements = new Dictionary<string, List<ElementInfo>>();
public static RecyclePool? GetPoolInstance(IDataTemplate dataTemplate)
{
s_pools.TryGetValue(dataTemplate, out var result);
return result;
}
public static void SetPoolInstance(IDataTemplate dataTemplate, RecyclePool value) => s_pools.Add(dataTemplate, value);
public void PutElement(IControl element, string key, IControl? owner)
{
var ownerAsPanel = EnsureOwnerIsPanelOrNull(owner);
var elementInfo = new ElementInfo(element, ownerAsPanel);
if (!_elements.TryGetValue(key, out var pool))
{
pool = new List<ElementInfo>();
_elements.Add(key, pool);
}
pool.Add(elementInfo);
}
public IControl? TryGetElement(string key, IControl? owner)
{
if (_elements.TryGetValue(key, out var elements))
{
if (elements.Count > 0)
{
// Prefer an element from the same owner or with no owner so that we don't incur
// the enter/leave cost during recycling.
// TODO: prioritize elements with the same owner to those without an owner.
var elementInfo = elements.FirstOrDefault(x => x.Owner == owner) ?? elements.LastOrDefault();
elements.Remove(elementInfo!);
var ownerAsPanel = EnsureOwnerIsPanelOrNull(owner);
if (elementInfo!.Owner != null && elementInfo.Owner != ownerAsPanel)
{
// Element is still under its parent. remove it from its parent.
var panel = elementInfo.Owner;
if (panel != null)
{
int childIndex = panel.Children.IndexOf(elementInfo.Element);
if (childIndex == -1)
{
throw new KeyNotFoundException("ItemsRepeater's child not found in its Children collection.");
}
panel.Children.RemoveAt(childIndex);
}
}
return elementInfo.Element;
}
}
return null;
}
internal string GetReuseKey(IControl element) => ((Control)element).GetValue(ReuseKeyProperty);
internal void SetReuseKey(IControl element, string value) => ((Control)element).SetValue(ReuseKeyProperty, value);
private IPanel? EnsureOwnerIsPanelOrNull(IControl? owner)
{
if (owner is IPanel panel)
{
return panel;
}
else if (owner != null)
{
throw new InvalidOperationException("Owner must be IPanel or null.");
}
return null;
}
private class ElementInfo
{
public ElementInfo(IControl element, IPanel? owner)
{
Element = element;
Owner = owner;
}
public IControl Element { get; }
public IPanel? Owner { get;}
}
}
}