Skip to content

Commit

Permalink
Implement PointerGestureRecognizer (#9592)
Browse files Browse the repository at this point in the history
* Initial PointerGestureRecognizer code

* Update PointerGestureRecognizer.cs

* Clean up code and add nullability

* Fix bad merge

* Remove test code

* Add sample to GestureGallery

* Copy and refactor GetPosition logic used in TapGesture

* Clean up code and use GetPosition

* Update control gallery sample

* Add public API file changes

* Clean up code

* Clean up more code

Co-authored-by: Shane Neuville <shneuvil@microsoft.com>
  • Loading branch information
rachelkang and PureWeen authored Aug 26, 2022
1 parent abd82aa commit 41b1c9e
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<views:BasePage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Pages.PointerGestureGalleryPage"
xmlns:views="clr-namespace:Maui.Controls.Sample.Pages.Base">
<StackLayout>
<Label
x:Name="hoverLabel"
FontSize="24"
Text="Hover me!">
<Label.GestureRecognizers>
<PointerGestureRecognizer PointerEntered="HoverBegan" PointerExited="HoverEnded" PointerMoved="HoverMoved" />
</Label.GestureRecognizers>
</Label>
<Label x:Name="positionLabel" Text="Hover above label to reveal pointer position"/>
</StackLayout>
</views:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample.Pages
{
public partial class PointerGestureGalleryPage
{
public PointerGestureGalleryPage()
{
InitializeComponent();
}

void HoverBegan(object sender, PointerEventArgs e)
{
hoverLabel.Text = "Thanks for hovering me!";
}

void HoverEnded(object sender, PointerEventArgs e)
{
hoverLabel.Text = "Hover me again!";
positionLabel.Text = "Hover above label to reveal pointer position again";
}

void HoverMoved(object sender, PointerEventArgs e)
{
positionLabel.Text = $"Pointer position is at: {e.GetPosition((View)sender)}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
"Focus and onfocus views, detect when a view gains focus and more."),

new SectionModel(typeof(GesturesPage), "Gestures",
"Use tap, pinch, pan, swipe, and drag and drop gestures on View instances."),
"Use tap, pinch, pan, swipe, drag and drop, and pointer gestures on View instances."),

new SectionModel(typeof(InputTransparentPage), "InputTransparent",
"Manage whether a view participates in the user interaction cycle."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
"Swipe Gesture."),
new SectionModel(typeof(TapGestureGalleryPage), "Tap Gesture",
"Tap Gesture."),
new SectionModel(typeof(PointerGestureGalleryPage), "Pointer Gesture",
"Pointer Gesture."),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Imaging;
Expand Down Expand Up @@ -318,6 +319,9 @@ void ClearContainerEventHandlers()
_container.PointerExited -= OnPointerExited;
_container.PointerReleased -= OnPointerReleased;
_container.PointerCanceled -= OnPointerCanceled;
_container.PointerEntered -= OnPgrPointerEntered;
_container.PointerExited -= OnPgrPointerExited;
_container.PointerMoved -= OnPgrPointerMoved;
}
}

Expand Down Expand Up @@ -481,6 +485,40 @@ void OnPointerReleased(object sender, PointerRoutedEventArgs e)
PanComplete(true);
}

void OnPgrPointerEntered(object sender, PointerRoutedEventArgs e)
=> HandlePgrPointerEvent(e, (view, recognizer)
=> recognizer.SendPointerEntered(view, (relativeTo) => GetPosition(relativeTo, e)));

void OnPgrPointerExited(object sender, PointerRoutedEventArgs e)
=> HandlePgrPointerEvent(e, (view, recognizer)
=> recognizer.SendPointerExited(view, (relativeTo) => GetPosition(relativeTo, e)));

void OnPgrPointerMoved(object sender, PointerRoutedEventArgs e)
=> HandlePgrPointerEvent(e, (view, recognizer)
=> recognizer.SendPointerMoved(view, (relativeTo) => GetPosition(relativeTo, e)));

