Skip to content

Commit

Permalink
chore: Propagate reloads to static resources
Browse files Browse the repository at this point in the history
Extend the machinery for theme change handling, to ensure that reloaded resources are also applied to StaticResource assignations.
  • Loading branch information
davidjohnoliver committed Nov 4, 2021
1 parent 8df6db9 commit bc4e66a
Show file tree
Hide file tree
Showing 25 changed files with 199 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,10 @@ public bool UseFluentStyles
}
#if HAS_UNO
// Force the in app styles to reload
Application.Current.Resources?.UpdateThemeBindings();
Uno.UI.ResourceResolver.UpdateSystemThemeBindings();
Application.PropagateThemeChanged(Windows.UI.Xaml.Window.Current.Content);
var updateReason = ResourceUpdateReason.ThemeResource;
Application.Current.Resources?.UpdateThemeBindings(updateReason);
Uno.UI.ResourceResolver.UpdateSystemThemeBindings(updateReason);
Application.PropagateResourcesChanged(Windows.UI.Xaml.Window.Current.Content, updateReason);
#endif
RaisePropertyChanged();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ internal partial class XamlFileGenerator
/// </summary>
private string SingletonClassName => $"ResourceDictionarySingleton__{_fileDefinition.ShortId}";

private bool IsHotReloadEnabled => _isDebug;

private const string DictionaryProviderInterfaceName = "global::Uno.UI.IXamlResourceDictionaryProvider";

static XamlFileGenerator()
Expand Down Expand Up @@ -1669,10 +1671,11 @@ private void BuildPropertySetter(IIndentedStringBuilder writer, string fullTarge
ParseContextPropertyAccess,
propertyType
);

if (valueObject.Type.Name == "ThemeResource")
var isThemeResource = valueObject?.Type.Name == "ThemeResource";
var isStaticResourceForHotReload = IsHotReloadEnabled && valueObject?.Type.Name == "StaticResource";
if (valueObject != null && (isThemeResource || isStaticResourceForHotReload))
{
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess);
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1}, {2}, {3})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess, isThemeResource ? "true" : "false", IsHotReloadEnabled ? "true" : "false");
}

writer.AppendLineInvariant(lineEnding);
Expand Down Expand Up @@ -1734,9 +1737,11 @@ private void BuildPropertySetter(IIndentedStringBuilder writer, string fullTarge
currentResourceOwner?.Dispose();
}

if (valueObject.Type.Name == "ThemeResource")
var isThemeResource = valueObject?.Type.Name == "ThemeResource";
var isStaticResourceForHotReload = IsHotReloadEnabled && valueObject?.Type.Name == "StaticResource";
if (valueObject != null && (isThemeResource || isStaticResourceForHotReload))
{
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess);
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1}, {2}, {3})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess, isThemeResource ? "true" : "false", IsHotReloadEnabled ? "true" : "false");
}

