Skip to content

Commit

Permalink
feat: Respond to top-level resource updates
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjohnoliver committed Nov 3, 2021
1 parent 685b396 commit 8df6db9
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ internal partial class XamlCodeGeneration
private readonly string _projectDirectory;
private readonly string _projectFullPath;
private readonly bool _outputSourceComments = true;
private readonly bool _xamlResourcesTrimming; private readonly RoslynMetadataHelper _metadataHelper;
private readonly bool _xamlResourcesTrimming;
private readonly RoslynMetadataHelper _metadataHelper;

/// <summary>
/// If set, code generated from XAML will be annotated with the source method and line # in XamlFileGenerator, for easier debugging.
Expand Down Expand Up @@ -408,7 +409,7 @@ IEnumerable<Exception> Flatten(Exception ex)

return Location.Create(
xamlFile.Path,
xamlText.Lines.ElementAtOrDefault(xamlParsingException.LineNumber.Value-1).Span,
xamlText.Lines.ElementAtOrDefault(xamlParsingException.LineNumber.Value - 1).Span,
new LinePositionSpan(linePosition, linePosition)
);
}
Expand Down Expand Up @@ -439,7 +440,7 @@ private void BuildAmbientResources(XamlFileDefinition[] files, XamlGlobalStaticR
from module in sym.Modules
from reference in module.ReferencedAssemblies

// Only consider assemblies that reference Uno.UI
// Only consider assemblies that reference Uno.UI
where reference.Name == "Uno.UI" || sym.Name == "Uno.UI"

// Don't consider Uno.UI.Fluent assemblies, as they manage their own initialization
Expand Down Expand Up @@ -556,7 +557,8 @@ private string[] GetResourceKeys(CancellationToken ct)
resourceKeys = _resourceFiles
.AsParallel()
.WithCancellation(ct)
.SelectMany(file => {
.SelectMany(file =>
{
this.Log().Info("Parse resource file : " + file);

//load document
Expand Down Expand Up @@ -716,7 +718,7 @@ private string GenerateGlobalResources(IEnumerable<XamlFileDefinition> files, Xa
{
writer.AppendLineInvariant("_dictionariesRegistered = true;");

if(!IsUnoAssembly && !IsUnoFluentAssembly)
if (!IsUnoAssembly && !IsUnoFluentAssembly)
{
// For third-party libraries, expose all files using standard uri
foreach (var file in files.Where(IsResourceDictionary))
Expand All @@ -738,7 +740,7 @@ private string GenerateGlobalResources(IEnumerable<XamlFileDefinition> files, Xa
void RegisterForFile(string baseFilePath, string url)
{
var file = files.FirstOrDefault(f =>
f.FilePath.Substring(_projectDirectory.Length+1).Equals(baseFilePath, StringComparison.OrdinalIgnoreCase));
f.FilePath.Substring(_projectDirectory.Length + 1).Equals(baseFilePath, StringComparison.OrdinalIgnoreCase));

if (file != null)
{
Expand Down Expand Up @@ -766,10 +768,12 @@ void RegisterForXamlFile(XamlFileDefinition file, string url)
foreach (var file in files.Where(IsResourceDictionary))
{
// We leave context null because local resources should be found through Application.Resources
writer.AppendLineInvariant("global::Uno.UI.ResourceResolver.RegisterResourceDictionaryBySource(uri: \"{0}{1}\", context: null, dictionary: () => {2}_ResourceDictionary);",
writer.AppendLineInvariant("global::Uno.UI.ResourceResolver.RegisterResourceDictionaryBySource(uri: \"{0}{1}\", context: null, dictionary: () => {2}_ResourceDictionary, {3});",
XamlFilePathHelper.LocalResourcePrefix,
map.GetSourceLink(file),
file.UniqueID
file.UniqueID,
// Make ResourceDictionary retrievable by Hot Reload
_isDebug ? $"\"{file.FilePath.Replace("\\", "/")}\"" : "null"
);
// Local resources can also be found through the ms-appx:/// prefix
writer.AppendLineInvariant("global::Uno.UI.ResourceResolver.RegisterResourceDictionaryBySource(uri: \"{0}{1}\", context: null, dictionary: () => {2}_ResourceDictionary);",
Expand All @@ -789,7 +793,7 @@ void RegisterForXamlFile(XamlFileDefinition file, string url)

// Generate all the partial methods, even if they don't exist. That avoids
// having to sync the generation of the files with this global table.
foreach (var file in files.Select(f=>f.UniqueID).Distinct())
foreach (var file in files.Select(f => f.UniqueID).Distinct())
{
writer.AppendLineInvariant("static partial void RegisterDefaultStyles_{0}();", file);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ private async Task ReloadFile(FileReload fileReload)
break;
}
}

if (ResourceResolver.RetrieveDictionaryForFilePath(uri.AbsolutePath) is { } targetDictionary)
{
var replacementDictionary = (ResourceDictionary)XamlReader.Load(fileReload.Content);
targetDictionary.CopyFrom(replacementDictionary);
Application.Current.UpdateResourceBindingsForHotReload();
}
}
catch (Exception e)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Uno.UI/UI/Xaml/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ private void SetRequestedTheme(ApplicationTheme requestedTheme)
}
}

internal void UpdateResourceBindingsForHotReload() => OnRequestedThemeChanged();

private void OnRequestedThemeChanged()
{
if (GetTreeRoot() is { } root)
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ private bool ContainsKeyThemeMerged(in ResourceKey resourceKey, in ResourceKey a
/// <summary>
/// Copy another dictionary's contents, this is used when setting the <see cref="Source"/> property
/// </summary>
private void CopyFrom(ResourceDictionary source)
internal void CopyFrom(ResourceDictionary source)
{
_values.Clear();
_mergedDictionaries.Clear();
Expand Down
26 changes: 24 additions & 2 deletions src/Uno.UI/UI/Xaml/ResourceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public static class ResourceResolver

private static readonly Dictionary<string, Func<ResourceDictionary>> _registeredDictionariesByUri = new Dictionary<string, Func<ResourceDictionary>>(StringComparer.InvariantCultureIgnoreCase);
private static readonly Dictionary<string, ResourceDictionary> _registeredDictionariesByAssembly = new Dictionary<string, ResourceDictionary>();
/// <summary>
/// This is used by hot reload (since converting the file path to a Source is impractical at runtime).
/// </summary>
private static readonly Dictionary<string, Func<ResourceDictionary>> _registeredDictionariesByFilepath = new Dictionary<string, Func<ResourceDictionary>>(StringComparer.InvariantCultureIgnoreCase);

private static int _assemblyRef = -1;

Expand Down Expand Up @@ -244,7 +248,7 @@ private static bool TryStaticRetrieval(in SpecializedResourceDictionary.Resource

while (sourcesEnumerator.MoveNext())
{

var source = sourcesEnumerator.Current;

var dictionary = (source.Target as FrameworkElement)?.Resources
Expand Down Expand Up @@ -375,12 +379,15 @@ internal static void PopScope()
/// </summary>
internal static IDisposable WriteInitiateGlobalStaticResourcesEventActivity() => _trace.WriteEventActivity(TraceProvider.InitGenericXamlStart, TraceProvider.InitGenericXamlStop);

[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterResourceDictionaryBySource(string uri, XamlParseContext context, Func<ResourceDictionary> dictionary)
=> RegisterResourceDictionaryBySource(uri, context, dictionary, null);
/// <summary>
/// Register a dictionary for a given source, this is used for retrieval when setting the Source property in code-behind or to an
/// external resource.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void RegisterResourceDictionaryBySource(string uri, XamlParseContext context, Func<ResourceDictionary> dictionary)
public static void RegisterResourceDictionaryBySource(string uri, XamlParseContext context, Func<ResourceDictionary> dictionary, string filePath)
{
_registeredDictionariesByUri[uri] = dictionary;

Expand All @@ -392,6 +399,11 @@ public static void RegisterResourceDictionaryBySource(string uri, XamlParseConte
_assemblyRef++; // We don't actually use this key, we just need it to be unique
assemblyDict[_assemblyRef] = initializer;
}

if (filePath != null)
{
_registeredDictionariesByFilepath[filePath] = dictionary;
}
}

/// <summary>
Expand Down Expand Up @@ -433,6 +445,16 @@ public static ResourceDictionary RetrieveDictionaryForSource(string source, stri
throw new InvalidOperationException($"Cannot locate resource from '{source}'");
}

internal static ResourceDictionary RetrieveDictionaryForFilePath(string filePath)
{
if (_registeredDictionariesByFilepath.TryGetValue(filePath, out var func))
{
return func();
}

return null;
}

/// <summary>
/// Retrieves a resource for a {CustomResource} markup, with the <see cref="CustomXamlResourceLoader"/> currently set.
/// </summary>
Expand Down

0 comments on commit 8df6db9

Please sign in to comment.