-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Changes from 9 commits
a2a7461
bece5a3
4576274
50fecf4
53715c7
467a2fa
28c8e7c
1e58d0b
b8d8877
c0be3de
755cad6
2f3ef13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
{ | ||
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 |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
} | ||
} | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why there wouldn't be a Position? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user passes an invalid element |
||
{ | ||
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) | ||
|
@@ -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; | ||
|
@@ -545,6 +576,7 @@ bool ValidateGesture(TapGestureRecognizer g) | |
} | ||
} | ||
|
||
|
||
void SwipeComplete(bool success) | ||
{ | ||
var view = Element as View; | ||
|
@@ -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(); | ||
|
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); | ||
} | ||
} |
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)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can do it like this cmd?.CanExecute(PointerMovedCommandParameter) == true There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?.Invoke(sender, new PointerEventArgs(getPosition)); | ||||||
} | ||||||
} | ||||||
} |
There was a problem hiding this comment.
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