Skip to content

Commit dd79425

Browse files
mattleibowrmarinho
authored andcommitted
Try and order the state changes better
1 parent 72e7d0c commit dd79425

2 files changed

Lines changed: 35 additions & 36 deletions

File tree

src/Controls/src/Core/VisualElement/VisualElement.cs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,39 +1606,43 @@ private protected void SetPointerOver(bool value, bool callChangeVisualState = t
16061606
/// </summary>
16071607
protected internal virtual void ChangeVisualState()
16081608
{
1609-
// States regarding a control's enabled or pointer events influence the main style
1610-
// of the element.
1611-
//
1612-
// Disabled, PointerOver, Pressed and Normal are typically put into the
1613-
// CommonStates visual state group.
1614-
if (!IsEnabled)
1609+
// A disabled control should never be in a focused state as part of the feature
1610+
// of being disabled is that it cannot receive focus. If it was in focus, then
1611+
// it has to go out of focus.
1612+
var shouldFocus = IsFocused && IsEnabled;
1613+
1614+
// If the control cannot have focus, make sure it appears unfocused by moving to
1615+
// the Unfocused state.
1616+
if (!shouldFocus)
16151617
{
1616-
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Disabled);
1618+
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Unfocused);
16171619
}
1618-
else if (IsPointerOver)
1620+
1621+
// Set the Disabled or Normal states depending on the value of IsEnabled and
1622+
// IsPointerOver. We set the PointerOver state later, after the Focused state.
1623+
if (!IsEnabled)
16191624
{
1620-
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.PointerOver);
1625+
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Disabled);
16211626
}
1622-
else
1627+
else if (!IsPointerOver)
16231628
{
16241629
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
16251630
}
16261631

1627-
// Focus needs to be handled independently; otherwise, if no actual Focus state is supplied
1628-
// in the control's visual states, the state can end up stuck in PointerOver after the pointer
1629-
// exits and the control still has focus.
1630-
//
1631-
// Focused and Unfocused states are typically put into the FocusStates visual state group.
1632-
if (IsFocused && IsEnabled)
1632+
// Go to the Focus state after the Normal state, so that the Focus state can
1633+
// override the Normal state's properties if a control is both focused and
1634+
// hovered.
1635+
if (shouldFocus)
16331636
{
16341637
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Focused);
16351638
}
1636-
else
1639+
1640+
// The PointerOver state is applied last so that it can override all the states. Even
1641+
// though this state is separate here, it should still be part of the CommonStates
1642+
// visual state group.
1643+
if (IsPointerOver)
16371644
{
1638-
// A disabled control should never be in a focused state as part of the feature
1639-
// of being disabled is that it cannot receive focus. If it was in focus, then
1640-
// it has to go out of focus.
1641-
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Unfocused);
1645+
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.PointerOver);
16421646
}
16431647
}
16441648

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19752.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,11 @@ public class Issue19752(TestDevice device) : _IssuesUITest(device)
1212
protected override bool ResetAfterEachTest => true;
1313

1414
[Test]
15-
public void InitialStateAreAllUnfocused()
15+
public void InitialStateAreAllCorrect()
1616
{
17-
var buttonsIds = new[] { "button1", "button2", "button3" };
18-
19-
foreach (var buttonsId in buttonsIds)
20-
{
21-
// it is normal AND unfocused, but we are using the same Text
22-
// property to make sure focused runs after normal
23-
Assert.That(App.FindElement(buttonsId).GetText(), Is.EqualTo("Unfocused"));
24-
}
17+
Assert.That(App.FindElement("button1").GetText(), Is.EqualTo("Normal"));
18+
Assert.That(App.FindElement("button2").GetText(), Is.EqualTo("Disabled"));
19+
Assert.That(App.FindElement("button3").GetText(), Is.EqualTo("Normal"));
2520
}
2621

2722
[Test]
@@ -52,14 +47,14 @@ public void HoveringOverButtonMovesToPointerOverState()
5247
// }
5348

5449
[Test]
55-
public void PressingAndReleasingButtonMovesToFocusedState()
50+
public void PressingAndReleasingButtonMovesToPointerOverState()
5651
{
5752
var rectBefore = App.FindElement("button1").GetRect();
5853

5954
App.Tap("button1");
6055

61-
// pressing a button sets it to be focused
62-
Assert.That(App.FindElement("button1").GetText(), Is.EqualTo("Focused"));
56+
// pressing a button sets it to be focused, but the pointer over state is appplied after
57+
Assert.That(App.FindElement("button1").GetText(), Is.EqualTo("PointerOver"));
6358

6459
// we are shrinking the focused button a bit
6560
var rectAfter = App.FindElement("button1").GetRect();
@@ -112,16 +107,16 @@ public void DisablingUnfocusedButtonMovesToDisabledState()
112107
}
113108

114109
[Test]
115-
public void DisablingFocusedButtonMovesToUnfocusedState()
110+
public void DisablingFocusedButtonMovesToDisabledState()
116111
{
117112
var rectBefore = App.FindElement("button3").GetRect();
118113

119114
App.Tap("button1"); // focus button 1
120115
App.Tap("button2"); // move the focus to button 2, but then disable it forcing focus to button 3
121116
App.Tap("button3"); // disable the focused button
122117

123-
// this disabled the button, but the focus change is applied on top of the normal state
124-
Assert.That(App.FindElement("button3").GetText(), Is.EqualTo("Unfocused"));
118+
// this disables the button, but the unfocus change is applied before all states
119+
Assert.That(App.FindElement("button3").GetText(), Is.EqualTo("Disabled"));
125120

126121
// we are shrinking the focused button a bit, so it should have been unfocused after disabling
127122
var rectAfter = App.FindElement("button3").GetRect();

0 commit comments

Comments
 (0)