writer.AppendLineInvariant(lineEnding);
Expand Down Expand Up @@ -3763,7 +3768,7 @@ private void BuildComplexPropertyValue(IIndentedStringBuilder writer, XamlMember
if (IsDependencyProperty(member.Member))
{
var propertyOwner = GetType(member.Member.DeclaringType);
writer.AppendLineInvariant("global::Uno.UI.ResourceResolverSingleton.Instance.ApplyResource({0}, {1}.{2}Property, \"{3}\", isThemeResourceExtension: {4}, context: {5});", closureName, GetGlobalizedTypeName(propertyOwner.ToDisplayString()), member.Member.Name, resourceKey, isThemeResourceExtension ? "true" : "false", ParseContextPropertyAccess);
writer.AppendLineInvariant("global::Uno.UI.ResourceResolverSingleton.Instance.ApplyResource({0}, {1}.{2}Property, \"{3}\", isThemeResourceExtension: {4}, isHotReloadSupported: {5}, context: {6});", closureName, GetGlobalizedTypeName(propertyOwner.ToDisplayString()), member.Member.Name, resourceKey, isThemeResourceExtension ? "true" : "false", IsHotReloadEnabled ? "true" : "false", ParseContextPropertyAccess);
}
else if (IsAttachedProperty(member))
{
Expand Down Expand Up @@ -5303,9 +5308,11 @@ private void BuildChild(IIndentedStringBuilder writer, XamlMemberDefinition? own

// Set ThemeResource information, if any, on Setter
var valueObject = valueNode.Objects.FirstOrDefault();
if (valueObject?.Type.Name == "ThemeResource")
var isThemeResource = valueObject?.Type.Name == "ThemeResource";
var isStaticResourceForHotReload = IsHotReloadEnabled && valueObject?.Type.Name == "StaticResource";
if (valueObject != null && (isThemeResource || isStaticResourceForHotReload))
{
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess);
writer.AppendLineInvariant(".ApplyThemeResourceUpdateValues(\"{0}\", {1}, {2}, {3})", valueObject.Members.FirstOrDefault()?.Value, ParseContextPropertyAccess, isThemeResource ? "true" : "false", IsHotReloadEnabled ? "true" : "false");
}
}
else if (fullTypeName == XamlConstants.Types.ResourceDictionary)
Expand Down
18 changes: 17 additions & 1 deletion src/Uno.UI/Helpers/Xaml/SetterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,35 @@
using Uno.Extensions;
using Uno.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace Uno.UI.Helpers.Xaml
{
public static class SetterExtensions
{
[EditorBrowsable(EditorBrowsableState.Never)]
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
// This is normally called from code generated by the Xaml parser
// This is normally called from code generated by the Xaml parser. This overload is kept for backwards compatibility.
public static Setter ApplyThemeResourceUpdateValues(this Setter setter, string themeResourceName, object parseContext)
=> ApplyThemeResourceUpdateValues(setter, themeResourceName, parseContext, isTheme: true, isHotReload: false);

[EditorBrowsable(EditorBrowsableState.Never)]
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
// This is normally called from code generated by the Xaml parser
public static Setter ApplyThemeResourceUpdateValues(this Setter setter, string themeResourceName, object parseContext, bool isTheme, bool isHotReload)
{
setter.ThemeResourceKey = !themeResourceName.IsNullOrEmpty() ? themeResourceName : null;
setter.ThemeResourceContext = parseContext as XamlParseContext;

if (isTheme)
{
setter.ResourceBindingUpdateReason |= ResourceUpdateReason.ThemeResource;
}
if (isHotReload)
{
setter.ResourceBindingUpdateReason |= ResourceUpdateReason.HotReload;
}

return setter;
}
}
Expand Down
21 changes: 12 additions & 9 deletions src/Uno.UI/UI/Xaml/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#else
using View = Windows.UI.Xaml.UIElement;
using ViewGroup = Windows.UI.Xaml.UIElement;
using Windows.UI.Xaml.Data;
#endif

namespace Windows.UI.Xaml
Expand Down Expand Up @@ -322,19 +323,21 @@ private void SetRequestedTheme(ApplicationTheme requestedTheme)
}
}

internal void UpdateResourceBindingsForHotReload() => OnRequestedThemeChanged();
internal void UpdateResourceBindingsForHotReload() => OnResourcesChanged(ResourceUpdateReason.HotReload);

private void OnRequestedThemeChanged()
private void OnRequestedThemeChanged() => OnResourcesChanged(ResourceUpdateReason.ThemeResource);

