ModuleIconResolver.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.IO; 6 using System.Linq; 7 using System.Xml.Linq; 8 9 namespace Microsoft.PowerToys.Tools.XamlIndexBuilder 10 { 11 public static class ModuleIconResolver 12 { 13 // Hardcoded page-level overrides for module -> icon path 14 private static readonly System.Collections.Generic.Dictionary<string, string> FileNameOverrides = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.OrdinalIgnoreCase) 15 { 16 // Example overrides; expand as needed 17 { "FancyZonesPage.xaml", "/Assets/Settings/Icons/FancyZones.png" }, 18 { "FileLocksmithPage.xaml", "/Assets/Settings/Icons/FileLocksmith.png" }, 19 { "CmdNotFoundPage.xaml", "/Assets/Settings/Icons/CommandNotFound.png" }, 20 { "PowerLauncherPage.xaml", "/Assets/Settings/Icons/PowerToysRun.png" }, 21 }; 22 23 // Contract: 24 // - Input: absolute path to the module XAML file (e.g., FancyZonesPage.xaml) 25 // - Output: app-relative icon path (e.g., "/Assets/Settings/Icons/FancyZones.png"), or null if not found 26 // - Strategy: take the first SettingsCard under the page and read its HeaderIcon value 27 public static string ResolveIconFromFirstSettingsCard(string xamlFilePath) 28 { 29 if (string.IsNullOrWhiteSpace(xamlFilePath)) 30 { 31 return null; 32 } 33 34 try 35 { 36 var doc = XDocument.Load(xamlFilePath); 37 38 // Prefer looking inside SettingsPageControl.ModuleContent to avoid picking cards in Resources/DataTemplates 39 var pageControl = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "SettingsPageControl"); 40 41 if (pageControl != null) 42 { 43 // Locate the property element <SettingsPageControl.ModuleContent> 44 var moduleContent = pageControl 45 .Elements() 46 .FirstOrDefault(e => e.Name.LocalName.EndsWith(".ModuleContent", System.StringComparison.OrdinalIgnoreCase)) 47 ?? pageControl 48 .Descendants() 49 .FirstOrDefault(e => e.Name.LocalName.EndsWith(".ModuleContent", System.StringComparison.OrdinalIgnoreCase)); 50 51 if (moduleContent != null) 52 { 53 // Find the first SettingsCard under ModuleContent and try to read its HeaderIcon 54 var firstCardUnderModule = moduleContent 55 .Descendants() 56 .FirstOrDefault(e => e.Name.LocalName == "SettingsCard"); 57 58 if (firstCardUnderModule != null) 59 { 60 var icon = Program.ExtractIconValue(firstCardUnderModule); 61 if (!string.IsNullOrWhiteSpace(icon)) 62 { 63 return icon; 64 } 65 } 66 } 67 } 68 69 // Fallback to hardcoded overrides by file name 70 var fileName = Path.GetFileName(xamlFilePath); 71 if (!string.IsNullOrEmpty(fileName) && FileNameOverrides.TryGetValue(fileName, out var overrideIcon)) 72 { 73 return overrideIcon; 74 } 75 76 return null; 77 } 78 catch 79 { 80 // Non-fatal: let caller decide fallback 81 return null; 82 } 83 } 84 } 85 }