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

Add visual state for PointerOver #10003

Merged
merged 17 commits into from
Sep 20, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,67 @@
x:Name="entry3"
Text=""
Placeholder="Type something to enable 2nd Entry" />
<Label
Text="Button with Normal and PointerOver visual states:"
Style="{StaticResource Headline}" />
<Button
Text="Hover me to see color change"
WidthRequest="300"
HorizontalOptions="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightBlue" />
<Setter Property="BorderColor" Value="LightBlue" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderColor" Value="LightBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</Button>
<Label
Text="Button with Normal, PointerOver, and Pressed visual states:"
Style="{StaticResource Headline}" />
<Label
Text="The Normal and PointerOver states for this button are the same. This demonstrates how the PointerOver state can be used to set the visual state of the button after it has been clicked so that it's no longer Pressed."
FontSize="Body"/>
<Button
Text="Click me to see color change and revert back"
WidthRequest="300"
HorizontalOptions="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightBlue" />
<Setter Property="BorderColor" Value="LightBlue" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightBlue" />
<Setter Property="BorderColor" Value="LightBlue" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderColor" Value="LightBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</Button>
</StackLayout>
</views:BasePage.Content>
</views:BasePage>
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ public FrameworkElement? Container
}
}

ObservableCollection<IGestureRecognizer>? ElementGestureRecognizers
{
get
{
if (_handler?.VirtualView is IGestureController gc &&
gc.CompositeGestureRecognizers is ObservableCollection<IGestureRecognizer> oc)
return oc;

return null;
}
}

// TODO MAUI
// Do we need to provide a hook for this in the handlers?
// For now I just built this ugly matching statement
Expand Down Expand Up @@ -272,11 +284,8 @@ public VisualElement? Element
var view = _element as View;
if (view != null)
{
var oldRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
oldRecognizers.CollectionChanged -= _collectionChangedHandler;

if ((view as IGestureController)?.CompositeGestureRecognizers is ObservableCollection<IGestureRecognizer> oc)
oc.CollectionChanged -= _collectionChangedHandler;
if (ElementGestureRecognizers != null)
ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
}
}

Expand All @@ -287,16 +296,13 @@ public VisualElement? Element
var view = _element as View;
if (view != null)
{
var newRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
newRecognizers.CollectionChanged += _collectionChangedHandler;

if ((view as IGestureController)?.CompositeGestureRecognizers is ObservableCollection<IGestureRecognizer> oc)
oc.CollectionChanged += _collectionChangedHandler;
if (ElementGestureRecognizers != null)
ElementGestureRecognizers.CollectionChanged += _collectionChangedHandler;
}
}
}
}

public void Dispose()
{
Dispose(true);
Expand Down Expand Up @@ -343,8 +349,8 @@ protected virtual void Dispose(bool disposing)
var view = _element as View;
if (view != null)
{
var oldRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers;
oldRecognizers.CollectionChanged -= _collectionChangedHandler;
if (ElementGestureRecognizers != null)
ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
}
}

