// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; using Avalonia.VisualTree; namespace Avalonia.Input { /// /// Implements input-related functionality for a control. /// public class InputElement : Interactive, IInputElement { /// /// Defines the property. /// public static readonly StyledProperty FocusableProperty = AvaloniaProperty.Register(nameof(Focusable)); /// /// Defines the property. /// public static readonly StyledProperty IsEnabledProperty = AvaloniaProperty.Register(nameof(IsEnabled), true); /// /// Defines the property. /// public static readonly DirectProperty IsEffectivelyEnabledProperty = AvaloniaProperty.RegisterDirect( nameof(IsEffectivelyEnabled), o => o.IsEffectivelyEnabled); /// /// Gets or sets associated mouse cursor. /// public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); /// /// Defines the property. /// public static readonly DirectProperty IsFocusedProperty = AvaloniaProperty.RegisterDirect(nameof(IsFocused), o => o.IsFocused); /// /// Defines the property. /// public static readonly StyledProperty IsHitTestVisibleProperty = AvaloniaProperty.Register(nameof(IsHitTestVisible), true); /// /// Defines the property. /// public static readonly DirectProperty IsPointerOverProperty = AvaloniaProperty.RegisterDirect(nameof(IsPointerOver), o => o.IsPointerOver); /// /// Defines the event. /// public static readonly RoutedEvent GotFocusEvent = RoutedEvent.Register(nameof(GotFocus), RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent LostFocusEvent = RoutedEvent.Register(nameof(LostFocus), RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent KeyDownEvent = RoutedEvent.Register( "KeyDown", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent KeyUpEvent = RoutedEvent.Register( "KeyUp", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent TextInputEvent = RoutedEvent.Register( "TextInput", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent PointerEnterEvent = RoutedEvent.Register(nameof(PointerEnter), RoutingStrategies.Direct); /// /// Defines the event. /// public static readonly RoutedEvent PointerLeaveEvent = RoutedEvent.Register(nameof(PointerLeave), RoutingStrategies.Direct); /// /// Defines the event. /// public static readonly RoutedEvent PointerMovedEvent = RoutedEvent.Register( "PointerMove", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent PointerPressedEvent = RoutedEvent.Register( "PointerPressed", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent PointerReleasedEvent = RoutedEvent.Register( "PointerReleased", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the routed event. /// public static readonly RoutedEvent PointerCaptureLostEvent = RoutedEvent.Register( "PointerCaptureLost", RoutingStrategies.Direct); /// /// Defines the event. /// public static readonly RoutedEvent PointerWheelChangedEvent = RoutedEvent.Register( "PointerWheelChanged", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; /// /// Defines the event. /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isPointerOver; private GestureRecognizerCollection _gestureRecognizers; /// /// Initializes static members of the class. /// static InputElement() { IsEnabledProperty.Changed.Subscribe(IsEnabledChanged); GotFocusEvent.AddClassHandler((x, e) => x.OnGotFocus(e)); LostFocusEvent.AddClassHandler((x, e) => x.OnLostFocus(e)); KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e)); KeyUpEvent.AddClassHandler((x, e) => x.OnKeyUp(e)); TextInputEvent.AddClassHandler((x, e) => x.OnTextInput(e)); PointerEnterEvent.AddClassHandler((x, e) => x.OnPointerEnterCore(e)); PointerLeaveEvent.AddClassHandler((x, e) => x.OnPointerLeaveCore(e)); PointerMovedEvent.AddClassHandler((x, e) => x.OnPointerMoved(e)); PointerPressedEvent.AddClassHandler((x, e) => x.OnPointerPressed(e)); PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); } public InputElement() { UpdatePseudoClasses(IsFocused, IsPointerOver); } /// /// Occurs when the control receives focus. /// public event EventHandler GotFocus { add { AddHandler(GotFocusEvent, value); } remove { RemoveHandler(GotFocusEvent, value); } } /// /// Occurs when the control loses focus. /// public event EventHandler LostFocus { add { AddHandler(LostFocusEvent, value); } remove { RemoveHandler(LostFocusEvent, value); } } /// /// Occurs when a key is pressed while the control has focus. /// public event EventHandler KeyDown { add { AddHandler(KeyDownEvent, value); } remove { RemoveHandler(KeyDownEvent, value); } } /// /// Occurs when a key is released while the control has focus. /// public event EventHandler KeyUp { add { AddHandler(KeyUpEvent, value); } remove { RemoveHandler(KeyUpEvent, value); } } /// /// Occurs when a user typed some text while the control has focus. /// public event EventHandler TextInput { add { AddHandler(TextInputEvent, value); } remove { RemoveHandler(TextInputEvent, value); } } /// /// Occurs when the pointer enters the control. /// public event EventHandler PointerEnter { add { AddHandler(PointerEnterEvent, value); } remove { RemoveHandler(PointerEnterEvent, value); } } /// /// Occurs when the pointer leaves the control. /// public event EventHandler PointerLeave { add { AddHandler(PointerLeaveEvent, value); } remove { RemoveHandler(PointerLeaveEvent, value); } } /// /// Occurs when the pointer moves over the control. /// public event EventHandler PointerMoved { add { AddHandler(PointerMovedEvent, value); } remove { RemoveHandler(PointerMovedEvent, value); } } /// /// Occurs when the pointer is pressed over the control. /// public event EventHandler PointerPressed { add { AddHandler(PointerPressedEvent, value); } remove { RemoveHandler(PointerPressedEvent, value); } } /// /// Occurs when the pointer is released over the control. /// public event EventHandler PointerReleased { add { AddHandler(PointerReleasedEvent, value); } remove { RemoveHandler(PointerReleasedEvent, value); } } /// /// Occurs when the control or its child control loses the pointer capture for any reason, /// event will not be triggered for a parent control if capture was transferred to another child of that parent control /// public event EventHandler PointerCaptureLost { add => AddHandler(PointerCaptureLostEvent, value); remove => RemoveHandler(PointerCaptureLostEvent, value); } /// /// Occurs when the mouse wheen is scrolled over the control. /// public event EventHandler PointerWheelChanged { add { AddHandler(PointerWheelChangedEvent, value); } remove { RemoveHandler(PointerWheelChangedEvent, value); } } /// /// Occurs when a tap gesture occurs on the control. /// public event EventHandler Tapped { add { AddHandler(TappedEvent, value); } remove { RemoveHandler(TappedEvent, value); } } /// /// Occurs when a double-tap gesture occurs on the control. /// public event EventHandler DoubleTapped { add { AddHandler(DoubleTappedEvent, value); } remove { RemoveHandler(DoubleTappedEvent, value); } } /// /// Gets or sets a value indicating whether the control can receive focus. /// public bool Focusable { get { return GetValue(FocusableProperty); } set { SetValue(FocusableProperty, value); } } /// /// Gets or sets a value indicating whether the control is enabled for user interaction. /// public bool IsEnabled { get { return GetValue(IsEnabledProperty); } set { SetValue(IsEnabledProperty, value); } } /// /// Gets or sets associated mouse cursor. /// public Cursor Cursor { get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } /// /// Gets a value indicating whether the control is focused. /// public bool IsFocused { get { return _isFocused; } private set { SetAndRaise(IsFocusedProperty, ref _isFocused, value); } } /// /// Gets or sets a value indicating whether the control is considered for hit testing. /// public bool IsHitTestVisible { get { return GetValue(IsHitTestVisibleProperty); } set { SetValue(IsHitTestVisibleProperty, value); } } /// /// Gets a value indicating whether the pointer is currently over the control. /// public bool IsPointerOver { get { return _isPointerOver; } internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); } } /// public bool IsEffectivelyEnabled { get => _isEffectivelyEnabled; private set { SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value); PseudoClasses.Set(":disabled", !value); } } public List KeyBindings { get; } = new List(); /// /// Allows a derived class to override the enabled state of the control. /// /// /// Derived controls may wish to disable the enabled state of the control without overwriting the /// user-supplied setting. This can be done by overriding this property /// to return the overridden enabled state. If the value returned from /// should change, then the derived control should call . /// protected virtual bool IsEnabledCore => IsEnabled; public GestureRecognizerCollection GestureRecognizers => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); /// /// Focuses the control. /// public void Focus() { FocusManager.Instance?.Focus(this); } /// protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); if (IsFocused) { FocusManager.Instance.Focus(null); } } /// protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTreeCore(e); UpdateIsEffectivelyEnabled(); } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnGotFocus(GotFocusEventArgs e) { IsFocused = e.Source == this; } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnLostFocus(RoutedEventArgs e) { IsFocused = false; } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnKeyDown(KeyEventArgs e) { } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnKeyUp(KeyEventArgs e) { } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnTextInput(TextInputEventArgs e) { } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerEnter(PointerEventArgs e) { } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerLeave(PointerEventArgs e) { } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerMoved(PointerEventArgs e) { if (_gestureRecognizers?.HandlePointerMoved(e) == true) e.Handled = true; } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerPressed(PointerPressedEventArgs e) { if (_gestureRecognizers?.HandlePointerPressed(e) == true) e.Handled = true; } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerReleased(PointerReleasedEventArgs e) { if (_gestureRecognizers?.HandlePointerReleased(e) == true) e.Handled = true; } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { _gestureRecognizers?.HandlePointerCaptureLost(e); } /// /// Called before the event occurs. /// /// The event args. protected virtual void OnPointerWheelChanged(PointerWheelEventArgs e) { } protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) { base.OnPropertyChanged(property, oldValue, newValue, priority); if (property == IsFocusedProperty) { UpdatePseudoClasses(newValue.GetValueOrDefault(), null); } else if (property == IsPointerOverProperty) { UpdatePseudoClasses(null, newValue.GetValueOrDefault()); } } /// /// Updates the property value according to the parent /// control's enabled state and . /// protected void UpdateIsEffectivelyEnabled() { UpdateIsEffectivelyEnabled(this.GetVisualParent()); } private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) { ((InputElement)e.Sender).UpdateIsEffectivelyEnabled(); } /// /// Called before the event occurs. /// /// The event args. private void OnPointerEnterCore(PointerEventArgs e) { IsPointerOver = true; OnPointerEnter(e); } /// /// Called before the event occurs. /// /// The event args. private void OnPointerLeaveCore(PointerEventArgs e) { IsPointerOver = false; OnPointerLeave(e); } /// /// Updates the property based on the parent's /// . /// /// The parent control. private void UpdateIsEffectivelyEnabled(InputElement parent) { IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true); // PERF-SENSITIVE: This is called on entire hierarchy and using foreach or LINQ // will cause extra allocations and overhead. var children = VisualChildren; // ReSharper disable once ForCanBeConvertedToForeach for (int i = 0; i < children.Count; ++i) { var child = children[i] as InputElement; child?.UpdateIsEffectivelyEnabled(this); } } private void UpdatePseudoClasses(bool? isFocused, bool? isPointerOver) { if (isFocused.HasValue) { PseudoClasses.Set(":focus", isFocused.Value); } if (isPointerOver.HasValue) { PseudoClasses.Set(":pointerover", isPointerOver.Value); } } } }