Skip to content

Commit 8df6db9

Browse files
feat: Respond to top-level resource updates
1 parent 685b396 commit 8df6db9

File tree

5 files changed

+47
-12
lines changed

5 files changed

+47
-12
lines changed

src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ internal partial class XamlCodeGeneration
5252
private readonly string _projectDirectory;
5353
private readonly string _projectFullPath;
5454
private readonly bool _outputSourceComments = true;
55-
private readonly bool _xamlResourcesTrimming; private readonly RoslynMetadataHelper _metadataHelper;
55+
private readonly bool _xamlResourcesTrimming;
56+
private readonly RoslynMetadataHelper _metadataHelper;
5657

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

409410
return Location.Create(
410411
xamlFile.Path,
411-
xamlText.Lines.ElementAtOrDefault(xamlParsingException.LineNumber.Value-1).Span,
412+
xamlText.Lines.ElementAtOrDefault(xamlParsingException.LineNumber.Value - 1).Span,
412413
new LinePositionSpan(linePosition, linePosition)
413414
);
414415
}
@@ -439,7 +440,7 @@ private void BuildAmbientResources(XamlFileDefinition[] files, XamlGlobalStaticR
439440
from module in sym.Modules
440441
from reference in module.ReferencedAssemblies
441442

442-
// Only consider assemblies that reference Uno.UI
443+
// Only consider assemblies that reference Uno.UI
443444
where reference.Name == "Uno.UI" || sym.Name == "Uno.UI"
444445

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

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

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

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

790794
// Generate all the partial methods, even if they don't exist. That avoids
791795
// having to sync the generation of the files with this global table.
792-
foreach (var file in files.Select(f=>f.UniqueID).Distinct())
796+
foreach (var file in files.Select(f => f.UniqueID).Distinct())
793797
{
794798
writer.AppendLineInvariant("static partial void RegisterDefaultStyles_{0}();", file);
795799
}

src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ private async Task ReloadFile(FileReload fileReload)
9696
break;
9797
}
9898
}
99+
100+
if (ResourceResolver.RetrieveDictionaryForFilePath(uri.AbsolutePath) is { } targetDictionary)
101+
{
102+
var replacementDictionary = (ResourceDictionary)XamlReader.Load(fileReload.Content);
103+
targetDictionary.CopyFrom(replacementDictionary);
104+
Application.Current.UpdateResourceBindingsForHotReload();
105+
}
99106
}
100107
catch (Exception e)
101108
{

src/Uno.UI/UI/Xaml/Application.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ private void SetRequestedTheme(ApplicationTheme requestedTheme)
322322
}
323323
}
324324

325+
internal void UpdateResourceBindingsForHotReload() => OnRequestedThemeChanged();
326+
325327
private void OnRequestedThemeChanged()
326328
{
327329
if (GetTreeRoot() is { } root)

src/Uno.UI/UI/Xaml/ResourceDictionary.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ private bool ContainsKeyThemeMerged(in ResourceKey resourceKey, in ResourceKey a
417417
/// <summary>
418418
/// Copy another dictionary's contents, this is used when setting the <see cref="Source"/> property
419419
/// </summary>
420-
private void CopyFrom(ResourceDictionary source)
420+
internal void CopyFrom(ResourceDictionary source)
421421
{
422422
_values.Clear();
423423
_mergedDictionaries.Clear();

src/Uno.UI/UI/Xaml/ResourceResolver.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public static class ResourceResolver
3030

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

3438
private static int _assemblyRef = -1;
3539

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

245249
while (sourcesEnumerator.MoveNext())
246250
{
247-
251+
248252
var source = sourcesEnumerator.Current;
249253

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

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

@@ -392,6 +399,11 @@ public static void RegisterResourceDictionaryBySource(string uri, XamlParseConte
392399
_assemblyRef++; // We don't actually use this key, we just need it to be unique
393400
assemblyDict[_assemblyRef] = initializer;
394401
}
402+
403+
if (filePath != null)
404+
{
405+
_registeredDictionariesByFilepath[filePath] = dictionary;
406+
}
395407
}
396408

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

448+
internal static ResourceDictionary RetrieveDictionaryForFilePath(string filePath)
449+
{
450+
if (_registeredDictionariesByFilepath.TryGetValue(filePath, out var func))
451+
{
452+
return func();
453+
}
454+
455+
return null;
456+
}
457+
436458
/// <summary>
437459
/// Retrieves a resource for a {CustomResource} markup, with the <see cref="CustomXamlResourceLoader"/> currently set.
438460
/// </summary>

0 commit comments

Comments
 (0)