Expand Down Expand Up @@ -503,7 +509,7 @@ private void HandlePgrPointerEvent(PointerRoutedEventArgs e, Action<View, Pointe
if (view == null)
return;

var pointerGestures = view.GestureRecognizers.GetGesturesFor<PointerGestureRecognizer>();
var pointerGestures = ElementGestureRecognizers.GetGesturesFor<PointerGestureRecognizer>();
foreach (var recognizer in pointerGestures)
{
SendPointerEvent.Invoke(view, recognizer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ static readonly Microsoft.Maui.Controls.Window.XProperty -> Microsoft.Maui.Contr
static readonly Microsoft.Maui.Controls.Window.YProperty -> 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?
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static readonly Microsoft.Maui.Controls.Window.XProperty -> Microsoft.Maui.Contr
static readonly Microsoft.Maui.Controls.Window.YProperty -> 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?
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static readonly Microsoft.Maui.Controls.Window.XProperty -> Microsoft.Maui.Contr
static readonly Microsoft.Maui.Controls.Window.YProperty -> 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?
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3556,6 +3556,7 @@ virtual Microsoft.Maui.Controls.Window.OnStopped() -> void
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.Focused = "Focused" -> string
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.Normal = "Normal" -> string
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.Selected = "Selected" -> string
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.AbsoluteLayout.GetLayoutBounds(Microsoft.Maui.IView view) -> Microsoft.Maui.Graphics.Rect
~Microsoft.Maui.Controls.AbsoluteLayout.GetLayoutFlags(Microsoft.Maui.IView view) -> Microsoft.Maui.Layouts.AbsoluteLayoutFlags
~Microsoft.Maui.Controls.AbsoluteLayout.SetLayoutBounds(Microsoft.Maui.IView view, Microsoft.Maui.Graphics.Rect bounds) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ static readonly Microsoft.Maui.Controls.Window.YProperty -> Microsoft.Maui.Contr
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.ToolTipProperties.TextProperty -> Microsoft.Maui.Controls.BindableProperty!
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ static readonly Microsoft.Maui.Controls.Window.YProperty -> Microsoft.Maui.Contr
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?
static readonly Microsoft.Maui.Controls.ToolTipProperties.TextProperty -> Microsoft.Maui.Controls.BindableProperty!
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ static readonly Microsoft.Maui.Controls.Window.YProperty -> Microsoft.Maui.Contr
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?
static readonly Microsoft.Maui.Controls.ToolTipProperties.TextProperty -> Microsoft.Maui.Controls.BindableProperty!
~const Microsoft.Maui.Controls.VisualStateManager.CommonStates.PointerOver = "PointerOver" -> string
~Microsoft.Maui.Controls.MenuFlyout.Add(Microsoft.Maui.IMenuElement item) -> void
~Microsoft.Maui.Controls.MenuFlyout.Contains(Microsoft.Maui.IMenuElement item) -> bool
~Microsoft.Maui.Controls.MenuFlyout.CopyTo(Microsoft.Maui.IMenuElement[] array, int arrayIndex) -> void
Expand Down
26 changes: 25 additions & 1 deletion src/Controls/src/Core/View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ static void OnMarginBottomPropertyChanged(BindableObject bindable, object oldVal

readonly ObservableCollection<IGestureRecognizer> _gestureRecognizers = new ObservableCollection<IGestureRecognizer>();

PointerGestureRecognizer _recognizerForPointerOverState;

protected internal View()
{
_gestureRecognizers.CollectionChanged += (sender, args) =>
Expand Down Expand Up @@ -127,6 +129,9 @@ void RemoveItems(IEnumerable<IElementDefinition> elements)

foreach (IElementDefinition item in GestureController.CompositeGestureRecognizers.OfType<IElementDefinition>())
{
if (item == _recognizerForPointerOverState)
continue;

if (_gestureRecognizers.Contains((IGestureRecognizer)item))
item.Parent = this;
else
Expand All @@ -151,7 +156,26 @@ public IList<IGestureRecognizer> GestureRecognizers

IList<IGestureRecognizer> IGestureController.CompositeGestureRecognizers
{
get { return _compositeGestureRecognizers ?? (_compositeGestureRecognizers = new ObservableCollection<IGestureRecognizer>()); }
get
{
if (_compositeGestureRecognizers is not null)
return _compositeGestureRecognizers;

_recognizerForPointerOverState = new PointerGestureRecognizer();

_recognizerForPointerOverState.PointerEntered += (s, e) =>
{
IsPointerOver = true;
};

_recognizerForPointerOverState.PointerExited += (s, e) =>
{
IsPointerOver = false;
};

_compositeGestureRecognizers = new ObservableCollection<IGestureRecognizer>() { _recognizerForPointerOverState };
return _compositeGestureRecognizers;
}
}

/// <include file="../../docs/Microsoft.Maui.Controls/View.xml" path="//Member[@MemberName='GetChildElements']/Docs/*" />
Expand Down
17 changes: 17 additions & 0 deletions src/Controls/src/Core/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,10 +1087,27 @@ void PropagateBindingContextToStateTriggers()

internal void ChangeVisualStateInternal() => ChangeVisualState();

bool _isPointerOver;

internal bool IsPointerOver
{
get { return _isPointerOver; }
private protected set
{
if (value == _isPointerOver)
return;

_isPointerOver = value;
ChangeVisualState();
}
}

protected internal virtual void ChangeVisualState()
{
if (!IsEnabled)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Disabled);
else if (IsPointerOver)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.PointerOver);
else if (IsFocused)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Focused);
else
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Core/VisualStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class CommonStates
public const string Disabled = "Disabled";
public const string Focused = "Focused";
public const string Selected = "Selected";
public const string PointerOver = "PointerOver";
}

/// <include file="../../docs/Microsoft.Maui.Controls/VisualStateManager.xml" path="//Member[@MemberName='VisualStateGroupsProperty']/Docs/*" />
Expand Down
6 changes: 3 additions & 3 deletions src/Controls/tests/Core.UnitTests/ViewUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -662,10 +662,10 @@ public void ClearingGestureRecognizers()
var gestureRecognizer = new TapGestureRecognizer();

view.GestureRecognizers.Add(gestureRecognizer);
Assert.Equal(2, (view as IGestureController).CompositeGestureRecognizers.Count);

view.GestureRecognizers.Clear();


Assert.Equal(0, (view as IGestureController).CompositeGestureRecognizers.Count);
Assert.Equal(1, (view as IGestureController).CompositeGestureRecognizers.Count);
Assert.Null(gestureRecognizer.Parent);
}

Expand Down