diff --git a/src/Net_GE45/HandyControlDemo_Net_GE45/HandyControlDemo_Net_GE45.csproj b/src/Net_GE45/HandyControlDemo_Net_GE45/HandyControlDemo_Net_GE45.csproj
index e5a45bc4..57947a1f 100644
--- a/src/Net_GE45/HandyControlDemo_Net_GE45/HandyControlDemo_Net_GE45.csproj
+++ b/src/Net_GE45/HandyControlDemo_Net_GE45/HandyControlDemo_Net_GE45.csproj
@@ -457,9 +457,6 @@
TextTemplatingFileGenerator
MessageToken.cs
-
- Resources\Img\LeftMainContent\PaneLayout_16x.png
-
Resources\Img\LeftMainContent\brackets_Square_16xLG.png
diff --git a/src/Shared/HandyControl_Shared/Data/Args/KeyboardHookEventArgs.cs b/src/Shared/HandyControl_Shared/Data/Args/KeyboardHookEventArgs.cs
index 717c6384..4f50f3e1 100644
--- a/src/Shared/HandyControl_Shared/Data/Args/KeyboardHookEventArgs.cs
+++ b/src/Shared/HandyControl_Shared/Data/Args/KeyboardHookEventArgs.cs
@@ -3,7 +3,7 @@ using System.Windows.Input;
namespace HandyControl.Data
{
- internal class KeyboardHookEventArgs : EventArgs
+ public class KeyboardHookEventArgs : EventArgs
{
public bool IsSystemKey { get; }
diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems
index 2253c400..32e07fa8 100644
--- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems
+++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems
@@ -271,6 +271,10 @@
+
+
+
+
diff --git a/src/Shared/HandyControl_Shared/Tools/Hook/KeyboardHook.cs b/src/Shared/HandyControl_Shared/Tools/Hook/KeyboardHook.cs
index 32bb8dcf..07ce92b6 100644
--- a/src/Shared/HandyControl_Shared/Tools/Hook/KeyboardHook.cs
+++ b/src/Shared/HandyControl_Shared/Tools/Hook/KeyboardHook.cs
@@ -6,7 +6,7 @@ using HandyControl.Tools.Interop;
namespace HandyControl.Tools
{
- internal class KeyboardHook
+ public class KeyboardHook
{
public static event EventHandler KeyDown;
diff --git a/src/Shared/HandyControl_Shared/Tools/Pool/IPool!1.cs b/src/Shared/HandyControl_Shared/Tools/Pool/IPool!1.cs
new file mode 100644
index 00000000..5eeeac75
--- /dev/null
+++ b/src/Shared/HandyControl_Shared/Tools/Pool/IPool!1.cs
@@ -0,0 +1,9 @@
+namespace HandyControl.Tools
+{
+ public interface IPool
+ {
+ T Acquire();
+
+ bool Release(T instance);
+ }
+}
diff --git a/src/Shared/HandyControl_Shared/Tools/Pool/SimplePool!1.cs b/src/Shared/HandyControl_Shared/Tools/Pool/SimplePool!1.cs
new file mode 100644
index 00000000..53731a46
--- /dev/null
+++ b/src/Shared/HandyControl_Shared/Tools/Pool/SimplePool!1.cs
@@ -0,0 +1,64 @@
+//adapted from https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Pools.java
+
+using System;
+
+namespace HandyControl.Tools
+{
+ public class SimplePool : IPool
+ {
+ private readonly object[] _pool;
+
+ private int _poolSize;
+
+ public SimplePool(int maxPoolSize)
+ {
+ if (maxPoolSize <= 0)
+ {
+ throw new ArgumentException("The max pool size must be > 0");
+ }
+ _pool = new object[maxPoolSize];
+ }
+
+ public virtual T Acquire()
+ {
+ if (_poolSize > 0)
+ {
+ var lastPooledIndex = _poolSize - 1;
+ var instance = (T)_pool[lastPooledIndex];
+ _pool[lastPooledIndex] = null;
+ _poolSize--;
+ return instance;
+ }
+ return default;
+ }
+
+ public virtual bool Release(T instance)
+ {
+ if (IsInPool(instance))
+ {
+ throw new Exception("Already in the pool!");
+ }
+
+ if (_poolSize < _pool.Length)
+ {
+ _pool[_poolSize] = instance;
+ _poolSize++;
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool IsInPool(T instance)
+ {
+ for (var i = 0; i < _poolSize; i++)
+ {
+ if (Equals(_pool[i], instance))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/Shared/HandyControl_Shared/Tools/Pool/SynchronizedPool!1.cs b/src/Shared/HandyControl_Shared/Tools/Pool/SynchronizedPool!1.cs
new file mode 100644
index 00000000..0a430778
--- /dev/null
+++ b/src/Shared/HandyControl_Shared/Tools/Pool/SynchronizedPool!1.cs
@@ -0,0 +1,27 @@
+namespace HandyControl.Tools
+{
+ public class SynchronizedPool : SimplePool
+ {
+ private readonly object _lockObj = new object();
+
+ public SynchronizedPool(int maxPoolSize) : base(maxPoolSize)
+ {
+
+ }
+
+ public override T Acquire()
+ {
+ lock (_lockObj)
+ {
+ return base.Acquire();
+ }
+ }
+
+ public override bool Release(T element)
+ {
+ lock (_lockObj) {
+ return base.Release(element);
+ }
+ }
+ }
+}
diff --git a/src/Shared/HandyControl_Shared/Tools/Queue/Deque!1.cs b/src/Shared/HandyControl_Shared/Tools/Queue/Deque!1.cs
new file mode 100644
index 00000000..d15c992c
--- /dev/null
+++ b/src/Shared/HandyControl_Shared/Tools/Queue/Deque!1.cs
@@ -0,0 +1,345 @@
+// adapted from https://www.c-sharpcorner.com/uploadfile/b942f9/implementing-a-double-ended-queue-or-deque-in-c-sharp/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace HandyControl.Tools
+{
+ [Serializable]
+ public class Deque : IEnumerable, ICollection
+ {
+ [NonSerialized]
+ private object _syncRoot;
+
+ private List _front;
+
+ private List _back;
+
+ private int _frontDeleted;
+
+ private int _backDeleted;
+
+ public int Capacity => _front.Capacity + _back.Capacity;
+
+ public int Count => _front.Count + _back.Count - _frontDeleted - _backDeleted;
+
+ public bool IsEmpty => Count == 0;
+
+ public IEnumerable Reversed
+ {
+ get
+ {
+ if (_back.Count - _backDeleted > 0)
+ {
+ for (var i = _back.Count - 1; i >= _backDeleted; i--) yield return _back[i];
+ }
+
+ if (_front.Count - _frontDeleted > 0)
+ {
+ for (var i = _frontDeleted; i < _front.Count; i++) yield return _front[i];
+ }
+ }
+ }
+
+ public Deque()
+ {
+ _front = new List();
+ _back = new List();
+ }
+
+ public Deque(int capacity)
+ {
+ if (capacity < 0) throw new ArgumentException("Capacity cannot be negative");
+ var temp = capacity / 2;
+ var temp2 = capacity - temp;
+ _front = new List(temp);
+ _back = new List(temp2);
+ }
+
+ public Deque(IEnumerable backCollection) : this(backCollection, null)
+ {
+
+ }
+
+ public Deque(IEnumerable backCollection, IEnumerable frontCollection)
+ {
+ if (backCollection == null && frontCollection == null) throw new ArgumentException("Collections cannot both be null");
+ _front = new List();
+ _back = new List();
+
+ if (backCollection != null)
+ {
+ foreach (var item in backCollection) _back.Add(item);
+ }
+
+ if (frontCollection != null)
+ {
+ foreach (var item in frontCollection) _front.Add(item);
+ }
+ }
+
+ public void AddFirst(T item)
+ {
+ if (_frontDeleted > 0 && _front.Count == _front.Capacity)
+ {
+ _front.RemoveRange(0, _frontDeleted);
+ _frontDeleted = 0;
+ }
+
+ _front.Add(item);
+ }
+
+ public void AddLast(T item)
+ {
+ if (_backDeleted > 0 && _back.Count == _back.Capacity)
+ {
+ _back.RemoveRange(0, _backDeleted);
+ _backDeleted = 0;
+ }
+
+ _back.Add(item);
+ }
+
+ public void AddRangeFirst(IEnumerable range)
+ {
+ if (range == null) return;
+
+ foreach (var item in range) AddFirst(item);
+ }
+
+ public void AddRangeLast(IEnumerable range)
+ {
+ if (range == null) return;
+
+ foreach (var item in range) AddLast(item);
+ }
+
+ public void Clear()
+ {
+ _front.Clear();
+ _back.Clear();
+ _frontDeleted = 0;
+ _backDeleted = 0;
+ }
+
+ public bool Contains(T item)
+ {
+ for (var i = _frontDeleted; i < _front.Count; i++)
+ {
+ if (Equals(_front[i], item)) return true;
+ }
+
+ for (var i = _backDeleted; i < _back.Count; i++)
+ {
+ if (Equals(_back[i], item)) return true;
+ }
+
+ return false;
+ }
+
+ public void CopyTo(T[] array, int index)
+ {
+ if (array == null) throw new ArgumentNullException("Array cannot be null");
+ if (index < 0) throw new ArgumentOutOfRangeException("Index cannot be negative");
+ if (array.Length < index + Count) throw new ArgumentException("Index is invalid");
+ var i = index;
+
+ foreach (var item in this)
+ {
+ array[i++] = item;
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ if (_front.Count - _frontDeleted > 0)
+ {
+ for (var i = _front.Count - 1; i >= _frontDeleted; i--) yield return _front[i];
+ }
+
+ if (_back.Count - _backDeleted > 0)
+ {
+ for (var i = _backDeleted; i < _back.Count; i++) yield return _back[i];
+ }
+ }
+
+ public T PeekFirst()
+ {
+ if (_front.Count > _frontDeleted)
+ {
+ return _front[_front.Count - 1];
+ }
+
+ if (_back.Count > _backDeleted)
+ {
+ return _back[_backDeleted];
+ }
+
+ throw new InvalidOperationException("Can't peek at empty Deque");
+ }
+
+ public T PeekLast()
+ {
+ if (_back.Count > _backDeleted)
+ {
+ return _back[_back.Count - 1];
+ }
+
+ if (_front.Count > _frontDeleted)
+ {
+ return _front[_frontDeleted];
+ }
+
+ throw new InvalidOperationException("Can't peek at empty Deque");
+ }
+
+ public T PopFirst()
+ {
+ T result;
+
+ if (_front.Count > _frontDeleted)
+ {
+ result = _front[_front.Count - 1];
+ _front.RemoveAt(_front.Count - 1);
+ }
+ else if (_back.Count > _backDeleted)
+ {
+ result = _back[_backDeleted];
+ _backDeleted++;
+ }
+ else
+ {
+ throw new InvalidOperationException("Can't pop empty Deque");
+ }
+
+ return result;
+ }
+
+
+ public T PopLast()
+ {
+ T result;
+
+ if (_back.Count > _backDeleted)
+ {
+ result = _back[_back.Count - 1];
+ _back.RemoveAt(_back.Count - 1);
+ }
+ else if (_front.Count > _frontDeleted)
+ {
+ result = _front[_frontDeleted];
+ _frontDeleted++;
+ }
+ else
+ {
+ throw new InvalidOperationException("Can't pop empty Deque");
+ }
+
+ return result;
+ }
+
+ public void Reverse()
+ {
+ var temp = _front;
+ _front = _back;
+ _back = temp;
+ var temp2 = _frontDeleted;
+ _frontDeleted = _backDeleted;
+ _backDeleted = temp2;
+ }
+
+ public T[] ToArray()
+ {
+ if (Count == 0) return new T[0];
+ var result = new T[Count];
+ CopyTo(result, 0);
+ return result;
+ }
+
+ public void TrimExcess()
+ {
+ if (_frontDeleted > 0)
+ {
+ _front.RemoveRange(0, _frontDeleted);
+ _frontDeleted = 0;
+ }
+
+ if (_backDeleted > 0)
+ {
+ _back.RemoveRange(0, _backDeleted);
+ _backDeleted = 0;
+ }
+
+ _front.TrimExcess();
+ _back.TrimExcess();
+ }
+
+ public bool TryPeekFirst(out T item)
+ {
+ if (!IsEmpty)
+ {
+ item = PeekFirst();
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public bool TryPeekLast(out T item)
+ {
+ if (!IsEmpty)
+ {
+ item = PeekLast();
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public bool TryPopFirst(out T item)
+ {
+ if (!IsEmpty)
+ {
+ item = PopFirst();
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public bool TryPopLast(out T item)
+ {
+ if (!IsEmpty)
+ {
+ item = PopLast();
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ Interlocked.CompareExchange