private void HandlePgrPointerEvent(PointerRoutedEventArgs e, Action<View, PointerGestureRecognizer> SendPointerEvent)
{
var view = Element as View;
if (view == null)
return;

var pointerGestures = view.GestureRecognizers.GetGesturesFor<PointerGestureRecognizer>();
foreach (var recognizer in pointerGestures)
{
SendPointerEvent.Invoke(view, recognizer);
}
}

Point? GetPosition(IElement? relativeTo, RoutedEventArgs e)
{
var result = e.GetPositionRelativeToElement(relativeTo);
if (result == null)
return null;

return new Point(result.Value.X, result.Value.Y);
}

void OnTap(object sender, RoutedEventArgs e)
{
var view = Element as View;
Expand Down Expand Up @@ -510,14 +548,7 @@ bool ProcessGestureRecognizers(IEnumerable<TapGestureRecognizer>? tapGestures)

foreach (var recognizer in tapGestures)
{
recognizer.SendTapped(view, (relativeTo) =>
{
var result = e.GetPositionRelativeToElement(relativeTo);
if (result == null)
return null;
return new Point(result.Value.X, result.Value.Y);
});
recognizer.SendTapped(view, (relativeTo) => GetPosition(relativeTo, e));

e.SetHandled(true);
handled = true;
Expand Down Expand Up @@ -545,6 +576,7 @@ bool ValidateGesture(TapGestureRecognizer g)
}
}


void SwipeComplete(bool success)
{
var view = Element as View;
Expand Down Expand Up @@ -679,6 +711,10 @@ void UpdatingGestureRecognizers()
}
}

_container.PointerEntered += OnPgrPointerEntered;
_container.PointerExited += OnPgrPointerExited;
_container.PointerMoved += OnPgrPointerMoved;

bool hasSwipeGesture = gestures.GetGesturesFor<SwipeGestureRecognizer>().GetEnumerator().MoveNext();
bool hasPinchGesture = gestures.GetGesturesFor<PinchGestureRecognizer>().GetEnumerator().MoveNext();
bool hasPanGesture = gestures.GetGesturesFor<PanGestureRecognizer>().GetEnumerator().MoveNext();
Expand Down
28 changes: 28 additions & 0 deletions src/Controls/src/Core/PointerEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#nullable enable

using Microsoft.Maui.Graphics;
using System;

namespace Microsoft.Maui.Controls
{
/// <summary>
/// Arguments for PointerGestureRecognizer events.
/// </summary>
public class PointerEventArgs : EventArgs
{

Func<IElement?, Point?>? _getPosition;

public PointerEventArgs()
{
}

internal PointerEventArgs(Func<IElement?, Point?>? getPosition)
{
_getPosition = getPosition;
}

public virtual Point? GetPosition(Element? relativeTo) =>
_getPosition?.Invoke(relativeTo);
}
}
97 changes: 97 additions & 0 deletions src/Controls/src/Core/PointerGestureRecognizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#nullable enable
using System;
using Microsoft.Maui.Graphics;
using System.Windows.Input;

