diff --git a/src/.nuspec/Uno.Resizetizer.targets b/src/.nuspec/Uno.Resizetizer.targets index 9cd40b93..bbd23fb5 100644 --- a/src/.nuspec/Uno.Resizetizer.targets +++ b/src/.nuspec/Uno.Resizetizer.targets @@ -48,10 +48,6 @@ AssemblyFile="$(_UnoResizetizerTaskAssemblyName)" TaskName="Uno.Resizetizer.GenerateWasmSplashAssets_v0"/> - - @@ -231,6 +227,8 @@ _ComputeAndroidResourcePaths; $(UnoResizetizeCollectItemsAfterTargets); UnoAssetsGeneration; + GenerateMSBuildEditorConfigFileShouldRun; + GenerateMSBuildEditorConfigFileCore; @@ -418,6 +416,7 @@ WriteOnlyWhenDifferent="true"/> + @@ -589,25 +588,12 @@ - - - - $([MSBuild]::ValueOrDefault('$(ApplicationTitle)', '$(AssemblyName)')) - - - - - - + + + + + + + netstandard2.0 + false + Uno.Resizetizer + true + analyzers/dotnet/cs + latest + false + + System.Runtime.CompilerServices.IsExternalInit; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs new file mode 100644 index 00000000..10d1ff95 --- /dev/null +++ b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using CodeGenHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Resizetizer.Generators; + +[Generator(LanguageNames.CSharp)] +internal sealed class WindowTitleGenerator : IIncrementalGenerator +{ + private const string UnoResizetizerIcon = nameof(UnoResizetizerIcon); + private const string IsUnoHead = nameof(IsUnoHead); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Get the AnalyzerConfigOptionsProvider + var optionsProvider = context.AnalyzerConfigOptionsProvider; + var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.Assembly.Name); + var additionalTextsProvider = context.AdditionalTextsProvider; + + var extensionPropertiesProvider = optionsProvider.Combine(assemblyNameProvider).Select((x, cancellationToken) => + { + var (options, assemblyName) = x; + if (!GetProperty(options.GlobalOptions, IsUnoHead)) + { + return null; + } + + var rootNamespace = GetPropertyValue(options.GlobalOptions, "RootNamespace"); + var windowTitle = GetPropertyValue(options.GlobalOptions, "ApplicationTitle"); + if (string.IsNullOrEmpty(windowTitle)) + { + windowTitle = assemblyName; + } + + return string.IsNullOrEmpty(rootNamespace) || string.IsNullOrEmpty(windowTitle) ? null : new ExtensionPropertiesContext(rootNamespace, windowTitle); + }); + + // Combine optionsProvider and compilationProvider + var iconNameProvider = additionalTextsProvider + .Where(x => Path.GetFileName(x.Path).Equals("UnoImage.inputs", StringComparison.InvariantCultureIgnoreCase)) + .Select((additionalText, cancellationToken) => + { + var sourceText = additionalText.GetText(cancellationToken); + return ParseFile(sourceText.ToString()); + }) + .Where(x => !string.IsNullOrEmpty(x)) + .Select((x, _) => Path.GetFileNameWithoutExtension(x)); + + // Define the source generator logic + var sourceCodeProvider = iconNameProvider.Combine(extensionPropertiesProvider).Select((x, _) => + { + var (iconName, coreContext) = x; + if (string.IsNullOrEmpty(iconName) || string.IsNullOrEmpty(coreContext?.RootNamespace) || string.IsNullOrEmpty(coreContext?.WindowTitle)) + return null; + + return new ExtensionGenerationContext(coreContext.RootNamespace, iconName, coreContext.WindowTitle); + }).Where(result => result != null); + + // Register the source generator logic to add the generated source code + context.RegisterSourceOutput(sourceCodeProvider, (sourceContext, extensionContext) => + { + if (!string.IsNullOrEmpty(extensionContext.WindowTitle)) + { + AddSource(sourceContext, GenerateLegacyNamespaceCompat()); + AddSource(sourceContext, GenerateWindowTitleExtension(extensionContext.RootNamespace, extensionContext.IconName, extensionContext.WindowTitle)); + } + }); + } + + internal record ExtensionPropertiesContext(string RootNamespace, string WindowTitle); + + internal record ExtensionGenerationContext(string RootNamespace, string IconName, string WindowTitle); + + private static string ParseFile(string content) + { + // Split the content into lines + var lines = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + // Split the line into key-value pairs + var properties = line.Split(';').Select(property => property.Split('=')).ToDictionary(parts => parts[0], parts => parts.Length > 1 ? parts[1] : null); + + // Check if IsAppIcon is true + if (properties.TryGetValue("IsAppIcon", out var isAppIcon) && bool.TryParse(isAppIcon, out var isAppIconValue) && isAppIconValue) + { + // Return the file path + if (properties.TryGetValue("File", out var filePath)) + { + return filePath; + } + } + } + + // Return null if no app icon is found + return null; + } + + private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, string iconName, string windowTitle) + { + var builder = CodeBuilder.Create(rootNamespace) + .AddClass("WindowExtensions") + .MakeStaticClass() + .WithSummary(@"Extension methods for the class."); + + builder.AddMethod("SetWindowIcon") + .AddParameter("this global::Microsoft.UI.Xaml.Window", "window") + .MakeStaticMethod() + .MakePublicMethod() + .WithSummary(@"This will set the Window Icon for the given using the provided UnoIcon.") + .WithBody(w => + { + w.AppendUnindentedLine("#if WINDOWS && !HAS_UNO"); + w.AppendLine("var hWnd = global::WinRT.Interop.WindowNative.GetWindowHandle(window);"); + w.NewLine(); + w.AppendLine("// Retrieve the WindowId that corresponds to hWnd."); + w.AppendLine("global::Microsoft.UI.WindowId windowId = global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);"); + w.NewLine(); + w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window."); + w.AppendLine("global::Microsoft.UI.Windowing.AppWindow appWindow = global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);"); + w.AppendLine($@"appWindow.SetIcon(""{iconName}.ico"");"); + w.NewLine(); + w.AppendLine("// Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged"); + // We're no longer checking for IsPackaged as this seems to be needed when Packaged as well. + w.If(@"appWindow.Title == ""WinUI Desktop""") + .WithBody(b => + { + b.AppendLine($@"appWindow.Title = ""{windowTitle}"";"); + }) + .EndIf(); + w.AppendUnindentedLine("#endif"); + }); + + // NOTE: This method has been removed as it seems WinUI isn't setting the title when Packaged. Keeping in case we need this in the future. + //builder.AddMethod("IsPackaged") + // .WithReturnType("bool") + // .MakePrivateMethod() + // .MakeStaticMethod() + // .WithBody(w => + // { + // using (w.Block("try")) + // { + // w.AppendLine("return global::Windows.ApplicationModel.Package.Current != null;"); + // } + // using (w.Block("catch")) + // { + // w.AppendLine("return false;"); + // } + // }); + + return builder; + } + + private static string GetPropertyValue(AnalyzerConfigOptions options, string key) => + options.TryGetValue($"build_property.{key}", out var value) ? value : string.Empty; + + private static bool GetProperty(AnalyzerConfigOptions options, string key) => + bool.TryParse(GetPropertyValue(options, key), out var result) && result; + + private static bool HasUnoIcon(AnalyzerConfigOptions options, out string unoIcon) + { + unoIcon = GetPropertyValue(options, UnoResizetizerIcon); + return !string.IsNullOrEmpty(unoIcon) && !unoIcon.Contains(","); + } + + private static ClassBuilder GenerateLegacyNamespaceCompat() + { + return CodeBuilder.Create("Uno.Resizetizer") + .AddClass("__LegacyResizetizerSupport__") + .WithSummary("This is added to ensure the Uno.Resizetizer namespace is present to avoid breaking any applications.") + .MakeStaticClass(); + } + + private static void AddSource(SourceProductionContext context, ClassBuilder builder) => + context.AddSource($"{builder.FullyQualifiedName}.g.cs", SourceText.From(builder.Build(), Encoding.UTF8)); +} diff --git a/src/Resizetizer/src/Resizetizer.csproj b/src/Resizetizer/src/Resizetizer.csproj index dff9209b..de4c8946 100644 --- a/src/Resizetizer/src/Resizetizer.csproj +++ b/src/Resizetizer/src/Resizetizer.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -21,7 +21,7 @@ - Uno.Resizetizer_v0 + Uno.Resizetizer_v0 $(GITVERSION_SHA) Uno.Resizetizer Uno Platform package support for images. @@ -44,7 +44,7 @@ - + @@ -72,6 +72,12 @@ + + + false + + + @@ -119,4 +125,13 @@ Condition="Exists('$(NuGetPackageRoot)$(PackageId.ToLowerInvariant())') And '$(OS)' != 'Windows_NT'" /> + + + + + + diff --git a/src/Resizetizer/src/WindowIconGeneratorTask.cs b/src/Resizetizer/src/WindowIconGeneratorTask.cs deleted file mode 100644 index 257d3834..00000000 --- a/src/Resizetizer/src/WindowIconGeneratorTask.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Uno.Resizetizer; - -public class WindowIconGeneratorTask_V0 : Task -{ - private const string FileName = "Uno.Resizetizer.WindowIconExtensions.g.cs"; - - public ITaskItem[] UnoIcons { get; set; } - - [Required] - public string IntermediateOutputDirectory { get; set; } - - public string WindowTitle { get; set; } - - [Output] - public ITaskItem[] GeneratedClass { get; private set; } = Array.Empty(); - - public override bool Execute() - { - if (UnoIcons is null || UnoIcons.Length == 0) - { - return true; - } - - if(string.IsNullOrEmpty(IntermediateOutputDirectory)) - { - Log.LogError("The IntermediateOutputDirectory (typically the obj directory) is a required parameter but was null or empty."); - return false; - } - - var iconPath = UnoIcons[0].ItemSpec; - var iconName = Path.GetFileNameWithoutExtension(iconPath); - - var code = @$"//------------------------------------------------------------------------------ -// -// This code was auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Uno.Resizetizer -{{ - /// - /// Extension methods for the class. - /// - public static class WindowExtensions - {{ - /// - /// This will set the Window Icon for the given using - /// the provided UnoIcon. - /// - public static void SetWindowIcon(this global::Microsoft.UI.Xaml.Window window) - {{ -#if WINDOWS && !HAS_UNO - var hWnd = - global::WinRT.Interop.WindowNative.GetWindowHandle(window); - - // Retrieve the WindowId that corresponds to hWnd. - global::Microsoft.UI.WindowId windowId = - global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd); - - // Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window. - global::Microsoft.UI.Windowing.AppWindow appWindow = - global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId); - appWindow.SetIcon(""{iconName}.ico""); - - // Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged - if (!IsPackaged() && appWindow.Title == ""WinUI Desktop"") - {{ - appWindow.Title = ""{WindowTitle}""; - }} - - static bool IsPackaged() - {{ - try - {{ - if (global::Windows.ApplicationModel.Package.Current != null) - return true; - }} - catch - {{ - // no-op - }} - - return false; - }} -#endif - }} - }} -}}"; - - if(!Directory.Exists(IntermediateOutputDirectory)) - { - Directory.CreateDirectory(IntermediateOutputDirectory); - } - - var item = new TaskItem(Path.Combine(IntermediateOutputDirectory, FileName)); - File.WriteAllText(item.ItemSpec, code); - GeneratedClass = new [] { item }; - return true; - } -} diff --git a/src/Resizetizer/uno.resizetizer.sln b/src/Resizetizer/uno.resizetizer.sln index a80b9860..721110fc 100644 --- a/src/Resizetizer/uno.resizetizer.sln +++ b/src/Resizetizer/uno.resizetizer.sln @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.nuspec\Uno.Resizetizer.targets = ..\.nuspec\Uno.Resizetizer.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resizetizer.Generators", "Resizetizer.Generators\Resizetizer.Generators.csproj", "{9CDB1CAA-293A-434C-A092-29D0EE03496D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution DEBUG_RESIZETIZER|Any CPU = DEBUG_RESIZETIZER|Any CPU @@ -31,6 +33,12 @@ Global {7695AB69-4414-4539-8172-A78D8460F663}.Debug|Any CPU.Build.0 = Debug|Any CPU {7695AB69-4414-4539-8172-A78D8460F663}.Release|Any CPU.ActiveCfg = Release|Any CPU {7695AB69-4414-4539-8172-A78D8460F663}.Release|Any CPU.Build.0 = Release|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.DEBUG_RESIZETIZER|Any CPU.ActiveCfg = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.DEBUG_RESIZETIZER|Any CPU.Build.0 = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE