Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue19690.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 19690, "Button VisualStates do not work", PlatformAffected.iOS | PlatformAffected.Android | PlatformAffected.macOS)]
public class Issue19690 : ContentPage
{
const string CustomStateName = "Custom";

public Issue19690()
{
var button = new Issue19690CustomButton
{
Text = "Click Me",
AutomationId = "TestButton"
};

var statusLabel = new Label
{
Text = "Initial State",
AutomationId = "StatusLabel",
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 20, 0, 0)
};

button.Clicked += (s, e) =>
{
statusLabel.Text = button.IsCustom ? "Custom State" : "Normal State";
};

Content = new VerticalStackLayout
{
Padding = 20,
Children =
{
new Label
{
Text = "Click the button multiple times. The button should toggle between Normal (default colors) and Custom (Purple background, Yellow text) states.",
AutomationId = "InstructionLabel"
},
button,
statusLabel
}
};
}

class Issue19690CustomButton : Button
{
public static readonly BindableProperty IsCustomProperty =
BindableProperty.Create(nameof(IsCustom), typeof(bool), typeof(Issue19690CustomButton), false, propertyChanged: OnIsCustomChanged);

public bool IsCustom
{
get => (bool)GetValue(IsCustomProperty);
set => SetValue(IsCustomProperty, value);
}

public Issue19690CustomButton()
{
var visualStateGroups = new VisualStateGroupList();
var commonStates = new VisualStateGroup { Name = "CommonStates" };

commonStates.States.Add(new VisualState { Name = "Normal" });
commonStates.States.Add(new VisualState { Name = "Disabled" });
commonStates.States.Add(new VisualState { Name = "PointerOver" });
commonStates.States.Add(new VisualState { Name = "Pressed" });
commonStates.States.Add(new VisualState { Name = "Focused" });

var customState = new VisualState { Name = CustomStateName };
customState.Setters.Add(new Setter
{
Property = TextColorProperty,
Value = Colors.Blue
});
customState.Setters.Add(new Setter
{
Property = BackgroundColorProperty,
Value = Colors.Purple
});
commonStates.States.Add(customState);

visualStateGroups.Add(commonStates);
VisualStateManager.SetVisualStateGroups(this, visualStateGroups);

Clicked += OnButtonClicked;
}

void OnButtonClicked(object sender, EventArgs e)
{
IsCustom = !IsCustom;
}

protected internal override void ChangeVisualState()
{
if (IsCustom && IsEnabled)
{
VisualStateManager.GoToState(this, CustomStateName);
}
else
{
base.ChangeVisualState();
}
}

static void OnIsCustomChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is Issue19690CustomButton button)
{
button.ChangeVisualState();
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue19690 : _IssuesUITest
{
public Issue19690(TestDevice device) : base(device) { }

public override string Issue => "Button VisualStates do not work";

[Test]
[Category(UITestCategories.Button)]
public void ButtonVisualStatesShouldToggleBetweenNormalAndCustom()
{
App.WaitForElement("TestButton");

Exception? exception = null;
VerifyScreenshotOrSetException(ref exception, "Issue19690_InitialNormalState");

App.Tap("TestButton");
VerifyScreenshotOrSetException(ref exception, "Issue19690_CustomState");

App.Tap("TestButton");
VerifyScreenshotOrSetException(ref exception, "Issue19690_BackToNormalState");

if (exception != null)
{
throw exception;
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 19 additions & 1 deletion src/Core/src/Handlers/Button/ButtonHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public partial class ButtonHandler : ViewHandler<IButton, MaterialButton>
ButtonClickListener ClickListener { get; } = new ButtonClickListener();
ButtonTouchListener TouchListener { get; } = new ButtonTouchListener();

// Cached default Material theme text colors, captured before any MAUI property mapping.
// Restored when TextColor is set to null (e.g. when a VisualState setter is unapplied).
ColorStateList? _defaultTextColors;

protected override MaterialButton CreatePlatformView()
{
MaterialButton platformButton = new MauiMaterialButton(Context)
Expand All @@ -47,6 +51,9 @@ protected override void ConnectHandler(MaterialButton platformView)
platformView.FocusChange += OnNativeViewFocusChange;
platformView.LayoutChange += OnPlatformViewLayoutChange;

// Capture Material theme defaults before MAUI property mapping is applied
_defaultTextColors = platformView.TextColors;

base.ConnectHandler(platformView);
}

Expand All @@ -61,6 +68,8 @@ protected override void DisconnectHandler(MaterialButton platformView)
platformView.FocusChange -= OnNativeViewFocusChange;
platformView.LayoutChange -= OnPlatformViewLayoutChange;

_defaultTextColors = null;

ImageSourceLoader.Reset();

base.DisconnectHandler(platformView);
Expand Down Expand Up @@ -94,7 +103,16 @@ public static void MapText(IButtonHandler handler, IText button)

public static void MapTextColor(IButtonHandler handler, ITextStyle button)
{
handler.PlatformView?.UpdateTextColor(button);
if (button.TextColor is null)
{
// Restore the Material theme default colors captured before any MAUI mapping
if (handler is ButtonHandler buttonHandler && buttonHandler._defaultTextColors is not null)
handler.PlatformView?.SetTextColor(buttonHandler._defaultTextColors);
}
else
{
handler.PlatformView?.UpdateTextColor(button);
}
}

public static void MapCharacterSpacing(IButtonHandler handler, ITextStyle button)
Expand Down
8 changes: 7 additions & 1 deletion src/Core/src/Handlers/Button/ButtonHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@ public static void MapBackground(IButtonHandler handler, IButton button)
}
else
{
handler.PlatformView?.UpdateBackground(button);
handler.PlatformView?.UpdateBackground(button.Background);
}
}
#else
// TODO: Make this public in .NET 11
internal static void MapBackground(IButtonHandler handler, IButton button)
{
handler.PlatformView?.UpdateBackground(button.Background);
}
#endif

public static void MapStrokeColor(IButtonHandler handler, IButtonStroke buttonStroke)
Expand Down
31 changes: 31 additions & 0 deletions src/Core/src/Platform/iOS/ButtonExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Maui.Graphics;
using UIKit;

namespace Microsoft.Maui.Platform
Expand Down Expand Up @@ -31,7 +32,19 @@ public static void UpdateText(this UIButton platformButton, IText button) =>
public static void UpdateTextColor(this UIButton platformButton, ITextStyle button)
{
if (button.TextColor is null)
{
// Only clear explicit overrides when attached to a window.
// Skipping during initial render prevents clearing Appearance-proxy colors.
if (platformButton.Window is UIWindow window)
{
platformButton.SetTitleColor(null, UIControlState.Normal);
platformButton.SetTitleColor(null, UIControlState.Highlighted);
platformButton.SetTitleColor(null, UIControlState.Disabled);
platformButton.TintColor = window.TintColor;
}

return;
}

var color = button.TextColor.ToPlatform();

Expand All @@ -42,6 +55,24 @@ public static void UpdateTextColor(this UIButton platformButton, ITextStyle butt
platformButton.TintColor = color;
}

// TODO: Make this public in .NET 11
internal static void UpdateBackground(this UIButton platformButton, Graphics.Paint? paint)
{
// Remove previous background gradient layer if any
platformButton.RemoveBackgroundLayer();

if (paint.IsNullOrEmpty())
{
// Reset to clear background for buttons when paint is null.
// UIColor.Clear ensures proper transparency when VisualState setters are unapplied.
platformButton.BackgroundColor = UIColor.Clear;
return;
}

// Delegate to the standard view background update
ViewExtensions.UpdateBackground(platformButton, paint);
}

public static void UpdateCharacterSpacing(this UIButton platformButton, ITextStyle textStyle)
{
var attributedText = platformButton?.TitleLabel.AttributedText?.WithCharacterSpacing(textStyle.CharacterSpacing);
Expand Down
Loading