namespace Microsoft.Maui.Controls
{
/// <summary>
/// Provides pointer gesture recognition and events.
/// </summary>
public sealed class PointerGestureRecognizer : GestureRecognizer
{
public static readonly BindableProperty PointerEnteredCommandProperty = BindableProperty.Create(nameof(PointerEnteredCommand), typeof(ICommand), typeof(PointerGestureRecognizer), null);

public static readonly BindableProperty PointerEnteredCommandParameterProperty = BindableProperty.Create(nameof(PointerEnteredCommandParameter), typeof(object), typeof(PointerGestureRecognizer), null);

public static readonly BindableProperty PointerExitedCommandProperty = BindableProperty.Create(nameof(PointerExitedCommand), typeof(ICommand), typeof(PointerGestureRecognizer), null);

public static readonly BindableProperty PointerExitedCommandParameterProperty = BindableProperty.Create(nameof(PointerExitedCommandParameter), typeof(object), typeof(PointerGestureRecognizer), null);

public static readonly BindableProperty PointerMovedCommandProperty = BindableProperty.Create(nameof(PointerMovedCommand), typeof(ICommand), typeof(PointerGestureRecognizer), null);

public static readonly BindableProperty PointerMovedCommandParameterProperty = BindableProperty.Create(nameof(PointerMovedCommandParameter), typeof(object), typeof(PointerGestureRecognizer), null);

public PointerGestureRecognizer()
{
}

public event EventHandler<PointerEventArgs>? PointerEntered;
public event EventHandler<PointerEventArgs>? PointerExited;
public event EventHandler<PointerEventArgs>? PointerMoved;

public ICommand PointerEnteredCommand
{
get { return (ICommand)GetValue(PointerEnteredCommandProperty); }
set { SetValue(PointerEnteredCommandProperty, value); }
}

public ICommand PointerEnteredCommandParameter
{
get { return (ICommand)GetValue(PointerEnteredCommandParameterProperty); }
set { SetValue(PointerEnteredCommandParameterProperty, value); }
}
public ICommand PointerExitedCommand
{
get { return (ICommand)GetValue(PointerExitedCommandProperty); }
set { SetValue(PointerExitedCommandProperty, value); }
}
public ICommand PointerExitedCommandParameter
{
get { return (ICommand)GetValue(PointerExitedCommandParameterProperty); }
set { SetValue(PointerExitedCommandParameterProperty, value); }
}

public ICommand PointerMovedCommand
{
get { return (ICommand)GetValue(PointerMovedCommandProperty); }
set { SetValue(PointerMovedCommandProperty, value); }
}

public ICommand PointerMovedCommandParameter
{
get { return (ICommand)GetValue(PointerMovedCommandParameterProperty); }
set { SetValue(PointerMovedCommandParameterProperty, value); }
}

internal void SendPointerEntered(View sender, Func<IElement?, Point?>? getPosition)
{
ICommand cmd = PointerEnteredCommand;
if (cmd?.CanExecute(PointerEnteredCommandParameter) == true)
cmd.Execute(PointerEnteredCommandParameter);

EventHandler<PointerEventArgs>? handler = PointerEntered;
handler?.Invoke(sender, new PointerEventArgs(getPosition));
}

internal void SendPointerExited(View sender, Func<IElement?, Point?>? getPosition)
{
ICommand cmd = PointerExitedCommand;
if (cmd?.CanExecute(PointerExitedCommandParameter) == true)
cmd.Execute(PointerExitedCommandParameter);

EventHandler<PointerEventArgs>? handler = PointerExited;
handler?.Invoke(sender, new PointerEventArgs(getPosition));
}

internal void SendPointerMoved(View sender, Func<IElement?, Point?>? getPosition)
{
ICommand cmd = PointerMovedCommand;
if (cmd?.CanExecute(PointerMovedCommandParameter) == true)
cmd.Execute(PointerMovedCommandParameter);

EventHandler<PointerEventArgs>? handler = PointerMoved;
handler?.Invoke(sender, new PointerEventArgs(getPosition));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ Microsoft.Maui.Controls.MenuFlyout.IsReadOnly.get -> bool
Microsoft.Maui.Controls.MenuFlyout.RemoveAt(int index) -> void
Microsoft.Maui.Controls.MenuFlyoutSeparator
Microsoft.Maui.Controls.MenuFlyoutSeparator.MenuFlyoutSeparator() -> void
Microsoft.Maui.Controls.PointerEventArgs
Microsoft.Maui.Controls.PointerEventArgs.PointerEventArgs() -> void
Microsoft.Maui.Controls.PointerGestureRecognizer
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEntered -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameter.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExited -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameter.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerGestureRecognizer() -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMoved -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameter.set -> void
Microsoft.Maui.Controls.TapGestureRecognizer.Buttons.get -> Microsoft.Maui.Controls.ButtonsMask
Microsoft.Maui.Controls.TapGestureRecognizer.Buttons.set -> void
Microsoft.Maui.Controls.TapGestureRecognizer.Tapped -> System.EventHandler<Microsoft.Maui.Controls.TappedEventArgs!>?
Expand All @@ -25,7 +44,14 @@ override Microsoft.Maui.Controls.TemplatedView.ArrangeOverride(Microsoft.Maui.Gr
override Microsoft.Maui.Controls.TemplatedView.MeasureOverride(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size
static Microsoft.Maui.Controls.ToolTipProperties.GetText(Microsoft.Maui.Controls.BindableObject! bindable) -> object!
static Microsoft.Maui.Controls.ToolTipProperties.SetText(Microsoft.Maui.Controls.BindableObject! bindable, object! value) -> void
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.ToolTipProperties.TextProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.PointerEventArgs.GetPosition(Microsoft.Maui.Controls.Element? relativeTo) -> Microsoft.Maui.Graphics.Point?
virtual Microsoft.Maui.Controls.TappedEventArgs.GetPosition(Microsoft.Maui.Controls.Element? relativeTo) -> Microsoft.Maui.Graphics.Point?
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
Expand Down
26 changes: 26 additions & 0 deletions src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ Microsoft.Maui.Controls.MenuFlyout.IsReadOnly.get -> bool
Microsoft.Maui.Controls.MenuFlyout.RemoveAt(int index) -> void
Microsoft.Maui.Controls.MenuFlyoutSeparator
Microsoft.Maui.Controls.MenuFlyoutSeparator.MenuFlyoutSeparator() -> void
Microsoft.Maui.Controls.PointerEventArgs
Microsoft.Maui.Controls.PointerEventArgs.PointerEventArgs() -> void
Microsoft.Maui.Controls.PointerGestureRecognizer
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEntered -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameter.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExited -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameter.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerGestureRecognizer() -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMoved -> System.EventHandler<Microsoft.Maui.Controls.PointerEventArgs!>?
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommand.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommand.set -> void
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameter.get -> System.Windows.Input.ICommand!
Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameter.set -> void
Microsoft.Maui.Controls.TapGestureRecognizer.Buttons.get -> Microsoft.Maui.Controls.ButtonsMask
Microsoft.Maui.Controls.TapGestureRecognizer.Buttons.set -> void
Microsoft.Maui.Controls.TapGestureRecognizer.Tapped -> System.EventHandler<Microsoft.Maui.Controls.TappedEventArgs!>?
Expand All @@ -29,8 +48,15 @@ override Microsoft.Maui.Controls.TemplatedView.MeasureOverride(double widthConst
*REMOVED*override Microsoft.Maui.Controls.FlexLayout.MeasureOverride(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size
static Microsoft.Maui.Controls.ToolTipProperties.GetText(Microsoft.Maui.Controls.BindableObject! bindable) -> object!
static Microsoft.Maui.Controls.ToolTipProperties.SetText(Microsoft.Maui.Controls.BindableObject! bindable, object! value) -> void
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerEnteredCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerExitedCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandParameterProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.PointerGestureRecognizer.PointerMovedCommandProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.ToolTipProperties.TextProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.VisualElement.ZIndexProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.PointerEventArgs.GetPosition(Microsoft.Maui.Controls.Element? relativeTo) -> Microsoft.Maui.Graphics.Point?
virtual Microsoft.Maui.Controls.TappedEventArgs.GetPosition(Microsoft.Maui.Controls.Element? relativeTo) -> Microsoft.Maui.Graphics.Point?
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
Expand Down
Loading

0 comments on commit 41b1c9e

Please sign in to comment.