private void OnResourcesChanged(ResourceUpdateReason updateReason)
{
if (GetTreeRoot() is { } root)
{
// Update theme bindings in application resources
Resources?.UpdateThemeBindings();
Resources?.UpdateThemeBindings(updateReason);

// Update theme bindings in system resources
ResourceResolver.UpdateSystemThemeBindings();
ResourceResolver.UpdateSystemThemeBindings(updateReason);

PropagateThemeChanged(root);
PropagateResourcesChanged(root, updateReason);
}

// Start from the real root, which may not be a FrameworkElement on some platforms
Expand All @@ -354,28 +357,28 @@ View GetTreeRoot()
/// <summary>
/// Propagate theme changed to <paramref name="instance"/> and its descendants, to have them update any theme bindings.
/// </summary>
internal static void PropagateThemeChanged(object instance)
internal static void PropagateResourcesChanged(object instance, ResourceUpdateReason updateReason)
{

// Update ThemeResource references that have changed
if (instance is FrameworkElement fe)
{
fe.UpdateThemeBindings();
fe.UpdateThemeBindings(updateReason);
}

//Try Panel.Children before ViewGroup.GetChildren - this results in fewer allocations
if (instance is Controls.Panel p)
{
foreach (object o in p.Children)
{
PropagateThemeChanged(o);
PropagateResourcesChanged(o, updateReason);
}
}
else if (instance is ViewGroup g)
{
foreach (object o in g.GetChildren())
{
PropagateThemeChanged(o);
PropagateResourcesChanged(o, updateReason);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Controls/ComboBox/ComboBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public partial class ComboBox : Selector

public ComboBox()
{
ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "ComboBoxLightDismissOverlayBackground", isThemeResourceExtension: true);
ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "ComboBoxLightDismissOverlayBackground", isThemeResourceExtension: true, isHotReloadSupported: true);

DefaultStyleKey = typeof(ComboBox);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ContentDialog() : base()
LightDismissOverlayMode = LightDismissOverlayMode.On,
};

ResourceResolver.ApplyResource(_popup, Popup.LightDismissOverlayBackgroundProperty, "ContentDialogLightDismissOverlayBackground", isThemeResourceExtension: true);
ResourceResolver.ApplyResource(_popup, Popup.LightDismissOverlayBackgroundProperty, "ContentDialogLightDismissOverlayBackground", isThemeResourceExtension: true, isHotReloadSupported: true);

_popup.PopupPanel = new ContentDialogPopupPanel(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1033,9 +1033,9 @@ protected override void OnBackgroundChanged(DependencyPropertyChangedEventArgs e
UpdateBorder();
}

internal override void UpdateThemeBindings()
internal override void UpdateThemeBindings(ResourceUpdateReason updateReason)
{
base.UpdateThemeBindings();
base.UpdateThemeBindings(updateReason);
SetDefaultForeground(ForegroundProperty);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/Controls/Control/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ private void InitializeControl()

internal override bool IsEnabledOverride() => IsEnabled && base.IsEnabledOverride();

internal override void UpdateThemeBindings()
internal override void UpdateThemeBindings(Data.ResourceUpdateReason updateReason)
{
base.UpdateThemeBindings();
base.UpdateThemeBindings(updateReason);

//override the default value from dependency property based on application theme
SetDefaultForeground(ForegroundProperty);
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private void EnsurePopupCreated()
{
if (_popup == null)
{
ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "FlyoutLightDismissOverlayBackground", isThemeResourceExtension: true);
ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "FlyoutLightDismissOverlayBackground", isThemeResourceExtension: true, isHotReloadSupported: true);

var child = CreatePresenter();
_popup = new Popup()
Expand Down
14 changes: 7 additions & 7 deletions src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private void Initialize()
{
InitializePartial();

ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "PopupLightDismissOverlayBackground", isThemeResourceExtension: true);
ResourceResolver.ApplyResource(this, LightDismissOverlayBackgroundProperty, "PopupLightDismissOverlayBackground", isThemeResourceExtension: true, isHotReloadSupported: true);

ApplyLightDismissOverlayMode();
}
Expand All @@ -81,7 +81,7 @@ internal PopupPanel PopupPanel
set => SetValue(PopupPanelProperty, value);
}

public static DependencyProperty PopupPanelProperty { get ; } =
public static DependencyProperty PopupPanelProperty { get; } =
DependencyProperty.Register("PopupPanel", typeof(PopupPanel), typeof(Popup), new FrameworkPropertyMetadata(null, (s, e) => ((Popup)s)?.OnPopupPanelChanged((PopupPanel)e.OldValue, (PopupPanel)e.NewValue)));

private void OnPopupPanelChanged(PopupPanel oldHost, PopupPanel newHost)
Expand Down Expand Up @@ -154,7 +154,7 @@ partial void OnHorizontalOffsetChangedPartial(double oldHorizontalOffset, double
var position = e.GetCurrentPoint(content).Position;
if (
position.X < 0 || position.X > content.ActualWidth
|| position.Y < 0 || position.Y > content.ActualHeight)
|| position.Y < 0 || position.Y > content.ActualHeight)
{
popup.IsOpen = false;
}
Expand All @@ -173,12 +173,12 @@ partial void OnHorizontalOffsetChangedPartial(double oldHorizontalOffset, double
// But this would require us to refactor more deeply the Popup which is not the purpose of the current work.
};

internal override void UpdateThemeBindings()
internal override void UpdateThemeBindings(Data.ResourceUpdateReason updateReason)
{
base.UpdateThemeBindings();
base.UpdateThemeBindings(updateReason);

// Ensure bindings are updated on the child, which may be part of an isolated visual tree on some platforms (ie Android).
Application.PropagateThemeChanged(Child);
Application.PropagateResourcesChanged(Child, updateReason);
}

public LightDismissOverlayMode LightDismissOverlayMode
Expand Down Expand Up @@ -235,7 +235,7 @@ internal Brush LightDismissOverlayBackground
set { SetValue(LightDismissOverlayBackgroundProperty, value); }
}

internal static DependencyProperty LightDismissOverlayBackgroundProperty { get ; } =
internal static DependencyProperty LightDismissOverlayBackgroundProperty { get; } =
DependencyProperty.Register("LightDismissOverlayBackground", typeof(Brush), typeof(Popup), new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: (o, e) => ((Popup)o).ApplyLightDismissOverlayMode()));
}
}
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -938,9 +938,9 @@ private void InvalidateTextBlock()
private protected override double GetActualWidth() => DesiredSize.Width;
private protected override double GetActualHeight() => DesiredSize.Height;

