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();
}

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 @@ -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);
}
}
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
Loading