Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PointerGestureRecognizer #9592

Merged
merged 12 commits into from
Aug 26, 2022
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();
}

private void HoverBegan(object sender, PointerEventArgs e)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just little thing we don't use the "private" keyword because it's the default

{
hoverLabel.Text = "Thanks for hovering me!";
}

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

private 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 @@ -172,20 +172,6 @@ void SetupGestures()
{
platformView.Touch += OnPlatformViewTouched;
}

if (View is not Microsoft.Maui.IButton)
{
platformView.KeyPress += PlatformView_KeyPress;
platformView.Click += PlatformView_Click;
}
}

private void PlatformView_Click(object? sender, EventArgs e)
{
}

private void PlatformView_KeyPress(object? sender, AView.KeyEventArgs e)
{
}

void OnPlatformViewTouched(object? sender, AView.TouchEventArgs e)
Expand Down
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;
PureWeen marked this conversation as resolved.
Show resolved Hide resolved
_container.PointerMoved -= OnPgrPointerMoved;
}
}

Expand Down Expand Up @@ -481,12 +485,46 @@ 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why there wouldn't be a Position?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using Point from Maui.Graphics - to my knowledge, there's no Position there

ccing @PureWeen who implemented GetPosition in his other PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user passes an invalid element
The other option here would be to throw an exception

{
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;
if (view == null)
return;

var tapPosition = e.GetPositionRelativeToPlatformElement(Control);

if (tapPosition == null)
Expand All @@ -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
PureWeen marked this conversation as resolved.
Show resolved Hide resolved
{

Func<IElement?, Point?>? _getPosition;

public PointerEventArgs()
{
}

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

public virtual Point? GetPosition(Element? relativeTo) =>
_getPosition?.Invoke(relativeTo);
}
}
101 changes: 101 additions & 0 deletions src/Controls/src/Core/PointerGestureRecognizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#nullable enable
using System;
using System.ComponentModel;
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 != null && cmd.CanExecute(PointerEnteredCommandParameter))
cmd.Execute(PointerEnteredCommandParameter);

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

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

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

internal void SendPointerMoved(View sender, Func<IElement?, Point?>? getPosition)
{
ICommand cmd = PointerMovedCommand;
if (cmd != null && cmd.CanExecute(PointerMovedCommandParameter))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (cmd != null && cmd.CanExecute(PointerMovedCommandParameter))
if (cmd?.CanExecute(PointerMovedCommandParameter))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intellisense tells me this won't work since it can't implicitly convert type bool? to bool, so I'll leave this one as is for now.
Alternatively, I can cast it as a bool, but I prefer to leave it as is. Let me know what you think!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can do it like this

cmd?.CanExecute(PointerMovedCommandParameter) == true

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh of course, fixed!

cmd.Execute(PointerMovedCommandParameter);

EventHandler<PointerEventArgs>? handler = PointerMoved;
if (handler != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think you need this check since your are doing handler?. bellow

handler?.Invoke(sender, new PointerEventArgs(getPosition));
}
}
}
6 changes: 6 additions & 0 deletions src/Core/src/Platform/Windows/RoutedEventArgsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Maui.Graphics;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml;
using WPoint = Windows.Foundation.Point;
Expand Down Expand Up @@ -37,6 +38,11 @@ public static void SetHandled(this RoutedEventArgs e, bool value)
return t.GetPosition(relativeTo);
else if (e is DoubleTappedRoutedEventArgs dt)
return dt.GetPosition(relativeTo);
else if (e is PointerRoutedEventArgs p)
{
var point = p.GetCurrentPoint(relativeTo);
return new WPoint(point.Position.X, point.Position.Y);
}

return null;
}
Expand Down