Merge branch 'master' into aleader/notifications-registry
This commit is contained in:
Коммит
8876688b38
|
@ -18,11 +18,17 @@ A clear and concise description of what the bug is.
|
|||
- [ ] Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
- [ ] Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...) <!-- Being able to reproduce the problem in the sample app, really stream-lines the whole process in being able to discover, resolve, and validate bug fixes. -->
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
1. Given the following environment (Sample App w/ XAML, Project with Isolated setup, etc...)
|
||||
2. Go to '...'
|
||||
3. Click on '....'
|
||||
4. Scroll down to '....'
|
||||
5. See error
|
||||
|
||||
<!-- Provide as many code-snippets or XAML snippets where appropriate. -->
|
||||
|
||||
## Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
|
|
@ -157,8 +157,8 @@
|
|||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.UWP.Input.GazeInteraction\Microsoft.Toolkit.Uwp.Input.GazeInteraction.vcxproj">
|
||||
<Project>{a5e98964-45b1-442d-a07a-298a3221d81e}</Project>
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.Uwp.Input.GazeInteraction\Microsoft.Toolkit.Uwp.Input.GazeInteraction.csproj">
|
||||
<Project>{5bf75694-798a-43a0-8150-415de195359c}</Project>
|
||||
<Name>Microsoft.Toolkit.Uwp.Input.GazeInteraction</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GazeInputTest")]
|
||||
|
@ -20,13 +20,13 @@ using System.Runtime.InteropServices;
|
|||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
//[assembly: AssemblyVersion("1.0.0.0")]
|
||||
//[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
// [assembly: AssemblyVersion("1.0.0.0")]
|
||||
// [assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: ComVisible(false)]
|
|
@ -0,0 +1,826 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
#if NETCOREAPP3_1
|
||||
using System.Numerics;
|
||||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#if !NETSTANDARD1_4
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#endif
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// A configurable pool for <see cref="string"/> instances. This can be used to minimize allocations
|
||||
/// when creating multiple <see cref="string"/> instances from buffers of <see cref="char"/> values.
|
||||
/// The <see cref="GetOrAdd(ReadOnlySpan{char})"/> method provides a best-effort alternative to just creating
|
||||
/// a new <see cref="string"/> instance every time, in order to minimize the number of duplicated instances.
|
||||
/// The <see cref="StringPool"/> type will internally manage a highly efficient priority queue for the
|
||||
/// cached <see cref="string"/> instances, so that when the full capacity is reached, the least frequently
|
||||
/// used values will be automatically discarded to leave room for new values to cache.
|
||||
/// </summary>
|
||||
public sealed class StringPool
|
||||
{
|
||||
/// <summary>
|
||||
/// The size used by default by the parameterless constructor.
|
||||
/// </summary>
|
||||
private const int DefaultSize = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum size for <see cref="StringPool"/> instances.
|
||||
/// </summary>
|
||||
private const int MinimumSize = 32;
|
||||
|
||||
/// <summary>
|
||||
/// The current array of <see cref="FixedSizePriorityMap"/> instances in use.
|
||||
/// </summary>
|
||||
private readonly FixedSizePriorityMap[] maps;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of maps in use.
|
||||
/// </summary>
|
||||
private readonly int numberOfMaps;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringPool"/> class.
|
||||
/// </summary>
|
||||
public StringPool()
|
||||
: this(DefaultSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringPool"/> class.
|
||||
/// </summary>
|
||||
/// <param name="minimumSize">The minimum size for the pool to create.</param>
|
||||
public StringPool(int minimumSize)
|
||||
{
|
||||
if (minimumSize <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Set the minimum size
|
||||
minimumSize = Math.Max(minimumSize, MinimumSize);
|
||||
|
||||
// Calculates the rounded up factors for a specific size/factor pair
|
||||
static void FindFactors(int size, int factor, out int x, out int y)
|
||||
{
|
||||
double
|
||||
a = Math.Sqrt((double)size / factor),
|
||||
b = factor * a;
|
||||
|
||||
x = RoundUpPowerOfTwo((int)a);
|
||||
y = RoundUpPowerOfTwo((int)b);
|
||||
}
|
||||
|
||||
// We want to find two powers of 2 factors that produce a number
|
||||
// that is at least equal to the requested size. In order to find the
|
||||
// combination producing the optimal factors (with the product being as
|
||||
// close as possible to the requested size), we test a number of ratios
|
||||
// that we consider acceptable, and pick the best results produced.
|
||||
// The ratio between maps influences the number of objects being allocated,
|
||||
// as well as the multithreading performance when locking on maps.
|
||||
// We still want to contraint this number to avoid situations where we
|
||||
// have a way too high number of maps compared to total size.
|
||||
FindFactors(minimumSize, 2, out int x2, out int y2);
|
||||
FindFactors(minimumSize, 3, out int x3, out int y3);
|
||||
FindFactors(minimumSize, 4, out int x4, out int y4);
|
||||
|
||||
int
|
||||
p2 = x2 * y2,
|
||||
p3 = x3 * y3,
|
||||
p4 = x4 * y4;
|
||||
|
||||
if (p3 < p2)
|
||||
{
|
||||
p2 = p3;
|
||||
x2 = x3;
|
||||
y2 = y3;
|
||||
}
|
||||
|
||||
if (p4 < p2)
|
||||
{
|
||||
p2 = p4;
|
||||
x2 = x4;
|
||||
y2 = y4;
|
||||
}
|
||||
|
||||
Span<FixedSizePriorityMap> span = this.maps = new FixedSizePriorityMap[x2];
|
||||
|
||||
// We preallocate the maps in advance, since each bucket only contains the
|
||||
// array field, which is not preinitialized, so the allocations are minimal.
|
||||
// This lets us lock on each individual maps when retrieving a string instance.
|
||||
foreach (ref FixedSizePriorityMap map in span)
|
||||
{
|
||||
map = new FixedSizePriorityMap(y2);
|
||||
}
|
||||
|
||||
this.numberOfMaps = x2;
|
||||
|
||||
Size = p2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds up an <see cref="int"/> value to a power of 2.
|
||||
/// </summary>
|
||||
/// <param name="x">The input value to round up.</param>
|
||||
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int RoundUpPowerOfTwo(int x)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
return 1 << (32 - BitOperations.LeadingZeroCount((uint)(x - 1)));
|
||||
#else
|
||||
x--;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x++;
|
||||
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shared <see cref="StringPool"/> instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The shared pool provides a singleton, reusable <see cref="StringPool"/> instance that
|
||||
/// can be accessed directly, and that pools <see cref="string"/> instances for the entire
|
||||
/// process. Since <see cref="StringPool"/> is thread-safe, the shared instance can be used
|
||||
/// concurrently by multiple threads without the need for manual synchronization.
|
||||
/// </remarks>
|
||||
public static StringPool Shared { get; } = new StringPool();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of <see cref="string"/> that can be stored in the current instance.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores a <see cref="string"/> instance in the internal cache.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
|
||||
public void Add(string value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(value.AsSpan()),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
map.Add(value, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content, or stores the input one.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance with the contents to use.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>, cached if possible.</returns>
|
||||
public string GetOrAdd(string value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(value.AsSpan()),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.GetOrAdd(value, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content, or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
public string GetOrAdd(ReadOnlySpan<char> span)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(span),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.GetOrAdd(span, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content (converted to Unicode), or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use, in a specified encoding.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/> instance to use to decode the contents of <paramref name="span"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
public unsafe string GetOrAdd(ReadOnlySpan<byte> span, Encoding encoding)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int maxLength = encoding.GetMaxCharCount(span.Length);
|
||||
|
||||
using SpanOwner<char> buffer = SpanOwner<char>.Allocate(maxLength);
|
||||
|
||||
fixed (byte* source = span)
|
||||
fixed (char* destination = &buffer.DangerousGetReference())
|
||||
{
|
||||
int effectiveLength = encoding.GetChars(source, span.Length, destination, maxLength);
|
||||
|
||||
return GetOrAdd(new ReadOnlySpan<char>(destination, effectiveLength));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a cached <see cref="string"/> instance matching the input content, if present.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
|
||||
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
|
||||
public bool TryGet(ReadOnlySpan<char> span, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
value = string.Empty;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(span),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.TryGet(span, hashcode, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current instance and its associated maps.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
foreach (ref FixedSizePriorityMap map in this.maps.AsSpan())
|
||||
{
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
map.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A configurable map containing a group of cached <see cref="string"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Instances of this type are stored in an array within <see cref="StringPool"/> and they are
|
||||
/// always accessed by reference - essentially as if this type had been a class. The type is
|
||||
/// also private, so there's no risk for users to directly access it and accidentally copy an
|
||||
/// instance, which would lead to bugs due to values becoming out of sync with the internal state
|
||||
/// (that is, because instances would be copied by value, so primitive fields would not be shared).
|
||||
/// The reason why we're using a struct here is to remove an indirection level and improve cache
|
||||
/// locality when accessing individual buckets from the methods in the <see cref="StringPool"/> type.
|
||||
/// </remarks>
|
||||
private struct FixedSizePriorityMap
|
||||
{
|
||||
/// <summary>
|
||||
/// The index representing the end of a given list.
|
||||
/// </summary>
|
||||
private const int EndOfList = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The array of 1-based indices for the <see cref="MapEntry"/> items stored in <see cref="mapEntries"/>.
|
||||
/// </summary>
|
||||
private readonly int[] buckets;
|
||||
|
||||
/// <summary>
|
||||
/// The array of currently cached entries (ie. the lists for each hash group).
|
||||
/// </summary>
|
||||
private readonly MapEntry[] mapEntries;
|
||||
|
||||
/// <summary>
|
||||
/// The array of priority values associated to each item stored in <see cref="mapEntries"/>.
|
||||
/// </summary>
|
||||
private readonly HeapEntry[] heapEntries;
|
||||
|
||||
/// <summary>
|
||||
/// The current number of items stored in the map.
|
||||
/// </summary>
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// The current incremental timestamp for the items stored in <see cref="heapEntries"/>.
|
||||
/// </summary>
|
||||
private uint timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// A type representing a map entry, ie. a node in a given list.
|
||||
/// </summary>
|
||||
private struct MapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The precomputed hashcode for <see cref="Value"/>.
|
||||
/// </summary>
|
||||
public int HashCode;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="string"/> instance cached in this entry.
|
||||
/// </summary>
|
||||
public string? Value;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the next node in the current list.
|
||||
/// </summary>
|
||||
public int NextIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the heap entry corresponding to the current node.
|
||||
/// </summary>
|
||||
public int HeapIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type representing a heap entry, used to associate priority to each item.
|
||||
/// </summary>
|
||||
private struct HeapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The timestamp for the current entry (ie. the priority for the item).
|
||||
/// </summary>
|
||||
public uint Timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the map entry corresponding to the current item.
|
||||
/// </summary>
|
||||
public int MapIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FixedSizePriorityMap"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The fixed capacity of the current map.</param>
|
||||
public FixedSizePriorityMap(int capacity)
|
||||
{
|
||||
this.buckets = new int[capacity];
|
||||
this.mapEntries = new MapEntry[capacity];
|
||||
this.heapEntries = new HeapEntry[capacity];
|
||||
this.count = 0;
|
||||
this.timestamp = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="object"/> that can be used to synchronize access to the current instance.
|
||||
/// </summary>
|
||||
public object SyncRoot
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.buckets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.Add"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void Add(string value, int hashcode)
|
||||
{
|
||||
ref string target = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (Unsafe.AreSame(ref target, ref Unsafe.AsRef<string>(null)))
|
||||
{
|
||||
Insert(value, hashcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
target = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.GetOrAdd(string)"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe string GetOrAdd(string value, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Insert(value, hashcode);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.GetOrAdd(ReadOnlySpan{char})"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
string value = span.ToString();
|
||||
|
||||
Insert(value, hashcode);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.TryGet"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
|
||||
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
{
|
||||
value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current instance and throws away all the cached values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
this.buckets.AsSpan().Clear();
|
||||
this.mapEntries.AsSpan().Clear();
|
||||
this.heapEntries.AsSpan().Clear();
|
||||
this.count = 0;
|
||||
this.timestamp = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a target <see cref="string"/> instance, if it exists, and returns a reference to it.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the slot where the target <see cref="string"/> instance could be.</returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref MapEntry entry = ref Unsafe.AsRef<MapEntry>(null);
|
||||
int
|
||||
length = this.buckets.Length,
|
||||
bucketIndex = hashcode & (length - 1);
|
||||
|
||||
for (int i = this.buckets.DangerousGetReferenceAt(bucketIndex) - 1;
|
||||
(uint)i < (uint)length;
|
||||
i = entry.NextIndex)
|
||||
{
|
||||
entry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)i);
|
||||
|
||||
if (entry.HashCode == hashcode &&
|
||||
entry.Value!.AsSpan().SequenceEqual(span))
|
||||
{
|
||||
UpdateTimestamp(ref entry.HeapIndex);
|
||||
|
||||
return ref entry.Value!;
|
||||
}
|
||||
}
|
||||
|
||||
return ref Unsafe.AsRef<string>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new <see cref="string"/> instance in the current map, freeing up a space if needed.
|
||||
/// </summary>
|
||||
/// <param name="value">The new <see cref="string"/> instance to store.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void Insert(string value, int hashcode)
|
||||
{
|
||||
ref int bucketsRef = ref this.buckets.DangerousGetReference();
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
int entryIndex, heapIndex;
|
||||
|
||||
// If the current map is full, first get the oldest value, which is
|
||||
// always the first item in the heap. Then, free up a slot by
|
||||
// removing that, and insert the new value in that empty location.
|
||||
if (this.count == this.mapEntries.Length)
|
||||
{
|
||||
entryIndex = heapEntriesRef.MapIndex;
|
||||
heapIndex = 0;
|
||||
|
||||
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
|
||||
// The removal logic can be extremely optimized in this case, as we
|
||||
// can retrieve the precomputed hashcode for the target entry by doing
|
||||
// a lookup on the target map node, and we can also skip all the comparisons
|
||||
// while traversing the target chain since we know in advance the index of
|
||||
// the target node which will contain the item to remove from the map.
|
||||
Remove(removedEntry.HashCode, entryIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the free list is not empty, get that map node and update the field
|
||||
entryIndex = this.count;
|
||||
heapIndex = this.count;
|
||||
}
|
||||
|
||||
int bucketIndex = hashcode & (this.buckets.Length - 1);
|
||||
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (IntPtr)(void*)(uint)bucketIndex);
|
||||
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)heapIndex);
|
||||
|
||||
// Assign the values in the new map entry
|
||||
targetMapEntry.HashCode = hashcode;
|
||||
targetMapEntry.Value = value;
|
||||
targetMapEntry.NextIndex = targetBucket - 1;
|
||||
targetMapEntry.HeapIndex = heapIndex;
|
||||
|
||||
// Update the bucket slot and the current count
|
||||
targetBucket = entryIndex + 1;
|
||||
this.count++;
|
||||
|
||||
// Link the heap node with the current entry
|
||||
targetHeapEntry.MapIndex = entryIndex;
|
||||
|
||||
// Update the timestamp and balance the heap again
|
||||
UpdateTimestamp(ref targetMapEntry.HeapIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specified <see cref="string"/> instance from the map to free up one slot.
|
||||
/// </summary>
|
||||
/// <param name="hashcode">The precomputed hashcode of the instance to remove.</param>
|
||||
/// <param name="mapIndex">The index of the target map node to remove.</param>
|
||||
/// <remarks>The input <see cref="string"/> instance needs to already exist in the map.</remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void Remove(int hashcode, int mapIndex)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
int
|
||||
bucketIndex = hashcode & (this.buckets.Length - 1),
|
||||
entryIndex = this.buckets.DangerousGetReferenceAt(bucketIndex) - 1,
|
||||
lastIndex = EndOfList;
|
||||
|
||||
// We can just have an undefined loop, as the input
|
||||
// value we're looking for is guaranteed to be present
|
||||
while (true)
|
||||
{
|
||||
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
|
||||
|
||||
// Check the current value for a match
|
||||
if (entryIndex == mapIndex)
|
||||
{
|
||||
// If this was not the first list node, update the parent as well
|
||||
if (lastIndex != EndOfList)
|
||||
{
|
||||
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)lastIndex);
|
||||
|
||||
lastEntry.NextIndex = candidate.NextIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, update the target index from the bucket slot
|
||||
this.buckets.DangerousGetReferenceAt(bucketIndex) = candidate.NextIndex + 1;
|
||||
}
|
||||
|
||||
this.count--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the following node in the current list
|
||||
lastIndex = entryIndex;
|
||||
entryIndex = candidate.NextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timestamp of a heap node at the specified index (which is then synced back).
|
||||
/// </summary>
|
||||
/// <param name="heapIndex">The index of the target heap node to update.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void UpdateTimestamp(ref int heapIndex)
|
||||
{
|
||||
int
|
||||
currentIndex = heapIndex,
|
||||
count = this.count;
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
|
||||
uint timestamp = this.timestamp;
|
||||
|
||||
// Check if incrementing the current timestamp for the heap node to update
|
||||
// would result in an overflow. If that happened, we could end up violating
|
||||
// the min-heap property (the value of each node has to always be <= than that
|
||||
// of its child nodes), eg. if we were updating a node that was not the root.
|
||||
// In that scenario, we could end up with a node somewhere along the heap with
|
||||
// a value lower than that of its parent node (as the timestamp would be 0).
|
||||
// To guard against this, we just check the current timestamp value, and if
|
||||
// the maximum value has been reached, we reinitialize the entire heap. This
|
||||
// is done in a non-inlined call, so we don't increase the codegen size in this
|
||||
// method. The reinitialization simply traverses the heap in breadth-first order
|
||||
// (ie. level by level), and assigns incrementing timestamps to all nodes starting
|
||||
// from 0. The value of the current timestamp is then just set to the current size.
|
||||
if (timestamp == uint.MaxValue)
|
||||
{
|
||||
// We use a goto here as this path is very rarely taken. Doing so
|
||||
// causes the generated asm to contain a forward jump to the fallback
|
||||
// path if this branch is taken, whereas the normal execution path will
|
||||
// not need to execute any jumps at all. This is done to reduce the overhead
|
||||
// introduced by this check in all the invocations where this point is not reached.
|
||||
goto Fallback;
|
||||
}
|
||||
|
||||
Downheap:
|
||||
|
||||
// Assign a new timestamp to the target heap node. We use a
|
||||
// local incremental timestamp instead of using the system timer
|
||||
// as this greatly reduces the overhead and the time spent in system calls.
|
||||
// The uint type provides a large enough range and it's unlikely users would ever
|
||||
// exhaust it anyway (especially considering each map has a separate counter).
|
||||
root.Timestamp = this.timestamp = timestamp + 1;
|
||||
|
||||
// Once the timestamp is updated (which will cause the heap to become
|
||||
// unbalanced), start a sift down loop to balance the heap again.
|
||||
while (true)
|
||||
{
|
||||
// The heap is 0-based (so that the array length can remain the same
|
||||
// as the power of 2 value used for the other arrays in this type).
|
||||
// This means that children of each node are at positions:
|
||||
// - left: (2 * n) + 1
|
||||
// - right: (2 * n) + 2
|
||||
ref HeapEntry minimum = ref root;
|
||||
int
|
||||
left = (currentIndex * 2) + 1,
|
||||
right = (currentIndex * 2) + 2,
|
||||
targetIndex = currentIndex;
|
||||
|
||||
// Check and update the left child, if necessary
|
||||
if (left < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)left);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
minimum = ref child;
|
||||
targetIndex = left;
|
||||
}
|
||||
}
|
||||
|
||||
// Same check as above for the right child
|
||||
if (right < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)right);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
minimum = ref child;
|
||||
targetIndex = right;
|
||||
}
|
||||
}
|
||||
|
||||
// If no swap is pending, we can just stop here.
|
||||
// Before returning, we update the target index as well.
|
||||
if (Unsafe.AreSame(ref root, ref minimum))
|
||||
{
|
||||
heapIndex = targetIndex;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the indices in the respective map entries (accounting for the swap)
|
||||
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)root.MapIndex).HeapIndex = targetIndex;
|
||||
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)minimum.MapIndex).HeapIndex = currentIndex;
|
||||
|
||||
currentIndex = targetIndex;
|
||||
|
||||
// Swap the parent and child (so that the minimum value bubbles up)
|
||||
HeapEntry temp = root;
|
||||
|
||||
root = minimum;
|
||||
minimum = temp;
|
||||
|
||||
// Update the reference to the root node
|
||||
root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
|
||||
}
|
||||
|
||||
Fallback:
|
||||
|
||||
UpdateAllTimestamps();
|
||||
|
||||
// After having updated all the timestamps, if the heap contains N items, the
|
||||
// node in the bottom right corner will have a value of N - 1. Since the timestamp
|
||||
// is incremented by 1 before starting the downheap execution, here we simply
|
||||
// update the local timestamp to be N - 1, so that the code above will set the
|
||||
// timestamp of the node currently being updated to exactly N.
|
||||
timestamp = (uint)(count - 1);
|
||||
|
||||
goto Downheap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timestamp of all the current heap nodes in incrementing order.
|
||||
/// The heap is always guaranteed to be complete binary tree, so when it contains
|
||||
/// a given number of nodes, those are all contiguous from the start of the array.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void UpdateAllTimestamps()
|
||||
{
|
||||
int count = this.count;
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)i).Timestamp = (uint)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the (positive) hashcode for a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <returns>The hashcode for <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetHashCode(ReadOnlySpan<char> span)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return span.GetDjb2HashCode();
|
||||
#else
|
||||
return HashCode<char>.Combine(span);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minimumSize", "The requested size must be greater than 0");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,13 +66,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
int offset = (i * arrayData.Width) + j;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
|
@ -82,10 +82,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return ref array[i, j];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -274,11 +271,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this T[,] array, T value)
|
||||
public static unsafe int Count<T>(this T[,] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
@ -293,11 +290,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this T[,] array)
|
||||
public static unsafe int GetDjb2HashCode<T>(this T[,] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -62,12 +62,12 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
|
@ -76,10 +76,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return ref array[i];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -114,11 +111,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this T[] array, T value)
|
||||
public static unsafe int Count<T>(this T[] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
@ -185,11 +182,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this T[] array)
|
||||
public static unsafe int GetDjb2HashCode<T>(this T[] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)array.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Streams;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -18,17 +19,18 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memory"/>.</returns>
|
||||
/// <param name="memoryOwner">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memoryOwner"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The caller does not need to track the lifetime of the input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/>
|
||||
/// instance, as the returned <see cref="Stream"/> will take care of disposing that buffer when it is closed.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memoryOwner"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this IMemoryOwner<byte> memory)
|
||||
public static Stream AsStream(this IMemoryOwner<byte> memoryOwner)
|
||||
{
|
||||
return new IMemoryOwnerStream(memory);
|
||||
return MemoryStream.Create(memoryOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,12 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// In particular, the caller must ensure that the target buffer is not disposed as long
|
||||
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this Memory<byte> memory)
|
||||
{
|
||||
return new MemoryStream(memory);
|
||||
return MemoryStream.Create(memory, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
|
@ -26,11 +25,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// In particular, the caller must ensure that the target buffer is not disposed as long
|
||||
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
return new MemoryStream(memory);
|
||||
return MemoryStream.Create(memory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,10 +40,40 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Here we assume the input index will never be negative, so we do an unsafe cast to
|
||||
// force the JIT to skip the sign extension when going from int to native int.
|
||||
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
// L0003: movsxd rdx, edx
|
||||
// L0006: lea rax, [rax+rdx*4]
|
||||
// L000a: ret
|
||||
// =============================
|
||||
// Note the movsxd (move with sign extension) to expand the index passed in edx to
|
||||
// the whole rdx register. This is unnecessary and more expensive than just a mov,
|
||||
// which when done to a large register size automatically zeroes the upper bits.
|
||||
// With the (IntPtr)(void*)(uint) cast, we get the following codegen instead:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
// L0003: mov edx, edx
|
||||
// L0005: lea rax, [rax+rdx*4]
|
||||
// L0009: ret
|
||||
// =============================
|
||||
// Here we can see how the index is extended to a native integer with just a mov,
|
||||
// which effectively only zeroes the upper bits of the same register used as source.
|
||||
// These three casts are a bit verbose, but they do the trick on both 32 bit and 64
|
||||
// bit architectures, producing optimal code in both cases (they are either completely
|
||||
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
|
||||
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
|
||||
// then cast to void*, which lets the following IntPtr cast avoid the range check on 32 bit
|
||||
// (since uint could be out of range there if the original index was negative). The final
|
||||
// result is a clean mov as shown above. This will eventually be natively supported by the
|
||||
// JIT compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
|
||||
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
@ -87,7 +117,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Check whether the input is in range by first casting both
|
||||
// operands to uint and then comparing them, as this allows
|
||||
|
@ -106,12 +136,12 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
// lookup table can just be assumed to always be false.
|
||||
bool isInRange = (uint)i < (uint)span.Length;
|
||||
byte rangeFlag = Unsafe.As<bool, byte>(ref isInRange);
|
||||
int
|
||||
negativeFlag = rangeFlag - 1,
|
||||
uint
|
||||
negativeFlag = unchecked(rangeFlag - 1u),
|
||||
mask = ~negativeFlag,
|
||||
offset = i & mask;
|
||||
offset = (uint)i & mask;
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, offset);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset);
|
||||
|
||||
return ref r1;
|
||||
}
|
||||
|
@ -165,11 +195,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
@ -291,11 +321,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -40,10 +40,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
|
||||
public static unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
@ -140,11 +140,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this Span<T> span, T value)
|
||||
public static unsafe int Count<T>(this Span<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
@ -211,11 +211,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this Span<T> span)
|
||||
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref char DangerousGetReferenceAt(this string text, int i)
|
||||
public static unsafe ref char DangerousGetReferenceAt(this string text, int i)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
|
@ -57,7 +57,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
ref char ri = ref Unsafe.Add(ref r0, i);
|
||||
ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
@ -91,10 +91,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count(this string text, char c)
|
||||
public static unsafe int Count(this string text, char c)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)text.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)text.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, c);
|
||||
}
|
||||
|
@ -157,10 +157,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode(this string text)
|
||||
public static unsafe int GetDjb2HashCode(this string text)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)text.Length;
|
||||
IntPtr length = (IntPtr)(void*)(uint)text.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static int CombineValues(ReadOnlySpan<T> span)
|
||||
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
|
@ -67,13 +67,19 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
// compiler, so this branch will never actually be executed by the code.
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
{
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)span.Length);
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the info for the target memory area to process
|
||||
// Get the info for the target memory area to process.
|
||||
// The line below is computing the total byte size for the span,
|
||||
// and we cast both input factors to uint first to avoid sign extensions
|
||||
// (they're both guaranteed to always be positive values), and to let the
|
||||
// JIT avoid the 64 bit computation entirely when running in a 32 bit
|
||||
// process. In that case it will just compute the byte size as a 32 bit
|
||||
// multiplication with overflow, which is guaranteed never to happen anyway.
|
||||
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
|
||||
IntPtr length = (IntPtr)((long)span.Length * Unsafe.SizeOf<T>());
|
||||
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
|
||||
|
||||
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invoke(int i)
|
||||
public unsafe void Invoke(int i)
|
||||
{
|
||||
int
|
||||
low = i * this.batchSize,
|
||||
|
@ -147,7 +147,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
|
||||
for (int j = low; j < end; j++)
|
||||
{
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, j);
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(rj);
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invoke(int i)
|
||||
public unsafe void Invoke(int i)
|
||||
{
|
||||
int
|
||||
low = i * this.batchSize,
|
||||
|
@ -147,7 +147,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
|
||||
for (int j = low; j < end; j++)
|
||||
{
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, j);
|
||||
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
|
||||
|
||||
Unsafe.AsRef(this.action).Invoke(ref rj);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
- MemoryBufferWriter<T>: an IBufferWriter<T>: implementation that can wrap external Memory<T>: instances.
|
||||
- MemoryOwner<T>: an IMemoryOwner<T> implementation with an embedded length and a fast Span<T> accessor.
|
||||
- SpanOwner<T>: a stack-only type with the ability to rent a buffer of a specified length and getting a Span<T> from it.
|
||||
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
|
||||
- String, array, Span<T>, Memory<T> extensions and more, all focused on high performance.
|
||||
- HashCode<T>: a SIMD-enabled extension of HashCode to quickly process sequences of values.
|
||||
- BitHelper: a class with helper methods to perform bit operations on numeric types.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
|
@ -10,21 +11,24 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping an <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
internal sealed class IMemoryOwnerStream : MemoryStream
|
||||
/// <typeparam name="TSource">The type of source to use for the underlying data.</typeparam>
|
||||
internal sealed class IMemoryOwnerStream<TSource> : MemoryStream<TSource>
|
||||
where TSource : struct, ISpanOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance currently in use.
|
||||
/// The <see cref="IDisposable"/> instance currently in use.
|
||||
/// </summary>
|
||||
private readonly IMemoryOwner<byte> memory;
|
||||
private readonly IDisposable disposable;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IMemoryOwnerStream"/> class.
|
||||
/// Initializes a new instance of the <see cref="IMemoryOwnerStream{TSource}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance to use.</param>
|
||||
public IMemoryOwnerStream(IMemoryOwner<byte> memory)
|
||||
: base(memory.Memory)
|
||||
/// <param name="source">The input <typeparamref name="TSource"/> instance to use.</param>
|
||||
/// <param name="disposable">The <see cref="IDisposable"/> instance currently in use.</param>
|
||||
public IMemoryOwnerStream(TSource source, IDisposable disposable)
|
||||
: base(source, false)
|
||||
{
|
||||
this.memory = memory;
|
||||
this.disposable = disposable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -32,7 +36,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
this.memory.Dispose();
|
||||
this.disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
@ -8,16 +8,41 @@ using System.IO;
|
|||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
|
||||
/// </summary>
|
||||
internal partial class MemoryStream
|
||||
internal static partial class MemoryStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForEndOfStreamOnWrite()
|
||||
{
|
||||
throw new ArgumentException("The current stream can't contain the requested input data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
|
||||
/// </summary>
|
||||
public static void ThrowNotSupportedExceptionForSetLength()
|
||||
{
|
||||
throw new NotSupportedException("Setting the length is not supported for this stream.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
|
||||
/// </summary>
|
||||
/// <returns>Nothing, as this method throws unconditionally.</returns>
|
||||
public static long ThrowArgumentExceptionForSeekOrigin()
|
||||
{
|
||||
throw new ArgumentException("The input seek mode is not valid.", "origin");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when setting the <see cref="Stream.Position"/> property.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForPosition()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
|
||||
throw new ArgumentOutOfRangeException(nameof(Stream.Position), "The value for the property was not in the valid range.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -60,37 +85,12 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
throw new NotSupportedException("The current stream doesn't support writing.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
|
||||
{
|
||||
throw new ArgumentException("The current stream can't contain the requested input data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
|
||||
/// </summary>
|
||||
private static void ThrowNotSupportedExceptionForSetLength()
|
||||
{
|
||||
throw new NotSupportedException("Setting the length is not supported for this stream.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
|
||||
/// </summary>
|
||||
/// <returns>Nothing, as this method throws unconditionally.</returns>
|
||||
private static long ThrowArgumentExceptionForSeekOrigin()
|
||||
{
|
||||
throw new ArgumentException("The input seek mode is not valid.", "origin");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
|
||||
throw new ObjectDisposedException("source", "The current stream has already been disposed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
@ -8,9 +8,9 @@ using System.Runtime.CompilerServices;
|
|||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping a <see cref="System.Memory{T}"/> or <see cref="System.ReadOnlyMemory{T}"/> instance.
|
||||
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
|
||||
/// </summary>
|
||||
internal partial class MemoryStream
|
||||
internal static partial class MemoryStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates the <see cref="Stream.Position"/> argument.
|
||||
|
@ -18,7 +18,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <param name="position">The new <see cref="Stream.Position"/> value being set.</param>
|
||||
/// <param name="length">The maximum length of the target <see cref="Stream"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ValidatePosition(long position, int length)
|
||||
public static void ValidatePosition(long position, int length)
|
||||
{
|
||||
if ((ulong)position >= (ulong)length)
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <param name="offset">The offset within <paramref name="buffer"/>.</param>
|
||||
/// <param name="count">The number of elements to process within <paramref name="buffer"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ValidateBuffer(byte[]? buffer, int offset, int count)
|
||||
public static void ValidateBuffer(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
if (buffer is null)
|
||||
{
|
||||
|
@ -57,24 +57,24 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the <see cref="CanWrite"/> property.
|
||||
/// Validates the <see cref="MemoryStream{TSource}.CanWrite"/> property.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ValidateCanWrite()
|
||||
public static void ValidateCanWrite(bool canWrite)
|
||||
{
|
||||
if (!CanWrite)
|
||||
if (!canWrite)
|
||||
{
|
||||
ThrowNotSupportedExceptionForCanWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the current instance hasn't been disposed.
|
||||
/// Validates that a given <see cref="Stream"/> instance hasn't been disposed.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ValidateDisposed()
|
||||
public static void ValidateDisposed(bool disposed)
|
||||
{
|
||||
if (this.disposed)
|
||||
if (disposed)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
|
|
@ -1,315 +1,95 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type is not marked as <see langword="sealed"/> so that it can be inherited by
|
||||
/// <see cref="IMemoryOwnerStream"/>, which adds the <see cref="IDisposable"/> support for
|
||||
/// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT
|
||||
/// not being able to resolve the <see langword="callvirt"/> instruction, as this type is
|
||||
/// only exposed as a <see cref="Stream"/> anyway, so the generated code would be the same.
|
||||
/// </remarks>
|
||||
internal partial class MemoryStream : Stream
|
||||
internal static partial class MemoryStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether <see cref="memory"/> was actually a <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// Creates a new <see cref="Stream"/> from the input <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
private readonly bool isReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Memory{T}"/> instance currently in use.
|
||||
/// </summary>
|
||||
private Memory<byte> memory;
|
||||
|
||||
/// <summary>
|
||||
/// The current position within <see cref="memory"/>.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether or not the current instance has been disposed
|
||||
/// </summary>
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryStream"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="Memory{T}"/> instance to use.</param>
|
||||
public MemoryStream(Memory<byte> memory)
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
|
||||
/// <param name="isReadOnly">Indicates whether or not <paramref name="memory"/> can be written to.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping the underlying data for <paramref name="memory"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
public static Stream Create(ReadOnlyMemory<byte> memory, bool isReadOnly)
|
||||
{
|
||||
this.memory = memory;
|
||||
this.position = 0;
|
||||
this.isReadOnly = false;
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
// Return an empty stream if the memory was empty
|
||||
return new MemoryStream<ArrayOwner>(ArrayOwner.Empty, isReadOnly);
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
|
||||
{
|
||||
var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count);
|
||||
|
||||
return new MemoryStream<ArrayOwner>(arraySpanSource, isReadOnly);
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetMemoryManager<byte, MemoryManager<byte>>(memory, out var memoryManager, out int start, out int length))
|
||||
{
|
||||
MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length);
|
||||
|
||||
return new MemoryStream<MemoryManagerOwner>(memoryManagerSpanSource, isReadOnly);
|
||||
}
|
||||
|
||||
return ThrowNotSupportedExceptionForInvalidMemory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryStream"/> class.
|
||||
/// Creates a new <see cref="Stream"/> from the input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance to use.</param>
|
||||
public MemoryStream(ReadOnlyMemory<byte> memory)
|
||||
/// <param name="memoryOwner">The input <see cref="IMemoryOwner{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping the underlying data for <paramref name="memoryOwner"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memoryOwner"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
public static Stream Create(IMemoryOwner<byte> memoryOwner)
|
||||
{
|
||||
this.memory = MemoryMarshal.AsMemory(memory);
|
||||
this.position = 0;
|
||||
this.isReadOnly = true;
|
||||
Memory<byte> memory = memoryOwner.Memory;
|
||||
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
// Return an empty stream if the memory was empty
|
||||
return new IMemoryOwnerStream<ArrayOwner>(ArrayOwner.Empty, memoryOwner);
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
|
||||
{
|
||||
var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count);
|
||||
|
||||
return new IMemoryOwnerStream<ArrayOwner>(arraySpanSource, memoryOwner);
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetMemoryManager<byte, MemoryManager<byte>>(memory, out var memoryManager, out int start, out int length))
|
||||
{
|
||||
MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length);
|
||||
|
||||
return new IMemoryOwnerStream<MemoryManagerOwner>(memoryManagerSpanSource, memoryOwner);
|
||||
}
|
||||
|
||||
return ThrowNotSupportedExceptionForInvalidMemory();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanRead
|
||||
/// <summary>
|
||||
/// Throws a <see cref="ArgumentException"/> when a given <see cref="Memory{T}"/>
|
||||
/// or <see cref="IMemoryOwner{T}"/> instance has an unsupported backing store.
|
||||
/// </summary>
|
||||
/// <returns>Nothing, this method always throws.</returns>
|
||||
private static Stream ThrowNotSupportedExceptionForInvalidMemory()
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanSeek
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanWrite
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.isReadOnly && !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
return this.memory.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Position
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
return this.position;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidatePosition(value, this.memory.Length);
|
||||
|
||||
this.position = unchecked((int)value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CopyTo(destination, bufferSize);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int result = Read(buffer, offset, count);
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled<int>(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException<int>(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Write(buffer, offset, count);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
long index = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => this.position + offset,
|
||||
SeekOrigin.End => this.memory.Length + offset,
|
||||
_ => ThrowArgumentExceptionForSeekOrigin()
|
||||
};
|
||||
|
||||
ValidatePosition(index, this.memory.Length);
|
||||
|
||||
this.position = unchecked((int)index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void SetLength(long value)
|
||||
{
|
||||
ThrowNotSupportedExceptionForSetLength();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override int Read(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateBuffer(buffer, offset, count);
|
||||
|
||||
int
|
||||
bytesAvailable = this.memory.Length - this.position,
|
||||
bytesCopied = Math.Min(bytesAvailable, count);
|
||||
|
||||
Span<byte>
|
||||
source = this.memory.Span.Slice(this.position, bytesCopied),
|
||||
destination = buffer.AsSpan(offset, bytesCopied);
|
||||
|
||||
source.CopyTo(destination);
|
||||
|
||||
this.position += bytesCopied;
|
||||
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override int ReadByte()
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
if (this.position == this.memory.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.memory.Span[this.position++];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void Write(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
ValidateBuffer(buffer, offset, count);
|
||||
|
||||
Span<byte>
|
||||
source = buffer.AsSpan(offset, count),
|
||||
destination = this.memory.Span.Slice(this.position);
|
||||
|
||||
if (!source.TryCopyTo(destination))
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
}
|
||||
|
||||
this.position += source.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void WriteByte(byte value)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
|
||||
if (this.position == this.memory.Length)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
}
|
||||
|
||||
this.memory.Span[this.position++] = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
this.memory = default;
|
||||
throw new ArgumentException("The input instance doesn't have a valid underlying data store.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
internal partial class MemoryStream
|
||||
/// <inheritdoc cref="MemoryStream{TSource}"/>
|
||||
internal partial class MemoryStream<TSource>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyTo(Stream destination, int bufferSize)
|
||||
{
|
||||
ValidateDisposed();
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
Span<byte> source = this.memory.Span.Slice(this.position);
|
||||
Span<byte> source = this.source.Span.Slice(this.position);
|
||||
|
||||
this.position += source.Length;
|
||||
|
||||
|
@ -79,13 +77,13 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <inheritdoc/>
|
||||
public sealed override int Read(Span<byte> buffer)
|
||||
{
|
||||
ValidateDisposed();
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
int
|
||||
bytesAvailable = this.memory.Length - this.position,
|
||||
bytesAvailable = this.source.Length - this.position,
|
||||
bytesCopied = Math.Min(bytesAvailable, buffer.Length);
|
||||
|
||||
Span<byte> source = this.memory.Span.Slice(this.position, bytesCopied);
|
||||
Span<byte> source = this.source.Span.Slice(this.position, bytesCopied);
|
||||
|
||||
source.CopyTo(buffer);
|
||||
|
||||
|
@ -97,14 +95,14 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <inheritdoc/>
|
||||
public sealed override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
MemoryStream.ValidateCanWrite(CanWrite);
|
||||
|
||||
Span<byte> destination = this.memory.Span.Slice(this.position);
|
||||
Span<byte> destination = this.source.Span.Slice(this.position);
|
||||
|
||||
if (!buffer.TryCopyTo(destination))
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
}
|
||||
|
||||
this.position += buffer.Length;
|
|
@ -0,0 +1,305 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of source to use for the underlying data.</typeparam>
|
||||
/// <remarks>
|
||||
/// This type is not marked as <see langword="sealed"/> so that it can be inherited by
|
||||
/// <see cref="IMemoryOwnerStream{TSource}"/>, which adds the <see cref="IDisposable"/> support for
|
||||
/// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT
|
||||
/// not being able to resolve the <see langword="callvirt"/> instruction, as this type is
|
||||
/// only exposed as a <see cref="Stream"/> anyway, so the generated code would be the same.
|
||||
/// </remarks>
|
||||
internal partial class MemoryStream<TSource> : Stream
|
||||
where TSource : struct, ISpanOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether <see cref="source"/> can be written to.
|
||||
/// </summary>
|
||||
private readonly bool isReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// The <typeparamref name="TSource"/> instance currently in use.
|
||||
/// </summary>
|
||||
private TSource source;
|
||||
|
||||
/// <summary>
|
||||
/// The current position within <see cref="source"/>.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether or not the current instance has been disposed
|
||||
/// </summary>
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryStream{TSource}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="source">The input <typeparamref name="TSource"/> instance to use.</param>
|
||||
/// <param name="isReadOnly">Indicates whether <paramref name="source"/> can be written to.</param>
|
||||
public MemoryStream(TSource source, bool isReadOnly)
|
||||
{
|
||||
this.source = source;
|
||||
this.isReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanRead
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanSeek
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override bool CanWrite
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.isReadOnly && !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
return this.source.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Position
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
return this.position;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
MemoryStream.ValidatePosition(value, this.source.Length);
|
||||
|
||||
this.position = unchecked((int)value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CopyTo(destination, bufferSize);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int result = Read(buffer, offset, count);
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled<int>(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException<int>(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Write(buffer, offset, count);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
return Task.FromCanceled(e.CancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
long index = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => this.position + offset,
|
||||
SeekOrigin.End => this.source.Length + offset,
|
||||
_ => MemoryStream.ThrowArgumentExceptionForSeekOrigin()
|
||||
};
|
||||
|
||||
MemoryStream.ValidatePosition(index, this.source.Length);
|
||||
|
||||
this.position = unchecked((int)index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void SetLength(long value)
|
||||
{
|
||||
MemoryStream.ThrowNotSupportedExceptionForSetLength();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override int Read(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
MemoryStream.ValidateBuffer(buffer, offset, count);
|
||||
|
||||
int
|
||||
bytesAvailable = this.source.Length - this.position,
|
||||
bytesCopied = Math.Min(bytesAvailable, count);
|
||||
|
||||
Span<byte>
|
||||
source = this.source.Span.Slice(this.position, bytesCopied),
|
||||
destination = buffer.AsSpan(offset, bytesCopied);
|
||||
|
||||
source.CopyTo(destination);
|
||||
|
||||
this.position += bytesCopied;
|
||||
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override int ReadByte()
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
|
||||
if (this.position == this.source.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.source.Span[this.position++];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void Write(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
MemoryStream.ValidateCanWrite(CanWrite);
|
||||
MemoryStream.ValidateBuffer(buffer, offset, count);
|
||||
|
||||
Span<byte>
|
||||
source = buffer.AsSpan(offset, count),
|
||||
destination = this.source.Span.Slice(this.position);
|
||||
|
||||
if (!source.TryCopyTo(destination))
|
||||
{
|
||||
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
}
|
||||
|
||||
this.position += source.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void WriteByte(byte value)
|
||||
{
|
||||
MemoryStream.ValidateDisposed(this.disposed);
|
||||
MemoryStream.ValidateCanWrite(CanWrite);
|
||||
|
||||
if (this.position == this.source.Length)
|
||||
{
|
||||
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
|
||||
}
|
||||
|
||||
this.source.Span[this.position++] = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
this.source = default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ISpanOwner"/> implementation wrapping an array.
|
||||
/// </summary>
|
||||
internal readonly struct ArrayOwner : ISpanOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// The wrapped <see cref="byte"/> array.
|
||||
/// </summary>
|
||||
private readonly byte[] array;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
|
||||
/// <summary>
|
||||
/// The usable length within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayOwner"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The wrapped <see cref="byte"/> array.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="array"/>.</param>
|
||||
/// <param name="length">The usable length within <paramref name="array"/>.</param>
|
||||
public ArrayOwner(byte[] array, int offset, int length)
|
||||
{
|
||||
this.array = array;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="ArrayOwner"/> instance.
|
||||
/// </summary>
|
||||
public static ArrayOwner Empty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new ArrayOwner(Array.Empty<byte>(), 0, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Span<byte> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref byte r0 = ref this.array.DangerousGetReferenceAt(this.offset);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
#else
|
||||
return this.array.AsSpan(this.offset, this.length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for types acting as sources for <see cref="Span{T}"/> instances.
|
||||
/// </summary>
|
||||
internal interface ISpanOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the length of the underlying memory area.
|
||||
/// </summary>
|
||||
int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span{T}"/> instance wrapping the underlying memory area.
|
||||
/// </summary>
|
||||
Span<byte> Span { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ISpanOwner"/> implementation wrapping a <see cref="MemoryManager{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
internal readonly struct MemoryManagerOwner : ISpanOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// The wrapped <see cref="MemoryManager{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly MemoryManager<byte> memoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
|
||||
/// <summary>
|
||||
/// The usable length within <see cref="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryManagerOwner"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The wrapped <see cref="MemoryManager{T}"/> instance.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="length">The usable length within <paramref name="memoryManager"/>.</param>
|
||||
public MemoryManagerOwner(MemoryManager<byte> memoryManager, int offset, int length)
|
||||
{
|
||||
this.memoryManager = memoryManager;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Span<byte> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
// We can't use the same trick we use for arrays to optimize the creation of
|
||||
// the offset span, as otherwise a bugged MemoryManager<T> instance returning
|
||||
// a span of an incorrect size could cause an access violation. Instead, we just
|
||||
// get the span and then slice it, which will validate both offset and length.
|
||||
return this.memoryManager.GetSpan().Slice(this.offset, this.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -227,7 +227,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
|
||||
/// <param name="oldValue">The current property value.</param>
|
||||
/// <param name="newValue">The property's value after the change occurred.</param>
|
||||
/// <param name="model">The model </param>
|
||||
/// <param name="model">The model containing the property being updated.</param>
|
||||
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
|
@ -236,6 +236,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// raised if the current and new value for the target property are the same.
|
||||
/// </remarks>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
|
||||
{
|
||||
|
@ -263,11 +264,12 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <param name="oldValue">The current property value.</param>
|
||||
/// <param name="newValue">The property's value after the change occurred.</param>
|
||||
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
|
||||
/// <param name="model">The model </param>
|
||||
/// <param name="model">The model containing the property being updated.</param>
|
||||
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
if (comparer.Equals(oldValue, newValue))
|
||||
{
|
||||
|
|
|
@ -257,6 +257,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
|
||||
|
||||
|
@ -288,6 +289,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
|
||||
|
||||
|
|
|
@ -170,6 +170,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
if (validate)
|
||||
{
|
||||
|
@ -199,6 +200,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
|
||||
where TModel : class
|
||||
{
|
||||
if (validate)
|
||||
{
|
||||
|
|
|
@ -325,14 +325,12 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
|
||||
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
object[] handlers;
|
||||
object[] recipients;
|
||||
ref object handlersRef = ref Unsafe.AsRef<object>(null);
|
||||
ref object recipientsRef = ref Unsafe.AsRef<object>(null);
|
||||
object[] rentedArray;
|
||||
Span<object> pairs;
|
||||
int i = 0;
|
||||
|
||||
lock (this.recipientsMap)
|
||||
|
@ -358,10 +356,9 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
return message;
|
||||
}
|
||||
|
||||
handlers = ArrayPool<object>.Shared.Rent(totalHandlersCount);
|
||||
recipients = ArrayPool<object>.Shared.Rent(totalHandlersCount);
|
||||
handlersRef = ref handlers[0];
|
||||
recipientsRef = ref recipients[0];
|
||||
// Rent the array and also assign it to a span, which will be used to access values.
|
||||
// We're doing this to avoid the array covariance checks slowdown in the loops below.
|
||||
pairs = rentedArray = ArrayPool<object>.Shared.Rent(2 * totalHandlersCount);
|
||||
|
||||
// Copy the handlers to the local collection.
|
||||
// The array is oversized at this point, since it also includes
|
||||
|
@ -379,10 +376,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
// Pick the target handler, if the token is a match for the recipient
|
||||
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
|
||||
{
|
||||
// We can manually offset here to skip the bounds checks in this inner loop when
|
||||
// indexing the array (the size is already verified and guaranteed to be enough).
|
||||
Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)i) = handler!;
|
||||
Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)i++) = recipient;
|
||||
// This span access should always guaranteed to be valid due to the size of the
|
||||
// array being set according to the current total number of registered handlers,
|
||||
// which will always be greater or equal than the ones matching the previous test.
|
||||
// We're still using a checked span accesses here though to make sure an out of
|
||||
// bounds write can never happen even if an error was present in the logic above.
|
||||
pairs[2 * i] = handler!;
|
||||
pairs[(2 * i) + 1] = recipient;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -392,27 +393,20 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
// Invoke all the necessary handlers on the local copy of entries
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
// We're doing an unsafe cast to skip the type checks again.
|
||||
// See the comments in the UnregisterAll method for more info.
|
||||
object handler = Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)j);
|
||||
object recipient = Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)j);
|
||||
|
||||
// Here we perform an unsafe cast to enable covariance for delegate types.
|
||||
// We know that the input recipient will always respect the type constraints
|
||||
// of each original input delegate, and doing so allows us to still invoke
|
||||
// them all from here without worrying about specific generic type arguments.
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(handler)(recipient, message);
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(pairs[2 * j])(pairs[(2 * j) + 1], message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// As before, we also need to clear it first to avoid having potentially long
|
||||
// lasting memory leaks due to leftover references being stored in the pool.
|
||||
handlers.AsSpan(0, i).Clear();
|
||||
recipients.AsSpan(0, i).Clear();
|
||||
Array.Clear(rentedArray, 0, 2 * i);
|
||||
|
||||
ArrayPool<object>.Shared.Return(handlers);
|
||||
ArrayPool<object>.Shared.Return(recipients);
|
||||
ArrayPool<object>.Shared.Return(rentedArray);
|
||||
}
|
||||
|
||||
return message;
|
||||
|
|
|
@ -169,8 +169,8 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
ArrayPoolBufferWriter<object> recipients;
|
||||
ArrayPoolBufferWriter<object> handlers;
|
||||
ArrayPoolBufferWriter<object> bufferWriter;
|
||||
int i = 0;
|
||||
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
|
@ -182,8 +182,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
return message;
|
||||
}
|
||||
|
||||
recipients = ArrayPoolBufferWriter<object>.Create();
|
||||
handlers = ArrayPoolBufferWriter<object>.Create();
|
||||
bufferWriter = ArrayPoolBufferWriter<object>.Create();
|
||||
|
||||
// We need a local, temporary copy of all the pending recipients and handlers to
|
||||
// invoke, to avoid issues with handlers unregistering from messages while we're
|
||||
|
@ -197,32 +196,30 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
|
||||
if (map.TryGetValue(token, out object? handler))
|
||||
{
|
||||
recipients.Add(pair.Key);
|
||||
handlers.Add(handler!);
|
||||
bufferWriter.Add(handler!);
|
||||
bufferWriter.Add(pair.Key);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadOnlySpan<object>
|
||||
recipientsSpan = recipients.Span,
|
||||
handlersSpan = handlers.Span;
|
||||
ReadOnlySpan<object> pairs = bufferWriter.Span;
|
||||
|
||||
for (int i = 0; i < recipientsSpan.Length; i++)
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
// Just like in the other messenger, here we need an unsafe cast to be able to
|
||||
// invoke a generic delegate with a contravariant input argument, with a less
|
||||
// derived reference, without reflection. This is guaranteed to work by how the
|
||||
// messenger tracks registered recipients and their associated handlers, so the
|
||||
// type conversion will always be valid (the recipients are the rigth instances).
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(handlersSpan[i])(recipientsSpan[i], message);
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(pairs[2 * j])(pairs[(2 * j) + 1], message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
recipients.Dispose();
|
||||
handlers.Dispose();
|
||||
bufferWriter.Dispose();
|
||||
}
|
||||
|
||||
return message;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class ComboBoxItemGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal ComboBoxItemGazeTargetItem(UIElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(TargetElement);
|
||||
var comboBoxItemAutomationPeer = peer as ComboBoxItemAutomationPeer;
|
||||
var comboBoxItem = (ComboBoxItem)comboBoxItemAutomationPeer.Owner;
|
||||
|
||||
AutomationPeer ancestor = comboBoxItemAutomationPeer;
|
||||
var comboBoxAutomationPeer = ancestor as ComboBoxAutomationPeer;
|
||||
while (comboBoxAutomationPeer == null)
|
||||
{
|
||||
ancestor = ancestor.Navigate(AutomationNavigationDirection.Parent) as AutomationPeer;
|
||||
comboBoxAutomationPeer = ancestor as ComboBoxAutomationPeer;
|
||||
}
|
||||
|
||||
comboBoxItem.IsSelected = true;
|
||||
comboBoxAutomationPeer.Collapse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This parameter is passed to the GazeElement::Invoked event and allows
|
||||
/// the application to prevent default invocation when the user dwells on a control
|
||||
/// </summary>
|
||||
public sealed class DwellInvokedRoutedEventArgs : HandledEventArgs
|
||||
{
|
||||
internal DwellInvokedRoutedEventArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This parameter is passed to the GazeElement::Invoked event and allows
|
||||
/// the application to prevent default invocation when the user dwells on a control
|
||||
/// </summary>
|
||||
public ref class DwellInvokedRoutedEventArgs : public RoutedEventArgs
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The application should set this value to true to prevent invoking the control when the user dwells on a control
|
||||
/// </summary>
|
||||
property bool Handled;
|
||||
|
||||
internal:
|
||||
|
||||
DwellInvokedRoutedEventArgs()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,31 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This parameter is passed to the GazeElement.DwellProgressFeedback event. The event is fired to inform the application of the user's progress towards completing dwelling on a control
|
||||
/// </summary>
|
||||
public sealed class DwellProgressEventArgs : HandledEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an enum that reflects the current state of dwell progress
|
||||
/// </summary>
|
||||
public DwellProgressState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value between 0 and 1 that reflects the fraction of progress towards completing dwell
|
||||
/// </summary>
|
||||
public double Progress { get; }
|
||||
|
||||
internal DwellProgressEventArgs(DwellProgressState state, TimeSpan elapsedDuration, TimeSpan triggerDuration)
|
||||
{
|
||||
State = state;
|
||||
Progress = ((double)elapsedDuration.Ticks) / triggerDuration.Ticks;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DwellProgressState.h"
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This parameter is passed to the GazeElement.DwellProgressFeedback event. The event is fired to inform the application of the user's progress towards completing dwelling on a control
|
||||
/// </summary>
|
||||
public ref class DwellProgressEventArgs : public RoutedEventArgs
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// An enum that reflects the current state of dwell progress
|
||||
/// </summary>
|
||||
property DwellProgressState State { DwellProgressState get() { return _state; }}
|
||||
|
||||
/// <summary>
|
||||
/// A value between 0 and 1 that reflects the fraction of progress towards completing dwell
|
||||
/// </summary>
|
||||
property double Progress { double get() { return _progress; }}
|
||||
|
||||
/// <summary>
|
||||
/// A parameter for the application to set to true if it handles the event. If this parameter is set to true, the library suppresses default animation for dwell feedback on the control
|
||||
/// </summary>
|
||||
property bool Handled;
|
||||
|
||||
internal:
|
||||
DwellProgressEventArgs(DwellProgressState state, TimeSpan elapsedDuration, TimeSpan triggerDuration)
|
||||
{
|
||||
_state = state;
|
||||
_progress = ((double)elapsedDuration.Duration) / triggerDuration.Duration;
|
||||
}
|
||||
private:
|
||||
DwellProgressState _state;
|
||||
double _progress;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,32 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// An enum that reflects the current state of progress towards dwell when a user is focused on a control
|
||||
/// </summary>
|
||||
public enum DwellProgressState
|
||||
{
|
||||
/// <summary>
|
||||
/// User is not looking at the control
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze has entered control but we're not yet showing progress.
|
||||
/// </summary>
|
||||
Fixating,
|
||||
|
||||
/// <summary>
|
||||
/// User is continuing to focus on a control with an intent to dwell and invoke
|
||||
/// </summary>
|
||||
Progressing,
|
||||
|
||||
/// <summary>
|
||||
/// User has completed dwelling on a control
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// An enum that reflects the current state of progress towards dwell when a user is focused on a control
|
||||
/// </summary>
|
||||
public enum class DwellProgressState
|
||||
{
|
||||
/// <summary>
|
||||
/// User is not looking at the control
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze has entered control but we're not yet showing progress.
|
||||
/// </summary>
|
||||
Fixating,
|
||||
|
||||
/// <summary>
|
||||
/// User is continuing to focus on a control with an intent to dwell and invoke
|
||||
/// </summary>
|
||||
Progressing,
|
||||
|
||||
/// <summary>
|
||||
/// User has completed dwelling on a control
|
||||
/// </summary>
|
||||
Complete
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class ExpandCollapsePatternGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal ExpandCollapsePatternGazeTargetItem(UIElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(TargetElement);
|
||||
var provider = peer.GetPattern(PatternInterface.ExpandCollapse) as IExpandCollapseProvider;
|
||||
switch (provider.ExpandCollapseState)
|
||||
{
|
||||
case ExpandCollapseState.Collapsed:
|
||||
provider.Expand();
|
||||
break;
|
||||
|
||||
case ExpandCollapseState.Expanded:
|
||||
provider.Collapse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeCursor.h"
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
GazeCursor::GazeCursor()
|
||||
{
|
||||
_gazePopup = ref new Popup();
|
||||
_gazePopup->IsHitTestVisible = false;
|
||||
|
||||
auto gazeCursor = ref new Shapes::Ellipse();
|
||||
gazeCursor->Fill = ref new SolidColorBrush(Colors::IndianRed);
|
||||
gazeCursor->VerticalAlignment = Windows::UI::Xaml::VerticalAlignment::Top;
|
||||
gazeCursor->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left;
|
||||
gazeCursor->Width = 2 * CursorRadius;
|
||||
gazeCursor->Height = 2 * CursorRadius;
|
||||
gazeCursor->Margin = Thickness(-CursorRadius, -CursorRadius, 0, 0);
|
||||
gazeCursor->IsHitTestVisible = false;
|
||||
|
||||
_gazePopup->Child = gazeCursor;
|
||||
}
|
||||
|
||||
void GazeCursor::CursorRadius::set(int value)
|
||||
{
|
||||
_cursorRadius = value;
|
||||
auto gazeCursor = CursorElement;
|
||||
if (gazeCursor != nullptr)
|
||||
{
|
||||
gazeCursor->Width = 2 * _cursorRadius;
|
||||
gazeCursor->Height = 2 * _cursorRadius;
|
||||
gazeCursor->Margin = Thickness(-_cursorRadius, -_cursorRadius, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GazeCursor::IsCursorVisible::set(bool value)
|
||||
{
|
||||
_isCursorVisible = value;
|
||||
SetVisibility();
|
||||
}
|
||||
|
||||
void GazeCursor::IsGazeEntered::set(bool value)
|
||||
{
|
||||
_isGazeEntered = value;
|
||||
SetVisibility();
|
||||
}
|
||||
|
||||
void GazeCursor::LoadSettings(ValueSet^ settings)
|
||||
{
|
||||
if (settings->HasKey("GazeCursor.CursorRadius"))
|
||||
{
|
||||
CursorRadius = (int)(settings->Lookup("GazeCursor.CursorRadius"));
|
||||
}
|
||||
if (settings->HasKey("GazeCursor.CursorVisibility"))
|
||||
{
|
||||
IsCursorVisible = (bool)(settings->Lookup("GazeCursor.CursorVisibility"));
|
||||
}
|
||||
}
|
||||
|
||||
void GazeCursor::SetVisibility()
|
||||
{
|
||||
auto isOpen = _isCursorVisible && _isGazeEntered;
|
||||
if (_gazePopup->IsOpen != isOpen)
|
||||
{
|
||||
_gazePopup->IsOpen = isOpen;
|
||||
}
|
||||
else if (isOpen)
|
||||
{
|
||||
auto topmost = VisualTreeHelper::GetOpenPopups(Window::Current)->First()->Current;
|
||||
if (_gazePopup != topmost)
|
||||
{
|
||||
_gazePopup->IsOpen = false;
|
||||
_gazePopup->IsOpen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,173 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class GazeCursor
|
||||
{
|
||||
private const int DEFAULT_CURSOR_RADIUS = 5;
|
||||
private const bool DEFAULT_CURSOR_VISIBILITY = true;
|
||||
|
||||
public void LoadSettings(ValueSet settings)
|
||||
{
|
||||
if (settings.ContainsKey("GazeCursor.CursorRadius"))
|
||||
{
|
||||
CursorRadius = (int)settings["GazeCursor.CursorRadius"];
|
||||
}
|
||||
|
||||
if (settings.ContainsKey("GazeCursor.CursorVisibility"))
|
||||
{
|
||||
IsCursorVisible = (bool)settings["GazeCursor.CursorVisibility"];
|
||||
}
|
||||
}
|
||||
|
||||
public int CursorRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cursorRadius;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_cursorRadius = value;
|
||||
var gazeCursor = CursorElement;
|
||||
if (gazeCursor != null)
|
||||
{
|
||||
gazeCursor.Width = 2 * _cursorRadius;
|
||||
gazeCursor.Height = 2 * _cursorRadius;
|
||||
gazeCursor.Margin = new Thickness(-_cursorRadius, -_cursorRadius, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCursorVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isCursorVisible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_isCursorVisible = value;
|
||||
SetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsGazeEntered
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isGazeEntered;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_isGazeEntered = value;
|
||||
SetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public Point Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cursorPosition;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_cursorPosition = value;
|
||||
_gazePopup.HorizontalOffset = value.X;
|
||||
_gazePopup.VerticalOffset = value.Y;
|
||||
SetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public UIElement PopupChild
|
||||
{
|
||||
get
|
||||
{
|
||||
return _gazePopup.Child;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_gazePopup.Child = value;
|
||||
}
|
||||
}
|
||||
|
||||
public FrameworkElement CursorElement
|
||||
{
|
||||
get
|
||||
{
|
||||
return _gazePopup.Child as FrameworkElement;
|
||||
}
|
||||
}
|
||||
|
||||
internal GazeCursor()
|
||||
{
|
||||
_gazePopup = new Popup
|
||||
{
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
|
||||
var gazeCursor = new Windows.UI.Xaml.Shapes.Ellipse
|
||||
{
|
||||
Fill = new SolidColorBrush(Colors.IndianRed),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Width = 2 * CursorRadius,
|
||||
Height = 2 * CursorRadius,
|
||||
Margin = new Thickness(-CursorRadius, -CursorRadius, 0, 0),
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
|
||||
_gazePopup.Child = gazeCursor;
|
||||
}
|
||||
|
||||
private void SetVisibility()
|
||||
{
|
||||
var isOpen = _isCursorVisible && _isGazeEntered;
|
||||
if (_gazePopup.IsOpen != isOpen)
|
||||
{
|
||||
_gazePopup.IsOpen = isOpen;
|
||||
}
|
||||
else if (isOpen)
|
||||
{
|
||||
Popup topmost;
|
||||
|
||||
if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot") && _gazePopup.XamlRoot != null)
|
||||
{
|
||||
topmost = VisualTreeHelper.GetOpenPopupsForXamlRoot(_gazePopup.XamlRoot).First();
|
||||
}
|
||||
else
|
||||
{
|
||||
topmost = VisualTreeHelper.GetOpenPopups(Window.Current).First();
|
||||
}
|
||||
|
||||
if (_gazePopup != topmost)
|
||||
{
|
||||
_gazePopup.IsOpen = false;
|
||||
_gazePopup.IsOpen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Popup _gazePopup;
|
||||
private Point _cursorPosition = default;
|
||||
private int _cursorRadius = DEFAULT_CURSOR_RADIUS;
|
||||
private bool _isCursorVisible = DEFAULT_CURSOR_VISIBILITY;
|
||||
private bool _isGazeEntered;
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
using namespace Windows::UI::Xaml::Controls::Primitives;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
private ref class GazeCursor sealed
|
||||
{
|
||||
private:
|
||||
const int DEFAULT_CURSOR_RADIUS = 5;
|
||||
const bool DEFAULT_CURSOR_VISIBILITY = true;
|
||||
|
||||
public:
|
||||
void LoadSettings(ValueSet^ settings);
|
||||
property int CursorRadius
|
||||
{
|
||||
int get() { return _cursorRadius; }
|
||||
void set(int value);
|
||||
}
|
||||
|
||||
property bool IsCursorVisible
|
||||
{
|
||||
bool get() { return _isCursorVisible; }
|
||||
void set(bool value);
|
||||
}
|
||||
|
||||
property bool IsGazeEntered
|
||||
{
|
||||
bool get() { return _isGazeEntered; }
|
||||
void set(bool value);
|
||||
}
|
||||
|
||||
property Point Position
|
||||
{
|
||||
Point get()
|
||||
{
|
||||
return _cursorPosition;
|
||||
}
|
||||
|
||||
void set(Point value)
|
||||
{
|
||||
_cursorPosition = value;
|
||||
_gazePopup->HorizontalOffset = value.X;
|
||||
_gazePopup->VerticalOffset = value.Y;
|
||||
SetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
property UIElement^ PopupChild
|
||||
{
|
||||
UIElement^ get()
|
||||
{
|
||||
return _gazePopup->Child;
|
||||
}
|
||||
void set(UIElement^ value)
|
||||
{
|
||||
_gazePopup->Child = value;
|
||||
}
|
||||
}
|
||||
|
||||
property FrameworkElement^ CursorElement
|
||||
{
|
||||
FrameworkElement^ get()
|
||||
{
|
||||
return dynamic_cast<FrameworkElement^>(_gazePopup->Child);
|
||||
}
|
||||
}
|
||||
|
||||
internal:
|
||||
GazeCursor();
|
||||
|
||||
private:
|
||||
void SetVisibility();
|
||||
|
||||
Popup^ _gazePopup;
|
||||
Point _cursorPosition = {};
|
||||
int _cursorRadius = DEFAULT_CURSOR_RADIUS;
|
||||
bool _isCursorVisible = DEFAULT_CURSOR_VISIBILITY;
|
||||
bool _isGazeEntered;
|
||||
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,47 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Surrogate object attached to controls allowing subscription to per-control gaze events.
|
||||
/// </summary>
|
||||
public class GazeElement : DependencyObject
|
||||
{
|
||||
/// <summary>
|
||||
/// This event is fired when the state of the user's gaze on a control has changed
|
||||
/// </summary>
|
||||
public event EventHandler<StateChangedEventArgs> StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired when the user completed dwelling on a control and the control is about to be invoked by default. This event is fired to give the application an opportunity to prevent default invocation
|
||||
/// </summary>
|
||||
public event EventHandler<DwellInvokedRoutedEventArgs> Invoked;
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired to inform the application of the progress towards dwell
|
||||
/// </summary>
|
||||
public event EventHandler<DwellProgressEventArgs> DwellProgressFeedback;
|
||||
|
||||
internal void RaiseStateChanged(object sender, StateChangedEventArgs args)
|
||||
{
|
||||
StateChanged?.Invoke(sender, args);
|
||||
}
|
||||
|
||||
internal void RaiseInvoked(object sender, DwellInvokedRoutedEventArgs args)
|
||||
{
|
||||
Invoked?.Invoke(sender, args);
|
||||
}
|
||||
|
||||
internal bool RaiseProgressFeedback(object sender, DwellProgressState state, TimeSpan elapsedTime, TimeSpan triggerTime)
|
||||
{
|
||||
var args = new DwellProgressEventArgs(state, elapsedTime, triggerTime);
|
||||
DwellProgressFeedback?.Invoke(sender, args);
|
||||
return args.Handled;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DwellInvokedRoutedEventArgs.h"
|
||||
#include "DwellProgressEventArgs.h"
|
||||
#include "StateChangedEventArgs.h"
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::UI::Xaml;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// Surrogate object attached to controls allowing subscription to per-control gaze events.
|
||||
/// </summary>
|
||||
public ref class GazeElement sealed : public DependencyObject
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired when the state of the user's gaze on a control has changed
|
||||
/// </summary>
|
||||
event EventHandler<StateChangedEventArgs^>^ StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired when the user completed dwelling on a control and the control is about to be invoked by default. This event is fired to give the application an opportunity to prevent default invocation
|
||||
/// </summary>
|
||||
event EventHandler<DwellInvokedRoutedEventArgs^>^ Invoked;
|
||||
|
||||
/// <summary>
|
||||
/// This event is fired to inform the application of the progress towards dwell
|
||||
/// </summary>
|
||||
event EventHandler<DwellProgressEventArgs^>^ DwellProgressFeedback;
|
||||
|
||||
internal:
|
||||
|
||||
void RaiseStateChanged(Object^ sender, StateChangedEventArgs^ args) { StateChanged(sender, args); }
|
||||
|
||||
void RaiseInvoked(Object^ sender, DwellInvokedRoutedEventArgs^ args)
|
||||
{
|
||||
Invoked(sender, args);
|
||||
}
|
||||
|
||||
bool RaiseProgressFeedback(Object^ sender, DwellProgressState state, TimeSpan elapsedTime, TimeSpan triggerTime)
|
||||
{
|
||||
auto args = ref new DwellProgressEventArgs(state, elapsedTime, triggerTime);
|
||||
DwellProgressFeedback(sender, args);
|
||||
return args->Handled;
|
||||
}
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,5 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeEventArgs.h"
|
|
@ -0,0 +1,33 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArgs used to send Gaze events. See <see cref="GazePointer.GazeEvent"/>
|
||||
/// </summary>
|
||||
public sealed class GazeEventArgs : HandledEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the location of the Gaze event
|
||||
/// </summary>
|
||||
public Point Location { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the gaze event
|
||||
/// </summary>
|
||||
public TimeSpan Timestamp { get; private set; }
|
||||
|
||||
internal void Set(Point location, TimeSpan timestamp)
|
||||
{
|
||||
Handled = false;
|
||||
Location = location;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
public ref struct GazeEventArgs sealed
|
||||
{
|
||||
property bool Handled;
|
||||
property Point Location;
|
||||
property TimeSpan Timestamp;
|
||||
|
||||
GazeEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
void Set(Point location, TimeSpan timestamp)
|
||||
{
|
||||
Handled = false;
|
||||
Location = location;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,43 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeFeedbackPopupFactory.h"
|
||||
#include "GazeInput.h"
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
Popup^ GazeFeedbackPopupFactory::Get()
|
||||
{
|
||||
Popup^ popup;
|
||||
::Windows::UI::Xaml::Shapes::Rectangle^ rectangle;
|
||||
|
||||
if (s_cache->Size != 0)
|
||||
{
|
||||
popup = s_cache->GetAt(0);
|
||||
s_cache->RemoveAt(0);
|
||||
|
||||
rectangle = safe_cast<::Windows::UI::Xaml::Shapes::Rectangle^>(popup->Child);
|
||||
}
|
||||
else
|
||||
{
|
||||
popup = ref new Popup();
|
||||
|
||||
rectangle = ref new ::Windows::UI::Xaml::Shapes::Rectangle();
|
||||
rectangle->IsHitTestVisible = false;
|
||||
|
||||
popup->Child = rectangle;
|
||||
}
|
||||
|
||||
rectangle->StrokeThickness = GazeInput::DwellStrokeThickness;
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
void GazeFeedbackPopupFactory::Return(Popup^ popup)
|
||||
{
|
||||
popup->IsOpen = false;
|
||||
s_cache->Append(popup);
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,49 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class GazeFeedbackPopupFactory
|
||||
{
|
||||
private readonly List<Popup> _cache = new List<Popup>();
|
||||
|
||||
public Popup Get()
|
||||
{
|
||||
Popup popup;
|
||||
Windows.UI.Xaml.Shapes.Rectangle rectangle;
|
||||
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
popup = _cache[0];
|
||||
_cache.RemoveAt(0);
|
||||
|
||||
rectangle = popup.Child as Windows.UI.Xaml.Shapes.Rectangle;
|
||||
}
|
||||
else
|
||||
{
|
||||
popup = new Popup();
|
||||
|
||||
rectangle = new Windows.UI.Xaml.Shapes.Rectangle
|
||||
{
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
|
||||
popup.Child = rectangle;
|
||||
}
|
||||
|
||||
rectangle.StrokeThickness = GazeInput.DwellStrokeThickness;
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
public void Return(Popup popup)
|
||||
{
|
||||
popup.IsOpen = false;
|
||||
_cache.Add(popup);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
using namespace Platform::Collections;
|
||||
using namespace Windows::UI::Xaml::Controls::Primitives;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
private ref class GazeFeedbackPopupFactory
|
||||
{
|
||||
private:
|
||||
|
||||
Vector<Popup^>^ s_cache = ref new Vector<Popup^>();
|
||||
|
||||
public:
|
||||
|
||||
Popup^ Get();
|
||||
|
||||
void Return(Popup^ popup);
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This struct encapsulates the location and timestamp associated with the user's gaze
|
||||
/// and is used as an input and output parameter for the IGazeFilter.Update method
|
||||
/// </summary>
|
||||
internal struct GazeFilterArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current point in the gaze stream
|
||||
/// </summary>
|
||||
public Point Location => _location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp associated with the current point
|
||||
/// </summary>
|
||||
public TimeSpan Timestamp => _timestamp;
|
||||
|
||||
internal GazeFilterArgs(Point location, TimeSpan timestamp)
|
||||
{
|
||||
_location = location;
|
||||
_timestamp = timestamp;
|
||||
}
|
||||
|
||||
private Point _location;
|
||||
private TimeSpan _timestamp;
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeHidParsers.h"
|
||||
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
namespace GazeHidParsers {
|
||||
static HidNumericControlDescription ^ GetGazeUsageFromCollectionId(
|
||||
GazeDevicePreview ^ gazeDevice,
|
||||
uint16 childUsageId,
|
||||
uint16 parentUsageId)
|
||||
{
|
||||
IVectorView<HidNumericControlDescription ^> ^ numericControls = gazeDevice->GetNumericControlDescriptions(
|
||||
(USHORT)GazeHidUsages::UsagePage_EyeHeadTracker, childUsageId);
|
||||
|
||||
for (unsigned int i = 0; i < numericControls->Size; i++)
|
||||
{
|
||||
auto parentCollections = numericControls->GetAt(i)->ParentCollections;
|
||||
if (parentCollections->Size > 0 &&
|
||||
parentCollections->GetAt(0)->UsagePage == (USHORT)GazeHidUsages::UsagePage_EyeHeadTracker &&
|
||||
parentCollections->GetAt(0)->UsageId == parentUsageId)
|
||||
{
|
||||
return numericControls->GetAt(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#pragma region GazeHidPositionParser
|
||||
GazeHidPositionParser::GazeHidPositionParser(GazeDevicePreview ^ gazeDevice, uint16 usage)
|
||||
{
|
||||
_usage = usage;
|
||||
|
||||
// Find all the position usages from the device's
|
||||
// descriptor and store them for easy access
|
||||
_X = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_PositionX, _usage);
|
||||
_Y = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_PositionY, _usage);
|
||||
_Z = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_PositionZ, _usage);
|
||||
}
|
||||
|
||||
GazeHidPosition^ GazeHidPositionParser::GetPosition(HidInputReport ^ report)
|
||||
{
|
||||
GazeHidPosition^ result = nullptr;
|
||||
|
||||
if (_X != nullptr &&
|
||||
_Y != nullptr &&
|
||||
_Z != nullptr &&
|
||||
_usage != 0x0000)
|
||||
{
|
||||
auto descX = report->GetNumericControlByDescription(_X);
|
||||
auto descY = report->GetNumericControlByDescription(_Y);
|
||||
auto descZ = report->GetNumericControlByDescription(_Z);
|
||||
|
||||
auto controlDescX = descX->ControlDescription;
|
||||
auto controlDescY = descY->ControlDescription;
|
||||
auto controlDescZ = descZ->ControlDescription;
|
||||
|
||||
if ((controlDescX->LogicalMaximum < descX->ScaledValue || controlDescX->LogicalMinimum > descX->ScaledValue) ||
|
||||
(controlDescY->LogicalMaximum < descY->ScaledValue || controlDescY->LogicalMinimum > descY->ScaledValue) ||
|
||||
(controlDescZ->LogicalMaximum < descZ->ScaledValue || controlDescZ->LogicalMinimum > descZ->ScaledValue))
|
||||
{
|
||||
// One of the values is outside of the valid range.
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ref new GazeHidPosition();
|
||||
result->X = descX->ScaledValue;
|
||||
result->Y = descY->ScaledValue;
|
||||
result->Z = descZ->ScaledValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#pragma endregion GazeHidPositionParser
|
||||
|
||||
#pragma region GazeHidRotationParser
|
||||
GazeHidRotationParser::GazeHidRotationParser(GazeDevicePreview ^ gazeDevice, uint16 usage)
|
||||
{
|
||||
_usage = usage;
|
||||
|
||||
// Find all the rotation usages from the device's
|
||||
// descriptor and store them for easy access
|
||||
_X = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_RotationX, _usage);
|
||||
_Y = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_RotationY, _usage);
|
||||
_Z = GetGazeUsageFromCollectionId(gazeDevice, (USHORT)GazeHidUsages::Usage_RotationZ, _usage);
|
||||
}
|
||||
|
||||
GazeHidPosition^ GazeHidRotationParser::GetRotation(HidInputReport ^ report)
|
||||
{
|
||||
GazeHidPosition^ result = nullptr;
|
||||
|
||||
if (_X != nullptr &&
|
||||
_Y != nullptr &&
|
||||
_Z != nullptr &&
|
||||
_usage != 0x0000)
|
||||
{
|
||||
auto descX = report->GetNumericControlByDescription(_X);
|
||||
auto descY = report->GetNumericControlByDescription(_Y);
|
||||
auto descZ = report->GetNumericControlByDescription(_Z);
|
||||
|
||||
auto controlDescX = descX->ControlDescription;
|
||||
auto controlDescY = descY->ControlDescription;
|
||||
auto controlDescZ = descZ->ControlDescription;
|
||||
|
||||
if ((controlDescX->LogicalMaximum < descX->ScaledValue || controlDescX->LogicalMinimum > descX->ScaledValue) ||
|
||||
(controlDescY->LogicalMaximum < descY->ScaledValue || controlDescY->LogicalMinimum > descY->ScaledValue) ||
|
||||
(controlDescZ->LogicalMaximum < descZ->ScaledValue || controlDescZ->LogicalMinimum > descZ->ScaledValue))
|
||||
{
|
||||
// One of the values is outside of the valid range.
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ref new GazeHidPosition();
|
||||
result->X = descX->ScaledValue;
|
||||
result->Y = descY->ScaledValue;
|
||||
result->Z = descZ->ScaledValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#pragma endregion GazeHidRotationParser
|
||||
|
||||
#pragma region GazeHidPositionsParser
|
||||
GazeHidPositionsParser::GazeHidPositionsParser(GazeDevicePreview ^ gazeDevice)
|
||||
{
|
||||
_leftEyePositionParser = ref new GazeHidPositionParser(gazeDevice, (USHORT)GazeHidUsages::Usage_LeftEyePosition);
|
||||
_rightEyePositionParser = ref new GazeHidPositionParser(gazeDevice, (USHORT)GazeHidUsages::Usage_RightEyePosition);
|
||||
_headPositionParser = ref new GazeHidPositionParser(gazeDevice, (USHORT)GazeHidUsages::Usage_HeadPosition);
|
||||
_headRotationParser = ref new GazeHidRotationParser(gazeDevice, (USHORT)GazeHidUsages::Usage_HeadDirectionPoint);
|
||||
}
|
||||
|
||||
GazeHidPositions ^ GazeHidPositionsParser::GetGazeHidPositions(HidInputReport ^ report)
|
||||
{
|
||||
auto retval = ref new GazeHidPositions();
|
||||
|
||||
retval->LeftEyePosition = _leftEyePositionParser->GetPosition(report);
|
||||
retval->RightEyePosition = _rightEyePositionParser->GetPosition(report);
|
||||
retval->HeadPosition = _headPositionParser->GetPosition(report);
|
||||
retval->HeadRotation = _headRotationParser->GetRotation(report);
|
||||
|
||||
return retval;
|
||||
}
|
||||
#pragma endregion GazeHidPositionsParser
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,74 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GazeHidUsages.h"
|
||||
|
||||
using namespace Windows::Devices::HumanInterfaceDevice;
|
||||
using namespace Windows::Devices::Input::Preview;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
namespace GazeHidParsers {
|
||||
public ref class GazeHidPosition sealed
|
||||
{
|
||||
public:
|
||||
property long long X;
|
||||
property long long Y;
|
||||
property long long Z;
|
||||
};
|
||||
|
||||
public ref class GazeHidPositions sealed
|
||||
{
|
||||
public:
|
||||
property GazeHidPosition^ LeftEyePosition;
|
||||
property GazeHidPosition^ RightEyePosition;
|
||||
property GazeHidPosition^ HeadPosition;
|
||||
property GazeHidPosition^ HeadRotation;
|
||||
};
|
||||
|
||||
public ref class GazeHidPositionParser sealed
|
||||
{
|
||||
public:
|
||||
GazeHidPositionParser(GazeDevicePreview ^ gazeDevice, uint16 usage);
|
||||
|
||||
GazeHidPosition^ GetPosition(HidInputReport ^ report);
|
||||
|
||||
private:
|
||||
HidNumericControlDescription ^ _X = nullptr;
|
||||
HidNumericControlDescription ^ _Y = nullptr;
|
||||
HidNumericControlDescription ^ _Z = nullptr;
|
||||
uint16 _usage = 0x0000;
|
||||
};
|
||||
|
||||
public ref class GazeHidRotationParser sealed
|
||||
{
|
||||
public:
|
||||
GazeHidRotationParser(GazeDevicePreview ^ gazeDevice, uint16 usage);
|
||||
|
||||
GazeHidPosition^ GetRotation(HidInputReport^ report);
|
||||
|
||||
private:
|
||||
HidNumericControlDescription ^ _X = nullptr;
|
||||
HidNumericControlDescription ^ _Y = nullptr;
|
||||
HidNumericControlDescription ^ _Z = nullptr;
|
||||
uint16 _usage = 0x0000;
|
||||
};
|
||||
|
||||
public ref class GazeHidPositionsParser sealed
|
||||
{
|
||||
public:
|
||||
GazeHidPositionsParser(GazeDevicePreview ^ gazeDevice);
|
||||
|
||||
GazeHidPositions^ GetGazeHidPositions(HidInputReport ^ report);
|
||||
|
||||
private:
|
||||
GazeHidPositionParser ^ _leftEyePositionParser;
|
||||
GazeHidPositionParser ^ _rightEyePositionParser;
|
||||
GazeHidPositionParser ^ _headPositionParser;
|
||||
GazeHidRotationParser ^ _headRotationParser;
|
||||
};
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,34 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Devices.HumanInterfaceDevice;
|
||||
using Windows.Devices.Input.Preview;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
internal static class GazeHidParsersHelpers
|
||||
{
|
||||
public static HidNumericControlDescription GetGazeUsageFromCollectionId(
|
||||
GazeDevicePreview gazeDevice,
|
||||
ushort childUsageId,
|
||||
ushort parentUsageId)
|
||||
{
|
||||
var numericControls = gazeDevice.GetNumericControlDescriptions(
|
||||
(ushort)GazeHidUsages.UsagePage_EyeHeadTracker, childUsageId);
|
||||
|
||||
for (int i = 0; i < numericControls.Count; i++)
|
||||
{
|
||||
var parentCollections = numericControls[i].ParentCollections;
|
||||
if (parentCollections.Count > 0 &&
|
||||
parentCollections[0].UsagePage == (ushort)GazeHidUsages.UsagePage_EyeHeadTracker &&
|
||||
parentCollections[0].UsageId == parentUsageId)
|
||||
{
|
||||
return numericControls[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents one Hid position
|
||||
/// </summary>
|
||||
public class GazeHidPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the X axis of this position
|
||||
/// </summary>
|
||||
public long X { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Y axis of this position
|
||||
/// </summary>
|
||||
public long Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Z axis of this position
|
||||
/// </summary>
|
||||
public long Z { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Devices.HumanInterfaceDevice;
|
||||
using Windows.Devices.Input.Preview;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Hid Position Parser
|
||||
/// </summary>
|
||||
public class GazeHidPositionParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GazeHidPositionParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gazeDevice"><see cref="GazeDevicePreview"/> used to parse.</param>
|
||||
/// <param name="usage">The <see cref="GazeHidUsages"/> used to parse.</param>
|
||||
public GazeHidPositionParser(GazeDevicePreview gazeDevice, ushort usage)
|
||||
{
|
||||
_usage = usage;
|
||||
|
||||
// Find all the position usages from the device's
|
||||
// descriptor and store them for easy access
|
||||
_x = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_PositionX, _usage);
|
||||
_y = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_PositionY, _usage);
|
||||
_z = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_PositionZ, _usage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the position from the report.
|
||||
/// </summary>
|
||||
/// <param name="report">A <see cref="HidInputReport"/> object used on the parsing.</param>
|
||||
/// <returns>The parsed <see cref="GazeHidPositions"/> from the report.</returns>
|
||||
public GazeHidPosition GetPosition(HidInputReport report)
|
||||
{
|
||||
GazeHidPosition result = null;
|
||||
|
||||
if (_x != null &&
|
||||
_y != null &&
|
||||
_z != null &&
|
||||
_usage != 0x0000)
|
||||
{
|
||||
var descX = report.GetNumericControlByDescription(_x);
|
||||
var descY = report.GetNumericControlByDescription(_y);
|
||||
var descZ = report.GetNumericControlByDescription(_z);
|
||||
|
||||
var controlDescX = descX.ControlDescription;
|
||||
var controlDescY = descY.ControlDescription;
|
||||
var controlDescZ = descZ.ControlDescription;
|
||||
|
||||
if ((controlDescX.LogicalMaximum < descX.ScaledValue || controlDescX.LogicalMinimum > descX.ScaledValue) ||
|
||||
(controlDescY.LogicalMaximum < descY.ScaledValue || controlDescY.LogicalMinimum > descY.ScaledValue) ||
|
||||
(controlDescZ.LogicalMaximum < descZ.ScaledValue || controlDescZ.LogicalMinimum > descZ.ScaledValue))
|
||||
{
|
||||
// One of the values is outside of the valid range.
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new GazeHidPosition
|
||||
{
|
||||
X = descX.ScaledValue,
|
||||
Y = descY.ScaledValue,
|
||||
Z = descZ.ScaledValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private readonly HidNumericControlDescription _x = null;
|
||||
private readonly HidNumericControlDescription _y = null;
|
||||
private readonly HidNumericControlDescription _z = null;
|
||||
private readonly ushort _usage = 0x0000;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Hid positions
|
||||
/// </summary>
|
||||
public class GazeHidPositions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the left eye position
|
||||
/// </summary>
|
||||
public GazeHidPosition LeftEyePosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the right eye position
|
||||
/// </summary>
|
||||
public GazeHidPosition RightEyePosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the head position
|
||||
/// </summary>
|
||||
public GazeHidPosition HeadPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the head rotation
|
||||
/// </summary>
|
||||
public GazeHidPosition HeadRotation { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Devices.HumanInterfaceDevice;
|
||||
using Windows.Devices.Input.Preview;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Hid Positions Parser
|
||||
/// </summary>
|
||||
public class GazeHidPositionsParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GazeHidPositionsParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gazeDevice"><see cref="GazeDevicePreview"/> used to parse.</param>
|
||||
public GazeHidPositionsParser(GazeDevicePreview gazeDevice)
|
||||
{
|
||||
_leftEyePositionParser = new GazeHidPositionParser(gazeDevice, (ushort)GazeHidUsages.Usage_LeftEyePosition);
|
||||
_rightEyePositionParser = new GazeHidPositionParser(gazeDevice, (ushort)GazeHidUsages.Usage_RightEyePosition);
|
||||
_headPositionParser = new GazeHidPositionParser(gazeDevice, (ushort)GazeHidUsages.Usage_HeadPosition);
|
||||
_headRotationParser = new GazeHidRotationParser(gazeDevice, (ushort)GazeHidUsages.Usage_HeadDirectionPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the positions from the report.
|
||||
/// </summary>
|
||||
/// <param name="report">A <see cref="HidInputReport"/> object used on the parsing.</param>
|
||||
/// <returns>The parsed <see cref="GazeHidPositions"/> from the report.</returns>
|
||||
public GazeHidPositions GetGazeHidPositions(HidInputReport report)
|
||||
{
|
||||
return new GazeHidPositions
|
||||
{
|
||||
LeftEyePosition = this._leftEyePositionParser.GetPosition(report),
|
||||
RightEyePosition = this._rightEyePositionParser.GetPosition(report),
|
||||
HeadPosition = this._headPositionParser.GetPosition(report),
|
||||
HeadRotation = this._headRotationParser.GetRotation(report)
|
||||
};
|
||||
}
|
||||
|
||||
private readonly GazeHidPositionParser _leftEyePositionParser;
|
||||
private readonly GazeHidPositionParser _rightEyePositionParser;
|
||||
private readonly GazeHidPositionParser _headPositionParser;
|
||||
private readonly GazeHidRotationParser _headRotationParser;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Devices.HumanInterfaceDevice;
|
||||
using Windows.Devices.Input.Preview;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction.GazeHidParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Hid Rotation Parser
|
||||
/// </summary>
|
||||
public class GazeHidRotationParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GazeHidRotationParser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gazeDevice"><see cref="GazeDevicePreview"/> used to parse.</param>
|
||||
/// <param name="usage">The <see cref="GazeHidUsages"/> used to parse.</param>
|
||||
public GazeHidRotationParser(GazeDevicePreview gazeDevice, ushort usage)
|
||||
{
|
||||
_usage = usage;
|
||||
|
||||
// Find all the rotation usages from the device's
|
||||
// descriptor and store them for easy access
|
||||
_x = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_RotationX, _usage);
|
||||
_y = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_RotationY, _usage);
|
||||
_z = GazeHidParsersHelpers.GetGazeUsageFromCollectionId(gazeDevice, (ushort)GazeHidUsages.Usage_RotationZ, _usage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the rotation from the report.
|
||||
/// </summary>
|
||||
/// <param name="report">A <see cref="HidInputReport"/> object used on the parsing.</param>
|
||||
/// <returns>The parsed <see cref="GazeHidPosition"/> from the report.</returns>
|
||||
public GazeHidPosition GetRotation(HidInputReport report)
|
||||
{
|
||||
GazeHidPosition result = null;
|
||||
|
||||
if (_x != null &&
|
||||
_y != null &&
|
||||
_z != null &&
|
||||
_usage != 0x0000)
|
||||
{
|
||||
var descX = report.GetNumericControlByDescription(_x);
|
||||
var descY = report.GetNumericControlByDescription(_y);
|
||||
var descZ = report.GetNumericControlByDescription(_z);
|
||||
|
||||
var controlDescX = descX.ControlDescription;
|
||||
var controlDescY = descY.ControlDescription;
|
||||
var controlDescZ = descZ.ControlDescription;
|
||||
|
||||
if ((controlDescX.LogicalMaximum < descX.ScaledValue || controlDescX.LogicalMinimum > descX.ScaledValue) ||
|
||||
(controlDescY.LogicalMaximum < descY.ScaledValue || controlDescY.LogicalMinimum > descY.ScaledValue) ||
|
||||
(controlDescZ.LogicalMaximum < descZ.ScaledValue || controlDescZ.LogicalMinimum > descZ.ScaledValue))
|
||||
{
|
||||
// One of the values is outside of the valid range.
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new GazeHidPosition
|
||||
{
|
||||
X = descX.ScaledValue,
|
||||
Y = descY.ScaledValue,
|
||||
Z = descZ.ScaledValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private readonly HidNumericControlDescription _x = null;
|
||||
private readonly HidNumericControlDescription _y = null;
|
||||
private readonly HidNumericControlDescription _z = null;
|
||||
private readonly ushort _usage = 0x0000;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum specifies the various HID usages specified by the EyeHeadTracker HID specification
|
||||
///
|
||||
/// https://www.usb.org/sites/default/files/hutrr74_-_usage_page_for_head_and_eye_trackers_0.pdf
|
||||
/// </summary>
|
||||
public enum GazeHidUsages
|
||||
{
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
UsagePage_EyeHeadTracker = 0x0012,
|
||||
Usage_EyeTracker = 0x0001,
|
||||
Usage_HeadTracker = 0x0002,
|
||||
|
||||
// 0x0003-0x000F RESERVED
|
||||
Usage_TrackingData = 0x0010,
|
||||
Usage_Capabilities = 0x0011,
|
||||
Usage_Configuration = 0x0012,
|
||||
Usage_Status = 0x0013,
|
||||
Usage_Control = 0x0014,
|
||||
|
||||
// 0x0015-0x001F RESERVED
|
||||
Usage_Timestamp = 0x0020,
|
||||
Usage_PositionX = 0x0021,
|
||||
Usage_PositionY = 0x0022,
|
||||
Usage_PositionZ = 0x0023,
|
||||
Usage_GazePoint = 0x0024,
|
||||
Usage_LeftEyePosition = 0x0025,
|
||||
Usage_RightEyePosition = 0x0026,
|
||||
Usage_HeadPosition = 0x0027,
|
||||
Usage_HeadDirectionPoint = 0x0028,
|
||||
Usage_RotationX = 0x0029,
|
||||
Usage_RotationY = 0x002A,
|
||||
Usage_RotationZ = 0x002B,
|
||||
|
||||
// 0x002C-0x00FF RESERVED
|
||||
Usage_TrackerQuality = 0x0100,
|
||||
Usage_MinimumTrackingDistance = 0x0101,
|
||||
Usage_OptimumTrackingDistance = 0x0102,
|
||||
Usage_MaximumTrackingDistance = 0x0103,
|
||||
Usage_MaximumScreenPlaneWidth = 0x0104,
|
||||
Usage_MaximumScreenPlaneHeight = 0x0105,
|
||||
|
||||
// 0x0106-0x01FF RESERVED
|
||||
Usage_DisplayManufacturerId = 0x0200,
|
||||
Usage_DisplayProductId = 0x0201,
|
||||
Usage_DisplaySerialNumber = 0x0202,
|
||||
Usage_DisplayManufacturerDate = 0x0203,
|
||||
Usage_CalibratedScreenWidth = 0x0204,
|
||||
Usage_CalibratedScreenHeight = 0x0205,
|
||||
|
||||
// 0x0206-0x02FF RESERVED
|
||||
Usage_SamplingFrequency = 0x0300,
|
||||
Usage_ConfigurationStatus = 0x0301,
|
||||
|
||||
// 0x0302-0x03FF RESERVED
|
||||
Usage_DeviceModeRequest = 0x0400,
|
||||
|
||||
// 0x0401-0xFFFF RESERVED
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This enum specifies the various HID usages specified by the EyeHeadTracker HID specification
|
||||
///
|
||||
/// https://www.usb.org/sites/default/files/hutrr74_-_usage_page_for_head_and_eye_trackers_0.pdf
|
||||
/// </summary>
|
||||
public enum class GazeHidUsages
|
||||
{
|
||||
UsagePage_EyeHeadTracker = 0x0012,
|
||||
Usage_EyeTracker = 0x0001,
|
||||
Usage_HeadTracker = 0x0002,
|
||||
// 0x0003-0x000F RESERVED
|
||||
Usage_TrackingData = 0x0010,
|
||||
Usage_Capabilities = 0x0011,
|
||||
Usage_Configuration = 0x0012,
|
||||
Usage_Status = 0x0013,
|
||||
Usage_Control = 0x0014,
|
||||
// 0x0015-0x001F RESERVED
|
||||
Usage_Timestamp = 0x0020,
|
||||
Usage_PositionX = 0x0021,
|
||||
Usage_PositionY = 0x0022,
|
||||
Usage_PositionZ = 0x0023,
|
||||
Usage_GazePoint = 0x0024,
|
||||
Usage_LeftEyePosition = 0x0025,
|
||||
Usage_RightEyePosition = 0x0026,
|
||||
Usage_HeadPosition = 0x0027,
|
||||
Usage_HeadDirectionPoint = 0x0028,
|
||||
Usage_RotationX = 0x0029,
|
||||
Usage_RotationY = 0x002A,
|
||||
Usage_RotationZ = 0x002B,
|
||||
// 0x002C-0x00FF RESERVED
|
||||
Usage_TrackerQuality = 0x0100,
|
||||
Usage_MinimumTrackingDistance = 0x0101,
|
||||
Usage_OptimumTrackingDistance = 0x0102,
|
||||
Usage_MaximumTrackingDistance = 0x0103,
|
||||
Usage_MaximumScreenPlaneWidth = 0x0104,
|
||||
Usage_MaximumScreenPlaneHeight = 0x0105,
|
||||
// 0x0106-0x01FF RESERVED
|
||||
Usage_DisplayManufacturerId = 0x0200,
|
||||
Usage_DisplayProductId = 0x0201,
|
||||
Usage_DisplaySerialNumber = 0x0202,
|
||||
Usage_DisplayManufacturerDate = 0x0203,
|
||||
Usage_CalibratedScreenWidth = 0x0204,
|
||||
Usage_CalibratedScreenHeight = 0x0205,
|
||||
// 0x0206-0x02FF RESERVED
|
||||
Usage_SamplingFrequency = 0x0300,
|
||||
Usage_ConfigurationStatus = 0x0301,
|
||||
// 0x0302-0x03FF RESERVED
|
||||
Usage_DeviceModeRequest = 0x0400,
|
||||
// 0x0401-0xFFFF RESERVED
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,17 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal struct GazeHistoryItem
|
||||
{
|
||||
public GazeTargetItem HitTarget { get; set; }
|
||||
|
||||
public TimeSpan Timestamp { get; set; }
|
||||
|
||||
public TimeSpan Duration { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
ref class GazeTargetItem;
|
||||
|
||||
private ref struct GazeHistoryItem
|
||||
{
|
||||
property GazeTargetItem^ HitTarget;
|
||||
property TimeSpan Timestamp;
|
||||
property TimeSpan Duration;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,187 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeInput.h"
|
||||
|
||||
#include "GazeElement.h"
|
||||
#include "GazePointer.h"
|
||||
#include "GazePointerProxy.h"
|
||||
#include "GazeTargetItem.h"
|
||||
|
||||
using namespace Platform;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
Brush^ GazeInput::DwellFeedbackEnterBrush::get()
|
||||
{
|
||||
return GazePointer::Instance->_enterBrush;
|
||||
}
|
||||
|
||||
void GazeInput::DwellFeedbackEnterBrush::set(Brush^ value)
|
||||
{
|
||||
GazePointer::Instance->_enterBrush = value;
|
||||
}
|
||||
|
||||
Brush^ GazeInput::DwellFeedbackProgressBrush::get()
|
||||
{
|
||||
return GazePointer::Instance->_progressBrush;
|
||||
}
|
||||
|
||||
void GazeInput::DwellFeedbackProgressBrush::set(Brush^ value)
|
||||
{
|
||||
GazePointer::Instance->_progressBrush = value;
|
||||
}
|
||||
|
||||
Brush^ GazeInput::DwellFeedbackCompleteBrush::get()
|
||||
{
|
||||
return GazePointer::Instance->_completeBrush;
|
||||
}
|
||||
|
||||
void GazeInput::DwellFeedbackCompleteBrush::set(Brush^ value)
|
||||
{
|
||||
GazePointer::Instance->_completeBrush = value;
|
||||
}
|
||||
|
||||
double GazeInput::DwellStrokeThickness::get()
|
||||
{
|
||||
return GazePointer::Instance->_dwellStrokeThickness;
|
||||
}
|
||||
|
||||
void GazeInput::DwellStrokeThickness::set(double value)
|
||||
{
|
||||
GazePointer::Instance->_dwellStrokeThickness = value;
|
||||
}
|
||||
|
||||
Interaction GazeInput::Interaction::get()
|
||||
{
|
||||
return GazePointer::Instance->_interaction;
|
||||
}
|
||||
|
||||
void GazeInput::Interaction::set(GazeInteraction::Interaction value)
|
||||
{
|
||||
if (GazePointer::Instance->_interaction != value)
|
||||
{
|
||||
if (value == GazeInteraction::Interaction::Enabled)
|
||||
{
|
||||
GazePointer::Instance->AddRoot(0);
|
||||
}
|
||||
else if (GazePointer::Instance->_interaction == GazeInteraction::Interaction::Enabled)
|
||||
{
|
||||
GazePointer::Instance->RemoveRoot(0);
|
||||
}
|
||||
|
||||
GazePointer::Instance->_interaction = value;
|
||||
}
|
||||
}
|
||||
|
||||
TimeSpan GazeInput::UnsetTimeSpan = { -1 };
|
||||
|
||||
static void OnInteractionChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args)
|
||||
{
|
||||
auto element = safe_cast<FrameworkElement^>(ob);
|
||||
auto interaction = safe_cast<Interaction>(args->NewValue);
|
||||
GazePointerProxy::SetInteraction(element, interaction);
|
||||
}
|
||||
|
||||
static void OnIsCursorVisibleChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args)
|
||||
{
|
||||
GazePointer::Instance->IsCursorVisible = safe_cast<bool>(args->NewValue);
|
||||
}
|
||||
|
||||
static void OnCursorRadiusChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args)
|
||||
{
|
||||
GazePointer::Instance->CursorRadius = safe_cast<int>(args->NewValue);
|
||||
}
|
||||
|
||||
static void OnIsSwitchEnabledChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args)
|
||||
{
|
||||
GazePointer::Instance->IsSwitchEnabled = safe_cast<bool>(args->NewValue);
|
||||
}
|
||||
|
||||
static DependencyProperty^ s_interactionProperty = DependencyProperty::RegisterAttached("Interaction", Interaction::typeid, GazeInput::typeid,
|
||||
ref new PropertyMetadata(Interaction::Inherited, ref new PropertyChangedCallback(&OnInteractionChanged)));
|
||||
static DependencyProperty^ s_isCursorVisibleProperty = DependencyProperty::RegisterAttached("IsCursorVisible", bool::typeid, GazeInput::typeid,
|
||||
ref new PropertyMetadata(true, ref new PropertyChangedCallback(&OnIsCursorVisibleChanged)));
|
||||
static DependencyProperty^ s_cursorRadiusProperty = DependencyProperty::RegisterAttached("CursorRadius", int::typeid, GazeInput::typeid,
|
||||
ref new PropertyMetadata(6, ref new PropertyChangedCallback(&OnCursorRadiusChanged)));
|
||||
static DependencyProperty^ s_gazeElementProperty = DependencyProperty::RegisterAttached("GazeElement", GazeElement::typeid, GazeInput::typeid, ref new PropertyMetadata(nullptr));
|
||||
static DependencyProperty^ s_fixationDurationProperty = DependencyProperty::RegisterAttached("FixationDuration", TimeSpan::typeid, GazeInput::typeid, ref new PropertyMetadata(GazeInput::UnsetTimeSpan));
|
||||
static DependencyProperty^ s_dwellDurationProperty = DependencyProperty::RegisterAttached("DwellDuration", TimeSpan::typeid, GazeInput::typeid, ref new PropertyMetadata(GazeInput::UnsetTimeSpan));
|
||||
static DependencyProperty^ s_repeatDelayDurationProperty = DependencyProperty::RegisterAttached("RepeatDelayDuration", TimeSpan::typeid, GazeInput::typeid, ref new PropertyMetadata(GazeInput::UnsetTimeSpan));
|
||||
static DependencyProperty^ s_dwellRepeatDurationProperty = DependencyProperty::RegisterAttached("DwellRepeatDuration", TimeSpan::typeid, GazeInput::typeid, ref new PropertyMetadata(GazeInput::UnsetTimeSpan));
|
||||
static DependencyProperty^ s_thresholdDurationProperty = DependencyProperty::RegisterAttached("ThresholdDuration", TimeSpan::typeid, GazeInput::typeid, ref new PropertyMetadata(GazeInput::UnsetTimeSpan));
|
||||
static DependencyProperty^ s_maxRepeatCountProperty = DependencyProperty::RegisterAttached("MaxDwellRepeatCount", int::typeid, GazeInput::typeid, ref new PropertyMetadata(safe_cast<Object^>(0)));
|
||||
static DependencyProperty^ s_isSwitchEnabledProperty = DependencyProperty::RegisterAttached("IsSwitchEnabled", bool::typeid, GazeInput::typeid,
|
||||
ref new PropertyMetadata(false, ref new PropertyChangedCallback(&OnIsSwitchEnabledChanged)));
|
||||
|
||||
DependencyProperty^ GazeInput::InteractionProperty::get() { return s_interactionProperty; }
|
||||
DependencyProperty^ GazeInput::IsCursorVisibleProperty::get() { return s_isCursorVisibleProperty; }
|
||||
DependencyProperty^ GazeInput::CursorRadiusProperty::get() { return s_cursorRadiusProperty; }
|
||||
DependencyProperty^ GazeInput::GazeElementProperty::get() { return s_gazeElementProperty; }
|
||||
DependencyProperty^ GazeInput::FixationDurationProperty::get() { return s_fixationDurationProperty; }
|
||||
DependencyProperty^ GazeInput::DwellDurationProperty::get() { return s_dwellDurationProperty; }
|
||||
DependencyProperty^ GazeInput::RepeatDelayDurationProperty::get() { return s_repeatDelayDurationProperty; }
|
||||
DependencyProperty^ GazeInput::DwellRepeatDurationProperty::get() { return s_dwellRepeatDurationProperty; }
|
||||
DependencyProperty^ GazeInput::ThresholdDurationProperty::get() { return s_thresholdDurationProperty; }
|
||||
DependencyProperty^ GazeInput::MaxDwellRepeatCountProperty::get() { return s_maxRepeatCountProperty; }
|
||||
DependencyProperty^ GazeInput::IsSwitchEnabledProperty::get() { return s_isSwitchEnabledProperty; }
|
||||
|
||||
Interaction GazeInput::GetInteraction(UIElement^ element) { return safe_cast<GazeInteraction::Interaction>(element->GetValue(s_interactionProperty)); }
|
||||
bool GazeInput::GetIsCursorVisible(UIElement^ element) { return safe_cast<bool>(element->GetValue(s_isCursorVisibleProperty)); }
|
||||
int GazeInput::GetCursorRadius(UIElement^ element) { return safe_cast<int>(element->GetValue(s_cursorRadiusProperty)); }
|
||||
GazeElement^ GazeInput::GetGazeElement(UIElement^ element) { return safe_cast<GazeElement^>(element->GetValue(s_gazeElementProperty)); }
|
||||
TimeSpan GazeInput::GetFixationDuration(UIElement^ element) { return safe_cast<TimeSpan>(element->GetValue(s_fixationDurationProperty)); }
|
||||
TimeSpan GazeInput::GetDwellDuration(UIElement^ element) { return safe_cast<TimeSpan>(element->GetValue(s_dwellDurationProperty)); }
|
||||
TimeSpan GazeInput::GetRepeatDelayDuration(UIElement^ element) { return safe_cast<TimeSpan>(element->GetValue(s_repeatDelayDurationProperty)); }
|
||||
TimeSpan GazeInput::GetDwellRepeatDuration(UIElement^ element) { return safe_cast<TimeSpan>(element->GetValue(s_dwellRepeatDurationProperty)); }
|
||||
TimeSpan GazeInput::GetThresholdDuration(UIElement^ element) { return safe_cast<TimeSpan>(element->GetValue(s_thresholdDurationProperty)); }
|
||||
int GazeInput::GetMaxDwellRepeatCount(UIElement^ element) { return safe_cast<int>(element->GetValue(s_maxRepeatCountProperty)); }
|
||||
bool GazeInput::GetIsSwitchEnabled(UIElement^ element) { return safe_cast<bool>(element->GetValue(s_isSwitchEnabledProperty)); }
|
||||
|
||||
void GazeInput::SetInteraction(UIElement^ element, GazeInteraction::Interaction value) { element->SetValue(s_interactionProperty, value); }
|
||||
void GazeInput::SetIsCursorVisible(UIElement^ element, bool value) { element->SetValue(s_isCursorVisibleProperty, value); }
|
||||
void GazeInput::SetCursorRadius(UIElement^ element, int value) { element->SetValue(s_cursorRadiusProperty, value); }
|
||||
void GazeInput::SetGazeElement(UIElement^ element, GazeElement^ value) { element->SetValue(s_gazeElementProperty, value); }
|
||||
void GazeInput::SetFixationDuration(UIElement^ element, TimeSpan span) { element->SetValue(s_fixationDurationProperty, span); }
|
||||
void GazeInput::SetDwellDuration(UIElement^ element, TimeSpan span) { element->SetValue(s_dwellDurationProperty, span); }
|
||||
void GazeInput::SetRepeatDelayDuration(UIElement^ element, TimeSpan span) { element->SetValue(s_repeatDelayDurationProperty, span); }
|
||||
void GazeInput::SetDwellRepeatDuration(UIElement^ element, TimeSpan span) { element->SetValue(s_dwellRepeatDurationProperty, span); }
|
||||
void GazeInput::SetThresholdDuration(UIElement^ element, TimeSpan span) { element->SetValue(s_thresholdDurationProperty, span); }
|
||||
void GazeInput::SetMaxDwellRepeatCount(UIElement^ element, int value) { element->SetValue(s_maxRepeatCountProperty, value); }
|
||||
void GazeInput::SetIsSwitchEnabled(UIElement^ element, bool value) { element->SetValue(s_isSwitchEnabledProperty, value); }
|
||||
|
||||
GazePointer^ GazeInput::GetGazePointer(Page^ page)
|
||||
{
|
||||
return GazePointer::Instance;
|
||||
}
|
||||
|
||||
void GazeInput::Invoke(UIElement^ element)
|
||||
{
|
||||
auto item = GazeTargetItem::GetOrCreate(element);
|
||||
item->Invoke();
|
||||
}
|
||||
|
||||
void GazeInput::LoadSettings(ValueSet^ settings)
|
||||
{
|
||||
GazePointer::Instance->LoadSettings(settings);
|
||||
}
|
||||
|
||||
bool GazeInput::IsDeviceAvailable::get()
|
||||
{
|
||||
return GazePointer::Instance->IsDeviceAvailable;
|
||||
}
|
||||
|
||||
EventRegistrationToken GazeInput::IsDeviceAvailableChanged::add(EventHandler<Object^>^ handler)
|
||||
{
|
||||
return GazePointer::Instance->IsDeviceAvailableChanged += handler;
|
||||
}
|
||||
|
||||
void GazeInput::IsDeviceAvailableChanged::remove(EventRegistrationToken token)
|
||||
{
|
||||
GazePointer::Instance->IsDeviceAvailableChanged -= token;
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,432 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class primarily providing access to attached properties controlling gaze behavior.
|
||||
/// </summary>
|
||||
[Windows.Foundation.Metadata.WebHostHidden]
|
||||
public class GazeInput
|
||||
{
|
||||
internal static readonly TimeSpan UnsetTimeSpan = new TimeSpan(-1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Interaction dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty InteractionProperty { get; } = DependencyProperty.RegisterAttached("Interaction", typeof(Interaction), typeof(GazeInput), new PropertyMetadata(Interaction.Inherited, new PropertyChangedCallback(OnInteractionChanged)));
|
||||
|
||||
private static void OnInteractionChanged(DependencyObject ob, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var element = ob as FrameworkElement;
|
||||
var interaction = (Interaction)args.NewValue;
|
||||
GazePointerProxy.SetInteraction(element, interaction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IsCursorVisible dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty IsCursorVisibleProperty { get; } = DependencyProperty.RegisterAttached("IsCursorVisible", typeof(bool), typeof(GazeInput), new PropertyMetadata(true, new PropertyChangedCallback(OnIsCursorVisibleChanged)));
|
||||
|
||||
private static void OnIsCursorVisibleChanged(DependencyObject ob, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
GazePointer.Instance.IsCursorVisible = (bool)args.NewValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CursorRadius dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty CursorRadiusProperty { get; } = DependencyProperty.RegisterAttached("CursorRadius", typeof(int), typeof(GazeInput), new PropertyMetadata(6, new PropertyChangedCallback(OnCursorRadiusChanged)));
|
||||
|
||||
private static void OnCursorRadiusChanged(DependencyObject ob, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
GazePointer.Instance.CursorRadius = (int)args.NewValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GazeElement dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty GazeElementProperty { get; } = DependencyProperty.RegisterAttached("GazeElement", typeof(GazeElement), typeof(GazeInput), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FixationDuration dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty FixationDurationProperty { get; } = DependencyProperty.RegisterAttached("FixationDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DwellDuration dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty DwellDurationProperty { get; } = DependencyProperty.RegisterAttached("DwellDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RepeatDelayDuration dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty RepeatDelayDurationProperty { get; } = DependencyProperty.RegisterAttached("RepeatDelayDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DwellRepeatDuration dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty DwellRepeatDurationProperty { get; } = DependencyProperty.RegisterAttached("DwellRepeatDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ThresholdDuration dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty ThresholdDurationProperty { get; } = DependencyProperty.RegisterAttached("ThresholdDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MaxDwellRepeatCount dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty MaxDwellRepeatCountProperty { get; } = DependencyProperty.RegisterAttached("MaxDwellRepeatCount", typeof(int), typeof(GazeInput), new PropertyMetadata(0));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IsSwitchEnabled dependency property
|
||||
/// </summary>
|
||||
public static DependencyProperty IsSwitchEnabledProperty { get; } = DependencyProperty.RegisterAttached("IsSwitchEnabled", typeof(bool), typeof(GazeInput), new PropertyMetadata(false, new PropertyChangedCallback(OnIsSwitchEnabledChanged)));
|
||||
|
||||
private static void OnIsSwitchEnabledChanged(DependencyObject ob, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
GazePointer.Instance.IsSwitchEnabled = (bool)args.NewValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default indication that gaze entered a control
|
||||
/// </summary>
|
||||
public static Brush DwellFeedbackEnterBrush
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.EnterBrush;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
GazePointer.Instance.EnterBrush = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default animation for dwell press
|
||||
/// </summary>
|
||||
public static Brush DwellFeedbackProgressBrush
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.ProgressBrush;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
GazePointer.Instance.ProgressBrush = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default animation for dwell complete
|
||||
/// </summary>
|
||||
public static Brush DwellFeedbackCompleteBrush
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.CompleteBrush;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
GazePointer.Instance.CompleteBrush = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thickness of the lines animated for dwell.
|
||||
/// </summary>
|
||||
public static double DwellStrokeThickness
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.DwellStrokeThickness;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
GazePointer.Instance.DwellStrokeThickness = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the interaction default
|
||||
/// </summary>
|
||||
public static Interaction Interaction
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.Interaction;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (GazePointer.Instance.Interaction != value)
|
||||
{
|
||||
if (value == Interaction.Enabled)
|
||||
{
|
||||
GazePointer.Instance.AddRoot(0);
|
||||
}
|
||||
else if (GazePointer.Instance.Interaction == Interaction.Enabled)
|
||||
{
|
||||
GazePointer.Instance.RemoveRoot(0);
|
||||
}
|
||||
|
||||
GazePointer.Instance.Interaction = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of gaze interaction over that particular XAML element.
|
||||
/// </summary>
|
||||
/// <returns>The status of gaze interaction over that particular XAML element.</returns>
|
||||
public static Interaction GetInteraction(UIElement element)
|
||||
{
|
||||
return (Interaction)element.GetValue(InteractionProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether cursor is shown while user is looking at the school.
|
||||
/// </summary>
|
||||
/// <returns>True the cursor is shown while user is looking at the school; otherwise, false.</returns>
|
||||
public static bool GetIsCursorVisible(UIElement element)
|
||||
{
|
||||
return (bool)element.GetValue(IsCursorVisibleProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the gaze cursor radius.
|
||||
/// </summary>
|
||||
/// <returns>The size of the gaze cursor radius.</returns>
|
||||
public static int GetCursorRadius(UIElement element)
|
||||
{
|
||||
return (int)element.GetValue(CursorRadiusProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GazeElement associated with an UIElement.
|
||||
/// </summary>
|
||||
/// <returns>The GazeElement associated with an UIElement.</returns>
|
||||
public static GazeElement GetGazeElement(UIElement element)
|
||||
{
|
||||
return (GazeElement)element.GetValue(GazeElementProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration for the control to transition from the Enter state to the Fixation state. At this point, a StateChanged event is fired with PointerState set to Fixation. This event should be used to control the earliest visual feedback the application needs to provide to the user about the gaze location. The default is 350ms.
|
||||
/// </summary>
|
||||
/// <returns>Duration for the control to transition from the Enter state to the Fixation state.</returns>
|
||||
public static TimeSpan GetFixationDuration(UIElement element)
|
||||
{
|
||||
return (TimeSpan)element.GetValue(FixationDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it.
|
||||
/// </summary>
|
||||
/// <returns>The duration for the control to transition from the Fixation state to the Dwell state.</returns>
|
||||
public static TimeSpan GetDwellDuration(UIElement element)
|
||||
{
|
||||
return (TimeSpan)element.GetValue(DwellDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional duration for the first repeat to occur. This prevents inadvertent repeated invocation.
|
||||
/// </summary>
|
||||
/// <returns>The additional duration for the first repeat to occur.</returns>
|
||||
public static TimeSpan GetRepeatDelayDuration(UIElement element)
|
||||
{
|
||||
return (TimeSpan)element.GetValue(RepeatDelayDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration of repeated dwell invocations, should the user continue to dwell on the control. The first repeat will occur after an additional delay specified by RepeatDelayDuration. Subsequent repeats happen after every period of DwellRepeatDuration. A control is invoked repeatedly only if MaxDwellRepeatCount is set to greater than zero.
|
||||
/// </summary>
|
||||
/// <returns>The duration of repeated dwell invocations.</returns>
|
||||
public static TimeSpan GetDwellRepeatDuration(UIElement element)
|
||||
{
|
||||
return (TimeSpan)element.GetValue(DwellRepeatDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration that controls when the PointerState moves to either the Enter state or the Exit state. When this duration has elapsed after the user's gaze first enters a control, the PointerState is set to Enter. And when this duration has elapsed after the user's gaze has left the control, the PointerState is set to Exit. In both cases, a StateChanged event is fired. The default is 50ms.
|
||||
/// </summary>
|
||||
/// <returns>The duration that controls when the PointerState moves to either the Enter state or the Exit state.</returns>
|
||||
public static TimeSpan GetThresholdDuration(UIElement element)
|
||||
{
|
||||
return (TimeSpan)element.GetValue(ThresholdDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control. The default value is zero which disables repeated invocation of a control. Developers can set a higher value to enable repeated invocation.
|
||||
/// </summary>
|
||||
/// <returns>The maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control.</returns>
|
||||
public static int GetMaxDwellRepeatCount(UIElement element)
|
||||
{
|
||||
return (int)element.GetValue(MaxDwellRepeatCountProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether gaze plus switch is enabled.
|
||||
/// </summary>
|
||||
/// <returns>A boolean indicating whether gaze plus switch is enabled.</returns>
|
||||
public static bool GetIsSwitchEnabled(UIElement element)
|
||||
{
|
||||
return (bool)element.GetValue(IsSwitchEnabledProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the status of gaze interaction over that particular XAML element.
|
||||
/// </summary>
|
||||
public static void SetInteraction(UIElement element, Interaction value)
|
||||
{
|
||||
element.SetValue(InteractionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a boolean indicating whether cursor is shown while user is looking at the school.
|
||||
/// </summary>
|
||||
public static void SetIsCursorVisible(UIElement element, bool value)
|
||||
{
|
||||
element.SetValue(IsCursorVisibleProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the gaze cursor radius.
|
||||
/// </summary>
|
||||
public static void SetCursorRadius(UIElement element, int value)
|
||||
{
|
||||
element.SetValue(CursorRadiusProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the GazeElement associated with an UIElement.
|
||||
/// </summary>
|
||||
public static void SetGazeElement(UIElement element, GazeElement value)
|
||||
{
|
||||
element.SetValue(GazeElementProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration for the control to transition from the Enter state to the Fixation state. At this point, a StateChanged event is fired with PointerState set to Fixation. This event should be used to control the earliest visual feedback the application needs to provide to the user about the gaze location. The default is 350ms.
|
||||
/// </summary>
|
||||
public static void SetFixationDuration(UIElement element, TimeSpan span)
|
||||
{
|
||||
element.SetValue(FixationDurationProperty, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it.
|
||||
/// </summary>
|
||||
public static void SetDwellDuration(UIElement element, TimeSpan span)
|
||||
{
|
||||
element.SetValue(DwellDurationProperty, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation.
|
||||
/// </summary>
|
||||
public static void SetRepeatDelayDuration(UIElement element, TimeSpan span)
|
||||
{
|
||||
element.SetValue(RepeatDelayDurationProperty, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration of repeated dwell invocations, should the user continue to dwell on the control. The first repeat will occur after an additional delay specified by RepeatDelayDuration. Subsequent repeats happen after every period of DwellRepeatDuration. A control is invoked repeatedly only if MaxDwellRepeatCount is set to greater than zero.
|
||||
/// </summary>
|
||||
public static void SetDwellRepeatDuration(UIElement element, TimeSpan span)
|
||||
{
|
||||
element.SetValue(DwellRepeatDurationProperty, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration that controls when the PointerState moves to either the Enter state or the Exit state. When this duration has elapsed after the user's gaze first enters a control, the PointerState is set to Enter. And when this duration has elapsed after the user's gaze has left the control, the PointerState is set to Exit. In both cases, a StateChanged event is fired. The default is 50ms.
|
||||
/// </summary>
|
||||
public static void SetThresholdDuration(UIElement element, TimeSpan span)
|
||||
{
|
||||
element.SetValue(ThresholdDurationProperty, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control. The default value is zero which disables repeated invocation of a control. Developers can set a higher value to enable repeated invocation.
|
||||
/// </summary>
|
||||
public static void SetMaxDwellRepeatCount(UIElement element, int value)
|
||||
{
|
||||
element.SetValue(MaxDwellRepeatCountProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the boolean indicating whether gaze plus switch is enabled.
|
||||
/// </summary>
|
||||
public static void SetIsSwitchEnabled(UIElement element, bool value)
|
||||
{
|
||||
element.SetValue(IsSwitchEnabledProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GazePointer object.
|
||||
/// </summary>
|
||||
/// <returns>The GazePointer associated with that particular page.</returns>
|
||||
public static GazePointer GetGazePointer(Page page)
|
||||
{
|
||||
return GazePointer.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke the default action of the specified UIElement.
|
||||
/// </summary>
|
||||
public static void Invoke(UIElement element)
|
||||
{
|
||||
var item = GazeTargetItem.GetOrCreate(element);
|
||||
item.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a gaze input device is available, and hence whether there is any possibility of gaze events occurring in the application.
|
||||
/// </summary>
|
||||
public static bool IsDeviceAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
return GazePointer.Instance.IsDeviceAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered whenever IsDeviceAvailable changes value.
|
||||
/// </summary>
|
||||
public static event EventHandler<object> IsDeviceAvailableChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
GazePointer.Instance.IsDeviceAvailableChanged += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
GazePointer.Instance.IsDeviceAvailableChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a settings collection into GazeInput.
|
||||
/// Note: This must be loaded from a UI thread to be valid, since the GazeInput
|
||||
/// instance is tied to the UI thread.
|
||||
/// </summary>
|
||||
public static void LoadSettings(ValueSet settings)
|
||||
{
|
||||
GazePointer.Instance.LoadSettings(settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Interaction.h"
|
||||
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
ref class GazeElement;
|
||||
ref class GazePointer;
|
||||
|
||||
/// <summary>
|
||||
/// Static class primarily providing access to attached properties controlling gaze behavior.
|
||||
/// </summary>
|
||||
[Windows::Foundation::Metadata::WebHostHidden]
|
||||
public ref class GazeInput sealed
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the Interaction dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ InteractionProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the IsCursorVisible dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ IsCursorVisibleProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the CursorRadius dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ CursorRadiusProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the GazeElement dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ GazeElementProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the FixationDuration dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ FixationDurationProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the DwellDuration dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ DwellDurationProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the RepeatDelayDuration dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ RepeatDelayDurationProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the DwellRepeatDuration dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ DwellRepeatDurationProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the ThresholdDuration dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ ThresholdDurationProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the MaxDwellRepeatCount dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ MaxDwellRepeatCountProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the IsSwitchEnabled dependency property
|
||||
/// </summary>
|
||||
static property DependencyProperty^ IsSwitchEnabledProperty { DependencyProperty^ get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default indication that gaze entered a control
|
||||
/// </summary>
|
||||
static property Brush^ DwellFeedbackEnterBrush { Brush^ get(); void set(Brush^ value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default animation for dwell press
|
||||
/// </summary>
|
||||
static property Brush^ DwellFeedbackProgressBrush { Brush^ get(); void set(Brush^ value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when displaying the default animation for dwell complete
|
||||
/// </summary>
|
||||
static property Brush^ DwellFeedbackCompleteBrush { Brush^ get(); void set(Brush^ value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thickness of the lines animated for dwell.
|
||||
/// </summary>
|
||||
static property double DwellStrokeThickness { double get(); void set(double value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the interaction default
|
||||
/// </summary>
|
||||
static property GazeInteraction::Interaction Interaction { GazeInteraction::Interaction get(); void set(GazeInteraction::Interaction value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of gaze interaction over that particular XAML element.
|
||||
/// </summary>
|
||||
static GazeInteraction::Interaction GetInteraction(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets Boolean indicating whether cursor is shown while user is looking at the school.
|
||||
/// </summary>
|
||||
static bool GetIsCursorVisible(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the gaze cursor radius.
|
||||
/// </summary>
|
||||
static int GetCursorRadius(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GazeElement associated with an UIElement.
|
||||
/// </summary>
|
||||
static GazeElement^ GetGazeElement(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration for the control to transition from the Enter state to the Fixation state. At this point, a StateChanged event is fired with PointerState set to Fixation. This event should be used to control the earliest visual feedback the application needs to provide to the user about the gaze location. The default is 350ms.
|
||||
/// </summary>
|
||||
static TimeSpan GetFixationDuration(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it.
|
||||
/// </summary>
|
||||
static TimeSpan GetDwellDuration(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation.
|
||||
/// </summary>
|
||||
static TimeSpan GetRepeatDelayDuration(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration of repeated dwell invocations, should the user continue to dwell on the control. The first repeat will occur after an additional delay specified by RepeatDelayDuration. Subsequent repeats happen after every period of DwellRepeatDuration. A control is invoked repeatedly only if MaxDwellRepeatCount is set to greater than zero.
|
||||
/// </summary>
|
||||
static TimeSpan GetDwellRepeatDuration(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration that controls when the PointerState moves to either the Enter state or the Exit state. When this duration has elapsed after the user's gaze first enters a control, the PointerState is set to Enter. And when this duration has elapsed after the user's gaze has left the control, the PointerState is set to Exit. In both cases, a StateChanged event is fired. The default is 50ms.
|
||||
/// </summary>
|
||||
static TimeSpan GetThresholdDuration(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control. The default value is zero which disables repeated invocation of a control. Developers can set a higher value to enable repeated invocation.
|
||||
/// </summary>
|
||||
static int GetMaxDwellRepeatCount(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Boolean indicating whether gaze plus switch is enabled.
|
||||
/// </summary>
|
||||
static bool GetIsSwitchEnabled(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the status of gaze interaction over that particular XAML element.
|
||||
/// </summary>
|
||||
static void SetInteraction(UIElement^ element, GazeInteraction::Interaction value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets Boolean indicating whether cursor is shown while user is looking at the school.
|
||||
/// </summary>
|
||||
static void SetIsCursorVisible(UIElement^ element, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the gaze cursor radius.
|
||||
/// </summary>
|
||||
static void SetCursorRadius(UIElement^ element, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the GazeElement associated with an UIElement.
|
||||
/// </summary>
|
||||
static void SetGazeElement(UIElement^ element, GazeElement^ value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration for the control to transition from the Enter state to the Fixation state. At this point, a StateChanged event is fired with PointerState set to Fixation. This event should be used to control the earliest visual feedback the application needs to provide to the user about the gaze location. The default is 350ms.
|
||||
/// </summary>
|
||||
static void SetFixationDuration(UIElement^ element, TimeSpan span);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it.
|
||||
/// </summary>
|
||||
static void SetDwellDuration(UIElement^ element, TimeSpan span);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation.
|
||||
/// </summary>
|
||||
static void SetRepeatDelayDuration(UIElement^ element, TimeSpan span);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration of repeated dwell invocations, should the user continue to dwell on the control. The first repeat will occur after an additional delay specified by RepeatDelayDuration. Subsequent repeats happen after every period of DwellRepeatDuration. A control is invoked repeatedly only if MaxDwellRepeatCount is set to greater than zero.
|
||||
/// </summary>
|
||||
static void SetDwellRepeatDuration(UIElement^ element, TimeSpan span);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the duration that controls when the PointerState moves to either the Enter state or the Exit state. When this duration has elapsed after the user's gaze first enters a control, the PointerState is set to Enter. And when this duration has elapsed after the user's gaze has left the control, the PointerState is set to Exit. In both cases, a StateChanged event is fired. The default is 50ms.
|
||||
/// </summary>
|
||||
static void SetThresholdDuration(UIElement^ element, TimeSpan span);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control. The default value is zero which disables repeated invocation of a control. Developers can set a higher value to enable repeated invocation.
|
||||
/// </summary>
|
||||
static void SetMaxDwellRepeatCount(UIElement^ element, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Boolean indicating whether gaze plus switch is enabled.
|
||||
/// </summary>
|
||||
static void SetIsSwitchEnabled(UIElement^ element, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GazePointer object.
|
||||
/// </summary>
|
||||
static GazePointer^ GetGazePointer(Page^ page);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke the default action of the specified UIElement.
|
||||
/// </summary>
|
||||
static void Invoke(UIElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Reports whether a gaze input device is available, and hence whether there is any possibility of gaze events occurring in the application.
|
||||
/// </summary>
|
||||
static property bool IsDeviceAvailable { bool get(); }
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered whenever IsDeviceAvailable changes value.
|
||||
/// </summary>
|
||||
static event EventHandler<Object^>^ IsDeviceAvailableChanged
|
||||
{
|
||||
EventRegistrationToken add(EventHandler<Object^>^ handler);
|
||||
void remove(EventRegistrationToken token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a settings collection into GazeInput.
|
||||
/// Note: This must be loaded from a UI thread to be valid, since the GazeInput
|
||||
/// instance is tied to the UI thread.
|
||||
/// </summary>
|
||||
static void LoadSettings(ValueSet^ settings);
|
||||
|
||||
internal:
|
||||
|
||||
static TimeSpan UnsetTimeSpan;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,774 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "GazePointer.h"
|
||||
|
||||
#include "GazeElement.h"
|
||||
#include "GazeHistoryItem.h"
|
||||
#include "GazeTargetItem.h"
|
||||
#include "StateChangedEventArgs.h"
|
||||
|
||||
using namespace Platform;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
ref class NonInvokeGazeTargetItem sealed : GazeTargetItem
|
||||
{
|
||||
internal:
|
||||
|
||||
NonInvokeGazeTargetItem()
|
||||
: GazeTargetItem(ref new Page())
|
||||
{
|
||||
}
|
||||
|
||||
internal:
|
||||
|
||||
virtual property bool IsInvokable { bool get() override { return false; } }
|
||||
|
||||
void Invoke() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
GazePointer^ GazePointer::Instance::get()
|
||||
{
|
||||
thread_local static GazePointer^ value;
|
||||
if (value == nullptr)
|
||||
{
|
||||
value = ref new GazePointer();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void GazePointer::AddRoot(int proxyId)
|
||||
{
|
||||
_roots->InsertAt(0, proxyId);
|
||||
|
||||
if (_roots->Size == 1)
|
||||
{
|
||||
_isShuttingDown = false;
|
||||
InitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::RemoveRoot(int proxyId)
|
||||
{
|
||||
unsigned int index = 0;
|
||||
if (_roots->IndexOf(proxyId, &index))
|
||||
{
|
||||
_roots->RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (_roots->Size == 0)
|
||||
{
|
||||
_isShuttingDown = true;
|
||||
_gazeCursor->IsGazeEntered = false;
|
||||
DeinitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
GazePointer::GazePointer()
|
||||
{
|
||||
_nonInvokeGazeTargetItem = ref new NonInvokeGazeTargetItem();
|
||||
|
||||
// Default to not filtering sample data
|
||||
Filter = ref new NullFilter();
|
||||
|
||||
_gazeCursor = ref new GazeCursor();
|
||||
|
||||
// timer that gets called back if there gaze samples haven't been received in a while
|
||||
_eyesOffTimer = ref new DispatcherTimer();
|
||||
_eyesOffTimer->Tick += ref new EventHandler<Object^>(this, &GazePointer::OnEyesOff);
|
||||
|
||||
// provide a default of GAZE_IDLE_TIME microseconds to fire eyes off
|
||||
EyesOffDelay = GAZE_IDLE_TIME;
|
||||
|
||||
InitializeHistogram();
|
||||
|
||||
_devices = ref new Vector<GazeDevicePreview^>();
|
||||
_watcher = GazeInputSourcePreview::CreateWatcher();
|
||||
_watcher->Added += ref new TypedEventHandler<GazeDeviceWatcherPreview^, GazeDeviceWatcherAddedPreviewEventArgs^>(this, &GazePointer::OnDeviceAdded);
|
||||
_watcher->Removed += ref new TypedEventHandler<GazeDeviceWatcherPreview^, GazeDeviceWatcherRemovedPreviewEventArgs^>(this, &GazePointer::OnDeviceRemoved);
|
||||
_watcher->Start();
|
||||
}
|
||||
|
||||
EventRegistrationToken GazePointer::GazeEvent::add(EventHandler<GazeEventArgs^>^ handler)
|
||||
{
|
||||
_gazeEventCount++;
|
||||
return _gazeEvent += handler;
|
||||
}
|
||||
|
||||
void GazePointer::GazeEvent::remove(EventRegistrationToken token)
|
||||
{
|
||||
_gazeEventCount--;
|
||||
_gazeEvent -= token;
|
||||
}
|
||||
|
||||
void GazePointer::GazeEvent::raise(Object^ sender, GazeEventArgs^ e)
|
||||
{
|
||||
_gazeEvent(sender, e);
|
||||
}
|
||||
|
||||
void GazePointer::OnDeviceAdded(GazeDeviceWatcherPreview^ sender, GazeDeviceWatcherAddedPreviewEventArgs^ args)
|
||||
{
|
||||
_devices->Append(args->Device);
|
||||
|
||||
if (_devices->Size == 1)
|
||||
{
|
||||
IsDeviceAvailableChanged(nullptr, nullptr);
|
||||
|
||||
InitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::OnDeviceRemoved(GazeDeviceWatcherPreview^ sender, GazeDeviceWatcherRemovedPreviewEventArgs^ args)
|
||||
{
|
||||
auto index = 0u;
|
||||
while (index < _devices->Size && _devices->GetAt(index)->Id != args->Device->Id)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < _devices->Size)
|
||||
{
|
||||
_devices->RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_devices->RemoveAt(0);
|
||||
}
|
||||
|
||||
if (_devices->Size == 0)
|
||||
{
|
||||
IsDeviceAvailableChanged(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
GazePointer::~GazePointer()
|
||||
{
|
||||
_watcher->Added -= _deviceAddedToken;
|
||||
_watcher->Removed -= _deviceRemovedToken;
|
||||
|
||||
if (_gazeInputSource != nullptr)
|
||||
{
|
||||
_gazeInputSource->GazeEntered -= _gazeEnteredToken;
|
||||
_gazeInputSource->GazeMoved -= _gazeMovedToken;
|
||||
_gazeInputSource->GazeExited -= _gazeExitedToken;
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::LoadSettings(ValueSet^ settings)
|
||||
{
|
||||
_gazeCursor->LoadSettings(settings);
|
||||
Filter->LoadSettings(settings);
|
||||
|
||||
// TODO Add logic to protect against missing settings
|
||||
|
||||
if (settings->HasKey("GazePointer.FixationDelay"))
|
||||
{
|
||||
_defaultFixation = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.FixationDelay")));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.DwellDelay"))
|
||||
{
|
||||
_defaultDwell = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.DwellDelay")));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.DwellRepeatDelay"))
|
||||
{
|
||||
_defaultDwellRepeatDelay = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.DwellRepeatDelay")));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.RepeatDelay"))
|
||||
{
|
||||
_defaultRepeatDelay = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.RepeatDelay")));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.ThresholdDelay"))
|
||||
{
|
||||
_defaultThreshold = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.ThresholdDelay")));
|
||||
}
|
||||
|
||||
// TODO need to set fixation and dwell for all elements
|
||||
if (settings->HasKey("GazePointer.FixationDelay"))
|
||||
{
|
||||
SetElementStateDelay(_offScreenElement, PointerState::Fixation, TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.FixationDelay"))));
|
||||
}
|
||||
if (settings->HasKey("GazePointer.DwellDelay"))
|
||||
{
|
||||
SetElementStateDelay(_offScreenElement, PointerState::Dwell, TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.DwellDelay"))));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.GazeIdleTime"))
|
||||
{
|
||||
EyesOffDelay = TimeSpanFromMicroseconds((int)(settings->Lookup("GazePointer.GazeIdleTime")));
|
||||
}
|
||||
|
||||
if (settings->HasKey("GazePointer.IsSwitchEnabled"))
|
||||
{
|
||||
IsSwitchEnabled = (bool)(settings->Lookup("GazePointer.IsSwitchEnabled"));
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::InitializeHistogram()
|
||||
{
|
||||
_activeHitTargetTimes = ref new Vector<GazeTargetItem^>();
|
||||
|
||||
_offScreenElement = ref new UserControl();
|
||||
SetElementStateDelay(_offScreenElement, PointerState::Fixation, _defaultFixation);
|
||||
SetElementStateDelay(_offScreenElement, PointerState::Dwell, _defaultDwell);
|
||||
|
||||
_maxHistoryTime = DEFAULT_MAX_HISTORY_DURATION; // maintain about 3 seconds of history (in microseconds)
|
||||
_gazeHistory = ref new Vector<GazeHistoryItem^>();
|
||||
}
|
||||
|
||||
void GazePointer::InitializeGazeInputSource()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
if (_roots->Size != 0 && _devices->Size != 0)
|
||||
{
|
||||
if (_gazeInputSource == nullptr)
|
||||
{
|
||||
_gazeInputSource = GazeInputSourcePreview::GetForCurrentView();
|
||||
}
|
||||
|
||||
if (_gazeInputSource != nullptr)
|
||||
{
|
||||
_gazeEnteredToken = _gazeInputSource->GazeEntered += ref new TypedEventHandler<
|
||||
GazeInputSourcePreview^, GazeEnteredPreviewEventArgs^>(this, &GazePointer::OnGazeEntered);
|
||||
_gazeMovedToken = _gazeInputSource->GazeMoved += ref new TypedEventHandler<
|
||||
GazeInputSourcePreview^, GazeMovedPreviewEventArgs^>(this, &GazePointer::OnGazeMoved);
|
||||
_gazeExitedToken = _gazeInputSource->GazeExited += ref new TypedEventHandler<
|
||||
GazeInputSourcePreview^, GazeExitedPreviewEventArgs^>(this, &GazePointer::OnGazeExited);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::DeinitializeGazeInputSource()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
_initialized = false;
|
||||
|
||||
_gazeInputSource->GazeEntered -= _gazeEnteredToken;
|
||||
_gazeInputSource->GazeMoved -= _gazeMovedToken;
|
||||
_gazeInputSource->GazeExited -= _gazeExitedToken;
|
||||
}
|
||||
}
|
||||
|
||||
static DependencyProperty^ GetProperty(PointerState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PointerState::Fixation: return GazeInput::FixationDurationProperty;
|
||||
case PointerState::Dwell: return GazeInput::DwellDurationProperty;
|
||||
case PointerState::DwellRepeat: return GazeInput::DwellRepeatDurationProperty;
|
||||
case PointerState::Enter: return GazeInput::ThresholdDurationProperty;
|
||||
case PointerState::Exit: return GazeInput::ThresholdDurationProperty;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TimeSpan GazePointer::GetDefaultPropertyValue(PointerState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PointerState::Fixation: return _defaultFixation;
|
||||
case PointerState::Dwell: return _defaultDwell;
|
||||
case PointerState::DwellRepeat: return _defaultRepeatDelay;
|
||||
case PointerState::Enter: return _defaultThreshold;
|
||||
case PointerState::Exit: return _defaultThreshold;
|
||||
default: throw ref new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::SetElementStateDelay(UIElement ^element, PointerState relevantState, TimeSpan stateDelay)
|
||||
{
|
||||
auto property = GetProperty(relevantState);
|
||||
element->SetValue(property, stateDelay);
|
||||
|
||||
// fix up _maxHistoryTime in case the new param exceeds the history length we are currently tracking
|
||||
auto dwellTime = GetElementStateDelay(element, PointerState::Dwell);
|
||||
auto repeatTime = GetElementStateDelay(element, PointerState::DwellRepeat);
|
||||
_maxHistoryTime = 2 * max(dwellTime, repeatTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the parent to inherit properties from.
|
||||
/// </summary>
|
||||
static UIElement^ GetInheritenceParent(UIElement^ child)
|
||||
{
|
||||
// The result value.
|
||||
Object^ parent = nullptr;
|
||||
|
||||
// Get the automation peer...
|
||||
auto peer = FrameworkElementAutomationPeer::FromElement(child);
|
||||
if (peer != nullptr)
|
||||
{
|
||||
// ...if it exists, get the peer's parent...
|
||||
auto peerParent = dynamic_cast<FrameworkElementAutomationPeer^>(peer->Navigate(AutomationNavigationDirection::Parent));
|
||||
if (peerParent != nullptr)
|
||||
{
|
||||
// ...and if it has a parent, get the corresponding object.
|
||||
parent = peerParent->Owner;
|
||||
}
|
||||
}
|
||||
|
||||
// If the above failed to find a parent...
|
||||
if (parent == nullptr)
|
||||
{
|
||||
// ...use the visual parent.
|
||||
parent = VisualTreeHelper::GetParent(child);
|
||||
}
|
||||
|
||||
// Safely pun the value we found to a UIElement reference.
|
||||
return dynamic_cast<UIElement^>(parent);
|
||||
}
|
||||
|
||||
TimeSpan GazePointer::GetElementStateDelay(UIElement ^element, DependencyProperty^ property, TimeSpan defaultValue)
|
||||
{
|
||||
UIElement^ walker = element;
|
||||
Object^ valueAtWalker = walker->GetValue(property);
|
||||
|
||||
while (GazeInput::UnsetTimeSpan.Equals(valueAtWalker) && walker != nullptr)
|
||||
{
|
||||
walker = GetInheritenceParent(walker);
|
||||
|
||||
if (walker != nullptr)
|
||||
{
|
||||
valueAtWalker = walker->GetValue(property);
|
||||
}
|
||||
}
|
||||
|
||||
auto ticks = GazeInput::UnsetTimeSpan.Equals(valueAtWalker) ? defaultValue : safe_cast<TimeSpan>(valueAtWalker);
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
TimeSpan GazePointer::GetElementStateDelay(UIElement ^element, PointerState pointerState)
|
||||
{
|
||||
auto property = GetProperty(pointerState);
|
||||
auto defaultValue = GetDefaultPropertyValue(pointerState);
|
||||
auto ticks = GetElementStateDelay(element, property, defaultValue);
|
||||
|
||||
switch (pointerState)
|
||||
{
|
||||
case PointerState::Dwell:
|
||||
case PointerState::DwellRepeat:
|
||||
_maxHistoryTime = max(_maxHistoryTime, 2 * ticks);
|
||||
break;
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void GazePointer::Reset()
|
||||
{
|
||||
_activeHitTargetTimes->Clear();
|
||||
_gazeHistory->Clear();
|
||||
|
||||
_maxHistoryTime = DEFAULT_MAX_HISTORY_DURATION;
|
||||
}
|
||||
|
||||
GazeTargetItem^ GazePointer::GetHitTarget(Point gazePoint)
|
||||
{
|
||||
GazeTargetItem^ invokable;
|
||||
|
||||
switch (Window::Current->CoreWindow->ActivationMode)
|
||||
{
|
||||
default:
|
||||
if (!_isAlwaysActivated)
|
||||
{
|
||||
invokable = _nonInvokeGazeTargetItem;
|
||||
break;
|
||||
}
|
||||
|
||||
case CoreWindowActivationMode::ActivatedInForeground:
|
||||
case CoreWindowActivationMode::ActivatedNotForeground:
|
||||
auto elements = VisualTreeHelper::FindElementsInHostCoordinates(gazePoint, nullptr, false);
|
||||
auto first = elements->First();
|
||||
auto element = first->HasCurrent ? first->Current : nullptr;
|
||||
|
||||
invokable = nullptr;
|
||||
|
||||
if (element != nullptr)
|
||||
{
|
||||
invokable = GazeTargetItem::GetOrCreate(element);
|
||||
|
||||
while (element != nullptr && !invokable->IsInvokable)
|
||||
{
|
||||
element = dynamic_cast<UIElement^>(VisualTreeHelper::GetParent(element));
|
||||
|
||||
if (element != nullptr)
|
||||
{
|
||||
invokable = GazeTargetItem::GetOrCreate(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (element == nullptr || !invokable->IsInvokable)
|
||||
{
|
||||
invokable = _nonInvokeGazeTargetItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
Interaction interaction;
|
||||
do
|
||||
{
|
||||
interaction = GazeInput::GetInteraction(element);
|
||||
if (interaction == Interaction::Inherited)
|
||||
{
|
||||
element = GetInheritenceParent(element);
|
||||
}
|
||||
} while (interaction == Interaction::Inherited && element != nullptr);
|
||||
|
||||
if (interaction == Interaction::Inherited)
|
||||
{
|
||||
interaction = GazeInput::Interaction;
|
||||
}
|
||||
|
||||
if (interaction != Interaction::Enabled)
|
||||
{
|
||||
invokable = _nonInvokeGazeTargetItem;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return invokable;
|
||||
}
|
||||
|
||||
void GazePointer::ActivateGazeTargetItem(GazeTargetItem^ target)
|
||||
{
|
||||
unsigned int index;
|
||||
if (!_activeHitTargetTimes->IndexOf(target, &index))
|
||||
{
|
||||
_activeHitTargetTimes->Append(target);
|
||||
|
||||
// calculate the time that the first DwellRepeat needs to be fired after. this will be updated every time a DwellRepeat is
|
||||
// fired to keep track of when the next one is to be fired after that.
|
||||
auto nextStateTime = GetElementStateDelay(target->TargetElement, PointerState::Enter);
|
||||
|
||||
target->Reset(nextStateTime);
|
||||
}
|
||||
}
|
||||
|
||||
GazeTargetItem^ GazePointer::ResolveHitTarget(Point gazePoint, TimeSpan timestamp)
|
||||
{
|
||||
// TODO: The existence of a GazeTargetItem should be used to indicate that
|
||||
// the target item is invokable. The method of invocation should be stored
|
||||
// within the GazeTargetItem when it is created and not recalculated when
|
||||
// subsequently needed.
|
||||
|
||||
// create GazeHistoryItem to deal with this sample
|
||||
auto target = GetHitTarget(gazePoint);
|
||||
auto historyItem = ref new GazeHistoryItem();
|
||||
historyItem->HitTarget = target;
|
||||
historyItem->Timestamp = timestamp;
|
||||
historyItem->Duration = TimeSpanZero;
|
||||
assert(historyItem->HitTarget != nullptr);
|
||||
|
||||
// create new GazeTargetItem with a (default) total elapsed time of zero if one does not exist already.
|
||||
// this ensures that there will always be an entry for target elements in the code below.
|
||||
ActivateGazeTargetItem(target);
|
||||
target->LastTimestamp = timestamp;
|
||||
|
||||
// find elapsed time since we got the last hit target
|
||||
historyItem->Duration = timestamp - _lastTimestamp;
|
||||
if (historyItem->Duration > MAX_SINGLE_SAMPLE_DURATION)
|
||||
{
|
||||
historyItem->Duration = MAX_SINGLE_SAMPLE_DURATION;
|
||||
}
|
||||
_gazeHistory->Append(historyItem);
|
||||
|
||||
// update the time this particular hit target has accumulated
|
||||
target->DetailedTime += historyItem->Duration;
|
||||
|
||||
// drop the oldest samples from the list until we have samples only
|
||||
// within the window we are monitoring
|
||||
//
|
||||
// historyItem is the last item we just appended a few lines above.
|
||||
for (auto evOldest = _gazeHistory->GetAt(0);
|
||||
historyItem->Timestamp - evOldest->Timestamp > _maxHistoryTime;
|
||||
evOldest = _gazeHistory->GetAt(0))
|
||||
{
|
||||
_gazeHistory->RemoveAt(0);
|
||||
|
||||
// subtract the duration obtained from the oldest sample in _gazeHistory
|
||||
auto targetItem = evOldest->HitTarget;
|
||||
assert(targetItem->DetailedTime - evOldest->Duration >= TimeSpanZero);
|
||||
targetItem->DetailedTime -= evOldest->Duration;
|
||||
if (targetItem->ElementState != PointerState::PreEnter)
|
||||
{
|
||||
targetItem->OverflowTime += evOldest->Duration;
|
||||
}
|
||||
}
|
||||
|
||||
_lastTimestamp = timestamp;
|
||||
|
||||
// Return the most recent hit target
|
||||
// Intuition would tell us that we should return NOT the most recent
|
||||
// hitTarget, but the one with the most accumulated time in
|
||||
// in the maintained history. But the effect of that is that
|
||||
// the user will feel that they have clicked on the wrong thing
|
||||
// when they are looking at something else.
|
||||
// That is why we return the most recent hitTarget so that
|
||||
// when its dwell time has elapsed, it will be invoked
|
||||
return target;
|
||||
}
|
||||
|
||||
void GazePointer::OnEyesOff(Object ^sender, Object ^ea)
|
||||
{
|
||||
_eyesOffTimer->Stop();
|
||||
|
||||
CheckIfExiting(_lastTimestamp + EyesOffDelay);
|
||||
RaiseGazePointerEvent(nullptr, PointerState::Enter, EyesOffDelay);
|
||||
}
|
||||
|
||||
void GazePointer::CheckIfExiting(TimeSpan curTimestamp)
|
||||
{
|
||||
for (unsigned int index = 0; index < _activeHitTargetTimes->Size; index++)
|
||||
{
|
||||
auto targetItem = _activeHitTargetTimes->GetAt(index);
|
||||
auto targetElement = targetItem->TargetElement;
|
||||
auto exitDelay = GetElementStateDelay(targetElement, PointerState::Exit);
|
||||
|
||||
auto idleDuration = curTimestamp - targetItem->LastTimestamp;
|
||||
if (targetItem->ElementState != PointerState::PreEnter && idleDuration > exitDelay)
|
||||
{
|
||||
targetItem->ElementState = PointerState::PreEnter;
|
||||
|
||||
// Transitioning to exit - clear the cached fixated element
|
||||
_currentlyFixatedElement = nullptr;
|
||||
|
||||
RaiseGazePointerEvent(targetItem, PointerState::Exit, targetItem->ElapsedTime);
|
||||
targetItem->GiveFeedback();
|
||||
|
||||
_activeHitTargetTimes->RemoveAt(index);
|
||||
|
||||
// remove all history samples referring to deleted hit target
|
||||
for (unsigned i = 0; i < _gazeHistory->Size; )
|
||||
{
|
||||
auto hitTarget = _gazeHistory->GetAt(i)->HitTarget;
|
||||
if (hitTarget->TargetElement == targetElement)
|
||||
{
|
||||
_gazeHistory->RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// return because only one element can be exited at a time and at this point
|
||||
// we have done everything that we can do
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t *PointerStates[] = {
|
||||
L"Exit",
|
||||
L"PreEnter",
|
||||
L"Enter",
|
||||
L"Fixation",
|
||||
L"Dwell",
|
||||
L"DwellRepeat"
|
||||
};
|
||||
|
||||
void GazePointer::RaiseGazePointerEvent(GazeTargetItem^ target, PointerState state, TimeSpan elapsedTime)
|
||||
{
|
||||
auto control = target != nullptr ? target->TargetElement : nullptr;
|
||||
//assert(target != _rootElement);
|
||||
auto gpea = ref new StateChangedEventArgs(control, state, elapsedTime);
|
||||
//auto buttonObj = dynamic_cast<Button ^>(target);
|
||||
//if (buttonObj && buttonObj->Content)
|
||||
//{
|
||||
// String^ buttonText = dynamic_cast<String^>(buttonObj->Content);
|
||||
// Debug::WriteLine(L"GPE: %s -> %s, %d", buttonText, PointerStates[(int)state], elapsedTime);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Debug::WriteLine(L"GPE: 0x%08x -> %s, %d", target != nullptr ? target->GetHashCode() : 0, PointerStates[(int)state], elapsedTime);
|
||||
//}
|
||||
|
||||
auto gazeElement = target != nullptr ? GazeInput::GetGazeElement(control) : nullptr;
|
||||
|
||||
if (gazeElement != nullptr)
|
||||
{
|
||||
gazeElement->RaiseStateChanged(control, gpea);
|
||||
}
|
||||
|
||||
if (state == PointerState::Dwell)
|
||||
{
|
||||
auto handled = false;
|
||||
|
||||
if (gazeElement != nullptr)
|
||||
{
|
||||
auto args = ref new DwellInvokedRoutedEventArgs();
|
||||
gazeElement->RaiseInvoked(control, args);
|
||||
handled = args->Handled;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
target->Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::OnGazeEntered(GazeInputSourcePreview^ provider, GazeEnteredPreviewEventArgs^ args)
|
||||
{
|
||||
//Debug::WriteLine(L"Entered at %ld", args->CurrentPoint->Timestamp);
|
||||
_gazeCursor->IsGazeEntered = true;
|
||||
}
|
||||
|
||||
void GazePointer::OnGazeMoved(GazeInputSourcePreview^ provider, GazeMovedPreviewEventArgs^ args)
|
||||
{
|
||||
if (!_isShuttingDown)
|
||||
{
|
||||
auto intermediatePoints = args->GetIntermediatePoints();
|
||||
for each(auto point in intermediatePoints)
|
||||
{
|
||||
auto position = point->EyeGazePosition;
|
||||
if (position != nullptr)
|
||||
{
|
||||
_gazeCursor->IsGazeEntered = true;
|
||||
ProcessGazePoint(TimeSpanFromMicroseconds(point->Timestamp), position->Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug::WriteLine(L"Null position eaten at %ld", point->Timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointer::OnGazeExited(GazeInputSourcePreview^ provider, GazeExitedPreviewEventArgs^ args)
|
||||
{
|
||||
//Debug::WriteLine(L"Exited at %ld", args->CurrentPoint->Timestamp);
|
||||
_gazeCursor->IsGazeEntered = false;
|
||||
}
|
||||
|
||||
void GazePointer::ProcessGazePoint(TimeSpan timestamp, Point position)
|
||||
{
|
||||
auto ea = ref new GazeFilterArgs(position, timestamp);
|
||||
|
||||
auto fa = Filter->Update(ea);
|
||||
_gazeCursor->Position = fa->Location;
|
||||
|
||||
if (_gazeEventCount != 0)
|
||||
{
|
||||
_gazeEventArgs->Set(fa->Location, timestamp);
|
||||
GazeEvent(this, _gazeEventArgs);
|
||||
if (_gazeEventArgs->Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto targetItem = ResolveHitTarget(fa->Location, fa->Timestamp);
|
||||
assert(targetItem != nullptr);
|
||||
|
||||
//Debug::WriteLine(L"ProcessGazePoint: %llu -> [%d, %d], %llu", hitTarget->GetHashCode(), (int)fa->Location.X, (int)fa->Location.Y, fa->Timestamp);
|
||||
|
||||
// check to see if any element in _hitTargetTimes needs an exit event fired.
|
||||
// this ensures that all exit events are fired before enter event
|
||||
CheckIfExiting(fa->Timestamp);
|
||||
|
||||
PointerState nextState = static_cast<PointerState>(static_cast<int>(targetItem->ElementState) + 1);
|
||||
|
||||
//Debug::WriteLine(L"%llu -> State=%d, Elapsed=%d, NextStateTime=%d", targetItem->TargetElement, targetItem->ElementState, targetItem->ElapsedTime, targetItem->NextStateTime);
|
||||
|
||||
if (targetItem->ElapsedTime > targetItem->NextStateTime)
|
||||
{
|
||||
auto prevStateTime = targetItem->NextStateTime;
|
||||
|
||||
// prevent targetItem from ever actually transitioning into the DwellRepeat state so as
|
||||
// to continuously emit the DwellRepeat event
|
||||
if (nextState != PointerState::DwellRepeat)
|
||||
{
|
||||
targetItem->ElementState = nextState;
|
||||
nextState = static_cast<PointerState>(static_cast<int>(nextState) + 1); // nextState++
|
||||
targetItem->NextStateTime += GetElementStateDelay(targetItem->TargetElement, nextState);
|
||||
|
||||
if (targetItem->ElementState == PointerState::Dwell)
|
||||
{
|
||||
targetItem->NextStateTime += GetElementStateDelay(targetItem->TargetElement, GazeInput::RepeatDelayDurationProperty, _defaultDwellRepeatDelay);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// move the NextStateTime by one dwell period, while continuing to stay in Dwell state
|
||||
targetItem->NextStateTime += GetElementStateDelay(targetItem->TargetElement, PointerState::DwellRepeat);
|
||||
}
|
||||
|
||||
if (targetItem->ElementState == PointerState::Dwell)
|
||||
{
|
||||
targetItem->RepeatCount++;
|
||||
if (targetItem->MaxDwellRepeatCount < targetItem->RepeatCount)
|
||||
{
|
||||
targetItem->NextStateTime = TimeSpan{ MAXINT64 };
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem->ElementState == PointerState::Fixation)
|
||||
{
|
||||
// Cache the fixated item
|
||||
_currentlyFixatedElement = targetItem;
|
||||
|
||||
// We are about to transition into the Dwell state
|
||||
// If switch input is enabled, make sure dwell never completes
|
||||
// via eye gaze
|
||||
if (_isSwitchEnabled)
|
||||
{
|
||||
// Don't allow the next state (Dwell) to progress
|
||||
targetItem->NextStateTime = TimeSpan{ MAXINT64 };
|
||||
}
|
||||
}
|
||||
|
||||
RaiseGazePointerEvent(targetItem, targetItem->ElementState, targetItem->ElapsedTime);
|
||||
}
|
||||
|
||||
targetItem->GiveFeedback();
|
||||
|
||||
_eyesOffTimer->Start();
|
||||
_lastTimestamp = fa->Timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When in switch mode, will issue a click on the currently fixated element
|
||||
/// </summary>
|
||||
void GazePointer::Click()
|
||||
{
|
||||
if (_isSwitchEnabled &&
|
||||
_currentlyFixatedElement != nullptr)
|
||||
{
|
||||
_currentlyFixatedElement->Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run device calibration.
|
||||
/// </summary>
|
||||
IAsyncOperation<bool>^ GazePointer::RequestCalibrationAsync()
|
||||
{
|
||||
return _devices->Size == 1 ?
|
||||
_devices->GetAt(0)->RequestCalibrationAsync() :
|
||||
concurrency::create_async([] { return false; });
|
||||
}
|
||||
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,909 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Devices.Input.Preview;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Class of singleton object coordinating gaze input.
|
||||
/// </summary>
|
||||
public class GazePointer
|
||||
{
|
||||
// units in microseconds
|
||||
private static readonly TimeSpan DEFAULT_FIXATION_DELAY = new TimeSpan(3500000);
|
||||
private static readonly TimeSpan DEFAULT_DWELL_DELAY = new TimeSpan(4000000);
|
||||
private static readonly TimeSpan DEFAULT_DWELL_REPEAT_DELAY = new TimeSpan(4000000);
|
||||
private static readonly TimeSpan DEFAULT_REPEAT_DELAY = new TimeSpan(4000000);
|
||||
private static readonly TimeSpan DEFAULT_THRESHOLD_DELAY = new TimeSpan(500000);
|
||||
private static readonly TimeSpan DEFAULT_MAX_HISTORY_DURATION = new TimeSpan(30000000);
|
||||
private static readonly TimeSpan MAX_SINGLE_SAMPLE_DURATION = new TimeSpan(1000000);
|
||||
|
||||
private static readonly TimeSpan GAZE_IDLE_TIME = new TimeSpan(250000000);
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="GazePointer"/> class.
|
||||
/// </summary>
|
||||
~GazePointer()
|
||||
{
|
||||
_watcher.Added -= OnDeviceAdded;
|
||||
_watcher.Removed -= OnDeviceRemoved;
|
||||
|
||||
if (_gazeInputSource != null)
|
||||
{
|
||||
_gazeInputSource.GazeEntered -= OnGazeEntered;
|
||||
_gazeInputSource.GazeMoved -= OnGazeMoved;
|
||||
_gazeInputSource.GazeExited -= OnGazeExited;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a settings collection into GazePointer.
|
||||
/// </summary>
|
||||
public void LoadSettings(ValueSet settings)
|
||||
{
|
||||
_gazeCursor.LoadSettings(settings);
|
||||
Filter.LoadSettings(settings);
|
||||
|
||||
// TODO Add logic to protect against missing settings
|
||||
if (settings.TryGetValue("GazePointer.FixationDelay", out var fixationDelay))
|
||||
{
|
||||
_defaultFixation = new TimeSpan((int)fixationDelay * 10);
|
||||
SetElementStateDelay(_offScreenElement, PointerState.Fixation, new TimeSpan((int)fixationDelay * 10));
|
||||
}
|
||||
|
||||
if (settings.TryGetValue("GazePointer.DwellDelay", out var dwellDelay))
|
||||
{
|
||||
_defaultDwell = new TimeSpan((int)dwellDelay * 10);
|
||||
SetElementStateDelay(_offScreenElement, PointerState.Dwell, new TimeSpan((int)dwellDelay * 10));
|
||||
}
|
||||
|
||||
if (settings.TryGetValue("GazePointer.DwellRepeatDelay", out var dwellRepeatDelay))
|
||||
{
|
||||
_defaultDwellRepeatDelay = new TimeSpan((int)dwellRepeatDelay * 10);
|
||||
}
|
||||
|
||||
if (settings.TryGetValue("GazePointer.RepeatDelay", out var repeatDelay))
|
||||
{
|
||||
_defaultRepeatDelay = new TimeSpan((int)repeatDelay * 10);
|
||||
}
|
||||
|
||||
if (settings.TryGetValue("GazePointer.ThresholdDelay", out var thresholdDelay))
|
||||
{
|
||||
_defaultThreshold = new TimeSpan((int)thresholdDelay * 10);
|
||||
}
|
||||
|
||||
// TODO need to set fixation and dwell for all elements
|
||||
if (settings.TryGetValue("GazePointer.GazeIdleTime", out var gazeIdleTime))
|
||||
{
|
||||
EyesOffDelay = new TimeSpan((int)gazeIdleTime * 10);
|
||||
}
|
||||
|
||||
if (settings.TryGetValue("GazePointer.IsSwitchEnabled", out var isSwitchEnabled))
|
||||
{
|
||||
IsSwitchEnabled = (bool)isSwitchEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When in switch mode, will issue a click on the currently fixated element
|
||||
/// </summary>
|
||||
public void Click()
|
||||
{
|
||||
if (IsSwitchEnabled &&
|
||||
_currentlyFixatedElement != null)
|
||||
{
|
||||
_currentlyFixatedElement.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run device calibration.
|
||||
/// </summary>
|
||||
/// <returns>Task that returns true, if calibration completes successfully; otherwise, false.</returns>
|
||||
public Task<bool> RequestCalibrationAsync()
|
||||
{
|
||||
return _devices.Count == 1 ?
|
||||
_devices[0].RequestCalibrationAsync().AsTask() :
|
||||
Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for Gaze related events, of with a <see cref="GazeEventArgs"/> object.
|
||||
/// </summary>
|
||||
public event EventHandler<GazeEventArgs> GazeEvent
|
||||
{
|
||||
add
|
||||
{
|
||||
_gazeEventCount++;
|
||||
_gazeEvent += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_gazeEventCount--;
|
||||
_gazeEvent -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the UIElement representing the cursor.
|
||||
/// </summary>
|
||||
public UIElement CursorElement
|
||||
{
|
||||
get { return _gazeCursor.PopupChild; }
|
||||
set { _gazeCursor.PopupChild = value; }
|
||||
}
|
||||
|
||||
private EventHandler<GazeEventArgs> _gazeEvent;
|
||||
|
||||
private int _gazeEventCount = 0;
|
||||
|
||||
internal Brush EnterBrush { get; set; } = null;
|
||||
|
||||
internal Brush ProgressBrush { get; set; } = new SolidColorBrush(Colors.Green);
|
||||
|
||||
internal Brush CompleteBrush { get; set; } = new SolidColorBrush(Colors.Red);
|
||||
|
||||
internal double DwellStrokeThickness { get; set; } = 2;
|
||||
|
||||
internal Interaction Interaction { get; set; } = Interaction.Disabled;
|
||||
|
||||
internal GazeTargetItem NonInvokeGazeTargetItem { get; }
|
||||
|
||||
internal GazeFeedbackPopupFactory GazeFeedbackPopupFactory { get; } = new GazeFeedbackPopupFactory();
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_activeHitTargetTimes.Clear();
|
||||
_gazeHistory.Clear();
|
||||
|
||||
_maxHistoryTime = DEFAULT_MAX_HISTORY_DURATION;
|
||||
}
|
||||
|
||||
internal void SetElementStateDelay(UIElement element, PointerState relevantState, TimeSpan stateDelay)
|
||||
{
|
||||
var property = GetProperty(relevantState);
|
||||
element.SetValue(property, stateDelay);
|
||||
|
||||
// fix up _maxHistoryTime in case the new param exceeds the history length we are currently tracking
|
||||
var dwellTime = GetElementStateDelay(element, PointerState.Dwell);
|
||||
var repeatTime = GetElementStateDelay(element, PointerState.DwellRepeat);
|
||||
_maxHistoryTime = new TimeSpan(2 * Math.Max(dwellTime.Ticks, repeatTime.Ticks));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the parent to inherit properties from.
|
||||
/// </summary>
|
||||
private static UIElement GetInheritenceParent(UIElement child)
|
||||
{
|
||||
// The result value.
|
||||
object parent = null;
|
||||
|
||||
// Get the automation peer...
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(child);
|
||||
if (peer != null)
|
||||
{
|
||||
// ...if it exists, get the peer's parent...
|
||||
var peerParent = peer.Navigate(AutomationNavigationDirection.Parent) as FrameworkElementAutomationPeer;
|
||||
if (peerParent != null)
|
||||
{
|
||||
// ...and if it has a parent, get the corresponding object.
|
||||
parent = peerParent.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
// If the above failed to find a parent...
|
||||
if (parent == null)
|
||||
{
|
||||
// ...use the visual parent.
|
||||
parent = VisualTreeHelper.GetParent(child);
|
||||
}
|
||||
|
||||
// Safely pun the value we found to a UIElement reference.
|
||||
return parent as UIElement;
|
||||
}
|
||||
|
||||
internal TimeSpan GetElementStateDelay(UIElement element, DependencyProperty property, TimeSpan defaultValue)
|
||||
{
|
||||
UIElement walker = element;
|
||||
object valueAtWalker = walker.GetValue(property);
|
||||
|
||||
while (GazeInput.UnsetTimeSpan.Equals(valueAtWalker) && walker != null)
|
||||
{
|
||||
walker = GetInheritenceParent(walker);
|
||||
|
||||
if (walker != null)
|
||||
{
|
||||
valueAtWalker = walker.GetValue(property);
|
||||
}
|
||||
}
|
||||
|
||||
var ticks = GazeInput.UnsetTimeSpan.Equals(valueAtWalker) ? defaultValue : (TimeSpan)valueAtWalker;
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
internal TimeSpan GetElementStateDelay(UIElement element, PointerState pointerState)
|
||||
{
|
||||
var property = GetProperty(pointerState);
|
||||
var defaultValue = GetDefaultPropertyValue(pointerState);
|
||||
var ticks = GetElementStateDelay(element, property, defaultValue);
|
||||
|
||||
switch (pointerState)
|
||||
{
|
||||
case PointerState.Dwell:
|
||||
case PointerState.DwellRepeat:
|
||||
_maxHistoryTime = new TimeSpan(Math.Max(_maxHistoryTime.Ticks, 2 * ticks.Ticks));
|
||||
break;
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
// Provide a configurable delay for when the EyesOffDelay event is fired
|
||||
// GOTCHA: this value requires that _eyesOffTimer is instantiated so that it
|
||||
// can update the timer interval
|
||||
internal TimeSpan EyesOffDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
return _eyesOffDelay;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_eyesOffDelay = value;
|
||||
|
||||
// convert GAZE_IDLE_TIME units (microseconds) to 100-nanosecond units used
|
||||
// by TimeSpan struct
|
||||
_eyesOffTimer.Interval = EyesOffDelay;
|
||||
}
|
||||
}
|
||||
|
||||
// Pluggable filter for eye tracking sample data. This defaults to being set to the
|
||||
// NullFilter which performs no filtering of input samples.
|
||||
internal IGazeFilter Filter { get; set; }
|
||||
|
||||
internal bool IsCursorVisible
|
||||
{
|
||||
get { return _gazeCursor.IsCursorVisible; }
|
||||
set { _gazeCursor.IsCursorVisible = value; }
|
||||
}
|
||||
|
||||
internal int CursorRadius
|
||||
{
|
||||
get { return _gazeCursor.CursorRadius; }
|
||||
set { _gazeCursor.CursorRadius = value; }
|
||||
}
|
||||
|
||||
internal bool IsSwitchEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this GazePointer should always be activated or not.
|
||||
/// </summary>
|
||||
public bool IsAlwaysActivated { get; set; }
|
||||
|
||||
private static GazePointer _instance = null;
|
||||
|
||||
internal static GazePointer Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new GazePointer();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddRoot(int proxyId)
|
||||
{
|
||||
_roots.Insert(0, proxyId);
|
||||
|
||||
if (_roots.Count == 1)
|
||||
{
|
||||
_isShuttingDown = false;
|
||||
InitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveRoot(int proxyId)
|
||||
{
|
||||
int index;
|
||||
if ((index = _roots.IndexOf(proxyId)) != -1)
|
||||
{
|
||||
_roots.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Invalid proxyId when trying to remove a Root from the GazeProxy");
|
||||
}
|
||||
|
||||
if (_roots.Count == 0)
|
||||
{
|
||||
_isShuttingDown = true;
|
||||
_gazeCursor.IsGazeEntered = false;
|
||||
DeinitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsDeviceAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
return _devices.Count != 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal event EventHandler<object> IsDeviceAvailableChanged;
|
||||
|
||||
private GazePointer()
|
||||
{
|
||||
NonInvokeGazeTargetItem = new NonInvokeGazeTargetItem();
|
||||
|
||||
// Default to not filtering sample data
|
||||
Filter = new NullFilter();
|
||||
|
||||
_gazeCursor = new GazeCursor();
|
||||
|
||||
// timer that gets called back if there gaze samples haven't been received in a while
|
||||
_eyesOffTimer = new DispatcherTimer();
|
||||
_eyesOffTimer.Tick += OnEyesOff;
|
||||
|
||||
// provide a default of GAZE_IDLE_TIME microseconds to fire eyes off
|
||||
EyesOffDelay = GAZE_IDLE_TIME;
|
||||
|
||||
InitializeHistogram();
|
||||
|
||||
_devices = new List<GazeDevicePreview>();
|
||||
_watcher = GazeInputSourcePreview.CreateWatcher();
|
||||
_watcher.Added += OnDeviceAdded;
|
||||
_watcher.Removed += OnDeviceRemoved;
|
||||
_watcher.Start();
|
||||
}
|
||||
|
||||
private bool _initialized;
|
||||
private bool _isShuttingDown;
|
||||
|
||||
private static DependencyProperty GetProperty(PointerState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PointerState.Fixation: return GazeInput.FixationDurationProperty;
|
||||
case PointerState.Dwell: return GazeInput.DwellDurationProperty;
|
||||
case PointerState.DwellRepeat: return GazeInput.DwellRepeatDurationProperty;
|
||||
case PointerState.Enter: return GazeInput.ThresholdDurationProperty;
|
||||
case PointerState.Exit: return GazeInput.ThresholdDurationProperty;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetDefaultPropertyValue(PointerState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PointerState.Fixation: return _defaultFixation;
|
||||
case PointerState.Dwell: return _defaultDwell;
|
||||
case PointerState.DwellRepeat: return _defaultRepeatDelay;
|
||||
case PointerState.Enter: return _defaultThreshold;
|
||||
case PointerState.Exit: return _defaultThreshold;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeHistogram()
|
||||
{
|
||||
_activeHitTargetTimes = new List<GazeTargetItem>();
|
||||
|
||||
_offScreenElement = new UserControl();
|
||||
SetElementStateDelay(_offScreenElement, PointerState.Fixation, _defaultFixation);
|
||||
SetElementStateDelay(_offScreenElement, PointerState.Dwell, _defaultDwell);
|
||||
|
||||
_maxHistoryTime = DEFAULT_MAX_HISTORY_DURATION; // maintain about 3 seconds of history (in microseconds)
|
||||
_gazeHistory = new List<GazeHistoryItem>();
|
||||
}
|
||||
|
||||
private void InitializeGazeInputSource()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
if (_roots.Count != 0 && _devices.Count != 0)
|
||||
{
|
||||
if (_gazeInputSource == null)
|
||||
{
|
||||
_gazeInputSource = GazeInputSourcePreview.GetForCurrentView();
|
||||
}
|
||||
|
||||
if (_gazeInputSource != null)
|
||||
{
|
||||
_gazeInputSource.GazeEntered += OnGazeEntered;
|
||||
_gazeInputSource.GazeMoved += OnGazeMoved;
|
||||
_gazeInputSource.GazeExited += OnGazeExited;
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeinitializeGazeInputSource()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
_initialized = false;
|
||||
|
||||
_gazeInputSource.GazeEntered -= OnGazeEntered;
|
||||
_gazeInputSource.GazeMoved -= OnGazeMoved;
|
||||
_gazeInputSource.GazeExited -= OnGazeExited;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateGazeTargetItem(GazeTargetItem target)
|
||||
{
|
||||
if (_activeHitTargetTimes.IndexOf(target) == -1)
|
||||
{
|
||||
_activeHitTargetTimes.Add(target);
|
||||
|
||||
// calculate the time that the first DwellRepeat needs to be fired after. this will be updated every time a DwellRepeat is
|
||||
// fired to keep track of when the next one is to be fired after that.
|
||||
var nextStateTime = GetElementStateDelay(target.TargetElement, PointerState.Enter);
|
||||
|
||||
target.Reset(nextStateTime);
|
||||
}
|
||||
}
|
||||
|
||||
private GazeTargetItem GetHitTarget(Point gazePoint)
|
||||
{
|
||||
GazeTargetItem invokable = null;
|
||||
|
||||
if (!IsAlwaysActivated)
|
||||
{
|
||||
invokable = NonInvokeGazeTargetItem;
|
||||
}
|
||||
|
||||
switch (Window.Current.CoreWindow.ActivationMode)
|
||||
{
|
||||
case CoreWindowActivationMode.ActivatedInForeground:
|
||||
case CoreWindowActivationMode.ActivatedNotForeground:
|
||||
var elements = VisualTreeHelper.FindElementsInHostCoordinates(gazePoint, null, false);
|
||||
var element = elements.FirstOrDefault();
|
||||
|
||||
invokable = null;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
invokable = GazeTargetItem.GetOrCreate(element);
|
||||
|
||||
while (element != null && !invokable.IsInvokable)
|
||||
{
|
||||
element = VisualTreeHelper.GetParent(element) as UIElement;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
invokable = GazeTargetItem.GetOrCreate(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (element == null || !invokable.IsInvokable)
|
||||
{
|
||||
invokable = NonInvokeGazeTargetItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
Interaction interaction;
|
||||
do
|
||||
{
|
||||
interaction = GazeInput.GetInteraction(element);
|
||||
if (interaction == Interaction.Inherited)
|
||||
{
|
||||
element = GetInheritenceParent(element);
|
||||
}
|
||||
}
|
||||
while (interaction == Interaction.Inherited && element != null);
|
||||
|
||||
if (interaction == Interaction.Inherited)
|
||||
{
|
||||
interaction = GazeInput.Interaction;
|
||||
}
|
||||
|
||||
if (interaction != Interaction.Enabled)
|
||||
{
|
||||
invokable = NonInvokeGazeTargetItem;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return invokable;
|
||||
}
|
||||
|
||||
private GazeTargetItem ResolveHitTarget(Point gazePoint, TimeSpan timestamp)
|
||||
{
|
||||
// TODO: The existence of a GazeTargetItem should be used to indicate that
|
||||
// the target item is invokable. The method of invocation should be stored
|
||||
// within the GazeTargetItem when it is created and not recalculated when
|
||||
// subsequently needed.
|
||||
|
||||
// create GazeHistoryItem to deal with this sample
|
||||
var target = GetHitTarget(gazePoint);
|
||||
GazeHistoryItem historyItem = default;
|
||||
historyItem.HitTarget = target;
|
||||
historyItem.Timestamp = timestamp;
|
||||
historyItem.Duration = TimeSpan.Zero;
|
||||
Debug.Assert(historyItem.HitTarget != null, "historyItem.HitTarget should not be null");
|
||||
|
||||
// create new GazeTargetItem with a (default) total elapsed time of zero if one does not exist already.
|
||||
// this ensures that there will always be an entry for target elements in the code below.
|
||||
ActivateGazeTargetItem(target);
|
||||
target.LastTimestamp = timestamp;
|
||||
|
||||
// find elapsed time since we got the last hit target
|
||||
historyItem.Duration = timestamp - _lastTimestamp;
|
||||
if (historyItem.Duration > MAX_SINGLE_SAMPLE_DURATION)
|
||||
{
|
||||
historyItem.Duration = MAX_SINGLE_SAMPLE_DURATION;
|
||||
}
|
||||
|
||||
_gazeHistory.Add(historyItem);
|
||||
|
||||
// update the time this particular hit target has accumulated
|
||||
target.DetailedTime += historyItem.Duration;
|
||||
|
||||
// drop the oldest samples from the list until we have samples only
|
||||
// within the window we are monitoring
|
||||
//
|
||||
// historyItem is the last item we just appended a few lines above.
|
||||
for (var evOldest = _gazeHistory[0];
|
||||
historyItem.Timestamp - evOldest.Timestamp > _maxHistoryTime;
|
||||
evOldest = _gazeHistory[0])
|
||||
{
|
||||
_gazeHistory.RemoveAt(0);
|
||||
|
||||
// subtract the duration obtained from the oldest sample in _gazeHistory
|
||||
var targetItem = evOldest.HitTarget;
|
||||
Debug.Assert(targetItem.DetailedTime - evOldest.Duration >= TimeSpan.Zero, "DetailedTime of targetItem should be less than oldest history Duration");
|
||||
targetItem.DetailedTime -= evOldest.Duration;
|
||||
if (targetItem.ElementState != PointerState.PreEnter)
|
||||
{
|
||||
targetItem.OverflowTime += evOldest.Duration;
|
||||
}
|
||||
}
|
||||
|
||||
_lastTimestamp = timestamp;
|
||||
|
||||
// Return the most recent hit target
|
||||
// Intuition would tell us that we should return NOT the most recent
|
||||
// hitTarget, but the one with the most accumulated time in
|
||||
// in the maintained history. But the effect of that is that
|
||||
// the user will feel that they have clicked on the wrong thing
|
||||
// when they are looking at something else.
|
||||
// That is why we return the most recent hitTarget so that
|
||||
// when its dwell time has elapsed, it will be invoked
|
||||
return target;
|
||||
}
|
||||
|
||||
private void CheckIfExiting(TimeSpan curTimestamp)
|
||||
{
|
||||
for (int index = 0; index < _activeHitTargetTimes.Count; index++)
|
||||
{
|
||||
var targetItem = _activeHitTargetTimes[index];
|
||||
var targetElement = targetItem.TargetElement;
|
||||
var exitDelay = GetElementStateDelay(targetElement, PointerState.Exit);
|
||||
|
||||
var idleDuration = curTimestamp - targetItem.LastTimestamp;
|
||||
if (targetItem.ElementState != PointerState.PreEnter && idleDuration > exitDelay)
|
||||
{
|
||||
targetItem.ElementState = PointerState.PreEnter;
|
||||
|
||||
// Transitioning to exit - clear the cached fixated element
|
||||
_currentlyFixatedElement = null;
|
||||
|
||||
RaiseGazePointerEvent(targetItem, PointerState.Exit, targetItem.ElapsedTime);
|
||||
targetItem.GiveFeedback();
|
||||
|
||||
_activeHitTargetTimes.RemoveAt(index);
|
||||
|
||||
// remove all history samples referring to deleted hit target
|
||||
for (int i = 0; i < _gazeHistory.Count;)
|
||||
{
|
||||
var hitTarget = _gazeHistory[i].HitTarget;
|
||||
if (hitTarget.TargetElement == targetElement)
|
||||
{
|
||||
_gazeHistory.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// return because only one element can be exited at a time and at this point
|
||||
// we have done everything that we can do
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private const string[] pointerStates =
|
||||
{
|
||||
"Exit",
|
||||
"PreEnter",
|
||||
"Enter",
|
||||
"Fixation",
|
||||
"Dwel",
|
||||
"DwellRepeat"
|
||||
};
|
||||
*/
|
||||
|
||||
private void RaiseGazePointerEvent(GazeTargetItem target, PointerState state, TimeSpan elapsedTime)
|
||||
{
|
||||
var control = target?.TargetElement;
|
||||
|
||||
// assert(target != _rootElement);
|
||||
var gpea = new StateChangedEventArgs(control, state, elapsedTime);
|
||||
|
||||
/*
|
||||
* if (control is Button buttonObj && buttonObj.Content != null)
|
||||
* {
|
||||
* var buttonText = buttonObj.Content as string;
|
||||
* Debug.WriteLine("GPE: {0} . {1}, {2}", buttonText, PointerStates[(int)state], elapsedTime);
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* Debug.WriteLine("GPE: 0x%08x . %s, %d", target != null ? target.GetHashCode() : 0, PointerStates[(int)state], elapsedTime);
|
||||
* }
|
||||
*/
|
||||
|
||||
var gazeElement = target != null ? GazeInput.GetGazeElement(control) : null;
|
||||
|
||||
if (gazeElement != null)
|
||||
{
|
||||
gazeElement.RaiseStateChanged(control, gpea);
|
||||
}
|
||||
|
||||
if (state == PointerState.Dwell)
|
||||
{
|
||||
var handled = false;
|
||||
|
||||
if (gazeElement != null)
|
||||
{
|
||||
var args = new DwellInvokedRoutedEventArgs();
|
||||
gazeElement.RaiseInvoked(control, args);
|
||||
handled = args.Handled;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
target.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGazeEntered(
|
||||
GazeInputSourcePreview provider,
|
||||
GazeEnteredPreviewEventArgs args)
|
||||
{
|
||||
// Debug.WriteLine("Entered at %ld", args.CurrentPoint.Timestamp);
|
||||
_gazeCursor.IsGazeEntered = true;
|
||||
}
|
||||
|
||||
private void OnGazeMoved(
|
||||
GazeInputSourcePreview provider,
|
||||
GazeMovedPreviewEventArgs args)
|
||||
{
|
||||
if (!_isShuttingDown)
|
||||
{
|
||||
var intermediatePoints = args.GetIntermediatePoints();
|
||||
foreach (var point in intermediatePoints)
|
||||
{
|
||||
var position = point.EyeGazePosition;
|
||||
if (position != null)
|
||||
{
|
||||
_gazeCursor.IsGazeEntered = true;
|
||||
ProcessGazePoint(new TimeSpan((long)point.Timestamp * 10), position.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Debug.WriteLine("Null position eaten at %ld", point.Timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGazeExited(
|
||||
GazeInputSourcePreview provider,
|
||||
GazeExitedPreviewEventArgs args)
|
||||
{
|
||||
// Debug.WriteLine("Exited at %ld", args.CurrentPoint.Timestamp);
|
||||
_gazeCursor.IsGazeEntered = false;
|
||||
}
|
||||
|
||||
private void ProcessGazePoint(TimeSpan timestamp, Point position)
|
||||
{
|
||||
var ea = new GazeFilterArgs(position, timestamp);
|
||||
|
||||
var fa = Filter.Update(ea);
|
||||
_gazeCursor.Position = fa.Location;
|
||||
|
||||
if (_gazeEventCount != 0)
|
||||
{
|
||||
_gazeEventArgs.Set(fa.Location, timestamp);
|
||||
_gazeEvent?.Invoke(this, _gazeEventArgs);
|
||||
if (_gazeEventArgs.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var targetItem = ResolveHitTarget(fa.Location, fa.Timestamp);
|
||||
Debug.Assert(targetItem != null, "targetItem is null when processing gaze point");
|
||||
|
||||
// Debug.WriteLine("ProcessGazePoint. [{0}, {1}], {2}", (int)fa.Location.X, (int)fa.Location.Y, fa.Timestamp);
|
||||
|
||||
// check to see if any element in _hitTargetTimes needs an exit event fired.
|
||||
// this ensures that all exit events are fired before enter event
|
||||
CheckIfExiting(fa.Timestamp);
|
||||
|
||||
PointerState nextState = (PointerState)((int)targetItem.ElementState + 1);
|
||||
|
||||
// Debug.WriteLine(targetItem.TargetElement.ToString());
|
||||
// Debug.WriteLine("\tState={0}, Elapsed={1}, NextStateTime={2}", targetItem.ElementState, targetItem.ElapsedTime, targetItem.NextStateTime);
|
||||
if (targetItem.ElapsedTime > targetItem.NextStateTime)
|
||||
{
|
||||
var prevStateTime = targetItem.NextStateTime;
|
||||
////Debug.WriteLine(prevStateTime);
|
||||
|
||||
// prevent targetItem from ever actually transitioning into the DwellRepeat state so as
|
||||
// to continuously emit the DwellRepeat event
|
||||
if (nextState != PointerState.DwellRepeat)
|
||||
{
|
||||
targetItem.ElementState = nextState;
|
||||
nextState = (PointerState)((int)nextState + 1); // nextState++
|
||||
targetItem.NextStateTime += GetElementStateDelay(targetItem.TargetElement, nextState);
|
||||
|
||||
if (targetItem.ElementState == PointerState.Dwell)
|
||||
{
|
||||
targetItem.NextStateTime += GetElementStateDelay(targetItem.TargetElement, GazeInput.RepeatDelayDurationProperty, _defaultDwellRepeatDelay);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// move the NextStateTime by one dwell period, while continuing to stay in Dwell state
|
||||
targetItem.NextStateTime += GetElementStateDelay(targetItem.TargetElement, PointerState.DwellRepeat);
|
||||
}
|
||||
|
||||
if (targetItem.ElementState == PointerState.Dwell)
|
||||
{
|
||||
targetItem.RepeatCount++;
|
||||
if (targetItem.MaxDwellRepeatCount < targetItem.RepeatCount)
|
||||
{
|
||||
targetItem.NextStateTime = new TimeSpan(long.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem.ElementState == PointerState.Fixation)
|
||||
{
|
||||
// Cache the fixated item
|
||||
_currentlyFixatedElement = targetItem;
|
||||
|
||||
// We are about to transition into the Dwell state
|
||||
// If switch input is enabled, make sure dwell never completes
|
||||
// via eye gaze
|
||||
if (IsSwitchEnabled)
|
||||
{
|
||||
// Don't allow the next state (Dwell) to progress
|
||||
targetItem.NextStateTime = new TimeSpan(long.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
RaiseGazePointerEvent(targetItem, targetItem.ElementState, targetItem.ElapsedTime);
|
||||
}
|
||||
|
||||
targetItem.GiveFeedback();
|
||||
|
||||
_eyesOffTimer.Start();
|
||||
_lastTimestamp = fa.Timestamp;
|
||||
}
|
||||
|
||||
private void OnEyesOff(object sender, object ea)
|
||||
{
|
||||
_eyesOffTimer.Stop();
|
||||
|
||||
CheckIfExiting(_lastTimestamp + EyesOffDelay);
|
||||
RaiseGazePointerEvent(null, PointerState.Enter, EyesOffDelay);
|
||||
}
|
||||
|
||||
private void OnDeviceAdded(GazeDeviceWatcherPreview sender, GazeDeviceWatcherAddedPreviewEventArgs args)
|
||||
{
|
||||
_devices.Add(args.Device);
|
||||
|
||||
if (_devices.Count == 1)
|
||||
{
|
||||
IsDeviceAvailableChanged?.Invoke(null, null);
|
||||
|
||||
InitializeGazeInputSource();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeviceRemoved(GazeDeviceWatcherPreview sender, GazeDeviceWatcherRemovedPreviewEventArgs args)
|
||||
{
|
||||
var index = 0;
|
||||
while (index < _devices.Count && _devices.ElementAt(index).Id != args.Device.Id)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < _devices.Count)
|
||||
{
|
||||
_devices.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_devices.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (_devices.Count == 0)
|
||||
{
|
||||
IsDeviceAvailableChanged?.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<int> _roots = new List<int>();
|
||||
|
||||
private readonly DispatcherTimer _eyesOffTimer;
|
||||
|
||||
private readonly GazeCursor _gazeCursor;
|
||||
|
||||
private readonly GazeDeviceWatcherPreview _watcher;
|
||||
private readonly List<GazeDevicePreview> _devices;
|
||||
|
||||
private readonly GazeEventArgs _gazeEventArgs = new GazeEventArgs();
|
||||
|
||||
private TimeSpan _eyesOffDelay;
|
||||
|
||||
// _offScreenElement is a pseudo-element that represents the area outside
|
||||
// the screen so we can track how long the user has been looking outside
|
||||
// the screen and appropriately trigger the EyesOff event
|
||||
private Control _offScreenElement;
|
||||
|
||||
// The value is the total time that FrameworkElement has been gazed at
|
||||
private List<GazeTargetItem> _activeHitTargetTimes;
|
||||
|
||||
// A vector to track the history of observed gaze targets
|
||||
private List<GazeHistoryItem> _gazeHistory;
|
||||
private TimeSpan _maxHistoryTime;
|
||||
|
||||
// Used to determine if exit events need to be fired by adding GAZE_IDLE_TIME to the last
|
||||
// saved timestamp
|
||||
private TimeSpan _lastTimestamp;
|
||||
|
||||
private GazeInputSourcePreview _gazeInputSource;
|
||||
|
||||
private TimeSpan _defaultFixation = DEFAULT_FIXATION_DELAY;
|
||||
private TimeSpan _defaultDwell = DEFAULT_DWELL_DELAY;
|
||||
private TimeSpan _defaultDwellRepeatDelay = DEFAULT_DWELL_REPEAT_DELAY;
|
||||
private TimeSpan _defaultRepeatDelay = DEFAULT_REPEAT_DELAY;
|
||||
private TimeSpan _defaultThreshold = DEFAULT_THRESHOLD_DELAY;
|
||||
private GazeTargetItem _currentlyFixatedElement;
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GazeCursor.h"
|
||||
#include "GazeFeedbackPopupFactory.h"
|
||||
#include "GazeEventArgs.h"
|
||||
#include "IGazeFilter.h"
|
||||
#include "Interaction.h"
|
||||
#include "PointerState.h"
|
||||
|
||||
using namespace Platform::Collections;
|
||||
using namespace Windows::Devices::Input::Preview;
|
||||
using namespace Windows::Foundation;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
ref class GazeTargetItem;
|
||||
ref struct GazeHistoryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Class of singleton object coordinating gaze input.
|
||||
/// </summary>
|
||||
public ref class GazePointer sealed
|
||||
{
|
||||
private:
|
||||
|
||||
// units in microseconds
|
||||
const TimeSpan DEFAULT_FIXATION_DELAY = TimeSpanFromMicroseconds(350000);
|
||||
const TimeSpan DEFAULT_DWELL_DELAY = TimeSpanFromMicroseconds(400000);
|
||||
const TimeSpan DEFAULT_DWELL_REPEAT_DELAY = TimeSpanFromMicroseconds(400000);
|
||||
const TimeSpan DEFAULT_REPEAT_DELAY = TimeSpanFromMicroseconds(400000);
|
||||
const TimeSpan DEFAULT_THRESHOLD_DELAY = TimeSpanFromMicroseconds(50000);
|
||||
const TimeSpan DEFAULT_MAX_HISTORY_DURATION = TimeSpanFromMicroseconds(3000000);
|
||||
const TimeSpan MAX_SINGLE_SAMPLE_DURATION = TimeSpanFromMicroseconds(100000);
|
||||
|
||||
const TimeSpan GAZE_IDLE_TIME{ 25000000 };
|
||||
|
||||
public:
|
||||
virtual ~GazePointer();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a settings collection into GazePointer.
|
||||
/// </summary>
|
||||
void LoadSettings(ValueSet^ settings);
|
||||
|
||||
/// <summary>
|
||||
/// When in switch mode, will issue a click on the currently fixated element
|
||||
/// </summary>
|
||||
void Click();
|
||||
|
||||
/// <summary>
|
||||
/// Run device calibration.
|
||||
/// </summary>
|
||||
IAsyncOperation<bool>^ RequestCalibrationAsync();
|
||||
|
||||
event EventHandler<GazeEventArgs^>^ GazeEvent
|
||||
{
|
||||
EventRegistrationToken add(EventHandler<GazeEventArgs^>^ handler);
|
||||
void remove(EventRegistrationToken token);
|
||||
void raise(Object^ sender, GazeEventArgs^ e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UIElement representing the cursor.
|
||||
/// </summary>
|
||||
property UIElement^ CursorElement
|
||||
{
|
||||
UIElement^ get() { return _gazeCursor->PopupChild; }
|
||||
void set(UIElement^ value) { _gazeCursor->PopupChild = value; }
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
event EventHandler<GazeEventArgs^>^ _gazeEvent;
|
||||
GazeEventArgs^ const _gazeEventArgs = ref new GazeEventArgs();
|
||||
int _gazeEventCount = 0;
|
||||
|
||||
internal:
|
||||
Brush^ _enterBrush = nullptr;
|
||||
|
||||
Brush^ _progressBrush = ref new SolidColorBrush(Colors::Green);
|
||||
|
||||
Brush^ _completeBrush = ref new SolidColorBrush(Colors::Red);
|
||||
|
||||
double _dwellStrokeThickness = 2;
|
||||
|
||||
Interaction _interaction = Interaction::Disabled;
|
||||
|
||||
GazeTargetItem^ _nonInvokeGazeTargetItem;
|
||||
|
||||
GazeFeedbackPopupFactory^ _gazeFeedbackPopupFactory = ref new GazeFeedbackPopupFactory();
|
||||
|
||||
internal:
|
||||
void Reset();
|
||||
void SetElementStateDelay(UIElement ^element, PointerState pointerState, TimeSpan stateDelay);
|
||||
TimeSpan GetElementStateDelay(UIElement ^element, DependencyProperty^ property, TimeSpan defaultValue);
|
||||
TimeSpan GetElementStateDelay(UIElement^ element, PointerState pointerState);
|
||||
|
||||
// Provide a configurable delay for when the EyesOffDelay event is fired
|
||||
// GOTCHA: this value requires that _eyesOffTimer is instantiated so that it
|
||||
// can update the timer interval
|
||||
property TimeSpan EyesOffDelay
|
||||
{
|
||||
TimeSpan get() { return _eyesOffDelay; }
|
||||
void set(TimeSpan value)
|
||||
{
|
||||
_eyesOffDelay = value;
|
||||
|
||||
// convert GAZE_IDLE_TIME units (microseconds) to 100-nanosecond units used
|
||||
// by TimeSpan struct
|
||||
_eyesOffTimer->Interval = EyesOffDelay;
|
||||
}
|
||||
}
|
||||
|
||||
// Pluggable filter for eye tracking sample data. This defaults to being set to the
|
||||
// NullFilter which performs no filtering of input samples.
|
||||
property IGazeFilter^ Filter;
|
||||
|
||||
property bool IsCursorVisible
|
||||
{
|
||||
bool get() { return _gazeCursor->IsCursorVisible; }
|
||||
void set(bool value) { _gazeCursor->IsCursorVisible = value; }
|
||||
}
|
||||
|
||||
property int CursorRadius
|
||||
{
|
||||
int get() { return _gazeCursor->CursorRadius; }
|
||||
void set(int value) { _gazeCursor->CursorRadius = value; }
|
||||
}
|
||||
|
||||
property bool IsSwitchEnabled
|
||||
{
|
||||
bool get() { return _isSwitchEnabled; }
|
||||
void set(bool value) { _isSwitchEnabled = value; }
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
property bool IsAlwaysActivated
|
||||
{
|
||||
bool get() { return _isAlwaysActivated; }
|
||||
void set(bool value) { _isAlwaysActivated = value; }
|
||||
}
|
||||
|
||||
internal:
|
||||
|
||||
static property GazePointer^ Instance { GazePointer^ get(); }
|
||||
EventRegistrationToken _unloadedToken;
|
||||
|
||||
void AddRoot(int proxyId);
|
||||
void RemoveRoot(int proxyId);
|
||||
|
||||
|
||||
property bool IsDeviceAvailable { bool get() { return _devices->Size != 0; }}
|
||||
event EventHandler<Object^>^ IsDeviceAvailableChanged;
|
||||
|
||||
private:
|
||||
|
||||
GazePointer();
|
||||
|
||||
private:
|
||||
|
||||
bool _initialized;
|
||||
bool _isShuttingDown;
|
||||
|
||||
TimeSpan GetDefaultPropertyValue(PointerState state);
|
||||
|
||||
void InitializeHistogram();
|
||||
void InitializeGazeInputSource();
|
||||
void DeinitializeGazeInputSource();
|
||||
|
||||
void ActivateGazeTargetItem(GazeTargetItem^ target);
|
||||
GazeTargetItem^ GetHitTarget(Point gazePoint);
|
||||
GazeTargetItem^ ResolveHitTarget(Point gazePoint, TimeSpan timestamp);
|
||||
|
||||
void CheckIfExiting(TimeSpan curTimestamp);
|
||||
void RaiseGazePointerEvent(GazeTargetItem^ target, PointerState state, TimeSpan elapsedTime);
|
||||
|
||||
void OnGazeEntered(
|
||||
GazeInputSourcePreview^ provider,
|
||||
GazeEnteredPreviewEventArgs^ args);
|
||||
void OnGazeMoved(
|
||||
GazeInputSourcePreview^ provider,
|
||||
GazeMovedPreviewEventArgs^ args);
|
||||
void OnGazeExited(
|
||||
GazeInputSourcePreview^ provider,
|
||||
GazeExitedPreviewEventArgs^ args);
|
||||
|
||||
void ProcessGazePoint(TimeSpan timestamp, Point position);
|
||||
|
||||
void OnEyesOff(Object ^sender, Object ^ea);
|
||||
|
||||
void OnDeviceAdded(GazeDeviceWatcherPreview^ sender, GazeDeviceWatcherAddedPreviewEventArgs^ args);
|
||||
void OnDeviceRemoved(GazeDeviceWatcherPreview^ sender, GazeDeviceWatcherRemovedPreviewEventArgs^ args);
|
||||
|
||||
private:
|
||||
Vector<int>^ _roots = ref new Vector<int>();
|
||||
|
||||
TimeSpan _eyesOffDelay;
|
||||
|
||||
GazeCursor^ _gazeCursor;
|
||||
DispatcherTimer^ _eyesOffTimer;
|
||||
|
||||
// _offScreenElement is a pseudo-element that represents the area outside
|
||||
// the screen so we can track how long the user has been looking outside
|
||||
// the screen and appropriately trigger the EyesOff event
|
||||
Control^ _offScreenElement;
|
||||
|
||||
// The value is the total time that FrameworkElement has been gazed at
|
||||
Vector<GazeTargetItem^>^ _activeHitTargetTimes;
|
||||
|
||||
// A vector to track the history of observed gaze targets
|
||||
Vector<GazeHistoryItem^>^ _gazeHistory;
|
||||
TimeSpan _maxHistoryTime;
|
||||
|
||||
// Used to determine if exit events need to be fired by adding GAZE_IDLE_TIME to the last
|
||||
// saved timestamp
|
||||
TimeSpan _lastTimestamp;
|
||||
|
||||
GazeInputSourcePreview^ _gazeInputSource;
|
||||
EventRegistrationToken _gazeEnteredToken;
|
||||
EventRegistrationToken _gazeMovedToken;
|
||||
EventRegistrationToken _gazeExitedToken;
|
||||
|
||||
GazeDeviceWatcherPreview^ _watcher;
|
||||
Vector<GazeDevicePreview^>^ _devices;
|
||||
EventRegistrationToken _deviceAddedToken;
|
||||
EventRegistrationToken _deviceRemovedToken;
|
||||
|
||||
TimeSpan _defaultFixation = DEFAULT_FIXATION_DELAY;
|
||||
TimeSpan _defaultDwell = DEFAULT_DWELL_DELAY;
|
||||
TimeSpan _defaultDwellRepeatDelay = DEFAULT_DWELL_REPEAT_DELAY;
|
||||
TimeSpan _defaultRepeatDelay = DEFAULT_REPEAT_DELAY;
|
||||
TimeSpan _defaultThreshold = DEFAULT_THRESHOLD_DELAY;
|
||||
|
||||
bool _isAlwaysActivated;
|
||||
bool _isSwitchEnabled;
|
||||
GazeTargetItem^ _currentlyFixatedElement;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,139 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazePointerProxy.h"
|
||||
#include "GazePointer.h"
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// The IsLoaded heuristic for testing whether a FrameworkElement is in the visual tree.
|
||||
/// </summary>
|
||||
static bool IsLoadedHeuristic(FrameworkElement^ element)
|
||||
{
|
||||
bool isLoaded;
|
||||
|
||||
// element.Loaded has already happened if it is in the visual tree...
|
||||
auto parent = VisualTreeHelper::GetParent(element);
|
||||
if (parent != nullptr)
|
||||
{
|
||||
isLoaded = true;
|
||||
}
|
||||
// ...or...
|
||||
else
|
||||
{
|
||||
// ...if the element is a dynamically created Popup that has been opened.
|
||||
auto popup = dynamic_cast<Popup^>(element);
|
||||
isLoaded = popup != nullptr && popup->IsOpen;
|
||||
}
|
||||
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
DependencyProperty^ GazePointerProxy::GazePointerProxyProperty::get()
|
||||
{
|
||||
// The attached property registration.
|
||||
static auto value = DependencyProperty::RegisterAttached("_GazePointerProxy", GazePointerProxy::typeid, GazePointerProxy::typeid,
|
||||
ref new PropertyMetadata(nullptr));
|
||||
return value;
|
||||
}
|
||||
|
||||
GazePointerProxy::GazePointerProxy(FrameworkElement^ element)
|
||||
{
|
||||
static int lastId = 0;
|
||||
lastId++;
|
||||
_uniqueId = lastId;
|
||||
|
||||
_isLoaded = IsLoadedHeuristic(element);
|
||||
|
||||
// Start watching for the element to enter and leave the visual tree.
|
||||
element->Loaded += ref new RoutedEventHandler(this, &GazePointerProxy::OnLoaded);
|
||||
element->Unloaded += ref new RoutedEventHandler(this, &GazePointerProxy::OnUnloaded);
|
||||
}
|
||||
|
||||
void GazePointerProxy::SetInteraction(FrameworkElement^ element, Interaction value)
|
||||
{
|
||||
// Get or create a GazePointerProxy for element.
|
||||
auto proxy = safe_cast<GazePointerProxy^>(element->GetValue(GazePointerProxyProperty));
|
||||
if (proxy == nullptr)
|
||||
{
|
||||
proxy = ref new GazePointerProxy(element);
|
||||
element->SetValue(GazePointerProxyProperty, proxy);
|
||||
}
|
||||
|
||||
// Set the proxy's _isEnabled value.
|
||||
proxy->SetIsEnabled(element, value == Interaction::Enabled);
|
||||
}
|
||||
|
||||
void GazePointerProxy::SetIsEnabled(Object^ sender, bool value)
|
||||
{
|
||||
// If we have a new value...
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
// ...record the new value.
|
||||
_isEnabled = value;
|
||||
|
||||
// If we are in the visual tree...
|
||||
if (_isLoaded)
|
||||
{
|
||||
// ...if we're being enabled...
|
||||
if (value)
|
||||
{
|
||||
// ...count the element in...
|
||||
GazePointer::Instance->AddRoot(_uniqueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ...otherwise count the element out.
|
||||
GazePointer::Instance->RemoveRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointerProxy::OnLoaded(Object^ sender, RoutedEventArgs^ args)
|
||||
{
|
||||
assert(IsLoadedHeuristic(safe_cast<FrameworkElement^>(sender)));
|
||||
|
||||
if (!_isLoaded)
|
||||
{
|
||||
// Record that we are now loaded.
|
||||
_isLoaded = true;
|
||||
|
||||
// If we were previously enabled...
|
||||
if (_isEnabled)
|
||||
{
|
||||
// ...we can now be counted as actively enabled.
|
||||
GazePointer::Instance->AddRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug::WriteLine(L"Unexpected Load");
|
||||
}
|
||||
}
|
||||
|
||||
void GazePointerProxy::OnUnloaded(Object^ sender, RoutedEventArgs^ args)
|
||||
{
|
||||
assert(!IsLoadedHeuristic(safe_cast<FrameworkElement^>(sender)));
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
// Record that we have left the visual tree.
|
||||
_isLoaded = false;
|
||||
|
||||
// If we are set as enabled...
|
||||
if (_isEnabled)
|
||||
{
|
||||
// ...we no longer count as being actively enabled (because we have fallen out the visual tree).
|
||||
GazePointer::Instance->RemoveRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug::WriteLine(L"Unexpected unload");
|
||||
}
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,184 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class that helps track which UIElements in the visual tree are enabled.
|
||||
///
|
||||
/// The GazePointer is enabled when one or more UIElements in the visual tree have
|
||||
/// their GazeInput.InteractionProperty value set to Enabled. Notice that there are
|
||||
/// two conditions for enablement: that attached property is Enabled; that the UIElement
|
||||
/// is in the visual tree.
|
||||
/// </summary>
|
||||
internal class GazePointerProxy
|
||||
{
|
||||
/// <summary>
|
||||
/// The IsLoaded heuristic for testing whether a FrameworkElement is in the visual tree.
|
||||
/// </summary>
|
||||
private static bool IsLoadedHeuristic(FrameworkElement element)
|
||||
{
|
||||
bool isLoaded;
|
||||
|
||||
// element.Loaded has already happened if it is in the visual tree...
|
||||
var parent = VisualTreeHelper.GetParent(element);
|
||||
if (parent != null)
|
||||
{
|
||||
isLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ...or...
|
||||
// ...if the element is a dynamically created Popup that has been opened.
|
||||
var popup = element as Popup;
|
||||
isLoaded = popup != null && popup.IsOpen;
|
||||
}
|
||||
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A private attached property for associating an instance of this class with the UIElement
|
||||
/// to which it refers.
|
||||
/// </summary>
|
||||
private static readonly DependencyProperty _gazePointerProxyProperty = DependencyProperty.RegisterAttached("_GazePointerProxy", typeof(GazePointerProxy), typeof(GazePointerProxy), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Method called when the GazeInput.Interaction attached property is set to a new value.
|
||||
/// </summary>
|
||||
/// <param name="element">The element being set. May be null to indicate whole user interface.</param>
|
||||
/// <param name="value">The interaction enablement value being set.</param>
|
||||
internal static void SetInteraction(FrameworkElement element, Interaction value)
|
||||
{
|
||||
// Get or create a GazePointerProxy for element.
|
||||
if (!(element.GetValue(_gazePointerProxyProperty) is GazePointerProxy proxy))
|
||||
{
|
||||
proxy = new GazePointerProxy(element);
|
||||
element.SetValue(_gazePointerProxyProperty, proxy);
|
||||
}
|
||||
|
||||
// Set the proxy's _isEnabled value.
|
||||
proxy.SetIsEnabled(element, value == Interaction.Enabled);
|
||||
}
|
||||
|
||||
private static int _lastId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GazePointerProxy"/> class.
|
||||
/// </summary>
|
||||
/// <param name="element">The element proxy is attaching to.</param>
|
||||
private GazePointerProxy(FrameworkElement element)
|
||||
{
|
||||
_lastId++;
|
||||
_uniqueId = _lastId;
|
||||
|
||||
_isLoaded = IsLoadedHeuristic(element);
|
||||
|
||||
// Start watching for the element to enter and leave the visual tree.
|
||||
element.Loaded += OnLoaded;
|
||||
element.Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the enablement of this proxy.
|
||||
/// </summary>
|
||||
/// <param name="sender">The object setting the enable value.</param>
|
||||
/// <param name="value">The new enable value.</param>
|
||||
private void SetIsEnabled(object sender, bool value)
|
||||
{
|
||||
// If we have a new value...
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
// ...record the new value.
|
||||
_isEnabled = value;
|
||||
|
||||
// If we are in the visual tree...
|
||||
if (_isLoaded)
|
||||
{
|
||||
// ...if we're being enabled...
|
||||
if (value)
|
||||
{
|
||||
// ...count the element in...
|
||||
GazePointer.Instance.AddRoot(_uniqueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ...otherwise count the element out.
|
||||
GazePointer.Instance.RemoveRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The handler to be called when the corresponding element joins the visual tree.
|
||||
/// </summary>
|
||||
private void OnLoaded(object sender, RoutedEventArgs args)
|
||||
{
|
||||
Debug.Assert(IsLoadedHeuristic(sender as FrameworkElement), "Should not be loaded if this is called");
|
||||
|
||||
if (!_isLoaded)
|
||||
{
|
||||
// Record that we are now loaded.
|
||||
_isLoaded = true;
|
||||
|
||||
// If we were previously enabled...
|
||||
if (_isEnabled)
|
||||
{
|
||||
// ...we can now be counted as actively enabled.
|
||||
GazePointer.Instance.AddRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("Unexpected Load");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The handler to be called when the corresponding element leaves the visual tree.
|
||||
/// </summary>
|
||||
private void OnUnloaded(object sender, RoutedEventArgs args)
|
||||
{
|
||||
Debug.Assert(!IsLoadedHeuristic(sender as FrameworkElement), "Should be loaded if this is called");
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
// Record that we have left the visual tree.
|
||||
_isLoaded = false;
|
||||
|
||||
// If we are set as enabled...
|
||||
if (_isEnabled)
|
||||
{
|
||||
// ...we no longer count as being actively enabled (because we have fallen out the visual tree).
|
||||
GazePointer.Instance.RemoveRoot(_uniqueId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("Unexpected unload");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Non-zero ID associated with this instance.
|
||||
/// </summary>
|
||||
private readonly int _uniqueId;
|
||||
|
||||
/// <summary>
|
||||
/// Indicator that the corresponding element is part of the visual tree.
|
||||
/// </summary>
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean representing whether gaze is enabled for the corresponding element and its subtree.
|
||||
/// </summary>
|
||||
private bool _isEnabled;
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Interaction.h"
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// Helper class that helps track which UIElements in the visual tree are enabled.
|
||||
///
|
||||
/// The GazePointer is enabled when one or more UIElements in the visual tree have
|
||||
/// their GazeInput.InteractionProperty value set to Enabled. Notice that there are
|
||||
/// two conditions for enablement: that attached property is Enabled; that the UIElement
|
||||
/// is in the visual tree.
|
||||
/// </summary>
|
||||
private ref class GazePointerProxy sealed
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// A private attached property for associating an instance of this class with the UIElement
|
||||
/// to which it refers.
|
||||
/// </summary>
|
||||
static property DependencyProperty^ GazePointerProxyProperty { DependencyProperty^ get(); };
|
||||
|
||||
internal:
|
||||
|
||||
/// <summary>
|
||||
/// Method called when the GazeInput.Interaction attached property is set to a new value.
|
||||
/// </summary>
|
||||
/// <param name="element">The element being set. May be null to indicate whole user interface.</param>
|
||||
/// <param name="value">The interaction enablement value being set.</param>
|
||||
static void SetInteraction(FrameworkElement^ element, Interaction value);
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="element">The element proxy is attaching to.</param>
|
||||
GazePointerProxy(FrameworkElement^ element);
|
||||
|
||||
/// <summary>
|
||||
/// Set the enablement of this proxy.
|
||||
/// </summary>
|
||||
/// <param name="sender">The object setting the enable value.</param>
|
||||
/// <param name="value">The new enable value.</param>
|
||||
void SetIsEnabled(Object^ sender, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// The handler to be called when the corresponding element joins the visual tree.
|
||||
/// </summary>
|
||||
void OnLoaded(Object^ sender, RoutedEventArgs^ args);
|
||||
|
||||
/// <summary>
|
||||
/// The handler to be called when the corresponding element leaves the visual tree.
|
||||
/// </summary>
|
||||
void OnUnloaded(Object^ sender, RoutedEventArgs^ args);
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Non-zero ID associated with this instance.
|
||||
/// </summary>
|
||||
int _uniqueId;
|
||||
|
||||
/// <summary>
|
||||
/// Indicator that the corresponding element is part of the visual tree.
|
||||
/// </summary>
|
||||
bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean representing whether gaze is enabled for the corresponding element and its subtree.
|
||||
/// </summary>
|
||||
bool _isEnabled;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
||||
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeSettingsHelper.h"
|
||||
|
||||
using namespace concurrency;
|
||||
using namespace Windows::ApplicationModel::AppService;
|
||||
using namespace Windows::Foundation;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
GazeSettingsHelper::GazeSettingsHelper()
|
||||
{
|
||||
}
|
||||
|
||||
Windows::Foundation::IAsyncAction^ GazeSettingsHelper::RetrieveSharedSettings(ValueSet^ settings)
|
||||
{
|
||||
return create_async([settings] {
|
||||
// Setup a new app service connection
|
||||
AppServiceConnection^ connection = ref new AppServiceConnection();
|
||||
connection->AppServiceName = "com.microsoft.ectksettings";
|
||||
connection->PackageFamilyName = "Microsoft.EyeControlToolkitSettings_s9y1p3hwd5qda";
|
||||
|
||||
// open the connection
|
||||
return create_task(connection->OpenAsync()).then([settings, connection](AppServiceConnectionStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case AppServiceConnectionStatus::Success:
|
||||
// The new connection opened successfully
|
||||
// Set up the inputs and send a message to the service
|
||||
return create_task(connection->SendMessageAsync(ref new ValueSet()));
|
||||
break;
|
||||
|
||||
default:
|
||||
case AppServiceConnectionStatus::AppNotInstalled:
|
||||
case AppServiceConnectionStatus::AppUnavailable:
|
||||
case AppServiceConnectionStatus::AppServiceUnavailable:
|
||||
case AppServiceConnectionStatus::Unknown:
|
||||
// All return paths need to return a task of type AppServiceResponse, so fake it
|
||||
AppServiceResponse ^ response = nullptr;
|
||||
return task_from_result(response);
|
||||
}
|
||||
}).then([settings](AppServiceResponse^ response)
|
||||
{
|
||||
if (response == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response->Status)
|
||||
{
|
||||
case AppServiceResponseStatus::Success:
|
||||
for each (auto item in response->Message)
|
||||
{
|
||||
settings->Insert(item->Key, item->Value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case AppServiceResponseStatus::Failure:
|
||||
case AppServiceResponseStatus::ResourceLimitsExceeded:
|
||||
case AppServiceResponseStatus::Unknown:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}); // create_async()
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,79 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class to read a ValueSet and retrieve relevant settings
|
||||
/// </summary>
|
||||
public class GazeSettingsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves settings as a ValueSet from a shared store.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IAsyncAction"/> representing the asynchronous operation.</returns>
|
||||
public static IAsyncAction RetrieveSharedSettings(ValueSet settings)
|
||||
{
|
||||
async Task InternalRetrieveSharedSettings()
|
||||
{
|
||||
// Setup a new app service connection
|
||||
AppServiceConnection connection = new AppServiceConnection();
|
||||
connection.AppServiceName = "com.microsoft.ectksettings";
|
||||
connection.PackageFamilyName = "Microsoft.EyeControlToolkitSettings_s9y1p3hwd5qda";
|
||||
|
||||
// open the connection
|
||||
var status = await connection.OpenAsync();
|
||||
switch (status)
|
||||
{
|
||||
case AppServiceConnectionStatus.Success:
|
||||
// The new connection opened successfully
|
||||
// Set up the inputs and send a message to the service
|
||||
var response = await connection.SendMessageAsync(new ValueSet());
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response.Status)
|
||||
{
|
||||
case AppServiceResponseStatus.Success:
|
||||
foreach (var item in response.Message)
|
||||
{
|
||||
settings.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
case AppServiceResponseStatus.Failure:
|
||||
case AppServiceResponseStatus.ResourceLimitsExceeded:
|
||||
case AppServiceResponseStatus.Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
case AppServiceConnectionStatus.AppNotInstalled:
|
||||
case AppServiceConnectionStatus.AppUnavailable:
|
||||
case AppServiceConnectionStatus.AppServiceUnavailable:
|
||||
case AppServiceConnectionStatus.Unknown:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return InternalRetrieveSharedSettings().AsAsyncAction();
|
||||
}
|
||||
|
||||
private GazeSettingsHelper()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class to read a ValueSet and retrieve relevant settings
|
||||
/// </summary>
|
||||
public ref class GazeSettingsHelper sealed
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves settings as a ValueSet from a shared store.
|
||||
/// </summary>
|
||||
static Windows::Foundation::IAsyncAction^ RetrieveSharedSettings(ValueSet^ settings);
|
||||
|
||||
private:
|
||||
GazeSettingsHelper();
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,47 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeStats.h"
|
||||
|
||||
using namespace Platform::Collections;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
GazeStats::GazeStats(int maxHistoryLen)
|
||||
{
|
||||
_maxHistoryLen = maxHistoryLen;
|
||||
_history = ref new Vector<Point>();
|
||||
}
|
||||
|
||||
void GazeStats::Reset()
|
||||
{
|
||||
_sumX = 0;
|
||||
_sumY = 0;
|
||||
_sumSquaredX = 0;
|
||||
_sumSquaredY = 0;
|
||||
_history->Clear();
|
||||
}
|
||||
|
||||
void GazeStats::Update(float x, float y)
|
||||
{
|
||||
Point pt(x, y);
|
||||
_history->Append(pt);
|
||||
|
||||
if (_history->Size > _maxHistoryLen)
|
||||
{
|
||||
auto oldest = _history->GetAt(0);
|
||||
_history->RemoveAt(0);
|
||||
|
||||
_sumX -= oldest.X;
|
||||
_sumY -= oldest.Y;
|
||||
_sumSquaredX -= oldest.X * oldest.X;
|
||||
_sumSquaredY -= oldest.Y * oldest.Y;
|
||||
}
|
||||
_sumX += x;
|
||||
_sumY += y;
|
||||
_sumSquaredX += x * x;
|
||||
_sumSquaredY += y * y;
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,110 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides basic stats for eye gaze
|
||||
/// </summary>
|
||||
internal class GazeStats
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GazeStats"/> class.
|
||||
/// </summary>
|
||||
/// <param name="maxHistoryLen">Defines the size of the circular history for the calculations of stats</param>
|
||||
public GazeStats(int maxHistoryLen)
|
||||
{
|
||||
_maxHistoryLen = maxHistoryLen;
|
||||
_history = new List<Point>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the history, and stats
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_sumX = 0;
|
||||
_sumY = 0;
|
||||
_sumSquaredX = 0;
|
||||
_sumSquaredY = 0;
|
||||
_history.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the history
|
||||
/// </summary>
|
||||
/// <param name="x">X axis of the new point</param>
|
||||
/// <param name="y">Y axis of the new point</param>
|
||||
public void Update(float x, float y)
|
||||
{
|
||||
var pt = new Point(x, y);
|
||||
_history.Add(pt);
|
||||
|
||||
if (_history.Count > _maxHistoryLen)
|
||||
{
|
||||
var oldest = _history[0];
|
||||
_history.RemoveAt(0);
|
||||
|
||||
_sumX -= oldest.X;
|
||||
_sumY -= oldest.Y;
|
||||
_sumSquaredX -= oldest.X * oldest.X;
|
||||
_sumSquaredY -= oldest.Y * oldest.Y;
|
||||
}
|
||||
|
||||
_sumX += x;
|
||||
_sumY += y;
|
||||
_sumSquaredX += x * x;
|
||||
_sumSquaredY += y * y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mean of the history of points
|
||||
/// </summary>
|
||||
public Point Mean
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = _history.Count;
|
||||
if (count < _maxHistoryLen)
|
||||
{
|
||||
return new Point(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
return new Point((float)_sumX / count, (float)_sumY / count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StandardDeviation of the items on the history => sqrt(Variance) = sqrt(E[X^2] – (E[X])^2)
|
||||
/// </summary>
|
||||
public Point StandardDeviation
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = _history.Count;
|
||||
if (count < _maxHistoryLen)
|
||||
{
|
||||
return new Point(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
double meanX = _sumX / count;
|
||||
double meanY = _sumY / count;
|
||||
float stddevX = (float)Math.Sqrt((_sumSquaredX / count) - (meanX * meanX));
|
||||
float stddevY = (float)Math.Sqrt((_sumSquaredY / count) - (meanY * meanY));
|
||||
return new Point(stddevX, stddevY);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int _maxHistoryLen;
|
||||
private readonly List<Point> _history;
|
||||
private double _sumX;
|
||||
private double _sumY;
|
||||
private double _sumSquaredX;
|
||||
private double _sumSquaredY;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
using namespace Platform::Collections;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
public ref class GazeStats sealed
|
||||
{
|
||||
public:
|
||||
GazeStats(int maxHistoryLen);
|
||||
void Reset();
|
||||
void Update(float x, float y);
|
||||
|
||||
property Point Mean
|
||||
{
|
||||
Point get()
|
||||
{
|
||||
UINT count = _history->Size;
|
||||
return Point((float)_sumX / count, (float)_sumY / count);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// StdDev = sqrt(Variance) = sqrt(E[X^2] – (E[X])^2)
|
||||
//
|
||||
property Point StandardDeviation
|
||||
{
|
||||
Point get()
|
||||
{
|
||||
UINT count = _history->Size;
|
||||
if (count < _maxHistoryLen)
|
||||
{
|
||||
return Point(0.0f, 0.0f);
|
||||
}
|
||||
double meanX = _sumX / count;
|
||||
double meanY = _sumY / count;
|
||||
float stddevX = (float)sqrt((_sumSquaredX / count) - (meanX * meanX));
|
||||
float stddevY = (float)sqrt((_sumSquaredY / count) - (meanY * meanY));
|
||||
return Point(stddevX, stddevY);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
UINT _maxHistoryLen;
|
||||
double _sumX;
|
||||
double _sumY;
|
||||
double _sumSquaredX;
|
||||
double _sumSquaredY;
|
||||
Vector<Point>^ _history;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -1,304 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "GazeTargetItem.h"
|
||||
#include "GazePointer.h"
|
||||
|
||||
#include "GazeElement.h"
|
||||
#include "GazeFeedbackPopupFactory.h"
|
||||
|
||||
using namespace Windows::UI::Xaml::Automation;
|
||||
using namespace Windows::UI::Xaml::Automation::Provider;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
static DependencyProperty^ GazeTargetItemProperty = DependencyProperty::RegisterAttached("_GazeTargetItem", GazeTargetItem::typeid, GazeTargetItem::typeid, ref new PropertyMetadata(nullptr));
|
||||
|
||||
template<PatternInterface P, typename T>
|
||||
ref class PatternGazeTargetItem abstract : public GazeTargetItem
|
||||
{
|
||||
internal:
|
||||
|
||||
PatternGazeTargetItem(UIElement^ element)
|
||||
: GazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
static T^ GetPattern(AutomationPeer^ peer)
|
||||
{
|
||||
auto pattern = peer->GetPattern(P);
|
||||
return safe_cast<T^>(pattern);
|
||||
}
|
||||
|
||||
static bool IsCandidate(AutomationPeer^ peer)
|
||||
{
|
||||
auto provider = GetPattern(peer);
|
||||
return provider != nullptr;
|
||||
}
|
||||
|
||||
void Invoke() override sealed
|
||||
{
|
||||
auto peer = FrameworkElementAutomationPeer::FromElement(TargetElement);
|
||||
auto provider = GetPattern(peer);
|
||||
Invoke(provider);
|
||||
}
|
||||
|
||||
virtual void Invoke(T^ provider) = 0;
|
||||
};
|
||||
|
||||
ref class InvokePatternGazeTargetItem : public PatternGazeTargetItem<PatternInterface::Invoke, IInvokeProvider>
|
||||
{
|
||||
internal:
|
||||
|
||||
InvokePatternGazeTargetItem(UIElement^ element)
|
||||
: PatternGazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke(IInvokeProvider^ provider) override sealed
|
||||
{
|
||||
provider->Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
ref class TogglePatternGazeTargetItem : public PatternGazeTargetItem<PatternInterface::Toggle, IToggleProvider>
|
||||
{
|
||||
internal:
|
||||
|
||||
TogglePatternGazeTargetItem(UIElement^ element)
|
||||
: PatternGazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke(IToggleProvider^ provider) override
|
||||
{
|
||||
provider->Toggle();
|
||||
}
|
||||
};
|
||||
|
||||
ref class SelectionItemPatternGazeTargetItem : public PatternGazeTargetItem<PatternInterface::SelectionItem, ISelectionItemProvider>
|
||||
{
|
||||
internal:
|
||||
|
||||
SelectionItemPatternGazeTargetItem(UIElement^ element)
|
||||
: PatternGazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke(ISelectionItemProvider^ provider) override
|
||||
{
|
||||
provider->Select();
|
||||
}
|
||||
};
|
||||
|
||||
ref class ExpandCollapsePatternGazeTargetItem : public PatternGazeTargetItem<PatternInterface::ExpandCollapse, IExpandCollapseProvider>
|
||||
{
|
||||
internal:
|
||||
|
||||
ExpandCollapsePatternGazeTargetItem(UIElement^ element)
|
||||
: PatternGazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke(IExpandCollapseProvider^ provider) override
|
||||
{
|
||||
switch (provider->ExpandCollapseState)
|
||||
{
|
||||
case ExpandCollapseState::Collapsed:
|
||||
provider->Expand();
|
||||
break;
|
||||
|
||||
case ExpandCollapseState::Expanded:
|
||||
provider->Collapse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ref class ComboBoxItemGazeTargetItem sealed : GazeTargetItem
|
||||
{
|
||||
internal:
|
||||
|
||||
ComboBoxItemGazeTargetItem(UIElement^ element)
|
||||
: GazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke() override
|
||||
{
|
||||
auto peer = FrameworkElementAutomationPeer::FromElement(TargetElement);
|
||||
auto comboBoxItemAutomationPeer = dynamic_cast<ComboBoxItemAutomationPeer^>(peer);
|
||||
auto comboBoxItem = safe_cast<ComboBoxItem^>(comboBoxItemAutomationPeer->Owner);
|
||||
|
||||
AutomationPeer^ ancestor = comboBoxItemAutomationPeer;
|
||||
auto comboBoxAutomationPeer = dynamic_cast<ComboBoxAutomationPeer^>(ancestor);
|
||||
while (comboBoxAutomationPeer == nullptr)
|
||||
{
|
||||
ancestor = safe_cast<AutomationPeer^>(ancestor->Navigate(AutomationNavigationDirection::Parent));
|
||||
comboBoxAutomationPeer = dynamic_cast<ComboBoxAutomationPeer^>(ancestor);
|
||||
}
|
||||
|
||||
comboBoxItem->IsSelected = true;
|
||||
comboBoxAutomationPeer->Collapse();
|
||||
}
|
||||
};
|
||||
|
||||
ref class PivotItemGazeTargetItem sealed : GazeTargetItem
|
||||
{
|
||||
internal:
|
||||
|
||||
PivotItemGazeTargetItem(UIElement^ element)
|
||||
: GazeTargetItem(element)
|
||||
{
|
||||
}
|
||||
|
||||
void Invoke() override
|
||||
{
|
||||
auto headerItem = safe_cast<PivotHeaderItem^>(TargetElement);
|
||||
auto headerPanel = safe_cast<PivotHeaderPanel^>(VisualTreeHelper::GetParent(headerItem));
|
||||
unsigned index;
|
||||
headerPanel->Children->IndexOf(headerItem, &index);
|
||||
|
||||
DependencyObject^ walker = headerPanel;
|
||||
Pivot^ pivot;
|
||||
do
|
||||
{
|
||||
walker = VisualTreeHelper::GetParent(walker);
|
||||
pivot = dynamic_cast<Pivot^>(walker);
|
||||
} while (pivot == nullptr);
|
||||
|
||||
pivot->SelectedIndex = index;
|
||||
}
|
||||
};
|
||||
|
||||
GazeTargetItem^ GazeTargetItem::GetOrCreate(UIElement^ element)
|
||||
{
|
||||
GazeTargetItem^ item;
|
||||
|
||||
auto value = element->ReadLocalValue(GazeTargetItemProperty);
|
||||
|
||||
if (value != DependencyProperty::UnsetValue)
|
||||
{
|
||||
item = safe_cast<GazeTargetItem^>(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto peer = FrameworkElementAutomationPeer::FromElement(element);
|
||||
|
||||
if (peer == nullptr)
|
||||
{
|
||||
if (dynamic_cast<PivotHeaderItem^>(element) != nullptr)
|
||||
{
|
||||
item = ref new PivotItemGazeTargetItem(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = GazePointer::Instance->_nonInvokeGazeTargetItem;
|
||||
}
|
||||
}
|
||||
else if (InvokePatternGazeTargetItem::IsCandidate(peer))
|
||||
{
|
||||
item = ref new InvokePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (TogglePatternGazeTargetItem::IsCandidate(peer))
|
||||
{
|
||||
item = ref new TogglePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (SelectionItemPatternGazeTargetItem::IsCandidate(peer))
|
||||
{
|
||||
item = ref new SelectionItemPatternGazeTargetItem(element);
|
||||
}
|
||||
else if (ExpandCollapsePatternGazeTargetItem::IsCandidate(peer))
|
||||
{
|
||||
item = ref new ExpandCollapsePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (dynamic_cast<ComboBoxItemAutomationPeer^>(peer) != nullptr)
|
||||
{
|
||||
item = ref new ComboBoxItemGazeTargetItem(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = GazePointer::Instance->_nonInvokeGazeTargetItem;
|
||||
}
|
||||
|
||||
element->SetValue(GazeTargetItemProperty, item);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void GazeTargetItem::RaiseProgressEvent(DwellProgressState state)
|
||||
{
|
||||
// TODO: We should eliminate non-invokable controls before we arrive here!
|
||||
if (dynamic_cast<Page^>(TargetElement) != nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifiedProgressState != state || state == DwellProgressState::Progressing)
|
||||
{
|
||||
auto handled = false;
|
||||
|
||||
auto gazeElement = GazeInput::GetGazeElement(TargetElement);
|
||||
if (gazeElement != nullptr)
|
||||
{
|
||||
handled = gazeElement->RaiseProgressFeedback(TargetElement, state, ElapsedTime - _prevStateTime, _nextStateTime - _prevStateTime);
|
||||
}
|
||||
|
||||
if (!handled && state != DwellProgressState::Idle)
|
||||
{
|
||||
if (_feedbackPopup == nullptr)
|
||||
{
|
||||
_feedbackPopup = GazePointer::Instance->_gazeFeedbackPopupFactory->Get();
|
||||
}
|
||||
|
||||
auto control = safe_cast<FrameworkElement^>(TargetElement);
|
||||
|
||||
auto transform = control->TransformToVisual(_feedbackPopup);
|
||||
auto bounds = transform->TransformBounds(*ref new Rect(*ref new Point(0, 0),
|
||||
*ref new Size(safe_cast<float>(control->ActualWidth), safe_cast<float>(control->ActualHeight))));
|
||||
auto rectangle = safe_cast<::Windows::UI::Xaml::Shapes::Rectangle^>(_feedbackPopup->Child);
|
||||
|
||||
if (state == DwellProgressState::Progressing)
|
||||
{
|
||||
auto progress = ((double)(ElapsedTime - _prevStateTime).Duration) / (_nextStateTime - _prevStateTime).Duration;
|
||||
|
||||
if (0 <= progress && progress < 1)
|
||||
{
|
||||
rectangle->Stroke = GazeInput::DwellFeedbackProgressBrush;
|
||||
rectangle->Width = (1 - progress) * bounds.Width;
|
||||
rectangle->Height = (1 - progress) * bounds.Height;
|
||||
|
||||
_feedbackPopup->HorizontalOffset = bounds.Left + progress * bounds.Width / 2;
|
||||
_feedbackPopup->VerticalOffset = bounds.Top + progress * bounds.Height / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rectangle->Stroke = state == DwellProgressState::Fixating ?
|
||||
GazeInput::DwellFeedbackEnterBrush : GazeInput::DwellFeedbackCompleteBrush;
|
||||
rectangle->Width = bounds.Width;
|
||||
rectangle->Height = bounds.Height;
|
||||
|
||||
_feedbackPopup->HorizontalOffset = bounds.Left;
|
||||
_feedbackPopup->VerticalOffset = bounds.Top;
|
||||
}
|
||||
|
||||
_feedbackPopup->IsOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_feedbackPopup != nullptr)
|
||||
{
|
||||
GazePointer::Instance->_gazeFeedbackPopupFactory->Return(_feedbackPopup);
|
||||
_feedbackPopup = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_notifiedProgressState = state;
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,239 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal abstract class GazeTargetItem
|
||||
{
|
||||
private static readonly DependencyProperty _gazeTargetItemProperty = DependencyProperty.RegisterAttached("_GazeTargetItem", typeof(GazeTargetItem), typeof(GazeTargetItem), new PropertyMetadata(null));
|
||||
|
||||
internal TimeSpan DetailedTime { get; set; }
|
||||
|
||||
internal TimeSpan OverflowTime { get; set; }
|
||||
|
||||
internal TimeSpan ElapsedTime
|
||||
{
|
||||
get { return DetailedTime + OverflowTime; }
|
||||
}
|
||||
|
||||
internal TimeSpan NextStateTime { get; set; }
|
||||
|
||||
internal TimeSpan LastTimestamp { get; set; }
|
||||
|
||||
internal PointerState ElementState { get; set; }
|
||||
|
||||
internal UIElement TargetElement { get; set; }
|
||||
|
||||
internal int RepeatCount { get; set; }
|
||||
|
||||
internal int MaxDwellRepeatCount { get; set; }
|
||||
|
||||
internal GazeTargetItem(UIElement target)
|
||||
{
|
||||
TargetElement = target;
|
||||
}
|
||||
|
||||
internal static GazeTargetItem GetOrCreate(UIElement element)
|
||||
{
|
||||
GazeTargetItem item;
|
||||
|
||||
var value = element.ReadLocalValue(_gazeTargetItemProperty);
|
||||
|
||||
if (value != DependencyProperty.UnsetValue)
|
||||
{
|
||||
item = (GazeTargetItem)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(element);
|
||||
|
||||
if (peer == null)
|
||||
{
|
||||
if (element is PivotHeaderItem)
|
||||
{
|
||||
item = new PivotItemGazeTargetItem(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = GazePointer.Instance.NonInvokeGazeTargetItem;
|
||||
}
|
||||
}
|
||||
else if (peer.GetPattern(PatternInterface.Invoke) is IInvokeProvider)
|
||||
{
|
||||
item = new InvokePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (peer.GetPattern(PatternInterface.Toggle) is IToggleProvider)
|
||||
{
|
||||
item = new TogglePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (peer.GetPattern(PatternInterface.SelectionItem) is ISelectionItemProvider)
|
||||
{
|
||||
item = new SelectionItemPatternGazeTargetItem(element);
|
||||
}
|
||||
else if (peer.GetPattern(PatternInterface.ExpandCollapse) is IExpandCollapseProvider)
|
||||
{
|
||||
item = new ExpandCollapsePatternGazeTargetItem(element);
|
||||
}
|
||||
else if (peer is ComboBoxItemAutomationPeer)
|
||||
{
|
||||
item = new ComboBoxItemGazeTargetItem(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = GazePointer.Instance.NonInvokeGazeTargetItem;
|
||||
}
|
||||
|
||||
element.SetValue(_gazeTargetItemProperty, item);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
internal abstract void Invoke();
|
||||
|
||||
internal virtual bool IsInvokable
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
internal void Reset(TimeSpan nextStateTime)
|
||||
{
|
||||
ElementState = PointerState.PreEnter;
|
||||
DetailedTime = TimeSpan.Zero;
|
||||
OverflowTime = TimeSpan.Zero;
|
||||
NextStateTime = nextStateTime;
|
||||
RepeatCount = 0;
|
||||
MaxDwellRepeatCount = GazeInput.GetMaxDwellRepeatCount(TargetElement);
|
||||
}
|
||||
|
||||
internal void GiveFeedback()
|
||||
{
|
||||
if (_nextStateTime != NextStateTime)
|
||||
{
|
||||
_prevStateTime = _nextStateTime;
|
||||
_nextStateTime = NextStateTime;
|
||||
}
|
||||
|
||||
if (ElementState != _notifiedPointerState)
|
||||
{
|
||||
switch (ElementState)
|
||||
{
|
||||
case PointerState.Enter:
|
||||
RaiseProgressEvent(DwellProgressState.Fixating);
|
||||
break;
|
||||
|
||||
case PointerState.Dwell:
|
||||
case PointerState.Fixation:
|
||||
RaiseProgressEvent(DwellProgressState.Progressing);
|
||||
break;
|
||||
|
||||
case PointerState.Exit:
|
||||
case PointerState.PreEnter:
|
||||
RaiseProgressEvent(DwellProgressState.Idle);
|
||||
break;
|
||||
}
|
||||
|
||||
_notifiedPointerState = ElementState;
|
||||
}
|
||||
else if (ElementState == PointerState.Dwell || ElementState == PointerState.Fixation)
|
||||
{
|
||||
if (RepeatCount <= MaxDwellRepeatCount)
|
||||
{
|
||||
RaiseProgressEvent(DwellProgressState.Progressing);
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseProgressEvent(DwellProgressState.Complete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseProgressEvent(DwellProgressState state)
|
||||
{
|
||||
// TODO: We should eliminate non-invokable controls before we arrive here!
|
||||
if (TargetElement is Page)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifiedProgressState != state || state == DwellProgressState.Progressing)
|
||||
{
|
||||
var handled = false;
|
||||
|
||||
var gazeElement = GazeInput.GetGazeElement(TargetElement);
|
||||
if (gazeElement != null)
|
||||
{
|
||||
handled = gazeElement.RaiseProgressFeedback(TargetElement, state, ElapsedTime - _prevStateTime, _nextStateTime - _prevStateTime);
|
||||
}
|
||||
|
||||
if (!handled && state != DwellProgressState.Idle)
|
||||
{
|
||||
if (_feedbackPopup == null)
|
||||
{
|
||||
_feedbackPopup = GazePointer.Instance.GazeFeedbackPopupFactory.Get();
|
||||
}
|
||||
|
||||
var control = TargetElement as FrameworkElement;
|
||||
|
||||
var transform = control.TransformToVisual(_feedbackPopup);
|
||||
var bounds = transform.TransformBounds(new Rect(
|
||||
new Point(0, 0),
|
||||
new Size((float)control.ActualWidth, (float)control.ActualHeight)));
|
||||
var rectangle = (Windows.UI.Xaml.Shapes.Rectangle)_feedbackPopup.Child;
|
||||
|
||||
if (state == DwellProgressState.Progressing)
|
||||
{
|
||||
var progress = ((double)(ElapsedTime - _prevStateTime).Ticks) / (_nextStateTime - _prevStateTime).Ticks;
|
||||
|
||||
if (progress >= 0 && progress < 1)
|
||||
{
|
||||
rectangle.Stroke = GazeInput.DwellFeedbackProgressBrush;
|
||||
rectangle.Width = (1 - progress) * bounds.Width;
|
||||
rectangle.Height = (1 - progress) * bounds.Height;
|
||||
|
||||
_feedbackPopup.HorizontalOffset = bounds.Left + (progress * bounds.Width / 2);
|
||||
_feedbackPopup.VerticalOffset = bounds.Top + (progress * bounds.Height / 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rectangle.Stroke = state == DwellProgressState.Fixating ?
|
||||
GazeInput.DwellFeedbackEnterBrush : GazeInput.DwellFeedbackCompleteBrush;
|
||||
rectangle.Width = bounds.Width;
|
||||
rectangle.Height = bounds.Height;
|
||||
|
||||
_feedbackPopup.HorizontalOffset = bounds.Left;
|
||||
_feedbackPopup.VerticalOffset = bounds.Top;
|
||||
}
|
||||
|
||||
_feedbackPopup.IsOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_feedbackPopup != null)
|
||||
{
|
||||
GazePointer.Instance.GazeFeedbackPopupFactory.Return(_feedbackPopup);
|
||||
_feedbackPopup = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_notifiedProgressState = state;
|
||||
}
|
||||
|
||||
private PointerState _notifiedPointerState = PointerState.Exit;
|
||||
private TimeSpan _prevStateTime;
|
||||
private TimeSpan _nextStateTime;
|
||||
private DwellProgressState _notifiedProgressState = DwellProgressState.Idle;
|
||||
private Popup _feedbackPopup;
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DwellProgressState.h"
|
||||
#include "GazeInput.h"
|
||||
#include "PointerState.h"
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Controls::Primitives;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
private ref class GazeTargetItem abstract
|
||||
{
|
||||
internal:
|
||||
property TimeSpan DetailedTime;
|
||||
property TimeSpan OverflowTime;
|
||||
property TimeSpan ElapsedTime { TimeSpan get() { return DetailedTime + OverflowTime; } }
|
||||
property TimeSpan NextStateTime;
|
||||
property TimeSpan LastTimestamp;
|
||||
property PointerState ElementState;
|
||||
property UIElement^ TargetElement;
|
||||
property int RepeatCount;
|
||||
property int MaxDwellRepeatCount;
|
||||
|
||||
GazeTargetItem(UIElement^ target)
|
||||
{
|
||||
TargetElement = target;
|
||||
}
|
||||
|
||||
static GazeTargetItem^ GetOrCreate(UIElement^ element);
|
||||
|
||||
virtual void Invoke() = 0;
|
||||
|
||||
virtual property bool IsInvokable { bool get() { return true; } }
|
||||
|
||||
void Reset(TimeSpan nextStateTime)
|
||||
{
|
||||
ElementState = PointerState::PreEnter;
|
||||
DetailedTime = TimeSpanZero;
|
||||
OverflowTime = TimeSpanZero;
|
||||
NextStateTime = nextStateTime;
|
||||
RepeatCount = 0;
|
||||
MaxDwellRepeatCount = GazeInput::GetMaxDwellRepeatCount(TargetElement);
|
||||
}
|
||||
|
||||
void GiveFeedback()
|
||||
{
|
||||
if (_nextStateTime != NextStateTime)
|
||||
{
|
||||
_prevStateTime = _nextStateTime;
|
||||
_nextStateTime = NextStateTime;
|
||||
}
|
||||
|
||||
if (ElementState != _notifiedPointerState)
|
||||
{
|
||||
switch (ElementState)
|
||||
{
|
||||
case PointerState::Enter:
|
||||
RaiseProgressEvent(DwellProgressState::Fixating);
|
||||
break;
|
||||
|
||||
case PointerState::Dwell:
|
||||
case PointerState::Fixation:
|
||||
RaiseProgressEvent(DwellProgressState::Progressing);
|
||||
break;
|
||||
|
||||
case PointerState::Exit:
|
||||
case PointerState::PreEnter:
|
||||
RaiseProgressEvent(DwellProgressState::Idle);
|
||||
break;
|
||||
}
|
||||
|
||||
_notifiedPointerState = ElementState;
|
||||
}
|
||||
else if (ElementState == PointerState::Dwell || ElementState == PointerState::Fixation)
|
||||
{
|
||||
if (RepeatCount <= MaxDwellRepeatCount)
|
||||
{
|
||||
RaiseProgressEvent(DwellProgressState::Progressing);
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseProgressEvent(DwellProgressState::Complete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void RaiseProgressEvent(DwellProgressState state);
|
||||
|
||||
PointerState _notifiedPointerState = PointerState::Exit;
|
||||
TimeSpan _prevStateTime;
|
||||
TimeSpan _nextStateTime;
|
||||
DwellProgressState _notifiedProgressState = DwellProgressState::Idle;
|
||||
Popup^ _feedbackPopup;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,17 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
// Every filter must provide an Update method which transforms sample data
|
||||
// and returns filtered output
|
||||
internal interface IGazeFilter
|
||||
{
|
||||
GazeFilterArgs Update(GazeFilterArgs args);
|
||||
|
||||
void LoadSettings(ValueSet settings);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This struct encapsulates the location and timestamp associated with the user's gaze
|
||||
/// and is used as an input and output parameter for the IGazeFilter::Update method
|
||||
/// </summary>
|
||||
private ref struct GazeFilterArgs sealed
|
||||
{
|
||||
/// <summary>
|
||||
/// The current point in the gaze stream
|
||||
/// </summary>
|
||||
property Point Location {Point get() { return _location; }}
|
||||
|
||||
/// <summary>
|
||||
/// The timestamp associated with the current point
|
||||
/// </summary>
|
||||
property TimeSpan Timestamp {TimeSpan get() { return _timestamp; }}
|
||||
|
||||
internal:
|
||||
|
||||
GazeFilterArgs(Point location, TimeSpan timestamp)
|
||||
{
|
||||
_location = location;
|
||||
_timestamp = timestamp;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Point _location;
|
||||
TimeSpan _timestamp;
|
||||
};
|
||||
|
||||
// Every filter must provide an Update method which transforms sample data
|
||||
// and returns filtered output
|
||||
private interface class IGazeFilter
|
||||
{
|
||||
GazeFilterArgs^ Update(GazeFilterArgs^ args);
|
||||
void LoadSettings(ValueSet^ settings);
|
||||
};
|
||||
|
||||
|
||||
// Basic filter which performs no input filtering -- easy to
|
||||
// use as a default filter.
|
||||
private ref class NullFilter sealed : public IGazeFilter
|
||||
{
|
||||
public:
|
||||
virtual inline GazeFilterArgs^ Update(GazeFilterArgs^ args)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
||||
virtual inline void LoadSettings(ValueSet^ settings)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum indicates the current state of gaze interaction.
|
||||
/// </summary>
|
||||
public enum Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// The state of gaze interaction is inherited from the nearest parent
|
||||
/// </summary>
|
||||
Inherited,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze interaction is enabled
|
||||
/// </summary>
|
||||
Enabled,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze interaction is disabled
|
||||
/// </summary>
|
||||
Disabled
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This enum indicates the current state of gaze interaction.
|
||||
/// </summary>
|
||||
public enum class Interaction
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The state of gaze interaction is inherited from the nearest parent
|
||||
/// </summary>
|
||||
Inherited,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze interaction is enabled
|
||||
/// </summary>
|
||||
Enabled,
|
||||
|
||||
/// <summary>
|
||||
/// Gaze interaction is disabled
|
||||
/// </summary>
|
||||
Disabled
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,25 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class InvokePatternGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal InvokePatternGazeTargetItem(UIElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(TargetElement);
|
||||
var provider = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
|
||||
provider.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
/*
|
||||
* http://www.lifl.fr/~casiez/1euro/
|
||||
* http://www.lifl.fr/~casiez/publications/CHI2012-casiez.pdf
|
||||
*/
|
||||
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class LowpassFilter
|
||||
{
|
||||
public LowpassFilter()
|
||||
{
|
||||
Previous = new Point(0, 0);
|
||||
}
|
||||
|
||||
public LowpassFilter(Point initial)
|
||||
{
|
||||
Previous = initial;
|
||||
}
|
||||
|
||||
public Point Previous { get; set; }
|
||||
|
||||
public Point Update(Point point, Point alpha)
|
||||
{
|
||||
Point pt;
|
||||
pt.X = (alpha.X * point.X) + ((1 - alpha.X) * Previous.X);
|
||||
pt.Y = (alpha.Y * point.Y) + ((1 - alpha.Y) * Previous.Y);
|
||||
Previous = pt;
|
||||
return Previous;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PackagePlatform Condition="'$(Platform)' == 'Win32'">x86</PackagePlatform>
|
||||
<PackagePlatform Condition="'$(Platform)' != 'Win32'">$(Platform)</PackagePlatform>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'UAP'">
|
||||
<Reference Include="$(MSBuildThisFileDirectory)..\..\lib\uap10.0.17134\Microsoft.Toolkit.Uwp.Input.GazeInteraction.winmd">
|
||||
<Implementation>Microsoft.Toolkit.Uwp.Input.GazeInteraction.dll</Implementation>
|
||||
</Reference>
|
||||
<ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win10-$(PackagePlatform)\native\Microsoft.Toolkit.Uwp.Input.GazeInteraction.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,331 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|ARM">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{a5e98964-45b1-442d-a07a-298a3221d81e}</ProjectGuid>
|
||||
<Keyword>WindowsRuntimeComponent</Keyword>
|
||||
<RootNamespace>Microsoft.Toolkit.Uwp.Input.GazeInteraction</RootNamespace>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
<AppContainerApplication>true</AppContainerApplication>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
|
||||
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
||||
<ProjectName>Microsoft.Toolkit.Uwp.Input.GazeInteraction</ProjectName>
|
||||
<TargetFrameworks>UAP,Version=v10.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<OutDir>$(SolutionDir)$(MSBuildProjectName)\Output\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<OutDir>$(SolutionDir)$(MSBuildProjectName)\Output\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<OutDir>$(SolutionDir)$(MSBuildProjectName)\Output\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<OutDir>$(SolutionDir)$(MSBuildProjectName)\Output\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
<GenerateXMLDocumentationFiles>true</GenerateXMLDocumentationFiles>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
<GenerateXMLDocumentationFiles>true</GenerateXMLDocumentationFiles>
|
||||
<ControlFlowGuard>Guard</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
<ControlFlowGuard>Guard</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
<ControlFlowGuard>Guard</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
|
||||
<ControlFlowGuard>Guard</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="GazeEventArgs.h" />
|
||||
<ClInclude Include="GazeHidParsers.h" />
|
||||
<ClInclude Include="GazeHidUsages.h" />
|
||||
<ClInclude Include="GazeInput.h" />
|
||||
<ClInclude Include="GazeCursor.h" />
|
||||
<ClInclude Include="GazeElement.h" />
|
||||
<ClInclude Include="Interaction.h" />
|
||||
<ClInclude Include="GazeFeedbackPopupFactory.h" />
|
||||
<ClInclude Include="GazeHistoryItem.h" />
|
||||
<ClInclude Include="DwellInvokedRoutedEventArgs.h" />
|
||||
<ClInclude Include="GazePointer.h" />
|
||||
<ClInclude Include="StateChangedEventArgs.h" />
|
||||
<ClInclude Include="GazePointerProxy.h" />
|
||||
<ClInclude Include="PointerState.h" />
|
||||
<ClInclude Include="DwellProgressEventArgs.h" />
|
||||
<ClInclude Include="DwellProgressState.h" />
|
||||
<ClInclude Include="GazeSettingsHelper.h" />
|
||||
<ClInclude Include="GazeStats.h" />
|
||||
<ClInclude Include="GazeTargetItem.h" />
|
||||
<ClInclude Include="IGazeFilter.h" />
|
||||
<ClInclude Include="OneEuroFilter.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GazeEventArgs.cpp" />
|
||||
<ClCompile Include="GazeHidParsers.cpp" />
|
||||
<ClCompile Include="GazeInput.cpp" />
|
||||
<ClCompile Include="GazeCursor.cpp" />
|
||||
<ClCompile Include="GazeFeedbackPopupFactory.cpp" />
|
||||
<ClCompile Include="GazePointer.cpp" />
|
||||
<ClCompile Include="GazePointerProxy.cpp" />
|
||||
<ClCompile Include="GazeSettingsHelper.cpp" />
|
||||
<ClCompile Include="GazeStats.cpp" />
|
||||
<ClCompile Include="GazeTargetItem.cpp" />
|
||||
<ClCompile Include="OneEuroFilter.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="MSBuild.Sdk.Extras">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>uap10.0.17134</TargetFramework>
|
||||
<Title>Windows Community Toolkit Eye Gaze Library</Title>
|
||||
<Description>A library to integrate gaze interactions using eye trackers into UWP applications</Description>
|
||||
<PackageTags>UWP Toolkit Windows Eye Gaze EyeTracker</PackageTags>
|
||||
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,25 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class NonInvokeGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal NonInvokeGazeTargetItem()
|
||||
: base(new Page())
|
||||
{
|
||||
}
|
||||
|
||||
internal override bool IsInvokable
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
// Basic filter which performs no input filtering -- easy to
|
||||
// use as a default filter.
|
||||
internal class NullFilter : IGazeFilter
|
||||
{
|
||||
public virtual GazeFilterArgs Update(GazeFilterArgs args)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
||||
public virtual void LoadSettings(ValueSet settings)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#include "pch.h"
|
||||
#include "OneEuroFilter.h"
|
||||
|
||||
//
|
||||
// http://www.lifl.fr/~casiez/1euro/
|
||||
// http://www.lifl.fr/~casiez/publications/CHI2012-casiez.pdf
|
||||
//
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
OneEuroFilter::OneEuroFilter()
|
||||
{
|
||||
|
||||
_lastTimestamp = TimeSpanZero;
|
||||
Beta = ONEEUROFILTER_DEFAULT_BETA;
|
||||
Cutoff = ONEEUROFILTER_DEFAULT_CUTOFF;
|
||||
VelocityCutoff = ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF;
|
||||
}
|
||||
|
||||
OneEuroFilter::OneEuroFilter(float cutoff, float beta)
|
||||
{
|
||||
_lastTimestamp = TimeSpanZero;
|
||||
Beta = beta;
|
||||
Cutoff = cutoff;
|
||||
VelocityCutoff = ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF;
|
||||
}
|
||||
|
||||
GazeFilterArgs^ OneEuroFilter::Update(GazeFilterArgs^ args)
|
||||
{
|
||||
if (_lastTimestamp == TimeSpanZero)
|
||||
{
|
||||
_lastTimestamp = args->Timestamp;
|
||||
_pointFilter = ref new LowpassFilter(args->Location);
|
||||
_deltaFilter = ref new LowpassFilter(Point());
|
||||
return ref new GazeFilterArgs(args->Location, args->Timestamp);
|
||||
}
|
||||
|
||||
Point gazePoint = args->Location;
|
||||
|
||||
// Reducing _beta increases lag. Increasing beta decreases lag and improves response time
|
||||
// But a really high value of beta also contributes to jitter
|
||||
float beta = Beta;
|
||||
|
||||
// This simply represents the cutoff frequency. A lower value reduces jiiter
|
||||
// and higher value increases jitter
|
||||
float cf = Cutoff;
|
||||
Point cutoff = Point(cf, cf);
|
||||
|
||||
// determine sampling frequency based on last time stamp
|
||||
float samplingFrequency = 10000000.0f / max(1, (args->Timestamp - _lastTimestamp).Duration);
|
||||
_lastTimestamp = args->Timestamp;
|
||||
|
||||
// calculate change in distance...
|
||||
Point deltaDistance;
|
||||
deltaDistance.X = gazePoint.X - _pointFilter->Previous.X;
|
||||
deltaDistance.Y = gazePoint.Y - _pointFilter->Previous.Y;
|
||||
|
||||
// ...and velocity
|
||||
Point velocity(deltaDistance.X * samplingFrequency, deltaDistance.Y * samplingFrequency);
|
||||
|
||||
// find the alpha to use for the velocity filter
|
||||
float velocityAlpha = Alpha(samplingFrequency, VelocityCutoff);
|
||||
Point velocityAlphaPoint(velocityAlpha, velocityAlpha);
|
||||
|
||||
// find the filtered velocity
|
||||
Point filteredVelocity = _deltaFilter->Update(velocity, velocityAlphaPoint);
|
||||
|
||||
// ignore sign since it will be taken care of by deltaDistance
|
||||
filteredVelocity.X = abs(filteredVelocity.X);
|
||||
filteredVelocity.Y = abs(filteredVelocity.Y);
|
||||
|
||||
// compute new cutoff to use based on velocity
|
||||
cutoff.X += beta * filteredVelocity.X;
|
||||
cutoff.Y += beta * filteredVelocity.Y;
|
||||
|
||||
// find the new alpha to use to filter the points
|
||||
Point distanceAlpha(Alpha(samplingFrequency, cutoff.X), Alpha(samplingFrequency, cutoff.Y));
|
||||
|
||||
// find the filtered point
|
||||
Point filteredPoint = _pointFilter->Update(gazePoint, distanceAlpha);
|
||||
|
||||
// compute the new args
|
||||
auto fa = ref new GazeFilterArgs(filteredPoint, args->Timestamp);
|
||||
return fa;
|
||||
}
|
||||
|
||||
float OneEuroFilter::Alpha(float rate, float cutoff)
|
||||
{
|
||||
const float PI = 3.14159265f;
|
||||
float te = 1.0f / rate;
|
||||
float tau = (float)(1.0f / (2 * PI * cutoff));
|
||||
float alpha = te / (te + tau);
|
||||
return alpha;
|
||||
}
|
||||
void OneEuroFilter::LoadSettings(ValueSet^ settings)
|
||||
{
|
||||
if (settings->HasKey("OneEuroFilter.Beta"))
|
||||
{
|
||||
Beta = (float)(settings->Lookup("OneEuroFilter.Beta"));
|
||||
}
|
||||
if (settings->HasKey("OneEuroFilter.Cutoff"))
|
||||
{
|
||||
Cutoff = (float)(settings->Lookup("OneEuroFilter.Cutoff"));
|
||||
}
|
||||
if (settings->HasKey("OneEuroFilter.VelocityCutoff"))
|
||||
{
|
||||
VelocityCutoff = (float)(settings->Lookup("OneEuroFilter.VelocityCutoff"));
|
||||
}
|
||||
}
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,134 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
/*
|
||||
* http://www.lifl.fr/~casiez/1euro/
|
||||
* http://www.lifl.fr/~casiez/publications/CHI2012-casiez.pdf
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class OneEuroFilter : IGazeFilter
|
||||
{
|
||||
private const float ONEEUROFILTER_DEFAULT_BETA = 5.0f;
|
||||
private const float ONEEUROFILTER_DEFAULT_CUTOFF = 0.1f;
|
||||
private const float ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF = 1.0f;
|
||||
|
||||
public OneEuroFilter()
|
||||
{
|
||||
_lastTimestamp = TimeSpan.Zero;
|
||||
Beta = ONEEUROFILTER_DEFAULT_BETA;
|
||||
Cutoff = ONEEUROFILTER_DEFAULT_CUTOFF;
|
||||
VelocityCutoff = ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF;
|
||||
}
|
||||
|
||||
public OneEuroFilter(float cutoff, float beta)
|
||||
{
|
||||
_lastTimestamp = TimeSpan.Zero;
|
||||
Beta = beta;
|
||||
Cutoff = cutoff;
|
||||
VelocityCutoff = ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF;
|
||||
}
|
||||
|
||||
public virtual GazeFilterArgs Update(GazeFilterArgs args)
|
||||
{
|
||||
if (_lastTimestamp == TimeSpan.Zero)
|
||||
{
|
||||
_lastTimestamp = args.Timestamp;
|
||||
_pointFilter = new LowpassFilter(args.Location);
|
||||
_deltaFilter = new LowpassFilter(default);
|
||||
return new GazeFilterArgs(args.Location, args.Timestamp);
|
||||
}
|
||||
|
||||
Point gazePoint = args.Location;
|
||||
|
||||
// Reducing _beta increases lag. Increasing beta decreases lag and improves response time
|
||||
// But a really high value of beta also contributes to jitter
|
||||
float beta = Beta;
|
||||
|
||||
// This simply represents the cutoff frequency. A lower value reduces jiiter
|
||||
// and higher value increases jitter
|
||||
float cf = Cutoff;
|
||||
Point cutoff = new Point(cf, cf);
|
||||
|
||||
// determine sampling frequency based on last time stamp
|
||||
float samplingFrequency = 100000000.0f / Math.Max(1, (args.Timestamp - _lastTimestamp).Ticks);
|
||||
_lastTimestamp = args.Timestamp;
|
||||
|
||||
// calculate change in distance...
|
||||
Point deltaDistance;
|
||||
deltaDistance.X = gazePoint.X - _pointFilter.Previous.X;
|
||||
deltaDistance.Y = gazePoint.Y - _pointFilter.Previous.Y;
|
||||
|
||||
// ...and velocity
|
||||
var velocity = new Point(deltaDistance.X * samplingFrequency, deltaDistance.Y * samplingFrequency);
|
||||
|
||||
// find the alpha to use for the velocity filter
|
||||
float velocityAlpha = Alpha(samplingFrequency, VelocityCutoff);
|
||||
var velocityAlphaPoint = new Point(velocityAlpha, velocityAlpha);
|
||||
|
||||
// find the filtered velocity
|
||||
Point filteredVelocity = _deltaFilter.Update(velocity, velocityAlphaPoint);
|
||||
|
||||
// ignore sign since it will be taken care of by deltaDistance
|
||||
filteredVelocity.X = Math.Abs(filteredVelocity.X);
|
||||
filteredVelocity.Y = Math.Abs(filteredVelocity.Y);
|
||||
|
||||
// compute new cutoff to use based on velocity
|
||||
cutoff.X += beta * filteredVelocity.X;
|
||||
cutoff.Y += beta * filteredVelocity.Y;
|
||||
|
||||
// find the new alpha to use to filter the points
|
||||
var distanceAlpha = new Point(Alpha(samplingFrequency, (float)cutoff.X), Alpha(samplingFrequency, (float)cutoff.Y));
|
||||
|
||||
// find the filtered point
|
||||
Point filteredPoint = _pointFilter.Update(gazePoint, distanceAlpha);
|
||||
|
||||
// compute the new args
|
||||
var fa = new GazeFilterArgs(filteredPoint, args.Timestamp);
|
||||
return fa;
|
||||
}
|
||||
|
||||
public virtual void LoadSettings(ValueSet settings)
|
||||
{
|
||||
if (settings.ContainsKey("OneEuroFilter.Beta"))
|
||||
{
|
||||
Beta = (float)settings["OneEuroFilter.Beta"];
|
||||
}
|
||||
|
||||
if (settings.ContainsKey("OneEuroFilter.Cutoff"))
|
||||
{
|
||||
Cutoff = (float)settings["OneEuroFilter.Cutoff"];
|
||||
}
|
||||
|
||||
if (settings.ContainsKey("OneEuroFilter.VelocityCutoff"))
|
||||
{
|
||||
VelocityCutoff = (float)settings["OneEuroFilter.VelocityCutoff"];
|
||||
}
|
||||
}
|
||||
|
||||
public float Beta { get; set; }
|
||||
|
||||
public float Cutoff { get; set; }
|
||||
|
||||
public float VelocityCutoff { get; set; }
|
||||
|
||||
private float Alpha(float rate, float cutoff)
|
||||
{
|
||||
const float PI = 3.14159265f;
|
||||
float te = 1.0f / rate;
|
||||
float tau = (float)(1.0f / (2 * PI * cutoff));
|
||||
float alpha = te / (te + tau);
|
||||
return alpha;
|
||||
}
|
||||
|
||||
private TimeSpan _lastTimestamp;
|
||||
private LowpassFilter _pointFilter;
|
||||
private LowpassFilter _deltaFilter;
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IGazeFilter.h"
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
const float ONEEUROFILTER_DEFAULT_BETA = 5.0f;
|
||||
const float ONEEUROFILTER_DEFAULT_CUTOFF = 0.1f;
|
||||
const float ONEEUROFILTER_DEFAULT_VELOCITY_CUTOFF = 1.0f;
|
||||
|
||||
private ref class LowpassFilter sealed
|
||||
{
|
||||
public:
|
||||
LowpassFilter()
|
||||
{
|
||||
Previous = Point(0, 0);
|
||||
}
|
||||
|
||||
LowpassFilter(Point initial)
|
||||
{
|
||||
Previous = initial;
|
||||
}
|
||||
|
||||
property Point Previous;
|
||||
|
||||
Point Update(Point point, Point alpha)
|
||||
{
|
||||
Point pt;
|
||||
pt.X = (alpha.X * point.X) + ((1 - alpha.X) * Previous.X);
|
||||
pt.Y = (alpha.Y * point.Y) + ((1 - alpha.Y) * Previous.Y);
|
||||
Previous = pt;
|
||||
return Previous;
|
||||
}
|
||||
};
|
||||
|
||||
private ref class OneEuroFilter sealed : public IGazeFilter
|
||||
{
|
||||
public:
|
||||
OneEuroFilter();
|
||||
OneEuroFilter(float cutoff, float beta);
|
||||
virtual GazeFilterArgs^ Update(GazeFilterArgs^ args);
|
||||
virtual void LoadSettings(ValueSet^ settings);
|
||||
|
||||
public:
|
||||
property float Beta;
|
||||
property float Cutoff;
|
||||
property float VelocityCutoff;
|
||||
|
||||
private:
|
||||
float Alpha(float rate, float cutoff);
|
||||
|
||||
private:
|
||||
TimeSpan _lastTimestamp;
|
||||
LowpassFilter^ _pointFilter;
|
||||
LowpassFilter^ _deltaFilter;
|
||||
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,37 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class PivotItemGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal PivotItemGazeTargetItem(UIElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
var headerItem = (PivotHeaderItem)TargetElement;
|
||||
var headerPanel = (PivotHeaderPanel)VisualTreeHelper.GetParent(headerItem);
|
||||
int index = headerPanel.Children.IndexOf(headerItem);
|
||||
|
||||
DependencyObject walker = headerPanel;
|
||||
Pivot pivot;
|
||||
do
|
||||
{
|
||||
walker = VisualTreeHelper.GetParent(walker);
|
||||
pivot = walker as Pivot;
|
||||
}
|
||||
while (pivot == null);
|
||||
|
||||
pivot.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum reflects the states that a user's gaze through while interacting with a control using their eyes.
|
||||
/// </summary>
|
||||
public enum PointerState
|
||||
{
|
||||
/// <summary>
|
||||
/// User's gaze is no longer on the control
|
||||
/// </summary>
|
||||
Exit = 0,
|
||||
|
||||
// The order of the following elements is important because
|
||||
// they represent states that linearly transition to their
|
||||
// immediate successors.
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only
|
||||
/// </summary>
|
||||
PreEnter = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User's gaze has entered a control
|
||||
/// </summary>
|
||||
Enter = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User eye's are focused on the control
|
||||
/// </summary>
|
||||
Fixation = 3,
|
||||
|
||||
/// <summary>
|
||||
/// User is consciously dwelling on the control with an intent to invoke, e.g. click a button
|
||||
/// </summary>
|
||||
Dwell = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User is continuing to dwell on the control for repeated invocation. (For internal use only)
|
||||
/// </summary>
|
||||
DwellRepeat = 5
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This enum reflects the states that a user's gaze through while interacting with a control using their eyes.
|
||||
/// </summary>
|
||||
public enum class PointerState
|
||||
{
|
||||
/// <summary>
|
||||
/// User's gaze is no longer on the control
|
||||
/// </summary>
|
||||
Exit = 0,
|
||||
|
||||
// The order of the following elements is important because
|
||||
// they represent states that linearly transition to their
|
||||
// immediate successors.
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only
|
||||
/// </summary>
|
||||
PreEnter = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User's gaze has entered a control
|
||||
/// </summary>
|
||||
Enter = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User eye's are focused on the control
|
||||
/// </summary>
|
||||
Fixation = 3,
|
||||
|
||||
/// <summary>
|
||||
/// User is consciously dwelling on the control with an intent to invoke, e.g. click a button
|
||||
/// </summary>
|
||||
Dwell = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User is continuing to dwell on the control for repeated invocation. (For internal use only)
|
||||
/// </summary>
|
||||
DwellRepeat = 5
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
|
@ -0,0 +1,25 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Automation.Provider;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
internal class SelectionItemPatternGazeTargetItem : GazeTargetItem
|
||||
{
|
||||
internal SelectionItemPatternGazeTargetItem(UIElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Invoke()
|
||||
{
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(TargetElement);
|
||||
var provider = peer.GetPattern(PatternInterface.SelectionItem) as ISelectionItemProvider;
|
||||
provider.Select();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This parameter is passed to the StateChanged event.
|
||||
/// </summary>
|
||||
public class StateChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state of the user's gaze with respect to a control
|
||||
/// </summary>
|
||||
public PointerState PointerState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets elapsed time since the last state
|
||||
/// </summary>
|
||||
public TimeSpan ElapsedTime => _elapsedTime;
|
||||
|
||||
internal StateChangedEventArgs(UIElement target, PointerState state, TimeSpan elapsedTime)
|
||||
{
|
||||
_hitTarget = target;
|
||||
PointerState = state;
|
||||
_elapsedTime = elapsedTime;
|
||||
}
|
||||
|
||||
private readonly UIElement _hitTarget;
|
||||
private TimeSpan _elapsedTime;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
//Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
|
||||
//See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PointerState.h"
|
||||
|
||||
using namespace Windows::UI::Xaml;
|
||||
|
||||
BEGIN_NAMESPACE_GAZE_INPUT
|
||||
|
||||
/// <summary>
|
||||
/// This parameter is passed to the StateChanged event.
|
||||
/// </summary>
|
||||
public ref struct StateChangedEventArgs sealed
|
||||
{
|
||||
/// <summary>
|
||||
/// The state of user's gaze with respect to a control
|
||||
/// </summary>
|
||||
property GazeInteraction::PointerState PointerState {GazeInteraction::PointerState get() { return _pointerState; }}
|
||||
|
||||
/// <summary>
|
||||
/// Elapsed time since the last state
|
||||
/// </summary>
|
||||
property TimeSpan ElapsedTime {TimeSpan get() { return _elapsedTime; }}
|
||||
|
||||
internal:
|
||||
|
||||
StateChangedEventArgs(UIElement^ target, GazeInteraction::PointerState state, TimeSpan elapsedTime)
|
||||
{
|
||||
_hitTarget = target;
|
||||
_pointerState = state;
|
||||
_elapsedTime = elapsedTime;
|
||||
}
|
||||
|
||||
private:
|
||||
UIElement ^ _hitTarget;
|
||||
GazeInteraction::PointerState _pointerState;
|
||||
TimeSpan _elapsedTime;
|
||||
};
|
||||
|
||||
END_NAMESPACE_GAZE_INPUT
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче