Uno.WindowsCommunityToolkit/CommunityToolkit.WinUI.Inpu.../GazePointerProxy.cs

184 строки
6.6 KiB
C#

// 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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
namespace CommunityToolkit.WinUI.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;
}
}