diff --git a/GazeInputTest/GazeInputTest.csproj b/GazeInputTest/GazeInputTest.csproj index 9c699a3f5bf..9364b18cddc 100644 --- a/GazeInputTest/GazeInputTest.csproj +++ b/GazeInputTest/GazeInputTest.csproj @@ -157,8 +157,8 @@ - - {a5e98964-45b1-442d-a07a-298a3221d81e} + + {5bf75694-798a-43a0-8150-415de195359c} Microsoft.Toolkit.Uwp.Input.GazeInteraction diff --git a/GazeInputTest/Properties/AssemblyInfo.cs b/GazeInputTest/Properties/AssemblyInfo.cs index 4d6a9b7872f..ac358b76a12 100644 --- a/GazeInputTest/Properties/AssemblyInfo.cs +++ b/GazeInputTest/Properties/AssemblyInfo.cs @@ -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 @@ // 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)] \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ComboBoxItemGazeTargetItem.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ComboBoxItemGazeTargetItem.cs new file mode 100644 index 00000000000..e9f1aa85931 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ComboBoxItemGazeTargetItem.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.cs new file mode 100644 index 00000000000..fe953ca9eba --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.cs @@ -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 +{ + /// + /// This parameter is passed to the GazeElement::Invoked event and allows + /// the application to prevent default invocation when the user dwells on a control + /// + public sealed class DwellInvokedRoutedEventArgs : HandledEventArgs + { + internal DwellInvokedRoutedEventArgs() + { + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.h deleted file mode 100644 index 7ef31be8bb5..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellInvokedRoutedEventArgs.h +++ /dev/null @@ -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 - -/// -/// This parameter is passed to the GazeElement::Invoked event and allows -/// the application to prevent default invocation when the user dwells on a control -/// -public ref class DwellInvokedRoutedEventArgs : public RoutedEventArgs -{ -public: - - /// - /// The application should set this value to true to prevent invoking the control when the user dwells on a control - /// - property bool Handled; - -internal: - - DwellInvokedRoutedEventArgs() - { - } -}; - -END_NAMESPACE_GAZE_INPUT \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.cs new file mode 100644 index 00000000000..d7e1974a555 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.cs @@ -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 +{ + /// + /// 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 + /// + public sealed class DwellProgressEventArgs : HandledEventArgs + { + /// + /// Gets an enum that reflects the current state of dwell progress + /// + public DwellProgressState State { get; } + + /// + /// Gets a value between 0 and 1 that reflects the fraction of progress towards completing dwell + /// + public double Progress { get; } + + internal DwellProgressEventArgs(DwellProgressState state, TimeSpan elapsedDuration, TimeSpan triggerDuration) + { + State = state; + Progress = ((double)elapsedDuration.Ticks) / triggerDuration.Ticks; + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.h deleted file mode 100644 index 86c5b2ef5eb..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressEventArgs.h +++ /dev/null @@ -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 - -/// -/// 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 -/// -public ref class DwellProgressEventArgs : public RoutedEventArgs -{ -public: - - /// - /// An enum that reflects the current state of dwell progress - /// - property DwellProgressState State { DwellProgressState get() { return _state; }} - - /// - /// A value between 0 and 1 that reflects the fraction of progress towards completing dwell - /// - property double Progress { double get() { return _progress; }} - - /// - /// 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 - /// - 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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.cs new file mode 100644 index 00000000000..79c6275a23f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.cs @@ -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 +{ + /// + /// An enum that reflects the current state of progress towards dwell when a user is focused on a control + /// + public enum DwellProgressState + { + /// + /// User is not looking at the control + /// + Idle, + + /// + /// Gaze has entered control but we're not yet showing progress. + /// + Fixating, + + /// + /// User is continuing to focus on a control with an intent to dwell and invoke + /// + Progressing, + + /// + /// User has completed dwelling on a control + /// + Complete + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.h deleted file mode 100644 index a60c0c54d9b..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/DwellProgressState.h +++ /dev/null @@ -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 - -/// -/// An enum that reflects the current state of progress towards dwell when a user is focused on a control -/// -public enum class DwellProgressState -{ - /// - /// User is not looking at the control - /// - Idle, - - /// - /// Gaze has entered control but we're not yet showing progress. - /// - Fixating, - - /// - /// User is continuing to focus on a control with an intent to dwell and invoke - /// - Progressing, - - /// - /// User has completed dwelling on a control - /// - Complete -}; - -END_NAMESPACE_GAZE_INPUT \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ExpandCollapsePatternGazeTargetItem.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ExpandCollapsePatternGazeTargetItem.cs new file mode 100644 index 00000000000..cd6e5c58d9f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/ExpandCollapsePatternGazeTargetItem.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cpp deleted file mode 100644 index b8498d2dc22..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cpp +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cs new file mode 100644 index 00000000000..d4e95c2f1f0 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.cs @@ -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; + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.h deleted file mode 100644 index db92c5a229d..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeCursor.h +++ /dev/null @@ -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(_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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.cs new file mode 100644 index 00000000000..44a68306589 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.cs @@ -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 +{ + /// + /// Surrogate object attached to controls allowing subscription to per-control gaze events. + /// + public class GazeElement : DependencyObject + { + /// + /// This event is fired when the state of the user's gaze on a control has changed + /// + public event EventHandler StateChanged; + + /// + /// 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 + /// + public event EventHandler Invoked; + + /// + /// This event is fired to inform the application of the progress towards dwell + /// + public event EventHandler 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; + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.h deleted file mode 100644 index cabd7df89d9..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeElement.h +++ /dev/null @@ -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 - -/// -/// Surrogate object attached to controls allowing subscription to per-control gaze events. -/// -public ref class GazeElement sealed : public DependencyObject -{ -public: - - /// - /// This event is fired when the state of the user's gaze on a control has changed - /// - event EventHandler^ StateChanged; - - /// - /// 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 - /// - event EventHandler^ Invoked; - - /// - /// This event is fired to inform the application of the progress towards dwell - /// - event EventHandler^ 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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cpp deleted file mode 100644 index ee42274a31b..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cpp +++ /dev/null @@ -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" \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cs new file mode 100644 index 00000000000..85805fc098e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.cs @@ -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 +{ + /// + /// EventArgs used to send Gaze events. See + /// + public sealed class GazeEventArgs : HandledEventArgs + { + /// + /// Gets the location of the Gaze event + /// + public Point Location { get; private set; } + + /// + /// Gets the timestamp of the gaze event + /// + public TimeSpan Timestamp { get; private set; } + + internal void Set(Point location, TimeSpan timestamp) + { + Handled = false; + Location = location; + Timestamp = timestamp; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.h deleted file mode 100644 index 75f635d4aae..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeEventArgs.h +++ /dev/null @@ -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 diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cpp deleted file mode 100644 index 8311455240f..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cpp +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cs new file mode 100644 index 00000000000..e75146eabbb --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.cs @@ -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 _cache = new List(); + + 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); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.h deleted file mode 100644 index 33b514e0b9e..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFeedbackPopupFactory.h +++ /dev/null @@ -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^ s_cache = ref new Vector(); - -public: - - Popup^ Get(); - - void Return(Popup^ popup); -}; - -END_NAMESPACE_GAZE_INPUT diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFilterArgs.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFilterArgs.cs new file mode 100644 index 00000000000..9d0318f5ba9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeFilterArgs.cs @@ -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 +{ + /// + /// 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 + /// + internal struct GazeFilterArgs + { + /// + /// Gets the current point in the gaze stream + /// + public Point Location => _location; + + /// + /// Gets the timestamp associated with the current point + /// + public TimeSpan Timestamp => _timestamp; + + internal GazeFilterArgs(Point location, TimeSpan timestamp) + { + _location = location; + _timestamp = timestamp; + } + + private Point _location; + private TimeSpan _timestamp; + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.cpp deleted file mode 100644 index 628016c41df..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.cpp +++ /dev/null @@ -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 ^ 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 diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.h deleted file mode 100644 index f8b862ee40e..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers.h +++ /dev/null @@ -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 diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidParsersHelpers.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidParsersHelpers.cs new file mode 100644 index 00000000000..34781115470 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidParsersHelpers.cs @@ -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; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPosition.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPosition.cs new file mode 100644 index 00000000000..0da26a306f2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPosition.cs @@ -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 +{ + /// + /// Represents one Hid position + /// + public class GazeHidPosition + { + /// + /// Gets or sets the X axis of this position + /// + public long X { get; set; } + + /// + /// Gets or sets the Y axis of this position + /// + public long Y { get; set; } + + /// + /// Gets or sets the Z axis of this position + /// + public long Z { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionParser.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionParser.cs new file mode 100644 index 00000000000..e9e280089e9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionParser.cs @@ -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 +{ + /// + /// Hid Position Parser + /// + public class GazeHidPositionParser + { + /// + /// Initializes a new instance of the class. + /// + /// used to parse. + /// The used to parse. + 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); + } + + /// + /// Parses the position from the report. + /// + /// A object used on the parsing. + /// The parsed from the report. + 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; + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositions.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositions.cs new file mode 100644 index 00000000000..66e8d0d6297 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositions.cs @@ -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 +{ + /// + /// Represents the Hid positions + /// + public class GazeHidPositions + { + /// + /// Gets or sets the left eye position + /// + public GazeHidPosition LeftEyePosition { get; set; } + + /// + /// Gets or sets the right eye position + /// + public GazeHidPosition RightEyePosition { get; set; } + + /// + /// Gets or sets the head position + /// + public GazeHidPosition HeadPosition { get; set; } + + /// + /// Gets or sets the head rotation + /// + public GazeHidPosition HeadRotation { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionsParser.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionsParser.cs new file mode 100644 index 00000000000..9c9765169b1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidPositionsParser.cs @@ -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 +{ + /// + /// Hid Positions Parser + /// + public class GazeHidPositionsParser + { + /// + /// Initializes a new instance of the class. + /// + /// used to parse. + 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); + } + + /// + /// Parses the positions from the report. + /// + /// A object used on the parsing. + /// The parsed from the report. + 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; + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidRotationParser.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidRotationParser.cs new file mode 100644 index 00000000000..970c113f597 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidParsers/GazeHidRotationParser.cs @@ -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 +{ + /// + /// Hid Rotation Parser + /// + public class GazeHidRotationParser + { + /// + /// Initializes a new instance of the class. + /// + /// used to parse. + /// The used to parse. + 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); + } + + /// + /// Parses the rotation from the report. + /// + /// A object used on the parsing. + /// The parsed from the report. + 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; + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.cs new file mode 100644 index 00000000000..c816bec131b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.cs @@ -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 +{ + /// + /// 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 + /// + 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 + } +} diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.h deleted file mode 100644 index fd08d7a10b6..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHidUsages.h +++ /dev/null @@ -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 - -/// -/// 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 -/// -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 diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.cs new file mode 100644 index 00000000000..4dfe51ba96c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.h deleted file mode 100644 index b1c1121bb4f..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeHistoryItem.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cpp deleted file mode 100644 index 87f66b2cde2..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cpp +++ /dev/null @@ -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(ob); - auto interaction = safe_cast(args->NewValue); - GazePointerProxy::SetInteraction(element, interaction); -} - -static void OnIsCursorVisibleChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args) -{ - GazePointer::Instance->IsCursorVisible = safe_cast(args->NewValue); -} - -static void OnCursorRadiusChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args) -{ - GazePointer::Instance->CursorRadius = safe_cast(args->NewValue); -} - -static void OnIsSwitchEnabledChanged(DependencyObject^ ob, DependencyPropertyChangedEventArgs^ args) -{ - GazePointer::Instance->IsSwitchEnabled = safe_cast(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(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(element->GetValue(s_interactionProperty)); } -bool GazeInput::GetIsCursorVisible(UIElement^ element) { return safe_cast(element->GetValue(s_isCursorVisibleProperty)); } -int GazeInput::GetCursorRadius(UIElement^ element) { return safe_cast(element->GetValue(s_cursorRadiusProperty)); } -GazeElement^ GazeInput::GetGazeElement(UIElement^ element) { return safe_cast(element->GetValue(s_gazeElementProperty)); } -TimeSpan GazeInput::GetFixationDuration(UIElement^ element) { return safe_cast(element->GetValue(s_fixationDurationProperty)); } -TimeSpan GazeInput::GetDwellDuration(UIElement^ element) { return safe_cast(element->GetValue(s_dwellDurationProperty)); } -TimeSpan GazeInput::GetRepeatDelayDuration(UIElement^ element) { return safe_cast(element->GetValue(s_repeatDelayDurationProperty)); } -TimeSpan GazeInput::GetDwellRepeatDuration(UIElement^ element) { return safe_cast(element->GetValue(s_dwellRepeatDurationProperty)); } -TimeSpan GazeInput::GetThresholdDuration(UIElement^ element) { return safe_cast(element->GetValue(s_thresholdDurationProperty)); } -int GazeInput::GetMaxDwellRepeatCount(UIElement^ element) { return safe_cast(element->GetValue(s_maxRepeatCountProperty)); } -bool GazeInput::GetIsSwitchEnabled(UIElement^ element) { return safe_cast(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^ handler) -{ - return GazePointer::Instance->IsDeviceAvailableChanged += handler; -} - -void GazeInput::IsDeviceAvailableChanged::remove(EventRegistrationToken token) -{ - GazePointer::Instance->IsDeviceAvailableChanged -= token; -} - -END_NAMESPACE_GAZE_INPUT \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cs b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cs new file mode 100644 index 00000000000..2991c100256 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.cs @@ -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 +{ + /// + /// Static class primarily providing access to attached properties controlling gaze behavior. + /// + [Windows.Foundation.Metadata.WebHostHidden] + public class GazeInput + { + internal static readonly TimeSpan UnsetTimeSpan = new TimeSpan(-1); + + /// + /// Gets the Interaction dependency property + /// + 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); + } + + /// + /// Gets the IsCursorVisible dependency property + /// + 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; + } + + /// + /// Gets the CursorRadius dependency property + /// + 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; + } + + /// + /// Gets the GazeElement dependency property + /// + public static DependencyProperty GazeElementProperty { get; } = DependencyProperty.RegisterAttached("GazeElement", typeof(GazeElement), typeof(GazeInput), new PropertyMetadata(null)); + + /// + /// Gets the FixationDuration dependency property + /// + public static DependencyProperty FixationDurationProperty { get; } = DependencyProperty.RegisterAttached("FixationDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan)); + + /// + /// Gets the DwellDuration dependency property + /// + public static DependencyProperty DwellDurationProperty { get; } = DependencyProperty.RegisterAttached("DwellDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan)); + + /// + /// Gets the RepeatDelayDuration dependency property + /// + public static DependencyProperty RepeatDelayDurationProperty { get; } = DependencyProperty.RegisterAttached("RepeatDelayDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan)); + + /// + /// Gets the DwellRepeatDuration dependency property + /// + public static DependencyProperty DwellRepeatDurationProperty { get; } = DependencyProperty.RegisterAttached("DwellRepeatDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan)); + + /// + /// Gets the ThresholdDuration dependency property + /// + public static DependencyProperty ThresholdDurationProperty { get; } = DependencyProperty.RegisterAttached("ThresholdDuration", typeof(TimeSpan), typeof(GazeInput), new PropertyMetadata(UnsetTimeSpan)); + + /// + /// Gets the MaxDwellRepeatCount dependency property + /// + public static DependencyProperty MaxDwellRepeatCountProperty { get; } = DependencyProperty.RegisterAttached("MaxDwellRepeatCount", typeof(int), typeof(GazeInput), new PropertyMetadata(0)); + + /// + /// Gets the IsSwitchEnabled dependency property + /// + 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; + } + + /// + /// Gets or sets the brush to use when displaying the default indication that gaze entered a control + /// + public static Brush DwellFeedbackEnterBrush + { + get + { + return GazePointer.Instance.EnterBrush; + } + + set + { + GazePointer.Instance.EnterBrush = value; + } + } + + /// + /// Gets or sets the brush to use when displaying the default animation for dwell press + /// + public static Brush DwellFeedbackProgressBrush + { + get + { + return GazePointer.Instance.ProgressBrush; + } + + set + { + GazePointer.Instance.ProgressBrush = value; + } + } + + /// + /// Gets or sets the brush to use when displaying the default animation for dwell complete + /// + public static Brush DwellFeedbackCompleteBrush + { + get + { + return GazePointer.Instance.CompleteBrush; + } + + set + { + GazePointer.Instance.CompleteBrush = value; + } + } + + /// + /// Gets or sets the thickness of the lines animated for dwell. + /// + public static double DwellStrokeThickness + { + get + { + return GazePointer.Instance.DwellStrokeThickness; + } + + set + { + GazePointer.Instance.DwellStrokeThickness = value; + } + } + + /// + /// Gets or sets the interaction default + /// + 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; + } + } + } + + /// + /// Gets the status of gaze interaction over that particular XAML element. + /// + /// The status of gaze interaction over that particular XAML element. + public static Interaction GetInteraction(UIElement element) + { + return (Interaction)element.GetValue(InteractionProperty); + } + + /// + /// Gets a boolean indicating whether cursor is shown while user is looking at the school. + /// + /// True the cursor is shown while user is looking at the school; otherwise, false. + public static bool GetIsCursorVisible(UIElement element) + { + return (bool)element.GetValue(IsCursorVisibleProperty); + } + + /// + /// Gets the size of the gaze cursor radius. + /// + /// The size of the gaze cursor radius. + public static int GetCursorRadius(UIElement element) + { + return (int)element.GetValue(CursorRadiusProperty); + } + + /// + /// Gets the GazeElement associated with an UIElement. + /// + /// The GazeElement associated with an UIElement. + public static GazeElement GetGazeElement(UIElement element) + { + return (GazeElement)element.GetValue(GazeElementProperty); + } + + /// + /// 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. + /// + /// Duration for the control to transition from the Enter state to the Fixation state. + public static TimeSpan GetFixationDuration(UIElement element) + { + return (TimeSpan)element.GetValue(FixationDurationProperty); + } + + /// + /// 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. + /// + /// The duration for the control to transition from the Fixation state to the Dwell state. + public static TimeSpan GetDwellDuration(UIElement element) + { + return (TimeSpan)element.GetValue(DwellDurationProperty); + } + + /// + /// Gets the additional duration for the first repeat to occur. This prevents inadvertent repeated invocation. + /// + /// The additional duration for the first repeat to occur. + public static TimeSpan GetRepeatDelayDuration(UIElement element) + { + return (TimeSpan)element.GetValue(RepeatDelayDurationProperty); + } + + /// + /// 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. + /// + /// The duration of repeated dwell invocations. + public static TimeSpan GetDwellRepeatDuration(UIElement element) + { + return (TimeSpan)element.GetValue(DwellRepeatDurationProperty); + } + + /// + /// 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. + /// + /// The duration that controls when the PointerState moves to either the Enter state or the Exit state. + public static TimeSpan GetThresholdDuration(UIElement element) + { + return (TimeSpan)element.GetValue(ThresholdDurationProperty); + } + + /// + /// 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. + /// + /// The maximum times the control will invoked repeatedly without the user's gaze having to leave and re-enter the control. + public static int GetMaxDwellRepeatCount(UIElement element) + { + return (int)element.GetValue(MaxDwellRepeatCountProperty); + } + + /// + /// Gets a boolean indicating whether gaze plus switch is enabled. + /// + /// A boolean indicating whether gaze plus switch is enabled. + public static bool GetIsSwitchEnabled(UIElement element) + { + return (bool)element.GetValue(IsSwitchEnabledProperty); + } + + /// + /// Sets the status of gaze interaction over that particular XAML element. + /// + public static void SetInteraction(UIElement element, Interaction value) + { + element.SetValue(InteractionProperty, value); + } + + /// + /// Sets a boolean indicating whether cursor is shown while user is looking at the school. + /// + public static void SetIsCursorVisible(UIElement element, bool value) + { + element.SetValue(IsCursorVisibleProperty, value); + } + + /// + /// Sets the size of the gaze cursor radius. + /// + public static void SetCursorRadius(UIElement element, int value) + { + element.SetValue(CursorRadiusProperty, value); + } + + /// + /// Sets the GazeElement associated with an UIElement. + /// + public static void SetGazeElement(UIElement element, GazeElement value) + { + element.SetValue(GazeElementProperty, value); + } + + /// + /// 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. + /// + public static void SetFixationDuration(UIElement element, TimeSpan span) + { + element.SetValue(FixationDurationProperty, span); + } + + /// + /// 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. + /// + public static void SetDwellDuration(UIElement element, TimeSpan span) + { + element.SetValue(DwellDurationProperty, span); + } + + /// + /// Sets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation. + /// + public static void SetRepeatDelayDuration(UIElement element, TimeSpan span) + { + element.SetValue(RepeatDelayDurationProperty, span); + } + + /// + /// 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. + /// + public static void SetDwellRepeatDuration(UIElement element, TimeSpan span) + { + element.SetValue(DwellRepeatDurationProperty, span); + } + + /// + /// 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. + /// + public static void SetThresholdDuration(UIElement element, TimeSpan span) + { + element.SetValue(ThresholdDurationProperty, span); + } + + /// + /// 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. + /// + public static void SetMaxDwellRepeatCount(UIElement element, int value) + { + element.SetValue(MaxDwellRepeatCountProperty, value); + } + + /// + /// Sets the boolean indicating whether gaze plus switch is enabled. + /// + public static void SetIsSwitchEnabled(UIElement element, bool value) + { + element.SetValue(IsSwitchEnabledProperty, value); + } + + /// + /// Gets the GazePointer object. + /// + /// The GazePointer associated with that particular page. + public static GazePointer GetGazePointer(Page page) + { + return GazePointer.Instance; + } + + /// + /// Invoke the default action of the specified UIElement. + /// + public static void Invoke(UIElement element) + { + var item = GazeTargetItem.GetOrCreate(element); + item.Invoke(); + } + + /// + /// 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. + /// + public static bool IsDeviceAvailable + { + get + { + return GazePointer.Instance.IsDeviceAvailable; + } + } + + /// + /// Event triggered whenever IsDeviceAvailable changes value. + /// + public static event EventHandler IsDeviceAvailableChanged + { + add + { + GazePointer.Instance.IsDeviceAvailableChanged += value; + } + + remove + { + GazePointer.Instance.IsDeviceAvailableChanged -= value; + } + } + + /// + /// 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. + /// + public static void LoadSettings(ValueSet settings) + { + GazePointer.Instance.LoadSettings(settings); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h deleted file mode 100644 index 2c9f630b0d4..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h +++ /dev/null @@ -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; - -/// -/// Static class primarily providing access to attached properties controlling gaze behavior. -/// -[Windows::Foundation::Metadata::WebHostHidden] -public ref class GazeInput sealed -{ -public: - - /// - /// Identifies the Interaction dependency property - /// - static property DependencyProperty^ InteractionProperty { DependencyProperty^ get(); } - - /// - /// Identifies the IsCursorVisible dependency property - /// - static property DependencyProperty^ IsCursorVisibleProperty { DependencyProperty^ get(); } - - /// - /// Identifies the CursorRadius dependency property - /// - static property DependencyProperty^ CursorRadiusProperty { DependencyProperty^ get(); } - - /// - /// Identifies the GazeElement dependency property - /// - static property DependencyProperty^ GazeElementProperty { DependencyProperty^ get(); } - - /// - /// Identifies the FixationDuration dependency property - /// - static property DependencyProperty^ FixationDurationProperty { DependencyProperty^ get(); } - - /// - /// Identifies the DwellDuration dependency property - /// - static property DependencyProperty^ DwellDurationProperty { DependencyProperty^ get(); } - - /// - /// Identifies the RepeatDelayDuration dependency property - /// - static property DependencyProperty^ RepeatDelayDurationProperty { DependencyProperty^ get(); } - - /// - /// Identifies the DwellRepeatDuration dependency property - /// - static property DependencyProperty^ DwellRepeatDurationProperty { DependencyProperty^ get(); } - - /// - /// Identifies the ThresholdDuration dependency property - /// - static property DependencyProperty^ ThresholdDurationProperty { DependencyProperty^ get(); } - - /// - /// Identifies the MaxDwellRepeatCount dependency property - /// - static property DependencyProperty^ MaxDwellRepeatCountProperty { DependencyProperty^ get(); } - - /// - /// Identifies the IsSwitchEnabled dependency property - /// - static property DependencyProperty^ IsSwitchEnabledProperty { DependencyProperty^ get(); } - - /// - /// Gets or sets the brush to use when displaying the default indication that gaze entered a control - /// - static property Brush^ DwellFeedbackEnterBrush { Brush^ get(); void set(Brush^ value); } - - /// - /// Gets or sets the brush to use when displaying the default animation for dwell press - /// - static property Brush^ DwellFeedbackProgressBrush { Brush^ get(); void set(Brush^ value); } - - /// - /// Gets or sets the brush to use when displaying the default animation for dwell complete - /// - static property Brush^ DwellFeedbackCompleteBrush { Brush^ get(); void set(Brush^ value); } - - /// - /// Gets or sets the thickness of the lines animated for dwell. - /// - static property double DwellStrokeThickness { double get(); void set(double value); } - - /// - /// Gets or sets the interaction default - /// - static property GazeInteraction::Interaction Interaction { GazeInteraction::Interaction get(); void set(GazeInteraction::Interaction value); } - - /// - /// Gets the status of gaze interaction over that particular XAML element. - /// - static GazeInteraction::Interaction GetInteraction(UIElement^ element); - - /// - /// Gets Boolean indicating whether cursor is shown while user is looking at the school. - /// - static bool GetIsCursorVisible(UIElement^ element); - - /// - /// Gets the size of the gaze cursor radius. - /// - static int GetCursorRadius(UIElement^ element); - - /// - /// Gets the GazeElement associated with an UIElement. - /// - static GazeElement^ GetGazeElement(UIElement^ element); - - /// - /// 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. - /// - static TimeSpan GetFixationDuration(UIElement^ element); - - /// - /// 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. - /// - static TimeSpan GetDwellDuration(UIElement^ element); - - /// - /// Gets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation. - /// - static TimeSpan GetRepeatDelayDuration(UIElement^ element); - - /// - /// 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. - /// - static TimeSpan GetDwellRepeatDuration(UIElement^ element); - - /// - /// 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. - /// - static TimeSpan GetThresholdDuration(UIElement^ element); - - /// - /// 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. - /// - static int GetMaxDwellRepeatCount(UIElement^ element); - - /// - /// Gets the Boolean indicating whether gaze plus switch is enabled. - /// - static bool GetIsSwitchEnabled(UIElement^ element); - - /// - /// Sets the status of gaze interaction over that particular XAML element. - /// - static void SetInteraction(UIElement^ element, GazeInteraction::Interaction value); - - /// - /// Sets Boolean indicating whether cursor is shown while user is looking at the school. - /// - static void SetIsCursorVisible(UIElement^ element, bool value); - - /// - /// Sets the size of the gaze cursor radius. - /// - static void SetCursorRadius(UIElement^ element, int value); - - /// - /// Sets the GazeElement associated with an UIElement. - /// - static void SetGazeElement(UIElement^ element, GazeElement^ value); - - /// - /// 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. - /// - static void SetFixationDuration(UIElement^ element, TimeSpan span); - - /// - /// 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. - /// - static void SetDwellDuration(UIElement^ element, TimeSpan span); - - /// - /// Sets the additional duration for the first repeat to occur.This prevents inadvertent repeated invocation. - /// - static void SetRepeatDelayDuration(UIElement^ element, TimeSpan span); - - /// - /// 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. - /// - static void SetDwellRepeatDuration(UIElement^ element, TimeSpan span); - - /// - /// 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. - /// - static void SetThresholdDuration(UIElement^ element, TimeSpan span); - - /// - /// 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. - /// - static void SetMaxDwellRepeatCount(UIElement^ element, int value); - - /// - /// Sets the Boolean indicating whether gaze plus switch is enabled. - /// - static void SetIsSwitchEnabled(UIElement^ element, bool value); - - /// - /// Gets the GazePointer object. - /// - static GazePointer^ GetGazePointer(Page^ page); - - /// - /// Invoke the default action of the specified UIElement. - /// - static void Invoke(UIElement^ element); - - /// - /// Reports whether a gaze input device is available, and hence whether there is any possibility of gaze events occurring in the application. - /// - static property bool IsDeviceAvailable { bool get(); } - - /// - /// Event triggered whenever IsDeviceAvailable changes value. - /// - static event EventHandler^ IsDeviceAvailableChanged - { - EventRegistrationToken add(EventHandler^ handler); - void remove(EventRegistrationToken token); - } - - /// - /// 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. - /// - static void LoadSettings(ValueSet^ settings); - -internal: - - static TimeSpan UnsetTimeSpan; -}; - -END_NAMESPACE_GAZE_INPUT \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp deleted file mode 100644 index 9ae9c31754e..00000000000 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp +++ /dev/null @@ -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(this, &GazePointer::OnEyesOff); - - // provide a default of GAZE_IDLE_TIME microseconds to fire eyes off - EyesOffDelay = GAZE_IDLE_TIME; - - InitializeHistogram(); - - _devices = ref new Vector(); - _watcher = GazeInputSourcePreview::CreateWatcher(); - _watcher->Added += ref new TypedEventHandler(this, &GazePointer::OnDeviceAdded); - _watcher->Removed += ref new TypedEventHandler(this, &GazePointer::OnDeviceRemoved); - _watcher->Start(); -} - -EventRegistrationToken GazePointer::GazeEvent::add(EventHandler^ 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(); - - _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(); -} - -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); -} - -/// -/// Find the parent to inherit properties from. -/// -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(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(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(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(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