├── .build ├── .editorconfig ├── Build.cs ├── Configuration.cs ├── Directory.build.props ├── Directory.build.targets ├── _build.csproj └── _build.csproj.DotSettings ├── .editorconfig ├── .github ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── .nuke ├── build.schema.json └── parameters.json ├── Changelog.md ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.packages.props ├── GitVersion.yml ├── LICENSE ├── NuGet.Config ├── README.md ├── XAMLTools.sln ├── XAMLTools.sln.DotSettings ├── appveyor.yml ├── build.cmd ├── build.ps1 ├── build.sh ├── global.json └── src ├── SharedKey.snk ├── XAMLTools.Core ├── FrameworkStubs.cs ├── Helpers │ ├── FileHelper.cs │ └── MutexHelper.cs ├── ILogger.cs ├── ResourceDump │ └── ResourceDumper.cs ├── TraceLogger.cs ├── UTF8StringWriter.cs ├── XAMLColorSchemeGenerator │ ├── ColorScheme.Template.xaml │ ├── ColorSchemeGenerator.cs │ ├── GeneratorParameters.json │ └── ThemeGenerator.cs ├── XAMLCombine │ ├── IXamlCombinerOptions.cs │ ├── ResourceElement.cs │ └── XAMLCombiner.cs └── XAMLTools.Core.csproj ├── XAMLTools.MSBuild ├── MSBuildLogger.cs ├── XAMLColorSchemeGeneratorTask.cs ├── XAMLCombineTask.cs ├── XAMLCombineTaskItemOptions.cs ├── XAMLTools.MSBuild.csproj ├── build │ ├── XAMLTools.MSBuild.props │ └── XAMLTools.MSBuild.targets └── buildMultiTargeting │ ├── XAMLTools.MSBuild.props │ └── XAMLTools.MSBuild.targets ├── XAMLTools ├── App.net.config ├── App.netcoreapp.config ├── Commands │ ├── BaseOptions.cs │ ├── DumpResourcesOptions.cs │ ├── XAMLColorSchemeGeneratorOptions.cs │ └── XAMLCombineOptions.cs ├── ConsoleLogger.cs ├── Program.cs ├── XAMLTools.csproj └── app.manifest └── tests ├── XAMLTools.Tests ├── AssemblySetup.cs ├── MSBuild │ └── XAMLCombineTaskItemOptionsTests.cs ├── MSBuildCompileTests.cs ├── MutexHelperTests.cs ├── TestHelpers │ └── TestLogger.cs ├── XAMLCombinerTests.TestOutput.verified.xaml ├── XAMLCombinerTests.TestOutputWinUI.verified.xaml ├── XAMLCombinerTests.cs └── XAMLTools.Tests.csproj └── XAMLTools.WPFApp ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Controls ├── MyControl1.cs └── MyControl2.cs ├── Directory.build.props ├── Directory.build.targets ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Themes ├── ColorScheme.Template.xaml ├── Controls │ ├── Control1.xaml │ ├── Control2.xaml │ ├── Control3.xaml │ ├── DataTemplates.xaml │ └── zConverters.xaml ├── DuplicateKeys │ ├── Control1.xaml │ └── Control2.xaml ├── DuplicateNamespaces │ ├── Control1.xaml │ └── Control2.xaml ├── GeneratorParameters.json └── WinUI │ ├── Colors1.xaml │ ├── Colors2.xaml │ └── Common.xaml ├── XAMLTools.WPFApp.csproj └── build.cmd /.build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /.build/Build.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nuke.Common; 3 | using Nuke.Common.Execution; 4 | using Nuke.Common.IO; 5 | using Nuke.Common.ProjectModel; 6 | using Nuke.Common.Tooling; 7 | using Nuke.Common.Tools.DotNet; 8 | using Nuke.Common.Tools.GitVersion; 9 | 10 | using static Nuke.Common.IO.CompressionTasks; 11 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 12 | using static Nuke.Common.IO.FileSystemTasks; 13 | 14 | [UnsetVisualStudioEnvironmentVariables] 15 | class Build : NukeBuild 16 | { 17 | public static int Main () => Execute(x => x.Compile); 18 | 19 | protected override void OnBuildInitialized() 20 | { 21 | base.OnBuildInitialized(); 22 | 23 | ProcessTasks.DefaultLogInvocation = true; 24 | ProcessTasks.DefaultLogOutput = true; 25 | 26 | if (GitVersion is null 27 | && IsLocalBuild == false) 28 | { 29 | throw new Exception("Could not initialize GitVersion."); 30 | } 31 | 32 | Serilog.Log.Information("IsLocalBuild : {0}", IsLocalBuild.ToString()); 33 | 34 | Serilog.Log.Information("Informational Version: {0}", InformationalVersion); 35 | Serilog.Log.Information("SemVer Version: {0}", SemVer); 36 | Serilog.Log.Information("AssemblySemVer Version: {0}", AssemblySemVer); 37 | Serilog.Log.Information("MajorMinorPatch Version: {0}", MajorMinorPatch); 38 | Serilog.Log.Information("NuGet Version: {0}", NuGetVersion); 39 | } 40 | 41 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] 42 | readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; 43 | 44 | [Solution(GenerateProjects = true)] readonly Solution Solution; 45 | 46 | [GitVersion(Framework = "netcoreapp3.1")] readonly GitVersion GitVersion; 47 | 48 | string AssemblySemVer => GitVersion?.AssemblySemVer ?? "1.0.0"; 49 | string SemVer => GitVersion?.SemVer ?? "1.0.0"; 50 | string InformationalVersion => GitVersion?.InformationalVersion ?? "1.0.0"; 51 | string NuGetVersion => GitVersion?.NuGetVersion ?? "1.0.0"; 52 | string MajorMinorPatch => GitVersion?.MajorMinorPatch ?? "1.0.0"; 53 | string AssemblySemFileVer => GitVersion?.AssemblySemFileVer ?? "1.0.0"; 54 | 55 | AbsolutePath BuildBinDirectory => RootDirectory / "bin"; 56 | 57 | AbsolutePath SourceDirectory => RootDirectory / "src"; 58 | 59 | [Parameter] 60 | readonly AbsolutePath ArtifactsDirectory = RootDirectory / "artifacts"; 61 | 62 | AbsolutePath TestResultsDir => RootDirectory / "TestResults"; 63 | 64 | Target Restore => _ => _ 65 | .Executes(() => 66 | { 67 | }); 68 | 69 | Target Compile => _ => _ 70 | .DependsOn(Restore) 71 | .Executes(() => 72 | { 73 | DotNetBuild(s => s 74 | .SetProjectFile(Solution) 75 | .SetConfiguration("Debug") 76 | .SetAssemblyVersion(AssemblySemVer) 77 | .SetFileVersion(AssemblySemVer) 78 | .SetInformationalVersion(InformationalVersion)); 79 | 80 | DotNetBuild(s => s 81 | .SetProjectFile(Solution) 82 | .SetConfiguration("Release") 83 | .SetAssemblyVersion(AssemblySemVer) 84 | .SetFileVersion(AssemblySemVer) 85 | .SetInformationalVersion(InformationalVersion)); 86 | }); 87 | 88 | Target Test => _ => _ 89 | .After(Compile) 90 | .Before(Pack) 91 | .Executes(() => 92 | { 93 | EnsureCleanDirectory(TestResultsDir); 94 | 95 | DotNetTest(_ => _ 96 | .SetConfiguration(Configuration) 97 | .SetProjectFile(SourceDirectory / "tests" / "XAMLTools.Tests") 98 | .SetProcessWorkingDirectory(SourceDirectory / "tests" / "XAMLTools.Tests") 99 | .EnableNoBuild() 100 | .EnableNoRestore() 101 | .AddLoggers("trx") 102 | .SetResultsDirectory(TestResultsDir) 103 | .SetVerbosity(DotNetVerbosity.Normal)); 104 | }); 105 | 106 | Target Pack => _ => _ 107 | .DependsOn(Compile) 108 | .Produces(ArtifactsDirectory / "*.nupkg", ArtifactsDirectory / "*.zip") 109 | .Executes(() => 110 | { 111 | EnsureCleanDirectory(ArtifactsDirectory); 112 | 113 | DotNetPack(s => s 114 | .SetProject(SourceDirectory / "XAMLTools.MSBuild") 115 | .SetConfiguration(Configuration) 116 | 117 | .When(GitVersion is not null, x => x 118 | .SetProperty("RepositoryBranch", GitVersion?.BranchName) 119 | .SetProperty("RepositoryCommit", GitVersion?.Sha)) 120 | .SetVersion(NuGetVersion) 121 | .SetAssemblyVersion(AssemblySemVer) 122 | .SetFileVersion(AssemblySemFileVer) 123 | .SetInformationalVersion(InformationalVersion)); 124 | 125 | Compress(BuildBinDirectory / Configuration / "XAMLTools", ArtifactsDirectory / $"XAMLTools-v{NuGetVersion}.zip"); 126 | 127 | DotNetPack(s => s 128 | .SetProject(SourceDirectory / "XAMLTools") 129 | .SetConfiguration(Configuration) 130 | 131 | .When(GitVersion is not null, x => x 132 | .SetProperty("RepositoryBranch", GitVersion?.BranchName) 133 | .SetProperty("RepositoryCommit", GitVersion?.Sha)) 134 | .SetVersion(NuGetVersion) 135 | .SetAssemblyVersion(AssemblySemVer) 136 | .SetFileVersion(AssemblySemFileVer) 137 | .SetInformationalVersion(InformationalVersion)); 138 | }); 139 | 140 | // ReSharper disable once UnusedMember.Local 141 | // ReSharper disable once InconsistentNaming 142 | Target CI => _ => _ 143 | .DependsOn(Compile, Test, Pack); 144 | } 145 | -------------------------------------------------------------------------------- /.build/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Nuke.Common.Tooling; 3 | 4 | [TypeConverter(typeof(TypeConverter))] 5 | public class Configuration : Enumeration 6 | { 7 | public static Configuration Debug = new Configuration { Value = nameof(Debug) }; 8 | public static Configuration Release = new Configuration { Value = nameof(Release) }; 9 | 10 | public static implicit operator string(Configuration configuration) 11 | { 12 | return configuration.Value; 13 | } 14 | } -------------------------------------------------------------------------------- /.build/Directory.build.props: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /.build/Directory.build.targets: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /.build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.build/_build.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | Implicit 7 | Implicit 8 | ExpressionBody 9 | 0 10 | NEXT_LINE 11 | True 12 | False 13 | 120 14 | IF_OWNER_IS_SINGLE_LINE 15 | WRAP_IF_LONG 16 | False 17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 19 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 20 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | True 29 | True 30 | True 31 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line=crlf 5 | 6 | [*.{xaml,xml,nuspec}] 7 | indent_style=space 8 | indent_size=4 9 | 10 | [*.{md,yml}] 11 | indent_style=space 12 | indent_size=2 13 | 14 | [*.{csproj,props,targets}] 15 | indent_style=space 16 | indent_size=2 17 | 18 | # Code files 19 | [*.{cs,vb}] 20 | indent_style=space 21 | indent_size=4 22 | 23 | # Microsoft .NET properties 24 | csharp_space_after_cast=false 25 | 26 | dotnet_style_qualification_for_field = true:warning 27 | dotnet_style_qualification_for_property = true:warning 28 | dotnet_style_qualification_for_method = true:warning 29 | dotnet_style_qualification_for_event = true:warning 30 | dotnet_style_require_accessibility_modifiers = always:warning 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [batzen] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: System.Text.Json 10 | versions: 11 | - ">= 5.a, < 6" 12 | - dependency-name: Nuke.Common 13 | versions: 14 | - 5.1.0 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | /build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | [Ll]og/ 25 | ColorSchemes/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.binlog 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | *.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # scriptcs 169 | **/scriptcs_packages/* 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Windows Store app package directories and files 180 | AppPackages/ 181 | BundleArtifacts/ 182 | Package.StoreAssociation.xml 183 | _pkginfo.txt 184 | 185 | # Visual Studio cache files 186 | # files ending in .cache can be ignored 187 | *.[Cc]ache 188 | # but keep track of directories ending in .cache 189 | !*.[Cc]ache/ 190 | 191 | # Others 192 | ClientBin/ 193 | ~$* 194 | *~ 195 | *.dbmdl 196 | *.dbproj.schemaview 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | #.paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # cake 261 | tools/* 262 | !tools/packages.config 263 | 264 | # XamlStyler 265 | !XamlStyler/ 266 | src/tests/XAMLTools.WPFApp/Themes/Generic.xaml 267 | 268 | **/*.received.* 269 | -------------------------------------------------------------------------------- /.nuke/build.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Build Schema", 4 | "$ref": "#/definitions/build", 5 | "definitions": { 6 | "build": { 7 | "type": "object", 8 | "properties": { 9 | "ArtifactsDirectory": { 10 | "type": "string" 11 | }, 12 | "Configuration": { 13 | "type": "string", 14 | "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", 15 | "enum": [ 16 | "Debug", 17 | "Release" 18 | ] 19 | }, 20 | "Continue": { 21 | "type": "boolean", 22 | "description": "Indicates to continue a previously failed build attempt" 23 | }, 24 | "Help": { 25 | "type": "boolean", 26 | "description": "Shows the help text for this build assembly" 27 | }, 28 | "Host": { 29 | "type": "string", 30 | "description": "Host for execution. Default is 'automatic'", 31 | "enum": [ 32 | "AppVeyor", 33 | "AzurePipelines", 34 | "Bamboo", 35 | "Bitbucket", 36 | "Bitrise", 37 | "GitHubActions", 38 | "GitLab", 39 | "Jenkins", 40 | "Rider", 41 | "SpaceAutomation", 42 | "TeamCity", 43 | "Terminal", 44 | "TravisCI", 45 | "VisualStudio", 46 | "VSCode" 47 | ] 48 | }, 49 | "NoLogo": { 50 | "type": "boolean", 51 | "description": "Disables displaying the NUKE logo" 52 | }, 53 | "Partition": { 54 | "type": "string", 55 | "description": "Partition to use on CI" 56 | }, 57 | "Plan": { 58 | "type": "boolean", 59 | "description": "Shows the execution plan (HTML)" 60 | }, 61 | "Profile": { 62 | "type": "array", 63 | "description": "Defines the profiles to load", 64 | "items": { 65 | "type": "string" 66 | } 67 | }, 68 | "Root": { 69 | "type": "string", 70 | "description": "Root directory during build execution" 71 | }, 72 | "Skip": { 73 | "type": "array", 74 | "description": "List of targets to be skipped. Empty list skips all dependencies", 75 | "items": { 76 | "type": "string", 77 | "enum": [ 78 | "CI", 79 | "Compile", 80 | "Pack", 81 | "Restore", 82 | "Test" 83 | ] 84 | } 85 | }, 86 | "Solution": { 87 | "type": "string", 88 | "description": "Path to a solution file that is automatically loaded" 89 | }, 90 | "Target": { 91 | "type": "array", 92 | "description": "List of targets to be invoked. Default is '{default_target}'", 93 | "items": { 94 | "type": "string", 95 | "enum": [ 96 | "CI", 97 | "Compile", 98 | "Pack", 99 | "Restore", 100 | "Test" 101 | ] 102 | } 103 | }, 104 | "Verbosity": { 105 | "type": "string", 106 | "description": "Logging verbosity during build execution. Default is 'Normal'", 107 | "enum": [ 108 | "Minimal", 109 | "Normal", 110 | "Quiet", 111 | "Verbose" 112 | ] 113 | } 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "XAMLTools.sln" 4 | } -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog for XAMLTools 2 | 3 | ## 1.0.0 (preview) 4 | 5 | - Inital release 6 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | True 5 | true 6 | 10 7 | false 8 | en-US 9 | false 10 | enable 11 | embedded 12 | 13 | 14 | 15 | 16 | net472;net6.0 17 | true 18 | true 19 | false 20 | Release 21 | true 22 | 23 | $(MSBuildThisFileDirectory)/bin/$(Configuration) 24 | $(OutputRoot)/$(MSBuildProjectName)/ 25 | $(MSBuildThisFileDirectory)/artifacts/ 26 | 27 | 28 | 29 | 30 | $(NoWarn);CS1591 31 | $(NoError);CS1591 32 | 33 | 34 | 35 | 36 | true 37 | 38 | https://github.com/batzen/XAMLTools 39 | XAMLTools 40 | Copyright © 2015 - $([System.DateTime]::Today.ToString(yyyy)) Bastian Schmidt 41 | 42 | 4.0 43 | 44 | 45 | 46 | 47 | Bastian Schmidt 48 | MIT 49 | https://github.com/batzen/XAMLTools 50 | 51 | xaml color scheme generator 52 | https://github.com/batzen/XAMLTools/blob/develop/Changelog.md 53 | https://github.com/batzen/XAMLTools.git 54 | git 55 | 56 | false 57 | true 58 | true 59 | 60 | 61 | 62 | 63 | 64 | true 65 | 66 | true 67 | 68 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 69 | 70 | 71 | True 72 | 73 | 74 | 75 | 76 | True 77 | $(MSBuildThisFileDirectory)\src\SharedKey.snk 78 | false 79 | true 80 | 81 | 82 | 83 | false 84 | true 85 | $(DefineConstants);IsFullFramework 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Directory.packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | assembly-versioning-scheme: Major 2 | assembly-file-versioning-scheme: MajorMinorPatchTag 3 | mode: ContinuousDeployment 4 | next-version: 1.0.0 5 | branches: 6 | master: 7 | mode: ContinuousDeployment 8 | tag: rc 9 | prevent-increment-of-merged-branch-version: true 10 | track-merge-target: false 11 | is-release-branch: true 12 | develop: 13 | mode: ContinuousDeployment 14 | tag: alpha 15 | prevent-increment-of-merged-branch-version: true 16 | track-merge-target: true 17 | pull-request: 18 | mode: ContinuousDelivery 19 | ignore: 20 | sha: [] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2022 Bastian Schmidt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XAMLTools 2 | 3 | [![Build status](https://img.shields.io/appveyor/ci/batzen/XAMLTools.svg?style=flat-square)](https://ci.appveyor.com/project/batzen/XAMLTools) 4 | [![Release](https://img.shields.io/github/release/batzen/XAMLTools.svg?style=flat-square)](https://github.com/batzen/XAMLTools/releases/latest) 5 | [![Issues](https://img.shields.io/github/issues/batzen/XAMLTools.svg?style=flat-square)](https://github.com/batzen/XAMLTools/issues) 6 | [![Downloads](https://img.shields.io/nuget/dt/XAMLTools.MSBuild.svg?style=flat-square)](http://www.nuget.org/packages/XAMLTools.MSBuild/) 7 | [![Nuget](https://img.shields.io/nuget/vpre/XAMLTools.MSBuild.svg?style=flat-square)](http://nuget.org/packages/XAMLTools.MSBuild) 8 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/batzen/XAMLTools/blob/master/License.txt) 9 | 10 | Various tools for easing the development of XAML related applications. 11 | 12 | As i only use WPF myself everything is focused on WPF, but things should work for other XAML dialects (at least in theory). 13 | 14 | You can either use the commandline tool `XAMLTools` or the MSBuild version `XAMLTools.MSBuild` to make use of the provided functionalities. 15 | 16 | CI builds can be consumed from [appveyor](https://ci.appveyor.com/nuget/xamltools). 17 | 18 | ## XAMLCombine 19 | 20 | Combines multiple XAML files to one large file. 21 | This is useful when you want to provide one `Generic.xaml` instead of multiple small XAML files. 22 | Using one large XAML file not only makes it easier to consume, but can also drastically improving loading performance. 23 | 24 | ### Using the MSBuild-Task 25 | 26 | ``` 27 | 28 | Themes/Generic.xaml 29 | 30 | ``` 31 | 32 | The MSBuild-Task includes the items used for combining as pages during debug builds and removes them from pages during release builds. 33 | This is done to reduce the binary size for release builds and still enable intellisense in debug builds for those XAML files. 34 | 35 | **Remarks when using Rider** 36 | To get intellisense in debug builds inside the XAML files and to prevent duplicate display of those files you have to define: 37 | 38 | ``` 39 | 40 | $(DefaultItemExcludes);Themes\Controls\*.xaml 41 | 42 | 43 | 44 | 45 | 46 | ``` 47 | 48 | ### Using the executable 49 | 50 | `XAMLTools` accepts the following commandline parameters for the `combine` verb: 51 | 52 | - `-s "Path_To_Your_SourceFile"` => A file containing a new line separated list of files to combine (lines starting with # are skipped) 53 | - `-t "Path_To_Your_Target_File.xaml"` 54 | 55 | ## XAMLColorSchemeGenerator 56 | 57 | Generates color scheme XAML files while replacing certain parts of a template file. 58 | 59 | For an example on how this tool works see the [generator input](src/XAMLTools.Core/XAMLColorSchemeGenerator/GeneratorParameters.json) and [template](src/XAMLTools.Core/XAMLColorSchemeGenerator/ColorScheme.Template.xaml) files. 60 | 61 | ### Using the MSBuild-Task 62 | 63 | ``` 64 | 65 | Themes\GeneratorParameters.json 66 | Themes\ColorSchemes 67 | 68 | ``` 69 | 70 | ### Using the executable 71 | 72 | `XAMLTools` accepts the following commandline parameters for the `colorscheme` verb: 73 | 74 | - `-p "Path_To_Your_GeneratorParameters.json"` 75 | - `-t "Path_To_Your_ColorScheme.Template.xaml"` 76 | - `-o "Path_To_Your_Output_Folder"` 77 | -------------------------------------------------------------------------------- /XAMLTools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XAMLTools.Core", "src\XAMLTools.Core\XAMLTools.Core.csproj", "{9572E64E-0B2F-407B-AC14-141941CB7D6E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{246A4888-9B8A-4EED-88A9-1162D7C9AF7D}" 9 | ProjectSection(SolutionItems) = preProject 10 | appveyor.yml = appveyor.yml 11 | Directory.Build.props = Directory.Build.props 12 | Directory.Build.targets = Directory.Build.targets 13 | Directory.packages.props = Directory.packages.props 14 | GitVersion.yml = GitVersion.yml 15 | global.json = global.json 16 | README.md = README.md 17 | .editorconfig = .editorconfig 18 | Changelog.md = Changelog.md 19 | EndProjectSection 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", ".build\_build.csproj", "{5AF6F03B-F91D-4A74-83E6-7982BC836BAD}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XAMLTools", "src\XAMLTools\XAMLTools.csproj", "{66C28B35-B0DD-4049-8A16-2E10DBFD2ABA}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XAMLTools.MSBuild", "src\XAMLTools.MSBuild\XAMLTools.MSBuild.csproj", "{FDC4A636-24DE-43CE-B0AB-F469EDD3B687}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XAMLTools.WPFApp", "src\tests\XAMLTools.WPFApp\XAMLTools.WPFApp.csproj", "{283EF326-1400-4E07-A3C2-BF505782A293}" 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{117DC9D7-9E2D-4D23-955F-E7892DC949A7}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XAMLTools.Tests", "src\tests\XAMLTools.Tests\XAMLTools.Tests.csproj", "{5A42B28F-F438-44FD-B63A-6D3621989BEB}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {9572E64E-0B2F-407B-AC14-141941CB7D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {9572E64E-0B2F-407B-AC14-141941CB7D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {9572E64E-0B2F-407B-AC14-141941CB7D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {9572E64E-0B2F-407B-AC14-141941CB7D6E}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {5AF6F03B-F91D-4A74-83E6-7982BC836BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {5AF6F03B-F91D-4A74-83E6-7982BC836BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {66C28B35-B0DD-4049-8A16-2E10DBFD2ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {66C28B35-B0DD-4049-8A16-2E10DBFD2ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {66C28B35-B0DD-4049-8A16-2E10DBFD2ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {66C28B35-B0DD-4049-8A16-2E10DBFD2ABA}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {FDC4A636-24DE-43CE-B0AB-F469EDD3B687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {FDC4A636-24DE-43CE-B0AB-F469EDD3B687}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {FDC4A636-24DE-43CE-B0AB-F469EDD3B687}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {FDC4A636-24DE-43CE-B0AB-F469EDD3B687}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {283EF326-1400-4E07-A3C2-BF505782A293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {283EF326-1400-4E07-A3C2-BF505782A293}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {283EF326-1400-4E07-A3C2-BF505782A293}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {283EF326-1400-4E07-A3C2-BF505782A293}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {5A42B28F-F438-44FD-B63A-6D3621989BEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {5A42B28F-F438-44FD-B63A-6D3621989BEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {5A42B28F-F438-44FD-B63A-6D3621989BEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {5A42B28F-F438-44FD-B63A-6D3621989BEB}.Release|Any CPU.Build.0 = Release|Any CPU 61 | EndGlobalSection 62 | GlobalSection(SolutionProperties) = preSolution 63 | HideSolutionNode = FALSE 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {854C6F4F-4D88-4024-BEDC-34E4FCE36013} 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {283EF326-1400-4E07-A3C2-BF505782A293} = {117DC9D7-9E2D-4D23-955F-E7892DC949A7} 70 | {5A42B28F-F438-44FD-B63A-6D3621989BEB} = {117DC9D7-9E2D-4D23-955F-E7892DC949A7} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /XAMLTools.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | Required 3 | Required 4 | Required 5 | Required 6 | 7 | All 8 | True 9 | True 10 | True 11 | True 12 | True 13 | NEXT_LINE 14 | NEXT_LINE 15 | True 16 | True 17 | True 18 | NEXT_LINE 19 | NEXT_LINE 20 | NEVER 21 | NEVER 22 | True 23 | ALWAYS_USE 24 | False 25 | False 26 | False 27 | True 28 | True 29 | False 30 | CHOP_ALWAYS 31 | True 32 | True 33 | True 34 | HSL 35 | QAT 36 | XAML 37 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 38 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 39 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 40 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 41 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> 42 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 43 | True 44 | 1 45 | True 46 | True 47 | True 48 | True 49 | True 50 | True 51 | True 52 | True 53 | True 54 | True 55 | True -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | branches: 3 | only: 4 | - develop 5 | - master 6 | - /v\d*\.\d*\.\d*/ 7 | 8 | image: Visual Studio 2022 9 | test: off 10 | 11 | pull_requests: 12 | do_not_increment_build_number: false 13 | 14 | install: 15 | - ps: Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile 'dotnet-install.ps1' 16 | - ps: ./dotnet-install.ps1 -Version 6.0.300 -InstallDir "C:\Program Files\dotnet" 17 | 18 | build_script: 19 | - cmd: dotnet --info 20 | - cmd: build.cmd CI 21 | 22 | artifacts: 23 | - path: \artifacts\*.* 24 | 25 | nuget: 26 | disable_publish_on_pr: true -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | :; set -eo pipefail 2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 3 | :; ${SCRIPT_DIR}/build.sh "$@" 4 | :; exit $? 5 | 6 | @ECHO OFF 7 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* 8 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\.build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bash --version 2>&1 | head -n 1 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/.build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | export DOTNET_MULTILEVEL_LOOKUP=0 22 | 23 | ########################################################################### 24 | # EXECUTION 25 | ########################################################################### 26 | 27 | function FirstJsonValue { 28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" 29 | } 30 | 31 | # If dotnet CLI is installed globally and it matches requested version, use for execution 32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then 33 | export DOTNET_EXE="$(command -v dotnet)" 34 | else 35 | # Download install script 36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 37 | mkdir -p "$TEMP_DIRECTORY" 38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 39 | chmod +x "$DOTNET_INSTALL_FILE" 40 | 41 | # If global.json exists, load expected version 42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") 44 | if [[ "$DOTNET_VERSION" == "" ]]; then 45 | unset DOTNET_VERSION 46 | fi 47 | fi 48 | 49 | # Install by channel or version 50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 51 | if [[ -z ${DOTNET_VERSION+x} ]]; then 52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 53 | else 54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 55 | fi 56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 57 | fi 58 | 59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.400", 4 | "rollForward": "feature" 5 | } 6 | } -------------------------------------------------------------------------------- /src/SharedKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batzen/XAMLTools/69b6b28f1353d02e85d8f5a08eca8fa416cc44e0/src/SharedKey.snk -------------------------------------------------------------------------------- /src/XAMLTools.Core/FrameworkStubs.cs: -------------------------------------------------------------------------------- 1 | #if !NET60_OR_GREATER 2 | namespace System.Runtime.CompilerServices 3 | { 4 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 5 | public sealed class CallerArgumentExpressionAttribute : Attribute 6 | { 7 | public CallerArgumentExpressionAttribute(string parameterName) 8 | { 9 | this.ParameterName = parameterName; 10 | } 11 | 12 | public string ParameterName { get; } 13 | } 14 | 15 | public sealed class IsExternalInit : Attribute 16 | { 17 | } 18 | } 19 | #endif -------------------------------------------------------------------------------- /src/XAMLTools.Core/Helpers/FileHelper.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Helpers; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | public class FileHelper 9 | { 10 | private const int BufferSize = 32768; // 32 Kilobytes 11 | 12 | public static string ReadAllTextSharedWithRetry(string file, ushort retries = 5) 13 | { 14 | for (var i = 0; i < retries; i++) 15 | { 16 | try 17 | { 18 | return ReadAllTextShared(file); 19 | } 20 | catch (IOException) 21 | { 22 | if (i == retries - 1) 23 | { 24 | throw; 25 | } 26 | 27 | Thread.Sleep(TimeSpan.FromSeconds(1)); 28 | } 29 | } 30 | 31 | throw new IOException($"File \"{file}\" could not be read."); 32 | } 33 | 34 | public static string ReadAllTextShared(string file) 35 | { 36 | Stream? stream = null; 37 | try 38 | { 39 | stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize); 40 | 41 | using (var textReader = new StreamReader(stream, Encoding.UTF8)) 42 | { 43 | stream = null; 44 | return textReader.ReadToEnd(); 45 | } 46 | } 47 | finally 48 | { 49 | stream?.Dispose(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/Helpers/MutexHelper.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Helpers; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading; 7 | 8 | public class MutexHelper 9 | { 10 | public static T ExecuteLocked(Func action, string file, [CallerArgumentExpression("action")] string? caller = null, TimeSpan? timeout = null, string errorMessage = "Another instance of this application blocked the concurrent execution.") 11 | { 12 | var mutexName = "Local\\XamlTools_" + Path.GetFileName(file); 13 | 14 | using var mutex = new Mutex(false, mutexName); 15 | var acquired = false; 16 | 17 | try 18 | { 19 | acquired = mutex.WaitOne(timeout ?? TimeSpan.FromSeconds(10)); 20 | if (acquired == false) 21 | { 22 | throw new TimeoutException(errorMessage); 23 | } 24 | 25 | return action(); 26 | } 27 | finally 28 | { 29 | if (acquired) 30 | { 31 | mutex.ReleaseMutex(); 32 | } 33 | } 34 | } 35 | 36 | public static void ExecuteLocked(Action action, string file, [CallerArgumentExpression("action")] string? caller = null, TimeSpan? timeout = null, string errorMessage = "Another instance of this application blocked the concurrent execution.") 37 | { 38 | var mutexName = "Local\\XamlTools_" + Path.GetFileName(file); 39 | 40 | using var mutex = new Mutex(false, mutexName); 41 | var acquired = false; 42 | 43 | try 44 | { 45 | acquired = mutex.WaitOne(timeout ?? TimeSpan.FromSeconds(10)); 46 | if (acquired == false) 47 | { 48 | throw new TimeoutException(errorMessage); 49 | } 50 | 51 | action(); 52 | } 53 | finally 54 | { 55 | if (acquired) 56 | { 57 | mutex.ReleaseMutex(); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools; 2 | 3 | public interface ILogger 4 | { 5 | void Debug(string message); 6 | 7 | void Info(string message); 8 | 9 | void InfoImportant(string message); 10 | 11 | void Warn(string message); 12 | 13 | void Error(string message); 14 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/ResourceDump/ResourceDumper.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.ResourceDump; 2 | 3 | using System.Collections; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | public class ResourceDumper 10 | { 11 | public void DumpResources(string assemblyFile, string outputPath) 12 | { 13 | assemblyFile = Path.GetFullPath(assemblyFile); 14 | var assembly = Assembly.LoadFile(assemblyFile); 15 | 16 | var resourceNames = assembly.GetManifestResourceNames(); 17 | 18 | { 19 | var resourceNamesFile = Path.Combine(outputPath, "ResourceNames"); 20 | File.WriteAllLines(resourceNamesFile, resourceNames, Encoding.UTF8); 21 | } 22 | 23 | var xamlResourceName = resourceNames.FirstOrDefault(x => x.EndsWith(".g.resources")); 24 | 25 | if (string.IsNullOrEmpty(xamlResourceName) == false) 26 | { 27 | using var xamlResourceStream = assembly.GetManifestResourceStream(xamlResourceName)!; 28 | using var reader = new System.Resources.ResourceReader(xamlResourceStream); 29 | var xamlResourceNames = reader.Cast().Select(entry => (string)entry.Key).ToArray(); 30 | 31 | { 32 | var xamlResourceNamesFile = Path.Combine(outputPath, "XAMLResourceNames"); 33 | File.WriteAllLines(xamlResourceNamesFile, xamlResourceNames, Encoding.UTF8); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/TraceLogger.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools; 2 | 3 | using System.Diagnostics; 4 | 5 | public class TraceLogger : ILogger 6 | { 7 | public void Debug(string message) 8 | { 9 | Trace.WriteLine(message); 10 | } 11 | 12 | public void Info(string message) 13 | { 14 | Trace.WriteLine(message); 15 | } 16 | 17 | public void InfoImportant(string message) 18 | { 19 | Trace.WriteLine(message); 20 | } 21 | 22 | public void Warn(string message) 23 | { 24 | Trace.TraceWarning(message); 25 | } 26 | 27 | public void Error(string message) 28 | { 29 | Trace.TraceError(message); 30 | } 31 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/UTF8StringWriter.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools; 2 | 3 | using System.IO; 4 | using System.Text; 5 | 6 | public sealed class UTF8StringWriter : StringWriter 7 | { 8 | public override Encoding Encoding => Encoding.UTF8; 9 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLColorSchemeGenerator/ColorScheme.Template.xaml: -------------------------------------------------------------------------------- 1 |  8 | {{ThemeName}} 9 | Fluent.Ribbon 10 | {{ThemeDisplayName}} 11 | {{BaseColorScheme}} 12 | {{ColorScheme}} 13 | {{AlternativeColorScheme}} 14 | {{Fluent.Ribbon.Colors.AccentBaseColor}} 15 | 16 | {{IsHighContrast}} 17 | 18 | 19 | 20 | 21 | {{Fluent.Ribbon.Colors.AccentBaseColor}} 22 | 23 | {{Fluent.Ribbon.Colors.AccentColor80}} 24 | 25 | {{Fluent.Ribbon.Colors.AccentColor60}} 26 | 27 | {{Fluent.Ribbon.Colors.AccentColor40}} 28 | 29 | {{Fluent.Ribbon.Colors.AccentColor20}} 30 | 31 | {{Fluent.Ribbon.Colors.HighlightColor}} 32 | 33 | {{Fluent.Ribbon.Colors.AccentLight1}} 34 | {{Fluent.Ribbon.Colors.AccentLight2}} 35 | {{Fluent.Ribbon.Colors.AccentLight3}} 36 | {{Fluent.Ribbon.Colors.AccentDark1}} 37 | {{Fluent.Ribbon.Colors.AccentDark2}} 38 | {{Fluent.Ribbon.Colors.AccentDark3}} 39 | 40 | 41 | {{BlackColor}} 42 | {{WhiteColor}} 43 | {{Gray1}} 44 | {{Gray2}} 45 | {{Gray3}} 46 | {{Gray4}} 47 | {{Gray5}} 48 | {{Gray6}} 49 | {{Gray7}} 50 | {{Gray8}} 51 | {{Gray9}} 52 | {{Gray10}} 53 | 54 | #00FFFFFF 55 | #17FFFFFF 56 | 57 | 58 | {{Fluent.Ribbon.Colors.IdealForegroundColor}} 59 | {{Fluent.Ribbon.Colors.DarkIdealForegroundDisabledColor}} 60 | 61 | 62 | {{Fluent.Ribbon.Colors.ExtremeHighlightColor}} 63 | {{Fluent.Ribbon.Colors.DarkExtremeHighlightColor}} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLColorSchemeGenerator/ColorSchemeGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.XAMLColorSchemeGenerator 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using XAMLTools.Helpers; 9 | 10 | public class ColorSchemeGenerator 11 | { 12 | private const int BufferSize = 32768; // 32 Kilobytes 13 | 14 | public ILogger? Logger { get; set; } = new TraceLogger(); 15 | 16 | public IEnumerable GenerateColorSchemeFiles(string generatorParametersFile, string templateFile, string? outputPath = null) 17 | { 18 | var parameters = GetParametersFromFile(generatorParametersFile); 19 | 20 | outputPath ??= Path.GetDirectoryName(Path.GetFullPath(templateFile)); 21 | 22 | if (string.IsNullOrEmpty(outputPath)) 23 | { 24 | throw new Exception("OutputPath could not be determined."); 25 | } 26 | 27 | Directory.CreateDirectory(outputPath); 28 | 29 | var templateContent = File.ReadAllText(templateFile, Encoding.UTF8); 30 | 31 | var colorSchemesWithoutVariantName = parameters.ColorSchemes 32 | .Where(x => string.IsNullOrEmpty(x.ForColorSchemeVariant) || x.ForColorSchemeVariant == "None") 33 | .ToList(); 34 | var colorSchemesWithVariantName = parameters.ColorSchemes 35 | .Where(x => string.IsNullOrEmpty(x.ForColorSchemeVariant) == false && x.ForColorSchemeVariant != "None") 36 | .ToList(); 37 | 38 | foreach (var baseColorScheme in parameters.BaseColorSchemes) 39 | { 40 | if (colorSchemesWithoutVariantName.Count == 0 41 | && colorSchemesWithVariantName.Count == 0) 42 | { 43 | var themeName = baseColorScheme.Name; 44 | var colorSchemeName = string.Empty; 45 | var alternativeColorSchemeName = string.Empty; 46 | var themeDisplayName = baseColorScheme.Name; 47 | 48 | yield return this.GenerateColorSchemeFile(outputPath, templateContent, themeName, themeDisplayName, baseColorScheme.Name, colorSchemeName, alternativeColorSchemeName, false, baseColorScheme.Values, parameters.DefaultValues); 49 | } 50 | 51 | foreach (var colorScheme in colorSchemesWithoutVariantName) 52 | { 53 | if (string.IsNullOrEmpty(colorScheme.ForBaseColor) == false 54 | && colorScheme.ForBaseColor != baseColorScheme.Name) 55 | { 56 | continue; 57 | } 58 | 59 | var themeName = $"{baseColorScheme.Name}.{colorScheme.Name}"; 60 | var colorSchemeName = colorScheme.Name; 61 | var alternativeColorSchemeName = colorScheme.Name; 62 | var themeDisplayName = $"{colorSchemeName} ({baseColorScheme.Name})"; 63 | 64 | yield return this.GenerateColorSchemeFile(outputPath, templateContent, themeName, themeDisplayName, baseColorScheme.Name, colorSchemeName, alternativeColorSchemeName, colorScheme.IsHighContrast, colorScheme.Values, baseColorScheme.Values, parameters.DefaultValues); 65 | } 66 | 67 | foreach (var colorSchemeVariant in parameters.AdditionalColorSchemeVariants) 68 | { 69 | foreach (var colorScheme in parameters.ColorSchemes) 70 | { 71 | if (string.IsNullOrEmpty(colorScheme.ForBaseColor) == false 72 | && colorScheme.ForBaseColor != baseColorScheme.Name) 73 | { 74 | continue; 75 | } 76 | 77 | if (colorScheme.ForColorSchemeVariant == "None" 78 | // if there is a color scheme specific for the current variant skip the unspecific one 79 | || parameters.ColorSchemes.Any(x => x != colorScheme && colorScheme.Name == x.Name && colorScheme.IsHighContrast == x.IsHighContrast && x.ForColorSchemeVariant == colorSchemeVariant.Name)) 80 | { 81 | continue; 82 | } 83 | 84 | var themeName = $"{baseColorScheme.Name}.{colorScheme.Name}.{colorSchemeVariant.Name}"; 85 | var colorSchemeName = $"{colorScheme.Name}.{colorSchemeVariant.Name}"; 86 | var alternativeColorSchemeName = colorScheme.Name; 87 | var themeDisplayName = $"{colorSchemeName} ({baseColorScheme.Name})"; 88 | 89 | yield return this.GenerateColorSchemeFile(outputPath, templateContent, themeName, themeDisplayName, baseColorScheme.Name, colorSchemeName, alternativeColorSchemeName, colorScheme.IsHighContrast, colorScheme.Values, colorSchemeVariant.Values, baseColorScheme.Values, parameters.DefaultValues); 90 | } 91 | } 92 | } 93 | } 94 | 95 | public static ThemeGenerator.ThemeGeneratorParameters GetParametersFromFile(string inputFile) 96 | { 97 | return ThemeGenerator.Current.GetParametersFromString(FileHelper.ReadAllTextSharedWithRetry(inputFile)); 98 | } 99 | 100 | public string GenerateColorSchemeFile(string outputPath, string templateContent, string themeName, string themeDisplayName, string baseColorScheme, string colorScheme, string alternativeColorScheme, bool isHighContrast, params Dictionary[] valueSources) 101 | { 102 | if (isHighContrast) 103 | { 104 | themeDisplayName += " HighContrast"; 105 | } 106 | 107 | var themeTempFileContent = ThemeGenerator.Current.GenerateColorSchemeFileContent(templateContent, themeName, themeDisplayName, baseColorScheme, colorScheme, alternativeColorScheme, isHighContrast, valueSources); 108 | 109 | var themeFilename = $"{themeName}"; 110 | 111 | if (isHighContrast) 112 | { 113 | themeFilename += ".HighContrast"; 114 | } 115 | 116 | var themeFile = Path.Combine(outputPath, $"{themeFilename}.xaml"); 117 | themeFile = themeFile.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); 118 | 119 | var isNewFile = File.Exists(themeFile) == false; 120 | 121 | this.Logger?.Info($"Checking \"{themeFile}\"..."); 122 | 123 | var fileHasToBeWritten = isNewFile 124 | || FileHelper.ReadAllTextSharedWithRetry(themeFile) != themeTempFileContent; 125 | 126 | if (fileHasToBeWritten) 127 | { 128 | using (var sw = new StreamWriter(themeFile, false, Encoding.UTF8, BufferSize)) 129 | { 130 | sw.Write(themeTempFileContent); 131 | } 132 | 133 | this.Logger?.Info($"Resource Dictionary saved to \"{themeFile}\"."); 134 | } 135 | else 136 | { 137 | this.Logger?.Info("New Resource Dictionary did not differ from existing file. No new file written."); 138 | } 139 | 140 | return themeFile; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLColorSchemeGenerator/GeneratorParameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "DefaultValues": { 3 | "Fluent.Ribbon.Colors.IdealForegroundColor": "White", 4 | "Fluent.Ribbon.Colors.DarkIdealForegroundDisabledColor": "#ADADAD", 5 | "Fluent.Ribbon.Colors.ExtremeHighlightColor": "#FFFFD232", 6 | "Fluent.Ribbon.Colors.DarkExtremeHighlightColor": "#FFF29536", 7 | "Fluent.Ribbon.Brushes.ApplicationMenuItem.CheckBox.Background": "#FFFCF1C2", 8 | "Fluent.Ribbon.Brushes.ApplicationMenuItem.CheckBox.BorderBrush": "#FFF29536" 9 | }, 10 | "BaseColorSchemes": [ 11 | { 12 | "Name": "Dark", 13 | "Values": { 14 | "BlackColor": "#FFFFFFFF", 15 | "WhiteColor": "#FF252525", 16 | "Gray1": "#FFF9F9F9", 17 | "Gray2": "#FF7F7F7F", 18 | "Gray3": "#FF9D9D9D", 19 | "Gray4": "#FFA59F93", 20 | "Gray5": "#FFB9B9B9", 21 | "Gray6": "#FFCCCCCC", 22 | "Gray7": "#FF7E7E7E", 23 | "Gray8": "#FF454545", 24 | "Gray9": "#5EC9C9C9", 25 | "Gray10": "#FF2F2F2F", 26 | "Fluent.Ribbon.Brushes.LabelTextBrush": "{StaticResource Gray6}", 27 | "Fluent.Ribbon.Brushes.RibbonContextualTabGroup.TabItemMouseOverForeground": "{StaticResource BlackColor}", 28 | "Fluent.Ribbon.Brushes.RibbonGroupBox.Header.Foreground": "{StaticResource Gray6}", 29 | "Fluent.Ribbon.Brushes.RibbonTabControl.Content.Background": "#363636", 30 | "Fluent.Ribbon.Brushes.RibbonTabControl.Content.Foreground": "{StaticResource Gray6}", 31 | "Fluent.Ribbon.Brushes.RibbonTabItem.Foreground": "{StaticResource Gray5}", 32 | "Fluent.Ribbon.Brushes.RibbonTabItem.Active.Background": "#363636", 33 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Background": "#444444", 34 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Foreground": "{StaticResource BlackColor}", 35 | "Fluent.Ribbon.Colors.AccentLight1": "{{AccentDark1}}", 36 | "Fluent.Ribbon.Colors.AccentLight2": "{{AccentDark2}}", 37 | "Fluent.Ribbon.Colors.AccentLight3": "{{AccentDark3}}", 38 | "Fluent.Ribbon.Colors.AccentDark1": "{{AccentLight1}}", 39 | "Fluent.Ribbon.Colors.AccentDark2": "{{AccentLight2}}", 40 | "Fluent.Ribbon.Colors.AccentDark3": "{{AccentLight3}}" 41 | } 42 | }, 43 | { 44 | "Name": "Light", 45 | "Values": { 46 | "BlackColor": "#FF000000", 47 | "WhiteColor": "#FFFFFFFF", 48 | "Gray1": "#FF333333", 49 | "Gray2": "#FF7F7F7F", 50 | "Gray3": "#FF9D9D9D", 51 | "Gray4": "#FFA59F93", 52 | "Gray5": "#FFB9B9B9", 53 | "Gray6": "#FFCCCCCC", 54 | "Gray7": "#FFD8D8D9", 55 | "Gray8": "#FFE0E0E0", 56 | "Gray9": "#5EC9C9C9", 57 | "Gray10": "#FFF7F7F7", 58 | "Fluent.Ribbon.Brushes.LabelTextBrush": "{StaticResource BlackColor}", 59 | "Fluent.Ribbon.Brushes.RibbonGroupBox.Header.Foreground": "{StaticResource Gray2}", 60 | "Fluent.Ribbon.Brushes.RibbonContextualTabGroup.TabItemMouseOverForeground": "{StaticResource Fluent.Ribbon.Colors.HighlightColor}", 61 | "Fluent.Ribbon.Brushes.RibbonTabControl.Content.Background": "{StaticResource WhiteColor}", 62 | "Fluent.Ribbon.Brushes.RibbonTabControl.Content.Foreground": "{StaticResource BlackColor}", 63 | "Fluent.Ribbon.Brushes.RibbonTabItem.Foreground": "{StaticResource BlackColor}", 64 | "Fluent.Ribbon.Brushes.RibbonTabItem.Active.Background": "{StaticResource WhiteColor}", 65 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Background": "Transparent", 66 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Foreground": "{StaticResource Fluent.Ribbon.Colors.HighlightColor}", 67 | "Fluent.Ribbon.Colors.AccentLight1": "{{AccentLight1}}", 68 | "Fluent.Ribbon.Colors.AccentLight2": "{{AccentLight2}}", 69 | "Fluent.Ribbon.Colors.AccentLight3": "{{AccentLight3}}", 70 | "Fluent.Ribbon.Colors.AccentDark1": "{{AccentDark1}}", 71 | "Fluent.Ribbon.Colors.AccentDark2": "{{AccentDark2}}", 72 | "Fluent.Ribbon.Colors.AccentDark3": "{{AccentDark3}}" 73 | } 74 | } 75 | ], 76 | "AdditionalColorSchemeVariants": [ 77 | { 78 | "Name": "Colorful", 79 | "Values": { 80 | "Fluent.Ribbon.Colors.Images.DefaultColor": "{StaticResource Fluent.Ribbon.Colors.IdealForegroundColor}", 81 | "Fluent.Ribbon.Brushes.RibbonTabControl.Background": "{StaticResource Fluent.Ribbon.Colors.AccentBaseColor}", 82 | "Fluent.Ribbon.Brushes.RibbonContextualTabGroup.TabItemSelectedForeground": "{StaticResource Gray1}", 83 | "Fluent.Ribbon.Brushes.RibbonContextualTabGroup.TabItemMouseOverForeground": "{StaticResource Fluent.Ribbon.Colors.IdealForegroundColor}", 84 | "Fluent.Ribbon.Brushes.RibbonContextualTabGroup.TabItemSelectedMouseOverForeground": "{StaticResource Gray1}", 85 | "Fluent.Ribbon.Brushes.RibbonTabItem.Foreground": "{StaticResource Fluent.Ribbon.Colors.IdealForegroundColor}", 86 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Background": "#44FFFFFF", 87 | "Fluent.Ribbon.Brushes.RibbonTabItem.MouseOver.Foreground": "{StaticResource WhiteColor}", 88 | "Fluent.Ribbon.Brushes.RibbonTabItem.Selected.MouseOver.Foreground": "{StaticResource Gray1}", 89 | "Fluent.Ribbon.Brushes.RibbonWindow.TitleBackground": "{StaticResource Fluent.Ribbon.Colors.AccentBaseColor}", 90 | "Fluent.Ribbon.Brushes.RibbonWindow.TitleForeground": "{StaticResource Fluent.Ribbon.Colors.IdealForegroundColor}", 91 | "Fluent.Ribbon.Brushes.WindowCommands.CaptionButton.Foreground": "{StaticResource Fluent.Ribbon.Colors.IdealForegroundColor}" 92 | } 93 | } 94 | ], 95 | "ColorSchemes": [ 96 | { 97 | "Name": "Blue", 98 | "ForColorSchemeVariant": "Colorful", 99 | "Values": { 100 | "Fluent.Ribbon.Colors.AccentBaseColor": "#FF2B579A", 101 | "Fluent.Ribbon.Colors.AccentColor80": "#CC2B579A", 102 | "Fluent.Ribbon.Colors.AccentColor60": "#992B579A", 103 | "Fluent.Ribbon.Colors.AccentColor40": "#662B579A", 104 | "Fluent.Ribbon.Colors.AccentColor20": "#332B579A", 105 | "Fluent.Ribbon.Colors.HighlightColor": "#FF086F9E", 106 | "AccentLight1": "#FF1D89DE", 107 | "AccentLight2": "#FF3B9BE5", 108 | "AccentLight3": "#FF58ACED", 109 | "AccentDark1": "#FF0068C6", 110 | "AccentDark2": "#FF0058B4", 111 | "AccentDark3": "#FF0048A3" 112 | } 113 | }, 114 | { 115 | "Name": "Green", 116 | "ForBaseColor": "Light", 117 | "ForColorSchemeVariant": "None", 118 | "IsHighContrast": true, 119 | "Values": { 120 | "Fluent.Ribbon.Colors.AccentBaseColor": "#FF60A917", 121 | "Fluent.Ribbon.Colors.AccentColor80": "#CC60A917", 122 | "Fluent.Ribbon.Colors.AccentColor60": "#9960A917", 123 | "Fluent.Ribbon.Colors.AccentColor40": "#6660A917", 124 | "Fluent.Ribbon.Colors.AccentColor20": "#3360A917", 125 | "Fluent.Ribbon.Colors.HighlightColor": "#FF477D11", 126 | "AccentLight1": "#FF1D89DE", 127 | "AccentLight2": "#FF3B9BE5", 128 | "AccentLight3": "#FF58ACED", 129 | "AccentDark1": "#FF0068C6", 130 | "AccentDark2": "#FF0058B4", 131 | "AccentDark3": "#FF0048A3" 132 | } 133 | }, 134 | { 135 | "Name": "Green", 136 | "ForBaseColor": "Dark", 137 | "IsHighContrast": true, 138 | "Values": { 139 | "Fluent.Ribbon.Colors.AccentBaseColor": "#FF60A917", 140 | "Fluent.Ribbon.Colors.AccentColor80": "#CC60A917", 141 | "Fluent.Ribbon.Colors.AccentColor60": "#9960A917", 142 | "Fluent.Ribbon.Colors.AccentColor40": "#6660A917", 143 | "Fluent.Ribbon.Colors.AccentColor20": "#3360A917", 144 | "Fluent.Ribbon.Colors.HighlightColor": "#FF477D11", 145 | "AccentLight1": "#FF1D89DE", 146 | "AccentLight2": "#FF3B9BE5", 147 | "AccentLight3": "#FF58ACED", 148 | "AccentDark1": "#FF0068C6", 149 | "AccentDark2": "#FF0058B4", 150 | "AccentDark3": "#FF0048A3" 151 | } 152 | }, 153 | { 154 | "Name": "Blue", 155 | "Values": { 156 | "Fluent.Ribbon.Colors.AccentBaseColor": "#FF2B579A", 157 | "Fluent.Ribbon.Colors.AccentColor80": "#CC2B579A", 158 | "Fluent.Ribbon.Colors.AccentColor60": "#992B579A", 159 | "Fluent.Ribbon.Colors.AccentColor40": "#662B579A", 160 | "Fluent.Ribbon.Colors.AccentColor20": "#332B579A", 161 | "Fluent.Ribbon.Colors.HighlightColor": "#FF086F9E", 162 | "AccentLight1": "#FF1D89DE", 163 | "AccentLight2": "#FF3B9BE5", 164 | "AccentLight3": "#FF58ACED", 165 | "AccentDark1": "#FF0068C6", 166 | "AccentDark2": "#FF0058B4", 167 | "AccentDark3": "#FF0048A3" 168 | } 169 | }, 170 | { 171 | "Name": "Yellow", 172 | "Values": { 173 | "Fluent.Ribbon.Colors.AccentBaseColor": "#FFFEDE06", 174 | "Fluent.Ribbon.Colors.AccentColor80": "#CCFEDE06", 175 | "Fluent.Ribbon.Colors.AccentColor60": "#99FEDE06", 176 | "Fluent.Ribbon.Colors.AccentColor40": "#66FEDE06", 177 | "Fluent.Ribbon.Colors.AccentColor20": "#33FEDE06", 178 | "Fluent.Ribbon.Colors.HighlightColor": "#FFBBA404", 179 | "Fluent.Ribbon.Colors.IdealForegroundColor": "Black", 180 | "Fluent.Ribbon.Colors.DarkIdealForegroundDisabledColor": "Pink", 181 | "Fluent.Ribbon.Colors.ExtremeHighlightColor": "#FFFFD232", 182 | "Fluent.Ribbon.Colors.DarkExtremeHighlightColor": "#FFF29536", 183 | "AccentLight1": "#FF1D89DE", 184 | "AccentLight2": "#FF3B9BE5", 185 | "AccentLight3": "#FF58ACED", 186 | "AccentDark1": "#FF0068C6", 187 | "AccentDark2": "#FF0058B4", 188 | "AccentDark3": "#FF0048A3" 189 | } 190 | } 191 | ] 192 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLColorSchemeGenerator/ThemeGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.XAMLColorSchemeGenerator 2 | { 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using JetBrains.Annotations; 6 | 7 | // This class has to be kept in sync with https://github.com/ControlzEx/ControlzEx/blob/develop/src/ControlzEx/Theming/ThemeGenerator.cs 8 | // Please do not remove unused code/properties here as it makes syncing more difficult. 9 | [PublicAPI] 10 | public class ThemeGenerator 11 | { 12 | public static ThemeGenerator Current { get; set; } 13 | 14 | static ThemeGenerator() 15 | { 16 | Current = new ThemeGenerator(); 17 | } 18 | 19 | public virtual ThemeGeneratorParameters GetParametersFromString(string input) 20 | { 21 | return System.Text.Json.JsonSerializer.Deserialize(input) ?? new ThemeGeneratorParameters(); 22 | } 23 | 24 | // The order of the passed valueSources is important. 25 | // More specialized/concrete values must be passed first and more generic ones must follow. 26 | public virtual string GenerateColorSchemeFileContent(string templateContent, string themeName, string themeDisplayName, string baseColorScheme, string colorScheme, string alternativeColorScheme, bool isHighContrast, params Dictionary[] valueSources) 27 | { 28 | templateContent = templateContent.Replace("{{ThemeName}}", themeName); 29 | templateContent = templateContent.Replace("{{ThemeDisplayName}}", themeDisplayName); 30 | templateContent = templateContent.Replace("{{BaseColorScheme}}", baseColorScheme); 31 | templateContent = templateContent.Replace("{{ColorScheme}}", colorScheme); 32 | templateContent = templateContent.Replace("{{AlternativeColorScheme}}", alternativeColorScheme); 33 | templateContent = templateContent.Replace("{{IsHighContrast}}", isHighContrast.ToString()); 34 | 35 | bool contentChanged; 36 | 37 | // Loop till content does not change anymore. 38 | do 39 | { 40 | contentChanged = false; 41 | 42 | foreach (var valueSource in valueSources) 43 | { 44 | foreach (var value in valueSource) 45 | { 46 | var finalValue = value.Value; 47 | var newTemplateContent = templateContent.Replace($"{{{{{value.Key}}}}}", finalValue); 48 | 49 | if (templateContent != newTemplateContent) 50 | { 51 | contentChanged = true; 52 | } 53 | 54 | templateContent = newTemplateContent; 55 | } 56 | } 57 | } 58 | while (contentChanged is true); 59 | 60 | return templateContent; 61 | } 62 | 63 | [PublicAPI] 64 | public class ThemeGeneratorParameters 65 | { 66 | public Dictionary DefaultValues { get; set; } = new Dictionary(); 67 | 68 | public ThemeGeneratorBaseColorScheme[] BaseColorSchemes { get; set; } = new ThemeGeneratorBaseColorScheme[0]; 69 | 70 | public ThemeGeneratorColorScheme[] ColorSchemes { get; set; } = new ThemeGeneratorColorScheme[0]; 71 | 72 | public AdditionalColorSchemeVariant[] AdditionalColorSchemeVariants { get; set; } = new AdditionalColorSchemeVariant[0]; 73 | } 74 | 75 | [PublicAPI] 76 | [DebuggerDisplay("{" + nameof(Name) + "}")] 77 | public class ThemeGeneratorBaseColorScheme 78 | { 79 | public string Name { get; set; } = string.Empty; 80 | 81 | public Dictionary Values { get; set; } = new Dictionary(); 82 | } 83 | 84 | [PublicAPI] 85 | [DebuggerDisplay("{" + nameof(Name) + "}")] 86 | public class AdditionalColorSchemeVariant 87 | { 88 | public string Name { get; set; } = string.Empty; 89 | 90 | public Dictionary Values { get; set; } = new Dictionary(); 91 | } 92 | 93 | [PublicAPI] 94 | [DebuggerDisplay("{" + nameof(Name) + "}")] 95 | public class ThemeGeneratorColorScheme 96 | { 97 | public string Name { get; set; } = string.Empty; 98 | 99 | public string ForBaseColor { get; set; } = string.Empty; 100 | 101 | public string ForColorSchemeVariant { get; set; } = string.Empty; 102 | 103 | public bool IsHighContrast { get; set; } 104 | 105 | public Dictionary Values { get; set; } = new Dictionary(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLCombine/IXamlCombinerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.XAMLCombine; 2 | 3 | public interface IXamlCombinerOptions 4 | { 5 | bool ImportMergedResourceDictionaryReferences { get; } 6 | 7 | bool WriteFileHeader { get; } 8 | 9 | string FileHeader { get; } 10 | 11 | bool IncludeSourceFilesInFileHeader { get; } 12 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLCombine/ResourceElement.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.XAMLCombine 2 | { 3 | using System.Xml.Linq; 4 | 5 | /// 6 | /// Represents a XAML resource. 7 | /// 8 | public class ResourceElement 9 | { 10 | public ResourceElement(string key, XElement element, string[] usedKeys) 11 | { 12 | this.Key = key; 13 | this.Element = element; 14 | this.UsedKeys = usedKeys; 15 | } 16 | 17 | /// 18 | /// Resource key. 19 | /// 20 | public string Key { get; } 21 | 22 | /// 23 | /// Resource XML node. 24 | /// 25 | public XElement Element { get; } 26 | 27 | /// 28 | /// XAML keys used in this resource. 29 | /// 30 | public string[] UsedKeys { get; } 31 | 32 | public string? ElementDebugInfo { get; set; } 33 | 34 | public string GetElementDebugInfo() 35 | { 36 | if (string.IsNullOrEmpty(this.ElementDebugInfo) is false) 37 | { 38 | return this.ElementDebugInfo!; 39 | } 40 | 41 | return this.Element.ToString(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/XAMLTools.Core/XAMLTools.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | XAMLTools 5 | net472;netstandard2.0 6 | true 7 | Various tools for easing the development of XAML related applications. 8 | 9 | As i only use WPF myself everything is focused on WPF, but things should work for other XAML dialects (at least in theory). 10 | 11 | - XAMLCombine 12 | 13 | Combines multiple XAML files to one large file. 14 | This is useful when you want to provide one `Generic.xaml` instead of multiple small XAML files. 15 | Using one large XAML file not only makes it easier to consume, but can also drastically improving loading performance. 16 | 17 | - XAMLColorSchemeGenerator 18 | 19 | Generates color scheme XAML files while replacing certain parts of a template file. 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/MSBuildLogger.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.MSBuild; 2 | 3 | using Microsoft.Build.Framework; 4 | using ILogger = XAMLTools.ILogger; 5 | 6 | public class MSBuildLogger : ILogger 7 | { 8 | private readonly IBuildEngine buildEngine; 9 | private readonly string senderName; 10 | 11 | public MSBuildLogger(IBuildEngine buildEngine, string senderName) 12 | { 13 | this.buildEngine = buildEngine; 14 | this.senderName = senderName; 15 | } 16 | 17 | public void Debug(string message) 18 | { 19 | this.buildEngine.LogMessageEvent(new(message, string.Empty, this.senderName, MessageImportance.Low)); 20 | } 21 | 22 | public void Info(string message) 23 | { 24 | this.buildEngine.LogMessageEvent(new(message, string.Empty, this.senderName, MessageImportance.Normal)); 25 | } 26 | 27 | public void InfoImportant(string message) 28 | { 29 | this.buildEngine.LogMessageEvent(new(message, string.Empty, this.senderName, MessageImportance.High)); 30 | } 31 | 32 | public void Warn(string message) 33 | { 34 | this.buildEngine.LogWarningEvent(new(string.Empty, string.Empty, string.Empty, 0, 0, 0, 0, message, string.Empty, this.senderName)); 35 | } 36 | 37 | public void Error(string message) 38 | { 39 | this.buildEngine.LogErrorEvent(new(string.Empty, string.Empty, string.Empty, 0, 0, 0, 0, message, string.Empty, this.senderName)); 40 | } 41 | } -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/XAMLColorSchemeGeneratorTask.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.MSBuild 2 | { 3 | using System.Collections.Generic; 4 | using Microsoft.Build.Framework; 5 | using Microsoft.Build.Utilities; 6 | using XAMLTools.Helpers; 7 | using XAMLTools.XAMLColorSchemeGenerator; 8 | 9 | public class XAMLColorSchemeGeneratorTask : Task 10 | { 11 | public const string ParametersFileMetadataName = "ParametersFile"; 12 | 13 | public const string OutputPathMetadataName = "OutputPath"; 14 | 15 | [Required] 16 | public ITaskItem[] Items { get; set; } = null!; 17 | 18 | [Output] 19 | public ITaskItem[]? GeneratedFiles { get; set; } 20 | 21 | public override bool Execute() 22 | { 23 | var generatedFiles = new List(); 24 | 25 | foreach (var item in this.Items) 26 | { 27 | var templateFile = item.ItemSpec; 28 | var generatorParametersFile = item.GetMetadata(ParametersFileMetadataName); 29 | var outputPath = item.GetMetadata(OutputPathMetadataName); 30 | 31 | this.BuildEngine.LogMessageEvent(new BuildMessageEventArgs($"Generating XAML files from \"{templateFile}\" with \"{generatorParametersFile}\" to \"{outputPath}\".", string.Empty, nameof(XAMLColorSchemeGeneratorTask), MessageImportance.High)); 32 | 33 | var generator = new ColorSchemeGenerator 34 | { 35 | Logger = new MSBuildLogger(this.BuildEngine, nameof(XAMLColorSchemeGeneratorTask)) 36 | }; 37 | var currentGeneratedFiles = MutexHelper.ExecuteLocked(() => generator.GenerateColorSchemeFiles(generatorParametersFile, templateFile, outputPath), templateFile); 38 | 39 | foreach (var generatedFile in currentGeneratedFiles) 40 | { 41 | generatedFiles.Add(new TaskItem(generatedFile)); 42 | } 43 | } 44 | 45 | this.GeneratedFiles = generatedFiles.ToArray(); 46 | 47 | return true; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/XAMLCombineTask.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.MSBuild 2 | { 3 | using System; 4 | using Microsoft.Build.Framework; 5 | using Microsoft.Build.Utilities; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using XAMLTools.Helpers; 9 | using XAMLTools.XAMLCombine; 10 | 11 | public class XAMLCombineTask : Task 12 | { 13 | [Required] 14 | public ITaskItem[] Items { get; set; } = null!; 15 | 16 | [Output] 17 | public ITaskItem[]? GeneratedFiles { get; set; } 18 | 19 | public override bool Execute() 20 | { 21 | var generatedFiles = new List(); 22 | 23 | var grouped = this.Items.GroupBy(XAMLCombineTaskItemOptions.From); 24 | 25 | foreach (var group in grouped) 26 | { 27 | var options = group.Key; 28 | var targetFile = options.TargetFile; 29 | 30 | if (targetFile is null or { Length: 0 }) 31 | { 32 | continue; 33 | } 34 | 35 | var sourceFiles = group.Select(x => x.ItemSpec).ToList(); 36 | 37 | this.BuildEngine.LogMessageEvent(new BuildMessageEventArgs($"Generating combined XAML file \"{targetFile}\".", string.Empty, nameof(XAMLCombineTask), MessageImportance.High)); 38 | 39 | if (options.ImportMergedResourceDictionaryReferences) 40 | { 41 | this.BuildEngine.LogMessageEvent(new BuildMessageEventArgs($"Import for merged ResourceDictionary elements enabled for this generated content", string.Empty, nameof(XAMLCombine), MessageImportance.Low)); 42 | } 43 | 44 | var combiner = new XAMLCombiner 45 | { 46 | ImportMergedResourceDictionaryReferences = options.ImportMergedResourceDictionaryReferences, 47 | WriteFileHeader = options.WriteFileHeader, 48 | FileHeader = options.FileHeader, 49 | IncludeSourceFilesInFileHeader = options.IncludeSourceFilesInFileHeader, 50 | Logger = new MSBuildLogger(this.BuildEngine, nameof(XAMLCombineTask)) 51 | }; 52 | 53 | try 54 | { 55 | targetFile = MutexHelper.ExecuteLocked(() => combiner.Combine(sourceFiles, targetFile), targetFile); 56 | } 57 | catch (Exception exception) 58 | { 59 | this.BuildEngine.LogErrorEvent(new BuildErrorEventArgs("XAMLCombine", "XAMLCombine_Exception", string.Empty, 0, 0, 0, 0, exception.ToString(), string.Empty, nameof(XAMLCombine))); 60 | return false; 61 | } 62 | 63 | generatedFiles.Add(new TaskItem(targetFile)); 64 | } 65 | 66 | this.GeneratedFiles = generatedFiles.ToArray(); 67 | 68 | return true; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/XAMLCombineTaskItemOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.MSBuild; 2 | 3 | using System; 4 | using Microsoft.Build.Framework; 5 | using XAMLTools.XAMLCombine; 6 | 7 | public class XAMLCombineTaskItemOptions : IXamlCombinerOptions, IEquatable 8 | { 9 | public string TargetFile { get; init; } = string.Empty; 10 | 11 | public bool ImportMergedResourceDictionaryReferences { get; init; } = XAMLCombiner.ImportMergedResourceDictionaryReferencesDefault; 12 | 13 | public bool WriteFileHeader { get; init; } = XAMLCombiner.WriteFileHeaderDefault; 14 | 15 | public string FileHeader { get; init; } = XAMLCombiner.FileHeaderDefault; 16 | 17 | public bool IncludeSourceFilesInFileHeader { get; init; } = XAMLCombiner.IncludeSourceFilesInFileHeaderDefault; 18 | 19 | public static XAMLCombineTaskItemOptions From(ITaskItem taskItem) 20 | { 21 | var result = new XAMLCombineTaskItemOptions 22 | { 23 | TargetFile = taskItem.GetMetadata(nameof(TargetFile)), 24 | ImportMergedResourceDictionaryReferences = GetBool(taskItem, nameof(ImportMergedResourceDictionaryReferences), XAMLCombiner.ImportMergedResourceDictionaryReferencesDefault), 25 | WriteFileHeader = GetBool(taskItem, nameof(WriteFileHeader), XAMLCombiner.WriteFileHeaderDefault), 26 | FileHeader = GetString(taskItem, nameof(FileHeader), XAMLCombiner.FileHeaderDefault), 27 | IncludeSourceFilesInFileHeader = GetBool(taskItem, nameof(IncludeSourceFilesInFileHeader), XAMLCombiner.IncludeSourceFilesInFileHeaderDefault), 28 | }; 29 | 30 | return result; 31 | } 32 | 33 | public override bool Equals(object obj) 34 | { 35 | if (obj is not XAMLCombineTaskItemOptions other) 36 | { 37 | return false; 38 | } 39 | 40 | return this.Equals(other); 41 | } 42 | 43 | public bool Equals(XAMLCombineTaskItemOptions? other) 44 | { 45 | if (ReferenceEquals(null, other)) 46 | { 47 | return false; 48 | } 49 | 50 | if (ReferenceEquals(this, other)) 51 | { 52 | return true; 53 | } 54 | 55 | return this.TargetFile == other.TargetFile 56 | && this.ImportMergedResourceDictionaryReferences == other.ImportMergedResourceDictionaryReferences 57 | && this.WriteFileHeader == other.WriteFileHeader 58 | && this.FileHeader == other.FileHeader 59 | && this.IncludeSourceFilesInFileHeader == other.IncludeSourceFilesInFileHeader; 60 | } 61 | 62 | public override int GetHashCode() 63 | { 64 | unchecked 65 | { 66 | var hashCode = (this.TargetFile != null 67 | ? this.TargetFile.GetHashCode() 68 | : 0); 69 | hashCode = (hashCode * 397) ^ this.ImportMergedResourceDictionaryReferences.GetHashCode(); 70 | hashCode = (hashCode * 397) ^ this.WriteFileHeader.GetHashCode(); 71 | hashCode = (hashCode * 397) ^ this.FileHeader.GetHashCode(); 72 | hashCode = (hashCode * 397) ^ this.IncludeSourceFilesInFileHeader.GetHashCode(); 73 | return hashCode; 74 | } 75 | } 76 | 77 | public static bool operator ==(XAMLCombineTaskItemOptions? left, XAMLCombineTaskItemOptions? right) 78 | { 79 | return Equals(left, right); 80 | } 81 | 82 | public static bool operator !=(XAMLCombineTaskItemOptions? left, XAMLCombineTaskItemOptions? right) 83 | { 84 | return !Equals(left, right); 85 | } 86 | 87 | private static bool GetBool(ITaskItem taskItem, string metadataName, bool defaultValue) 88 | { 89 | var metaData = taskItem.GetMetadata(metadataName); 90 | 91 | return bool.TryParse(metaData, out var value) 92 | ? value 93 | : defaultValue; 94 | } 95 | 96 | private static string GetString(ITaskItem taskItem, string metadataName, string defaultValue) 97 | { 98 | var metaData = taskItem.GetMetadata(metadataName); 99 | 100 | if (string.IsNullOrEmpty(metaData)) 101 | { 102 | return defaultValue; 103 | } 104 | 105 | return metaData; 106 | } 107 | } -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/XAMLTools.MSBuild.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472;netstandard2.0 5 | true 6 | Generates color scheme xaml files while replacing certain parts of a template file. 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/build/XAMLTools.MSBuild.props: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | $(MSBuildThisFileDirectory) 6 | $(XAMLToolsPath)netstandard2.0 7 | $(XAMLToolsPath)net472 8 | $(XAMLToolsAssemblyDirectory)\$(MSBuildThisFileName).dll 9 | 10 | $(XAMLToolsPath)tools\netcoreapp3.1\ 11 | $(XAMLToolsPath)tools\net472\ 12 | $(XAMLToolsToolDirectory)\XAMLTools.exe 13 | 14 | 15 15 | $([System.Version]::Parse($(MSBuildVersion)).Major) 16 | 17 | False 18 | True 19 | 20 | true 21 | 22 | 23 | 24 | true 25 | 26 | false 27 | 28 | true 29 | 30 | 31 | 32 | true 33 | 34 | true 35 | 36 | true 37 | 38 | 39 | 40 | 42 | 43 | DispatchToInnerBuilds; 44 | 45 | 46 | 47 | DispatchToInnerBuilds; 48 | 49 | 50 | 51 | 52 | 54 | 55 | BeforeCompile; 56 | CoreCompile; 57 | 58 | 59 | 60 | BeforeCompile; 61 | CoreCompile; 62 | 63 | 64 | 65 | XAMLColorSchemeGenerator;XAMLCombine;$(MarkupCompilePass1DependsOn) 66 | 67 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/build/XAMLTools.MSBuild.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/buildMultiTargeting/XAMLTools.MSBuild.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/XAMLTools.MSBuild/buildMultiTargeting/XAMLTools.MSBuild.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/XAMLTools/App.net.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/XAMLTools/App.netcoreapp.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/XAMLTools/Commands/BaseOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Commands; 2 | 3 | using CommandLine; 4 | using JetBrains.Annotations; 5 | 6 | [PublicAPI] 7 | public class BaseOptions 8 | { 9 | [Option('v', HelpText = "Defines if logging should be verbose")] 10 | public bool Verbose { get; set; } 11 | } -------------------------------------------------------------------------------- /src/XAMLTools/Commands/DumpResourcesOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Commands; 2 | 3 | using System.Threading.Tasks; 4 | using CommandLine; 5 | using JetBrains.Annotations; 6 | using XAMLTools.ResourceDump; 7 | 8 | [PublicAPI] 9 | [Verb("dump-resources", HelpText = "Generate XAML color scheme files.")] 10 | public class DumpResourcesOptions : BaseOptions 11 | { 12 | [Option('a', Required = true, HelpText = "Assembly file")] 13 | public string AssemblyFile { get; set; } = null!; 14 | 15 | [Option('o', Required = true, HelpText = "Output path")] 16 | public string OutputPath { get; set; } = null!; 17 | 18 | public Task Execute() 19 | { 20 | var resourceDumper = new ResourceDumper(); 21 | 22 | resourceDumper.DumpResources(this.AssemblyFile, this.OutputPath); 23 | 24 | return Task.FromResult(0); 25 | } 26 | } -------------------------------------------------------------------------------- /src/XAMLTools/Commands/XAMLColorSchemeGeneratorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Commands; 2 | 3 | using System.Threading.Tasks; 4 | using CommandLine; 5 | using JetBrains.Annotations; 6 | using XamlTools; 7 | using XAMLTools.Helpers; 8 | using XAMLTools.XAMLColorSchemeGenerator; 9 | 10 | [PublicAPI] 11 | [Verb("colorscheme", HelpText = "Generate XAML color scheme files.")] 12 | public class XAMLColorSchemeGeneratorOptions : BaseOptions 13 | { 14 | [Option('p', Required = true, HelpText = "Parameters file")] 15 | public string ParametersFile { get; set; } = null!; 16 | 17 | [Option('t', Required = true, HelpText = "Template file")] 18 | public string TemplateFile { get; set; } = null!; 19 | 20 | [Option('o', Required = true, HelpText = "Output path")] 21 | public string OutputPath { get; set; } = null!; 22 | 23 | public Task Execute() 24 | { 25 | var generator = new ColorSchemeGenerator 26 | { 27 | Logger = new ConsoleLogger 28 | { 29 | Verbose = this.Verbose 30 | } 31 | }; 32 | 33 | MutexHelper.ExecuteLocked(() => generator.GenerateColorSchemeFiles(this.ParametersFile, this.TemplateFile, this.OutputPath), this.TemplateFile); 34 | 35 | return Task.FromResult(0); 36 | } 37 | } -------------------------------------------------------------------------------- /src/XAMLTools/Commands/XAMLCombineOptions.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Commands; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using CommandLine; 6 | using JetBrains.Annotations; 7 | using XamlTools; 8 | using XAMLTools.Helpers; 9 | using XAMLTools.XAMLCombine; 10 | 11 | [PublicAPI] 12 | [Verb("combine", HelpText = "Combine multiple XAML files to one target file.")] 13 | public class XAMLCombineOptions : BaseOptions, IXamlCombinerOptions 14 | { 15 | [Option('s', Required = true, HelpText = "Source file containing a new line separated list of files to combine")] 16 | public string SourceFile { get; set; } = null!; 17 | 18 | [Option('t', Required = true, HelpText = "Target file")] 19 | public string TargetFile { get; set; } = null!; 20 | 21 | [Option("md", Required = false, HelpText = "Import merged dictionary references from combined files to generated")] 22 | public bool ImportMergedResourceDictionaryReferences { get; set; } = false; 23 | 24 | [Option("WriteFileHeader", Required = false, HelpText = "Write file header or not")] 25 | public bool WriteFileHeader { get; set; } = XAMLCombiner.WriteFileHeaderDefault; 26 | 27 | [Option("FileHeader", Required = false, HelpText = "Text written as the file header")] 28 | public string FileHeader { get; set; } = XAMLCombiner.FileHeaderDefault; 29 | 30 | [Option("IncludeSourceFilesInFileHeader", Required = false, HelpText = "Include source files in file header")] 31 | public bool IncludeSourceFilesInFileHeader { get; set; } = XAMLCombiner.IncludeSourceFilesInFileHeaderDefault; 32 | 33 | public Task Execute() 34 | { 35 | var combiner = new XAMLCombiner 36 | { 37 | ImportMergedResourceDictionaryReferences = this.ImportMergedResourceDictionaryReferences, 38 | WriteFileHeader = this.WriteFileHeader, 39 | FileHeader = this.FileHeader, 40 | IncludeSourceFilesInFileHeader = this.IncludeSourceFilesInFileHeader, 41 | Logger = new ConsoleLogger 42 | { 43 | Verbose = this.Verbose 44 | } 45 | }; 46 | 47 | try 48 | { 49 | MutexHelper.ExecuteLocked(() => combiner.Combine(this.SourceFile, this.TargetFile), this.TargetFile); 50 | } 51 | catch (Exception) 52 | { 53 | return Task.FromResult(1); 54 | } 55 | 56 | return Task.FromResult(0); 57 | } 58 | } -------------------------------------------------------------------------------- /src/XAMLTools/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | namespace XamlTools; 2 | 3 | using System; 4 | using XAMLTools; 5 | 6 | public class ConsoleLogger : ILogger 7 | { 8 | public bool Verbose { get; set; } 9 | 10 | public void Debug(string message) 11 | { 12 | if (this.Verbose == false) 13 | { 14 | return; 15 | } 16 | 17 | Console.WriteLine(message); 18 | } 19 | 20 | public void Info(string message) 21 | { 22 | if (this.Verbose == false) 23 | { 24 | return; 25 | } 26 | 27 | Console.WriteLine(message); 28 | } 29 | 30 | public void InfoImportant(string message) 31 | { 32 | Console.WriteLine(message); 33 | } 34 | 35 | public void Warn(string message) 36 | { 37 | var foreground = Console.ForegroundColor; 38 | Console.ForegroundColor = ConsoleColor.DarkYellow; 39 | Console.WriteLine(message); 40 | Console.ForegroundColor = foreground; 41 | } 42 | 43 | public void Error(string message) 44 | { 45 | var foreground = Console.ForegroundColor; 46 | Console.ForegroundColor = ConsoleColor.DarkRed; 47 | Console.Error.WriteLine(message); 48 | Console.ForegroundColor = foreground; 49 | } 50 | } -------------------------------------------------------------------------------- /src/XAMLTools/Program.cs: -------------------------------------------------------------------------------- 1 | namespace XamlTools; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Runtime; 8 | using System.Threading.Tasks; 9 | using CommandLine; 10 | using XAMLTools.Commands; 11 | 12 | public static class Program 13 | { 14 | private static async Task Main(string[] args) 15 | { 16 | const string PROFILE_FILE = "XAMLTools.profile"; 17 | 18 | ProfileOptimization.SetProfileRoot(Path.GetTempPath()); 19 | ProfileOptimization.StartProfile(PROFILE_FILE); 20 | 21 | var stopwatch = Stopwatch.StartNew(); 22 | 23 | try 24 | { 25 | var result = Parser.Default 26 | .ParseArguments(args) 27 | .MapResult( 28 | async (XAMLCombineOptions options) => await options.Execute(), 29 | async (XAMLColorSchemeGeneratorOptions options) => await options.Execute(), 30 | async (DumpResourcesOptions options) => await options.Execute(), 31 | ErrorHandler); 32 | 33 | return await result; 34 | } 35 | catch (Exception e) 36 | { 37 | Console.WriteLine(e); 38 | 39 | if (Debugger.IsAttached) 40 | { 41 | Console.ReadLine(); 42 | } 43 | 44 | return 1; 45 | } 46 | finally 47 | { 48 | Console.WriteLine($"Execution time: {stopwatch.Elapsed}"); 49 | } 50 | } 51 | 52 | private static Task ErrorHandler(IEnumerable errors) 53 | { 54 | foreach (var error in errors) 55 | { 56 | Console.Error.WriteLine(error.ToString()); 57 | } 58 | 59 | return Task.FromResult(1); 60 | } 61 | } -------------------------------------------------------------------------------- /src/XAMLTools/XAMLTools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472;netcoreapp3.1;net6.0 5 | true 6 | Generates color scheme xaml files while replacing certain parts of a template file. 7 | 8 | 9 | 10 | Exe 11 | app.manifest 12 | 13 | 14 | 15 | 16 | $(AssemblyName).exe.config 17 | 18 | 19 | 20 | $(AssemblyName).exe.config 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/XAMLTools/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 6 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/AssemblySetup.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests; 2 | 3 | using System.Diagnostics; 4 | using DiffEngine; 5 | 6 | [SetUpFixture] 7 | public class AssemblySetup 8 | { 9 | [OneTimeSetUp] 10 | public static void SetUp() 11 | { 12 | DiffRunner.Disabled = Debugger.IsAttached == false; 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/MSBuild/XAMLCombineTaskItemOptionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests.MSBuild; 2 | 3 | using Microsoft.Build.Utilities; 4 | using XAMLTools.MSBuild; 5 | using XAMLTools.XAMLCombine; 6 | 7 | [TestFixture] 8 | public class XAMLCombineTaskItemOptionsTests 9 | { 10 | [Test] 11 | public void TestFromEmptyTaskItem() 12 | { 13 | var options = XAMLCombineTaskItemOptions.From(new TaskItem()); 14 | 15 | Assert.That(options.TargetFile, Is.Empty); 16 | Assert.That(options.ImportMergedResourceDictionaryReferences, Is.EqualTo(XAMLCombiner.ImportMergedResourceDictionaryReferencesDefault)); 17 | Assert.That(options.WriteFileHeader, Is.EqualTo(XAMLCombiner.WriteFileHeaderDefault)); 18 | Assert.That(options.FileHeader, Is.EqualTo(XAMLCombiner.FileHeaderDefault)); 19 | Assert.That(options.IncludeSourceFilesInFileHeader, Is.EqualTo(XAMLCombiner.IncludeSourceFilesInFileHeaderDefault)); 20 | } 21 | 22 | [Test] 23 | public void TestEquality() 24 | { 25 | var options = XAMLCombineTaskItemOptions.From(new TaskItem()); 26 | 27 | Assert.That(options, Is.EqualTo(new XAMLCombineTaskItemOptions())); 28 | } 29 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/MSBuildCompileTests.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests; 2 | 3 | using System.IO; 4 | using CliWrap; 5 | using CliWrap.Buffered; 6 | using NUnit.Framework; 7 | 8 | [TestFixture] 9 | public class MSBuildCompileTests 10 | { 11 | [Test] 12 | [TestCase("Debug")] 13 | [TestCase("Release")] 14 | public async Task CheckCompileOutputAfterGitClean(string configuration) 15 | { 16 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 17 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 18 | 19 | Assert.That(wpfAppDirectory, Does.Exist); 20 | 21 | #if NET472 22 | const string assemblyName = "XAMLTools.WPFApp.exe"; 23 | const string framework = "net472"; 24 | #else 25 | const string assemblyName = "XAMLTools.WPFApp.dll"; 26 | const string framework = "net6.0-windows"; 27 | #endif 28 | 29 | var binPath = Path.Combine(wpfAppDirectory, "bin", configuration, framework); 30 | 31 | { 32 | var result = await Cli.Wrap("git") 33 | .WithArguments($"clean -fxd") 34 | .WithWorkingDirectory(Path.Combine(wpfAppDirectory, "Themes")) 35 | .WithValidation(CommandResultValidation.None) 36 | .ExecuteBufferedAsync(); 37 | 38 | Assert.That(result.ExitCode, Is.EqualTo(0), result.StandardError); 39 | } 40 | 41 | { 42 | var result = await Cli.Wrap("dotnet") 43 | .WithArguments($"build -c {configuration} /p:XAMLColorSchemeGeneratorEnabled=true /p:XAMLCombineEnabled=true /nr:false --no-dependencies -v:diag") 44 | .WithWorkingDirectory(wpfAppDirectory) 45 | .WithValidation(CommandResultValidation.None) 46 | .ExecuteBufferedAsync(); 47 | 48 | Assert.That(result.ExitCode, Is.EqualTo(0), result.StandardOutput); 49 | } 50 | 51 | var assemblyFile = Path.Combine(binPath, assemblyName); 52 | var outputPath = Path.GetDirectoryName(assemblyFile)!; 53 | 54 | { 55 | var xamlToolsExe = Path.Combine(currentAssemblyDir, "XAMLTools.exe"); 56 | Assert.That(xamlToolsExe, Does.Exist); 57 | 58 | var result = await Cli.Wrap(xamlToolsExe) 59 | .WithArguments($"dump-resources -a \"{assemblyFile}\" -o \"{outputPath}\"") 60 | .WithWorkingDirectory(currentAssemblyDir) 61 | .WithValidation(CommandResultValidation.None) 62 | .ExecuteBufferedAsync(); 63 | 64 | Assert.That(result.ExitCode, Is.EqualTo(0), result.StandardError); 65 | } 66 | 67 | { 68 | var resourceNames = File.ReadAllLines(Path.Combine(outputPath, "ResourceNames")); 69 | 70 | Assert.That(resourceNames, Is.EquivalentTo(new[] 71 | { 72 | "XAMLTools.WPFApp.g.resources", 73 | "XAMLTools.WPFApp.Themes.ColorScheme.Template.xaml", 74 | "XAMLTools.WPFApp.Themes.GeneratorParameters.json" 75 | })); 76 | 77 | var xamlResourceNames = File.ReadAllLines(Path.Combine(outputPath, "XAMLResourceNames")); 78 | 79 | if (configuration == "Debug") 80 | { 81 | Assert.That(xamlResourceNames, Is.EquivalentTo( 82 | new[] 83 | { 84 | "themes/controls/datatemplates.baml", 85 | "themes/colorschemes/light.yellow.colorful.baml", 86 | "themes/colorschemes/dark.yellow.colorful.baml", 87 | "themes/colorschemes/light.yellow.baml", 88 | "themes/colorschemes/dark.blue.colorful.baml", 89 | "themes/colorschemes/dark.green.colorful.highcontrast.baml", 90 | "themes/colorschemes/dark.yellow.baml", 91 | "themes/generic.baml", 92 | "themes/colorschemes/light.blue.baml", 93 | "themes/colorschemes/light.blue.colorful.baml", 94 | "mainwindow.baml", 95 | "themes/colorschemes/dark.green.highcontrast.baml", 96 | "themes/colorschemes/dark.blue.baml", 97 | "themes/colorschemes/light.green.highcontrast.baml", 98 | "themes/controls/control3.baml", 99 | "themes/controls/control2.baml", 100 | "themes/controls/control1.baml", 101 | "themes/controls/zconverters.baml", 102 | })); 103 | } 104 | else 105 | { 106 | Assert.That(xamlResourceNames, Is.EquivalentTo( 107 | new[] 108 | { 109 | "themes/colorschemes/light.yellow.colorful.baml", 110 | "themes/colorschemes/dark.yellow.colorful.baml", 111 | "themes/colorschemes/light.yellow.baml", 112 | "themes/colorschemes/dark.blue.colorful.baml", 113 | "themes/colorschemes/dark.green.colorful.highcontrast.baml", 114 | "themes/colorschemes/dark.yellow.baml", 115 | "themes/generic.baml", 116 | "themes/colorschemes/light.blue.baml", 117 | "themes/colorschemes/light.blue.colorful.baml", 118 | "mainwindow.baml", 119 | "themes/colorschemes/dark.green.highcontrast.baml", 120 | "themes/colorschemes/dark.blue.baml", 121 | "themes/colorschemes/light.green.highcontrast.baml" 122 | })); 123 | } 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/MutexHelperTests.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests 2 | { 3 | using NUnit.Framework; 4 | using System; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | using XAMLTools.Helpers; 8 | 9 | [TestFixture] 10 | public class MutexHelperTests 11 | { 12 | [Test] 13 | public void MutexHelperTestTimeout() 14 | { 15 | var t1 = Task.Factory.StartNew(() => ThreadStartFunction()); 16 | var t2 = Task.Factory.StartNew(() => ThreadStartFunction()); 17 | var t3 = Task.Factory.StartNew(() => ThreadStartFunction()); 18 | var t4 = Task.Factory.StartNew(() => ThreadStartFunction()); 19 | var t5 = Task.Factory.StartNew(() => ThreadStartFunction()); 20 | 21 | try 22 | { 23 | Task.WaitAll(t1, t2, t3, t4, t5); 24 | } 25 | catch (AggregateException ex) 26 | { 27 | // Expect TimeoutException, Mutex shoudn't throw any ApplicationException 28 | var innerException = ex.InnerException; 29 | Assert.That(innerException is TimeoutException, Is.True, $"InnerException was {innerException?.GetType().Name}"); 30 | } 31 | } 32 | 33 | private void ThreadStartFunction() 34 | { 35 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 36 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 37 | var themeFilesDirectory = Path.GetFullPath(Path.Combine(wpfAppDirectory, "Themes/Controls")); 38 | var themeFileName = Directory.GetFiles(themeFilesDirectory, "*.xaml", SearchOption.AllDirectories).FirstOrDefault(); 39 | if (themeFileName is null) 40 | { 41 | throw new NullReferenceException(); 42 | } 43 | 44 | var mutexName = "Local\\XamlTools_" + Path.GetFileName(themeFileName); 45 | 46 | MutexHelper.ExecuteLocked(() => { Thread.Sleep(3000); }, themeFileName, timeout: TimeSpan.FromSeconds(2)); 47 | 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/TestHelpers/TestLogger.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests.TestHelpers; 2 | 3 | public class TestLogger : ILogger 4 | { 5 | public List Warnings { get; } = new(); 6 | 7 | public List Errors { get; } = new(); 8 | 9 | public void Debug(string message) 10 | { 11 | } 12 | 13 | public void Info(string message) 14 | { 15 | } 16 | 17 | public void InfoImportant(string message) 18 | { 19 | } 20 | 21 | public void Warn(string message) 22 | { 23 | this.Warnings.Add(message); 24 | } 25 | 26 | public void Error(string message) 27 | { 28 | this.Errors.Add(message); 29 | } 30 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/XAMLCombinerTests.TestOutput.verified.xaml: -------------------------------------------------------------------------------- 1 |  5 | 13 | 14 | #FF2B579A 15 | stringValue 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 53 | 56 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/XAMLCombinerTests.TestOutputWinUI.verified.xaml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | #FF5fb2f2 19 | 1 20 | 1 21 | 22 | 23 | 24 | 25 | 26 | 27 | #FF0063b1 28 | 1 29 | 1 30 | 31 | 32 | 33 | 34 | 35 | 36 | #FF5fb2f2 37 | 2 38 | 1 39 | 40 | 41 | 48 | 49 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/XAMLCombinerTests.cs: -------------------------------------------------------------------------------- 1 | namespace XAMLTools.Tests 2 | { 3 | using NUnit.Framework; 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using XAMLTools.Tests.TestHelpers; 9 | using XAMLTools.XAMLCombine; 10 | 11 | [TestFixture] 12 | internal class XAMLCombinerTests 13 | { 14 | private string targetFile = null!; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | this.targetFile = Path.Combine(Path.GetTempPath(), "XAMLCombinerTests_Generic.xaml"); 20 | 21 | File.Delete(this.targetFile); 22 | } 23 | 24 | [TearDown] 25 | public void TearDown() 26 | { 27 | } 28 | 29 | [Test] 30 | public async Task TestOutput() 31 | { 32 | var timeout = Debugger.IsAttached ? 500000 : 5000; 33 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 34 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 35 | var themeFilesDirectory = Path.GetFullPath(Path.Combine(wpfAppDirectory, "Themes/Controls")); 36 | var themeFilePaths = Directory.GetFiles(themeFilesDirectory, "*.xaml", SearchOption.AllDirectories).Reverse().ToArray(); 37 | 38 | var xamlCombiner = new XAMLCombiner(); 39 | 40 | using (var cts = new CancellationTokenSource()) 41 | { 42 | var combineTask = Task.Run(() => xamlCombiner.Combine(themeFilePaths, this.targetFile), cts.Token); 43 | var delayTask = Task.Delay(timeout, cts.Token); 44 | 45 | var timeoutTask = Task.WhenAny(combineTask, delayTask).ContinueWith(t => 46 | { 47 | if (!combineTask.IsCompleted) 48 | { 49 | cts.Cancel(); 50 | throw new TimeoutException("Timeout waiting for method after " + timeout); 51 | } 52 | 53 | Assert.That(combineTask.Exception, Is.Null, combineTask.Exception?.ToString()); 54 | }, cts.Token); 55 | 56 | await timeoutTask; 57 | } 58 | 59 | await Verifier.VerifyFile(this.targetFile); 60 | } 61 | 62 | [Test] 63 | public async Task TestOutputWinUI() 64 | { 65 | var timeout = Debugger.IsAttached ? 500000 : 5000; 66 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 67 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 68 | var themeFilesDirectory = Path.GetFullPath(Path.Combine(wpfAppDirectory, "Themes/WinUI")); 69 | var themeFilePaths = Directory.GetFiles(themeFilesDirectory, "*.xaml", SearchOption.AllDirectories).Reverse().ToArray(); 70 | 71 | var xamlCombiner = new XAMLCombiner(); 72 | 73 | using (var cts = new CancellationTokenSource()) 74 | { 75 | var combineTask = Task.Run(() => xamlCombiner.Combine(themeFilePaths, this.targetFile), cts.Token); 76 | var delayTask = Task.Delay(timeout, cts.Token); 77 | 78 | var timeoutTask = Task.WhenAny(combineTask, delayTask).ContinueWith(t => 79 | { 80 | if (!combineTask.IsCompleted) 81 | { 82 | cts.Cancel(); 83 | throw new TimeoutException("Timeout waiting for method after " + timeout); 84 | } 85 | 86 | Assert.That(combineTask.Exception, Is.Null, combineTask.Exception?.ToString()); 87 | }, cts.Token); 88 | 89 | await timeoutTask; 90 | } 91 | 92 | await Verifier.VerifyFile(this.targetFile); 93 | } 94 | 95 | [Test] 96 | public void TestDuplicateNamespaces() 97 | { 98 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 99 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 100 | var themeFilesDirectory = Path.GetFullPath(Path.Combine(wpfAppDirectory, "Themes/DuplicateNamespaces")); 101 | var themeFilePaths = Directory.GetFiles(themeFilesDirectory, "*.xaml", SearchOption.AllDirectories).Reverse().ToArray(); 102 | 103 | var xamlCombiner = new XAMLCombiner(); 104 | 105 | Assert.That(() => xamlCombiner.Combine(themeFilePaths, this.targetFile), 106 | Throws.Exception 107 | .With.Message 108 | .Contains("Namespace name \"controls\" with different values was seen in ")); 109 | } 110 | 111 | [Test] 112 | public void TestDuplicateKeys() 113 | { 114 | var currentAssemblyDir = Path.GetDirectoryName(this.GetType().Assembly.Location)!; 115 | var wpfAppDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDir, "../../../../src/tests/XAMLTools.WPFApp")); 116 | var themeFilesDirectory = Path.GetFullPath(Path.Combine(wpfAppDirectory, "Themes/DuplicateKeys")); 117 | var themeFilePaths = Directory.GetFiles(themeFilesDirectory, "*.xaml", SearchOption.AllDirectories).Reverse().ToArray(); 118 | 119 | var testLogger = new TestLogger(); 120 | 121 | var xamlCombiner = new XAMLCombiner 122 | { 123 | Logger = testLogger 124 | }; 125 | xamlCombiner.Combine(themeFilePaths, this.targetFile); 126 | 127 | Assert.That(testLogger.Errors, Is.Empty); 128 | Assert.That(testLogger.Warnings, Has.Count.EqualTo(1)); 129 | Assert.That(testLogger.Warnings[0], Does.StartWith("Key \"DuplicateDifferentContent\" was found in multiple imported files, with differing content, and was skipped.\r\nExisting: At: 9:6")); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.Tests/XAMLTools.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472;net6.0-windows 4 | enable 5 | enable 6 | True 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace XAMLTools.WPFApp 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Controls/MyControl1.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | namespace XAMLTools.WPFApp.Controls.MyControl1Namespace; 3 | 4 | using System.Windows.Controls; 5 | 6 | public class MyControl1 : Control 7 | { 8 | } 9 | 10 | public class Parameters 11 | { 12 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Controls/MyControl2.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | namespace XAMLTools.WPFApp.Controls.MyControl2Namespace; 3 | 4 | using System.Windows.Controls; 5 | 6 | public class MyControl2 : Control 7 | { 8 | } 9 | 10 | public class OtherParameters 11 | { 12 | } -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Directory.build.props: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Directory.build.targets: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace XAMLTools.WPFApp 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Themes/ColorScheme.Template.xaml: -------------------------------------------------------------------------------- 1 |  8 | {{ThemeName}} 9 | Fluent.Ribbon 10 | {{ThemeDisplayName}} 11 | {{BaseColorScheme}} 12 | {{ColorScheme}} 13 | {{AlternativeColorScheme}} 14 | {{Fluent.Ribbon.Colors.AccentBaseColor}} 15 | 16 | {{IsHighContrast}} 17 | 18 | 19 | 20 | 21 | {{Fluent.Ribbon.Colors.AccentBaseColor}} 22 | 23 | {{Fluent.Ribbon.Colors.AccentColor80}} 24 | 25 | {{Fluent.Ribbon.Colors.AccentColor60}} 26 | 27 | {{Fluent.Ribbon.Colors.AccentColor40}} 28 | 29 | {{Fluent.Ribbon.Colors.AccentColor20}} 30 | 31 | {{Fluent.Ribbon.Colors.HighlightColor}} 32 | 33 | {{Fluent.Ribbon.Colors.AccentLight1}} 34 | {{Fluent.Ribbon.Colors.AccentLight2}} 35 | {{Fluent.Ribbon.Colors.AccentLight3}} 36 | {{Fluent.Ribbon.Colors.AccentDark1}} 37 | {{Fluent.Ribbon.Colors.AccentDark2}} 38 | {{Fluent.Ribbon.Colors.AccentDark3}} 39 | 40 | 41 | {{BlackColor}} 42 | {{WhiteColor}} 43 | {{Gray1}} 44 | {{Gray2}} 45 | {{Gray3}} 46 | {{Gray4}} 47 | {{Gray5}} 48 | {{Gray6}} 49 | {{Gray7}} 50 | {{Gray8}} 51 | {{Gray9}} 52 | {{Gray10}} 53 | 54 | #00FFFFFF 55 | #17FFFFFF 56 | 57 | 58 | {{Fluent.Ribbon.Colors.IdealForegroundColor}} 59 | {{Fluent.Ribbon.Colors.DarkIdealForegroundDisabledColor}} 60 | 61 | 62 | {{Fluent.Ribbon.Colors.ExtremeHighlightColor}} 63 | {{Fluent.Ribbon.Colors.DarkExtremeHighlightColor}} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Themes/Controls/Control1.xaml: -------------------------------------------------------------------------------- 1 |  5 | 18 | 19 | #FF2B579A 20 | 21 | stringValue 22 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Themes/Controls/Control2.xaml: -------------------------------------------------------------------------------- 1 |  5 | 7 | 8 | 10 | 11 | #FF2B579A 12 | 13 | stringValue 14 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Themes/DuplicateKeys/Control2.xaml: -------------------------------------------------------------------------------- 1 |  4 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/Themes/DuplicateNamespaces/Control1.xaml: -------------------------------------------------------------------------------- 1 |  5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/XAMLTools.WPFApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net472;net6.0-windows 5 | 10 6 | enable 7 | true 8 | 9 | False 10 | False 11 | False 12 | ..\..\..\bin\$(Configuration)\XAMLTools.MSBuild\ 13 | 14 | 15 | 16 | 17 | 18 | $(DefaultItemExcludes);Themes/Controls/*.xaml 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Themes/Generic.xaml 32 | False 33 | 34 | 35 | 36 | 37 | 38 | Themes/GeneratorParameters.json 39 | Themes/ColorSchemes 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/tests/XAMLTools.WPFApp/build.cmd: -------------------------------------------------------------------------------- 1 | dotnet build -nodereuse:false -t:rebuild -c debug -p:XAMLCombineEnabled=true -p:XAMLColorSchemeGeneratorEnabled=true --------------------------------------------------------------------------------