internal override void UpdateThemeBindings()
internal override void UpdateThemeBindings(Data.ResourceUpdateReason updateReason)
{
base.UpdateThemeBindings();
base.UpdateThemeBindings(updateReason);

SetDefaultForeground(ForegroundProperty);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Data/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static void UpdateResourceBindings(this DependencyObject instance)
if(instance is IDependencyObjectStoreProvider provider)
{
provider.Store.ApplyElementNameBindings();
provider.Store.UpdateResourceBindings(false);
provider.Store.UpdateResourceBindings(ResourceUpdateReason.StaticResourceLoading);
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/Uno.UI/UI/Xaml/Data/ResourceBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ internal class ResourceBinding : BindingBase
/// </summary>
public SpecializedResourceDictionary.ResourceKey ResourceKey { get; }

/// <summary>
/// True if the original assignation used the ThemeResource extension, false if it used StaticResource. (This determines whether it
/// should be updated when the active theme changes.)
/// </summary>
public ResourceUpdateReason UpdateReason { get; }

public bool IsThemeResourceExtension { get; }

public bool IsPersistent => UpdateReason != ResourceUpdateReason.StaticResourceLoading;

public object? ParseContext { get; }

public DependencyPropertyValuePrecedences Precedence { get; }
Expand All @@ -32,10 +32,10 @@ internal class ResourceBinding : BindingBase
/// </summary>
public BindingPath? SetterBindingPath { get; }

public ResourceBinding(SpecializedResourceDictionary.ResourceKey resourceKey, bool isThemeResourceExtension, object? parseContext, DependencyPropertyValuePrecedences precedence, BindingPath? setterBindingPath)
public ResourceBinding(SpecializedResourceDictionary.ResourceKey resourceKey, ResourceUpdateReason updateReason, object? parseContext, DependencyPropertyValuePrecedences precedence, BindingPath? setterBindingPath)
{
ResourceKey = resourceKey;
IsThemeResourceExtension = isThemeResourceExtension;
UpdateReason = updateReason;
ParseContext = parseContext;
Precedence = precedence;
SetterBindingPath = setterBindingPath;
Expand Down
32 changes: 32 additions & 0 deletions src/Uno.UI/UI/Xaml/Data/ResourceUpdateReason.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Text;
using Uno.UI.DataBinding;

namespace Windows.UI.Xaml.Data
{
[Flags]
internal enum ResourceUpdateReason
{
None = 0,
/// <summary>
/// A static resource that could not be resolved at compile time, and should retry at loading. (This is something of a patch for compatibility gaps between Uno and WinUI)
/// </summary>
StaticResourceLoading = 1,
/// <summary>
/// An update associated with theme changing, or a resource binding that should be updated when theme changes
/// </summary>
ThemeResource = 2,
/// <summary>
/// An update associated with hot reload, or a resource binding that should be updated for hot-reload changes
/// </summary>
HotReload = 4,

/// <summary>
/// Updates that should be propagated recursively through the visual tree
/// </summary>
PropagatesThroughTree = ThemeResource | HotReload,
}
}
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/DependencyObjectStore.Binder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,14 +399,14 @@ public void SetBinding(DependencyProperty dependencyProperty, BindingBase bindin
}
}

internal void SetResourceBinding(DependencyProperty dependencyProperty, SpecializedResourceDictionary.ResourceKey resourceKey, bool isTheme, object context, DependencyPropertyValuePrecedences? precedence, BindingPath? setterBindingPath)
internal void SetResourceBinding(DependencyProperty dependencyProperty, SpecializedResourceDictionary.ResourceKey resourceKey, ResourceUpdateReason updateReason, object context, DependencyPropertyValuePrecedences? precedence, BindingPath? setterBindingPath)
{
if (precedence == null && _overriddenPrecedences?.Count > 0)
{
precedence = _overriddenPrecedences.Peek();
}

var binding = new ResourceBinding(resourceKey, isTheme, context, precedence ?? DependencyPropertyValuePrecedences.Local, setterBindingPath);
var binding = new ResourceBinding(resourceKey, updateReason, context, precedence ?? DependencyPropertyValuePrecedences.Local, setterBindingPath);
SetBinding(dependencyProperty, binding);
}

Expand Down
Loading

0 comments on commit bc4e66a

Please sign in to comment.