Skip to content

Commit

Permalink
fix: Re-evaluate visual state setters on theme change
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Mar 26, 2024
1 parent b8b580b commit 190c390
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Private.Infrastructure;
using SamplesApp.UITests;
using Uno.UI.RuntimeTests.Helpers;
using PersonPicture = Microsoft/* UWP don't rename */.UI.Xaml.Controls.PersonPicture;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls;

[TestClass]
[RunsOnUIThread]
public partial class Given_PersonPicture
{
private partial class MyPersonPicture : PersonPicture
{
public TextBlock InitialsTextBlock { get; private set; }

protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
InitialsTextBlock = (TextBlock)GetTemplateChild("InitialsTextBlock");
}
}

[TestMethod]
[UnoWorkItem("https://github.com/unoplatform/uno/issues/16006")]
public async Task TestInitialsTextBlockFontFamily()
{
var personPicture = new MyPersonPicture();
await UITestHelper.Load(personPicture);

#if WINAPPSDK
string symbolsFontName = "Segoe MDL2 Assets";
#else
string symbolsFontName = "ms-appx:///Uno.Fonts.Fluent/Fonts/uno-fluentui-assets.ttf";
#endif

var fontFamilyLight = personPicture.InitialsTextBlock.FontFamily.Source;
Assert.AreEqual(symbolsFontName, fontFamilyLight);

using (ThemeHelper.UseDarkTheme())
{
Assert.AreEqual(fontFamilyLight, personPicture.InitialsTextBlock.FontFamily.Source);
await TestServices.WindowHelper.WaitForIdle();
Assert.AreEqual(fontFamilyLight, personPicture.InitialsTextBlock.FontFamily.Source);
}

Assert.AreEqual(fontFamilyLight, personPicture.InitialsTextBlock.FontFamily.Source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media;
using Private.Infrastructure;
using Uno.UI.RuntimeTests.Helpers;
using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation.TestPages;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation;

[TestClass]
[RunsOnUIThread]
internal class Given_TemplateBindingAfterAnimation
{
[TestMethod]
public async Task When_TemplateBinding_And_Animation_Set_Local_On_TemplatedParent()
{
// Scenario being tested:
// - CustomButton style sets Foreground to Blue
// - A visual state sets tb1.Foreground to Red (we call GoToState in OnApplyTemplate)
// - tb1 Foreground has TemplateBinding to CustomButton's Foreground
var page = new TemplateBindingAfterAnimationPage();

await UITestHelper.Load(page);

var btn = page.customButton;
var tb1 = btn.TextBlockTemplateChildBoundToForeground;

Assert.AreEqual((btn.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Blue);
Assert.AreEqual((tb1.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Red);

btn.Foreground = new SolidColorBrush(Microsoft.UI.Colors.Green);

Assert.AreEqual((btn.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Green);
Assert.AreEqual((tb1.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Green);

await TestServices.WindowHelper.WaitForIdle();

Assert.AreEqual((btn.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Green);
Assert.AreEqual((tb1.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Green);
}

[TestMethod]
public async Task When_TemplateBinding_And_Animation_Change_Theme()
{
// Scenario being tested:
// - CustomButton style sets Background to {ThemeResource TemplateBindingAfterAnimationThemeColor1} (Light=White, Dark=LightGray)
// - A visual state sets tb2.Foreground to {ThemeResource TemplateBindingAfterAnimationThemeColor2} (Light=Brown, Dark=RosyBrown) (we call GoToState in OnApplyTemplate)
// - tb2 Foreground has TemplateBinding to CustomButton's Background
var page = new TemplateBindingAfterAnimationPage();

await UITestHelper.Load(page);

var btn = page.customButton;
var tb2 = btn.TextBlockTemplateChildBoundToBackground;

Assert.AreEqual((btn.Background as SolidColorBrush).Color, Microsoft.UI.Colors.White);
Assert.AreEqual((tb2.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.Brown);

using (ThemeHelper.UseDarkTheme())
{
Assert.AreEqual((btn.Background as SolidColorBrush).Color, Microsoft.UI.Colors.LightGray);

#if HAS_UNO
Assert.AreEqual((tb2.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.RosyBrown);
#else
Assert.AreEqual((tb2.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.LightGray);
#endif

await TestServices.WindowHelper.WaitForIdle();

Assert.AreEqual((tb2.Foreground as SolidColorBrush).Color, Microsoft.UI.Colors.RosyBrown);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<Page
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation.TestPages.TemplateBindingAfterAnimationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation.TestPages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="TemplateBindingAfterAnimationThemeColor1" Color="White" />
<SolidColorBrush x:Key="TemplateBindingAfterAnimationThemeColor2" Color="Brown" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="TemplateBindingAfterAnimationThemeColor1" Color="LightGray" />
<SolidColorBrush x:Key="TemplateBindingAfterAnimationThemeColor2" Color="RosyBrown" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

<Style x:Key="TemplateBindingAfterAnimationStyle" TargetType="local:CustomButton">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="{ThemeResource TemplateBindingAfterAnimationThemeColor1}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<StackPanel>
<TextBlock x:Name="tb1" Foreground="{TemplateBinding Foreground}" Text="Foreground bound to Foreground" />
<TextBlock x:Name="tb2" Foreground="{TemplateBinding Background}" Text="Foreground bound to Background" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MyVSGroup">
<VisualState x:Name="MyState">
<VisualState.Setters>
<Setter Target="tb1.Foreground" Value="Red" />
<Setter Target="tb2.Foreground" Value="{ThemeResource TemplateBindingAfterAnimationThemeColor2}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Grid.Resources>
<local:CustomButton Style="{StaticResource TemplateBindingAfterAnimationStyle}" x:FieldModifier="public" x:Name="customButton" />
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation.TestPages;

public sealed partial class TemplateBindingAfterAnimationPage : Page
{
public TemplateBindingAfterAnimationPage()
{
this.InitializeComponent();
}
}

public partial class CustomButton : Button
{
public TextBlock TextBlockTemplateChildBoundToForeground { get; private set; }
public TextBlock TextBlockTemplateChildBoundToBackground { get; private set; }

protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBlockTemplateChildBoundToForeground = (TextBlock)this.GetTemplateChild("tb1");
TextBlockTemplateChildBoundToBackground = (TextBlock)this.GetTemplateChild("tb2");
VisualStateManager.GoToState(this, "MyState", false);
}
}
12 changes: 12 additions & 0 deletions src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,18 @@ internal void ClearBinding()
}
else
{
if (precedence == DependencyPropertyValuePrecedences.Animations && (_flags & Flags.LocalValueNewerThanAnimationsValue) != 0)
{
// When setting BindingPath.Value, we do the following check:
// DependencyObjectStore.AreDifferent(value, _value.GetPrecedenceSpecificValue())
// Now, consider the following case:
// Animation value set to some value x, then Local value is set to some value y,
// then BindingPath.Value is trying to set Animation value to x
// in this case, we want to consider the values as different.
// So, we need to return the Local value when we are asked for Animation as the Local value is effectively overwriting the animation value.
precedence = DependencyPropertyValuePrecedences.Local;
}

return Unwrap(Stack[(int)precedence]);
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/Uno.UI/UI/Xaml/FrameworkElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Windows.UI.Core;
using System.ComponentModel;
using Uno.UI.DataBinding;
using Uno.UI.Dispatching;
using Uno.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Data;
Expand Down Expand Up @@ -883,6 +884,15 @@ internal virtual void UpdateThemeBindings(ResourceUpdateReason updateReason)
{
ActualThemeChanged?.Invoke(this, null);
}

if (this is Control @this && @this.GetTemplateRoot() is { } templateRoot)

Check failure on line 888 in src/Uno.UI/UI/Xaml/FrameworkElement.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/FrameworkElement.cs#L888

Offload the code that's conditional on this type test to the appropriate subclass and remove the condition.
{
var groups = VisualStateManager.GetVisualStateGroups(templateRoot);
foreach (var group in groups)
{
NativeDispatcher.Main.Enqueue(() => group.ReevaluateAppliedPropertySetters(@this));
}
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/Uno.UI/UI/Xaml/VisualStateGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,28 @@ private void VisualStateChanged(object sender, IVectorChangedEventArgs e)
RefreshStateTriggers();
}

internal void ReevaluateAppliedPropertySetters(Control control)
{
var state = VisualStateManager.GetCurrentState(control, Name);
if (state is null)
{
return;
}

try
{
ResourceResolver.PushNewScope(_xamlScope);
foreach (var setter in state.Setters)
{
(setter as Setter)?.ApplyValue(DependencyPropertyValuePrecedences.Animations, control);
}
}
finally
{
ResourceResolver.PopScope();
}
}

private void OnParentChanged(object instance, object key, DependencyObjectParentChangedEventArgs args)
{
RefreshStateTriggers(force: true);
Expand Down

0 comments on commit 190c390

Please sign in to comment.