├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── ShowMeTheXaml.Avalonia.AvaloniaEdit ├── CustomizeEditorBehavior.cs ├── ShowMeTheXaml.Avalonia.AvaloniaEdit.csproj ├── XamlDisplayAvaloniaEdit.cs ├── XamlDisplayAvaloniaEditPopupBehavior.cs ├── XamlDisplayAvaloniaEditTextBindingBehavior.cs ├── XamlDisplayAvaloniaEditThemeBehavior.cs ├── XamlDisplayStyles.axaml └── XamlDisplayStyles.cs ├── ShowMeTheXaml.Avalonia.Demo ├── .gitignore ├── App.xaml ├── App.xaml.cs ├── Assets │ └── avalonia-logo.ico ├── Models │ └── CatalogTheme.cs ├── Program.cs ├── ShowMeTheXaml.Avalonia.Demo.csproj ├── ViewLocator.cs ├── ViewModels │ ├── MainWindowViewModel.cs │ └── ViewModelBase.cs ├── Views │ ├── MainWindow.xaml │ └── MainWindow.xaml.cs └── nuget.config ├── ShowMeTheXaml.Avalonia.Generator ├── Infrastructure │ ├── Data │ │ ├── XamlDisplayContainer.cs │ │ ├── XamlDisplayInfo.cs │ │ └── XamlDisplayPosition.cs │ ├── IInfoResolver.cs │ ├── InfoReceiver.cs │ ├── InfoResolver.cs │ ├── MiniCompiler.cs │ ├── RoslynTypeSystem.cs │ └── XamlParsers │ │ ├── XamlAstObjectTextNode.cs │ │ └── XdXDocumentXamlParser.cs ├── ShowMeTheXaml.Avalonia.Generator.csproj ├── ShowMeTheXaml.Avalonia.Generator.props ├── ShowMeTheXamlCodeTemplatesGenerator.cs └── ShowMeTheXamlGenerator.cs ├── ShowMeTheXaml.Avalonia.sln └── ShowMeTheXaml.Avalonia ├── AlignmentYConverter.cs ├── AvaloniaRuntimeXamlLoaderHelper.cs ├── ShowMeTheXaml.Avalonia.csproj ├── XamlDisplay.xaml ├── XamlDisplay.xaml.cs ├── XamlDisplayInstanceData.cs └── XamlDisplayPopupBehavior.cs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | env: 4 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 5 | DOTNET_CLI_TELEMETRY_OPTOUT: true 6 | BUILD_VERSION: 1.5.1 7 | PACKAGE_RELEASE_NOTES: | 8 | Downgrade source generator TFM to netstandard2.0 to bypass the VS bullshit 9 | # NOTE: Instead of , use %2c 10 | 11 | on: 12 | push: 13 | pull_request: 14 | branches: 15 | - '**:**' 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v1 22 | with: 23 | submodules: 'recursive' 24 | - name: Setup .NET Core 25 | uses: actions/setup-dotnet@v4 26 | with: 27 | dotnet-version: 8 28 | - uses: actions/cache@v1 29 | with: 30 | path: ~/.nuget/packages 31 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-nuget- 34 | - name: Restore dependencies 35 | run: dotnet restore 36 | - name: Append ci info to version 37 | if: ${{ github.ref != 'refs/heads/master' && !contains(github.ref, 'rc/') }} 38 | run: | 39 | echo "BUILD_VERSION=$BUILD_VERSION-ci$GITHUB_RUN_NUMBER" >> $GITHUB_ENV 40 | echo "PACKAGE_RELEASE_NOTES=This is the package for CI build $GITHUB_RUN_ID" >> $GITHUB_ENV 41 | - name: Build with dotnet 42 | run: dotnet build --no-restore --configuration Release /property:Version=$BUILD_VERSION /property:PackageReleaseNotes="$PACKAGE_RELEASE_NOTES" 43 | - name: Upload artifacts 44 | uses: actions/upload-artifact@v3 45 | with: 46 | name: nupkg 47 | path: '**/*.nupkg' -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Artifacts publish 2 | 3 | env: 4 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 5 | DOTNET_CLI_TELEMETRY_OPTOUT: true 6 | 7 | # Artifacts from rc/** (release candidates) branches will be published on package feeds 8 | # This branches can contain release candidates, preview and other pre-release packages 9 | # While master branch will contain only stable releases 10 | 11 | # Also, I want to consider finding a proper name for such branches 12 | 13 | on: 14 | workflow_run: 15 | workflows: ["CI Build"] 16 | branches: 17 | - 'master' 18 | - 'rc/**' 19 | types: 20 | - completed 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Download artifact 27 | uses: dawidd6/action-download-artifact@v2 28 | with: 29 | workflow: build.yml 30 | workflow_conclusion: success 31 | branch: ${{ github.event.workflow_run.head_branch }} 32 | name: nupkg 33 | - name: Setup .NET 34 | uses: actions/setup-dotnet@v1 35 | with: 36 | dotnet-version: 6.0.x 37 | - name: Enable globstar 38 | run: | 39 | shopt -s globstar 40 | - name: Publish to Nuget 41 | run: | 42 | dotnet nuget push **/*.nupkg --api-key ${{secrets.NUGET_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate 43 | - name: Publish to GitHub Packages 44 | run: | 45 | dotnet nuget push **/*.nupkg --api-key ${{secrets.GITHUB_TOKEN}} --source https://nuget.pkg.github.com/AvaloniaUtils/index.json --skip-duplicate -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | 7 | # Created by https://www.toptal.com/developers/gitignore/api/csharp 8 | # Edit at https://www.toptal.com/developers/gitignore?templates=csharp 9 | 10 | ### Csharp ### 11 | ## Ignore Visual Studio temporary files, build results, and 12 | ## files generated by popular Visual Studio add-ons. 13 | ## 14 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 15 | 16 | # User-specific files 17 | *.rsuser 18 | *.suo 19 | *.user 20 | *.userosscache 21 | *.sln.docstates 22 | 23 | # User-specific files (MonoDevelop/Xamarin Studio) 24 | *.userprefs 25 | 26 | # Mono auto generated files 27 | mono_crash.* 28 | 29 | # Build results 30 | [Dd]ebug/ 31 | [Dd]ebugPublic/ 32 | [Rr]elease/ 33 | [Rr]eleases/ 34 | x64/ 35 | x86/ 36 | [Aa][Rr][Mm]/ 37 | [Aa][Rr][Mm]64/ 38 | bld/ 39 | [Bb]in/ 40 | [Oo]bj/ 41 | [Ll]og/ 42 | [Ll]ogs/ 43 | 44 | # Visual Studio 2015/2017 cache/options directory 45 | .vs/ 46 | # Uncomment if you have tasks that create the project's static files in wwwroot 47 | #wwwroot/ 48 | 49 | # Visual Studio 2017 auto generated files 50 | Generated\ Files/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | # NUnit 57 | *.VisualState.xml 58 | TestResult.xml 59 | nunit-*.xml 60 | 61 | # Build Results of an ATL Project 62 | [Dd]ebugPS/ 63 | [Rr]eleasePS/ 64 | dlldata.c 65 | 66 | # Benchmark Results 67 | BenchmarkDotNet.Artifacts/ 68 | 69 | # .NET Core 70 | project.lock.json 71 | project.fragment.lock.json 72 | artifacts/ 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | !*.Meta/ 84 | *.obj 85 | *.iobj 86 | *.pch 87 | *.pdb 88 | *.ipdb 89 | *.pgc 90 | *.pgd 91 | *.rsp 92 | *.sbr 93 | *.tlb 94 | *.tli 95 | *.tlh 96 | *.tmp 97 | *.tmp_proj 98 | *_wpftmp.csproj 99 | *.log 100 | *.vspscc 101 | *.vssscc 102 | .builds 103 | *.pidb 104 | *.svclog 105 | *.scc 106 | 107 | # Chutzpah Test files 108 | _Chutzpah* 109 | 110 | # Visual C++ cache files 111 | ipch/ 112 | *.aps 113 | *.ncb 114 | *.opendb 115 | *.opensdf 116 | *.sdf 117 | *.cachefile 118 | *.VC.db 119 | *.VC.VC.opendb 120 | 121 | # Visual Studio profiler 122 | *.psess 123 | *.vsp 124 | *.vspx 125 | *.sap 126 | 127 | # Visual Studio Trace Files 128 | *.e2e 129 | 130 | # TFS 2012 Local Workspace 131 | $tf/ 132 | 133 | # Guidance Automation Toolkit 134 | *.gpState 135 | 136 | # ReSharper is a .NET coding add-in 137 | _ReSharper*/ 138 | *.[Rr]e[Ss]harper 139 | *.DotSettings.user 140 | 141 | # TeamCity is a build add-in 142 | _TeamCity* 143 | 144 | # DotCover is a Code Coverage Tool 145 | *.dotCover 146 | 147 | # AxoCover is a Code Coverage Tool 148 | .axoCover/* 149 | !.axoCover/settings.json 150 | 151 | # Coverlet is a free, cross platform Code Coverage Tool 152 | coverage*[.json, .xml, .info] 153 | 154 | # Visual Studio code coverage results 155 | *.coverage 156 | *.coveragexml 157 | 158 | # NCrunch 159 | _NCrunch_* 160 | .*crunch*.local.xml 161 | nCrunchTemp_* 162 | 163 | # MightyMoose 164 | *.mm.* 165 | AutoTest.Net/ 166 | 167 | # Web workbench (sass) 168 | .sass-cache/ 169 | 170 | # Installshield output folder 171 | [Ee]xpress/ 172 | 173 | # DocProject is a documentation generator add-in 174 | DocProject/buildhelp/ 175 | DocProject/Help/*.HxT 176 | DocProject/Help/*.HxC 177 | DocProject/Help/*.hhc 178 | DocProject/Help/*.hhk 179 | DocProject/Help/*.hhp 180 | DocProject/Help/Html2 181 | DocProject/Help/html 182 | 183 | # Click-Once directory 184 | publish/ 185 | 186 | # Publish Web Output 187 | *.[Pp]ublish.xml 188 | *.azurePubxml 189 | # Note: Comment the next line if you want to checkin your web deploy settings, 190 | # but database connection strings (with potential passwords) will be unencrypted 191 | *.pubxml 192 | *.publishproj 193 | 194 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 195 | # checkin your Azure Web App publish settings, but sensitive information contained 196 | # in these scripts will be unencrypted 197 | PublishScripts/ 198 | 199 | # NuGet Packages 200 | *.nupkg 201 | # NuGet Symbol Packages 202 | *.snupkg 203 | # The packages folder can be ignored because of Package Restore 204 | **/[Pp]ackages/* 205 | # except build/, which is used as an MSBuild target. 206 | !**/[Pp]ackages/build/ 207 | # Uncomment if necessary however generally it will be regenerated when needed 208 | #!**/[Pp]ackages/repositories.config 209 | # NuGet v3's project.json files produces more ignorable files 210 | *.nuget.props 211 | *.nuget.targets 212 | 213 | # Microsoft Azure Build Output 214 | csx/ 215 | *.build.csdef 216 | 217 | # Microsoft Azure Emulator 218 | ecf/ 219 | rcf/ 220 | 221 | # Windows Store app package directories and files 222 | AppPackages/ 223 | BundleArtifacts/ 224 | Package.StoreAssociation.xml 225 | _pkginfo.txt 226 | *.appx 227 | *.appxbundle 228 | *.appxupload 229 | 230 | # Visual Studio cache files 231 | # files ending in .cache can be ignored 232 | *.[Cc]ache 233 | # but keep track of directories ending in .cache 234 | !?*.[Cc]ache/ 235 | 236 | # Others 237 | ClientBin/ 238 | ~$* 239 | *~ 240 | *.dbmdl 241 | *.dbproj.schemaview 242 | *.jfm 243 | *.pfx 244 | *.publishsettings 245 | orleans.codegen.cs 246 | 247 | # Including strong name files can present a security risk 248 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 249 | #*.snk 250 | 251 | # Since there are multiple workflows, uncomment next line to ignore bower_components 252 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 253 | #bower_components/ 254 | 255 | # RIA/Silverlight projects 256 | Generated_Code/ 257 | 258 | # Backup & report files from converting an old project file 259 | # to a newer Visual Studio version. Backup files are not needed, 260 | # because we have git ;-) 261 | _UpgradeReport_Files/ 262 | Backup*/ 263 | UpgradeLog*.XML 264 | UpgradeLog*.htm 265 | ServiceFabricBackup/ 266 | *.rptproj.bak 267 | 268 | # SQL Server files 269 | *.mdf 270 | *.ldf 271 | *.ndf 272 | 273 | # Business Intelligence projects 274 | *.rdl.data 275 | *.bim.layout 276 | *.bim_*.settings 277 | *.rptproj.rsuser 278 | *- [Bb]ackup.rdl 279 | *- [Bb]ackup ([0-9]).rdl 280 | *- [Bb]ackup ([0-9][0-9]).rdl 281 | 282 | # Microsoft Fakes 283 | FakesAssemblies/ 284 | 285 | # GhostDoc plugin setting file 286 | *.GhostDoc.xml 287 | 288 | # Node.js Tools for Visual Studio 289 | .ntvs_analysis.dat 290 | node_modules/ 291 | 292 | # Visual Studio 6 build log 293 | *.plg 294 | 295 | # Visual Studio 6 workspace options file 296 | *.opt 297 | 298 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 299 | *.vbw 300 | 301 | # Visual Studio LightSwitch build output 302 | **/*.HTMLClient/GeneratedArtifacts 303 | **/*.DesktopClient/GeneratedArtifacts 304 | **/*.DesktopClient/ModelManifest.xml 305 | **/*.Server/GeneratedArtifacts 306 | **/*.Server/ModelManifest.xml 307 | _Pvt_Extensions 308 | 309 | # Paket dependency manager 310 | .paket/paket.exe 311 | paket-files/ 312 | 313 | # FAKE - F# Make 314 | .fake/ 315 | 316 | # CodeRush personal settings 317 | .cr/personal 318 | 319 | # Python Tools for Visual Studio (PTVS) 320 | __pycache__/ 321 | *.pyc 322 | 323 | # Cake - Uncomment if you are using it 324 | # tools/** 325 | # !tools/packages.config 326 | 327 | # Tabs Studio 328 | *.tss 329 | 330 | # Telerik's JustMock configuration file 331 | *.jmconfig 332 | 333 | # BizTalk build output 334 | *.btp.cs 335 | *.btm.cs 336 | *.odx.cs 337 | *.xsd.cs 338 | 339 | # OpenCover UI analysis results 340 | OpenCover/ 341 | 342 | # Azure Stream Analytics local run output 343 | ASALocalRun/ 344 | 345 | # MSBuild Binary and Structured Log 346 | *.binlog 347 | 348 | # NVidia Nsight GPU debugger configuration file 349 | *.nvuser 350 | 351 | # MFractors (Xamarin productivity tool) working folder 352 | .mfractor/ 353 | 354 | # Local History for Visual Studio 355 | .localhistory/ 356 | 357 | # BeatPulse healthcheck temp database 358 | healthchecksdb 359 | 360 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 361 | MigrationBackup/ 362 | 363 | # Ionide (cross platform F# VS Code tools) working folder 364 | .ionide/ 365 | 366 | # End of https://www.toptal.com/developers/gitignore/api/csharp 367 | /.idea/* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/XamlX"] 2 | path = External/XamlX 3 | url = https://github.com/kekekeks/XamlX.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 SKProCH 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShowMeTheXaml.Avalonia 2 | 3 | An Avalonia component making it easy to show the corresponding XAML for custom styles and controls. It was built out of a 4 | need to shows the XAML for the theme library [Material.Avalonia](https://github.com/AvaloniaCommunity/Material.Avalonia). 5 | 6 | ## Getting started 7 | 8 | 1. Install `ShowMeTheXaml.Avalonia.Generator` [nuget package](https://www.nuget.org/packages/ShowMeTheXaml.Avalonia.Generator/): 9 | ```shell 10 | dotnet add package ShowMeTheXaml.Avalonia.Generator 11 | ``` 12 | This will also install the `ShowMeTheXaml.Avalonia` [nuget package](https://www.nuget.org/packages/ShowMeTheXaml.Avalonia/) as well. 13 | 14 | 2. Add XamlDisplay style to your app in `App.xaml`. See the example of `App.xaml`: 15 | ```xaml 16 | 17 | ... 18 | 19 | ... 20 | 21 | 22 | 23 | 24 | ... 25 | 26 | ``` 27 | 28 | 3. Initialize `DisplayContent` dictionary in `XamlDisplay` class: 29 | Add `UseXamlDisplay()` in `Program.cs` to `BuildAvaloniaApp` method. It should look like this: 30 | 31 | ```c# 32 | public static AppBuilder BuildAvaloniaApp() 33 | => AppBuilder.Configure() 34 | .UsePlatformDetect() 35 | .LogToTrace() 36 | // This line \/ 37 | .UseXamlDisplay() 38 | // This line /\ 39 | .UseReactiveUI(); 40 | ``` 41 | Or call `XamlDisplayInternalData.RegisterXamlDisplayData()` on your program startup. 42 | 43 | 4. Add `XamlDisplay` in your xaml. Set unique `UniqueId` property value. Example: 44 | ```xaml 45 | 46 | 47 | 48 | ``` 49 | 50 | --- 51 | 52 | # ShowMeTheXaml.Avalonia.AvaloniaEdit 53 | 54 | Style for displaying xaml content inside [AvaloniaEdit (AvalonEdit)](https://github.com/AvaloniaUI/AvaloniaEdit) 55 | 56 | ## Getting started 57 | Refer to usual [getting started](https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia#getting-started) **but:** 58 | 1. Instead `ShowMeTheXaml.Avalonia` use (install) `ShowMeTheXaml.Avalonia.AvaloniaEdit` [nuget package](https://www.nuget.org/packages/ShowMeTheXaml.Avalonia.AvaloniaEdit/) 59 | ```shell 60 | dotnet add package ShowMeTheXaml.Avalonia.AvaloniaEdit 61 | ``` 62 | 2. Use another style. Instead 63 | ```xaml 64 | 65 | ``` 66 | use 67 | ```xaml 68 | 69 | ``` 70 | 71 | Everything else remains the same. 72 | 73 | --- 74 | 75 | # Compiling sources 76 | 77 | 1. Clone this repo: 78 | ```shell 79 | git clone https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia.git 80 | ``` 81 | 82 | 2. Navigate to repo folder 83 | 3. Fetch all submodules: 84 | ```shell 85 | git submodule update --init --recursive 86 | ``` 87 | 88 | 4. Compile project: 89 | ```shell 90 | dotnet build 91 | ``` -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/CustomizeEditorBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Xaml.Interactivity; 4 | using AvaloniaEdit; 5 | 6 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit; 7 | 8 | /// 9 | /// SUITABLE ONLY FOR THIS PROJECT 10 | /// 11 | public class CustomizeEditorBehavior : Behavior { 12 | #region No width reduction 13 | 14 | private double _originalMinWidth; 15 | private IDisposable? _boundsChangedObservable; 16 | protected override void OnAttachedToVisualTree() { 17 | base.OnAttachedToVisualTree(); 18 | _originalMinWidth = AssociatedObject!.MinWidth; 19 | _boundsChangedObservable = AssociatedObject.GetObservable(Visual.BoundsProperty) 20 | .Subscribe(OnBoundsChanged); 21 | } 22 | private void OnBoundsChanged(Rect obj) { 23 | AssociatedObject!.MinWidth = Math.Min(Math.Max(obj.Width, AssociatedObject.MinWidth), AssociatedObject.MaxWidth); 24 | } 25 | 26 | protected override void OnDetachedFromVisualTree() { 27 | _boundsChangedObservable?.Dispose(); 28 | AssociatedObject!.MinWidth = _originalMinWidth; 29 | base.OnDetachedFromVisualTree(); 30 | } 31 | 32 | #endregion 33 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/ShowMeTheXaml.Avalonia.AvaloniaEdit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | latest 6 | enable 7 | true 8 | ShowMeTheXaml.Avalonia.AvaloniaEit 9 | SKProCH 10 | AvaloniaEdit (AvalonEdit) style for the ShowMeTheXAML.Avalonia XamlDisplay control. 11 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia 12 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia/blob/master/LICENSE 13 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia.git 14 | Git 15 | avalonia avaloniaui 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | XamlDisplayStyles.axaml 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayAvaloniaEdit.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using TextMateSharp.Grammars; 3 | 4 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit; 5 | 6 | public static class XamlDisplayAvaloniaEdit { 7 | public static readonly AttachedProperty CodeHighlightThemeNameProperty = 8 | AvaloniaProperty.RegisterAttached("CodeHighlightThemeName", typeof(XamlDisplayAvaloniaEdit)); 9 | 10 | public static ThemeName GetCodeHighlightThemeName(XamlDisplay element) { 11 | return element.GetValue(CodeHighlightThemeNameProperty); 12 | } 13 | 14 | public static void SetCodeHighlightThemeName(XamlDisplay element, ThemeName value) { 15 | element.SetValue(CodeHighlightThemeNameProperty, value); 16 | } 17 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayAvaloniaEditPopupBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using System.Threading; 6 | using System.Xml; 7 | using Avalonia; 8 | using Avalonia.Controls; 9 | using Avalonia.Interactivity; 10 | using Avalonia.Layout; 11 | using Avalonia.LogicalTree; 12 | using Avalonia.Media; 13 | using Avalonia.Media.Immutable; 14 | using Avalonia.Media.TextFormatting; 15 | using Avalonia.Threading; 16 | using Avalonia.VisualTree; 17 | using AvaloniaEdit.Rendering; 18 | 19 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit; 20 | 21 | public class XamlDisplayAvaloniaEditPopupBehavior : XamlDisplayAvaloniaEditTextBindingBehavior { 22 | public static readonly DirectProperty ApplyButtonProperty 23 | = AvaloniaProperty.RegisterDirect("ApplyButton", 24 | o => o.ApplyButton, 25 | (o, v) => o.ApplyButton = v); 26 | public static readonly DirectProperty ResetButtonProperty 27 | = AvaloniaProperty.RegisterDirect("ResetButton", 28 | o => o.ResetButton, 29 | (o, v) => o.ResetButton = v); 30 | public static readonly DirectProperty CommonErrorsTextBoxProperty 31 | = AvaloniaProperty.RegisterDirect("CommonErrorsTextBox", 32 | o => o.CommonErrorsTextBox, 33 | (o, v) => o.CommonErrorsTextBox = v); 34 | private Button _applyButton = null!; 35 | private TextBox _commonErrorsTextBox = null!; 36 | private Button _resetButton = null!; 37 | private Dictionary? _cachedNamespaceAliases; 38 | private ErrorsElementGenerator _errorsElementGenerator = new(); 39 | private IDisposable? _previewErrorsObservable; 40 | 41 | public Button ApplyButton { 42 | get => _applyButton; 43 | set => SetAndRaise(ApplyButtonProperty, ref _applyButton, value); 44 | } 45 | 46 | public Button ResetButton { 47 | get => _resetButton; 48 | set => SetAndRaise(ResetButtonProperty, ref _resetButton, value); 49 | } 50 | 51 | public TextBox CommonErrorsTextBox { 52 | get => _commonErrorsTextBox; 53 | set => SetAndRaise(CommonErrorsTextBoxProperty, ref _commonErrorsTextBox, value); 54 | } 55 | 56 | protected override void OnAttachedToVisualTree() { 57 | base.OnAttachedToVisualTree(); 58 | _previewErrorsObservable = Observable.FromEventPattern( 59 | action => MarkupTextEditor.TextChanged += action, 60 | action => MarkupTextEditor.TextChanged -= action) 61 | .Throttle(TimeSpan.FromMilliseconds(500)) 62 | .ObserveOn(SynchronizationContext.Current) 63 | .Select(pattern => MarkupTextEditor.Text) 64 | .Subscribe(s => LoadMarkupOrPrintErrors(s)); 65 | ResetButton.Click += ResetButtonOnClick; 66 | ApplyButton.Click += ApplyButtonOnClick; 67 | if (MarkupTextEditor.TextArea.TextView.ElementGenerators.Contains(_errorsElementGenerator)) return; 68 | // First time attached to tree 69 | MarkupTextEditor.TextArea.TextView.ElementGenerators.Add(_errorsElementGenerator); 70 | CommonErrorsTextBox.IsVisible = false; 71 | } 72 | 73 | protected override void OnDetachedFromVisualTree() { 74 | base.OnDetachedFromVisualTree(); 75 | _previewErrorsObservable?.Dispose(); 76 | ResetButton.Click -= ResetButtonOnClick; 77 | ApplyButton.Click -= ApplyButtonOnClick; 78 | } 79 | 80 | private void ApplyButtonOnClick(object sender, RoutedEventArgs e) { 81 | var markupText = MarkupTextEditor.Text; 82 | var result = LoadMarkupOrPrintErrors(markupText); 83 | if (result != null) { 84 | var xamlDisplay = LocateXamlDisplay(); 85 | xamlDisplay.Content = result; 86 | xamlDisplay.XamlText = markupText; 87 | } 88 | } 89 | 90 | private void ResetButtonOnClick(object sender, RoutedEventArgs e) { 91 | var xamlDisplay = LocateXamlDisplay(); 92 | xamlDisplay.Reset(); 93 | // Force reset text 94 | MarkupTextEditor.Text = xamlDisplay.XamlText; 95 | } 96 | 97 | private object? LoadMarkupOrPrintErrors(string xaml) { 98 | try { 99 | _cachedNamespaceAliases ??= LocateXamlDisplay().CurrentFileNamespaceAliases; 100 | var result = AvaloniaRuntimeXamlLoaderHelper.Parse(xaml, _cachedNamespaceAliases); 101 | CommonErrorsTextBox.IsVisible = false; 102 | if (_errorsElementGenerator.ExceptionText != null) { 103 | _errorsElementGenerator.ExceptionPosition = -1; 104 | _errorsElementGenerator.ExceptionText = null; 105 | MarkupTextEditor.TextArea.TextView.Redraw(); 106 | } 107 | return result; 108 | } 109 | catch (XmlException e) { 110 | CommonErrorsTextBox.IsVisible = false; 111 | var errorLine = MarkupTextEditor.Document.GetLineByNumber(e.LineNumber); 112 | _errorsElementGenerator.ExceptionPosition = errorLine.Offset + e.LinePosition - 1; 113 | _errorsElementGenerator.ExceptionText = e.Message; 114 | MarkupTextEditor.TextArea.TextView.Redraw(); 115 | } 116 | catch (Exception e) { 117 | CommonErrorsTextBox.IsVisible = true; 118 | CommonErrorsTextBox.Text = e.Message; 119 | } 120 | return null; 121 | } 122 | 123 | private XamlDisplay LocateXamlDisplay() => 124 | AssociatedObject!.FindLogicalAncestorOfType()!; 125 | 126 | private class ErrorsElementGenerator : VisualLineElementGenerator { 127 | public int ExceptionPosition { get; set; } = -1; 128 | public string? ExceptionText { get; set; } 129 | 130 | public override int GetFirstInterestedOffset(int startOffset) { 131 | return startOffset >= ExceptionPosition ? -1 : ExceptionPosition; 132 | } 133 | 134 | public override VisualLineElement ConstructElement(int offset) { 135 | return new ErrorInfoInlineElement(1, 0, ExceptionText!); 136 | } 137 | } 138 | 139 | private class ErrorInfoInlineElement : VisualLineElement { 140 | private readonly string _exceptionText; 141 | public ErrorInfoInlineElement(int visualLength, int documentLength, string exceptionText) : base(visualLength, documentLength) { 142 | _exceptionText = exceptionText; 143 | BackgroundBrush = Brushes.Transparent; 144 | } 145 | public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) { 146 | if (context == null) 147 | throw new ArgumentNullException(nameof(context)); 148 | 149 | return ErrorInfoObjectRun.CreateInstance(1, TextRunProperties, _exceptionText, context.TextView); 150 | } 151 | } 152 | 153 | private class ErrorInfoObjectRun : InlineObjectRun { 154 | private static readonly ImmutableSolidColorBrush BackgroundSolidColorBrush = new(Colors.Red); 155 | private static readonly PolylineGeometry PolylineGeometry = new() { Points = new Points { new(0, 5), new(5, 0), new(10, 5) } }; 156 | private double? _cachedLineHeight; 157 | private TextView _textView = null!; 158 | private ErrorInfoObjectRun(int length, TextRunProperties properties, Control errorInfoTextBlock) 159 | : base(length, properties, errorInfoTextBlock) { } 160 | public static ErrorInfoObjectRun CreateInstance(int length, TextRunProperties properties, string exceptionText, TextView contextTextView) { 161 | var defaultLineHeight = properties.FontRenderingEmSize * 1.35; 162 | var myRect = new ErrorInfoTextBlock(exceptionText, defaultLineHeight); 163 | var testInlineObjectRun = new ErrorInfoObjectRun(length, properties, myRect) { _textView = contextTextView }; 164 | return testInlineObjectRun; 165 | } 166 | 167 | public override void Draw(DrawingContext drawingContext, Point origin) { 168 | var lineSize = Math.Round(Properties!.FontRenderingEmSize * 1.35); 169 | PolylineGeometry.Transform = new TranslateTransform(origin.X - 5, lineSize - 5); 170 | drawingContext.DrawGeometry(BackgroundSolidColorBrush, null, PolylineGeometry); 171 | 172 | var defaultLineHeight = _cachedLineHeight ??= Math.Round(Properties.FontRenderingEmSize * 1.35); 173 | drawingContext.DrawRectangle(BackgroundSolidColorBrush, null, new Rect(0, defaultLineHeight, _textView.Bounds.Width, Element.DesiredSize.Height - defaultLineHeight)); 174 | } 175 | } 176 | 177 | private class ErrorInfoTextBlock : SelectableTextBlock { 178 | private readonly double _defaultLineHeight; 179 | private Rect _lastBounds; 180 | private TextView? _textView; 181 | public ErrorInfoTextBlock(string text, double defaultLineHeight) { 182 | _defaultLineHeight = defaultLineHeight; 183 | Text = text; 184 | ClipToBounds = false; 185 | HorizontalAlignment = HorizontalAlignment.Stretch; 186 | VerticalAlignment = VerticalAlignment.Top; 187 | TextWrapping = TextWrapping.Wrap; 188 | Margin = new Thickness(0, defaultLineHeight, 0, -defaultLineHeight); 189 | Background = new SolidColorBrush(Colors.Red); 190 | } 191 | protected override Size MeasureOverride(Size availableSize) { 192 | var width = GetTextView().Bounds.Width; 193 | var (_, height) = base.MeasureOverride(new Size(width, double.PositiveInfinity)); 194 | _lastBounds = new Rect(0, 0, width, height); 195 | return new Size(0, _defaultLineHeight + height); 196 | } 197 | 198 | protected override void ArrangeCore(Rect finalRect) { 199 | base.ArrangeCore(finalRect); 200 | Bounds = Bounds.WithX(0); 201 | } 202 | 203 | /// 204 | protected override void RenderTextLayout(DrawingContext context, Point origin) { 205 | if (Background != null) context.FillRectangle(Background, _lastBounds); 206 | base.RenderTextLayout(context, origin); 207 | } 208 | 209 | private TextView GetTextView() => _textView ??= (TextView)this.GetVisualAncestors().FirstOrDefault(visual => visual is TextView)!; 210 | } 211 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayAvaloniaEditTextBindingBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | using Avalonia.LogicalTree; 6 | using Avalonia.Xaml.Interactivity; 7 | using AvaloniaEdit; 8 | 9 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit; 10 | 11 | public class XamlDisplayAvaloniaEditTextBindingBehavior : Behavior { 12 | public static readonly DirectProperty MarkupTextEditorProperty 13 | = AvaloniaProperty.RegisterDirect("MarkupTextEditor", 14 | o => o.MarkupTextEditor, 15 | (o, v) => o.MarkupTextEditor = v); 16 | private TextEditor _markupTextEditor = null!; 17 | private bool _isTextAssigned; 18 | 19 | public TextEditor MarkupTextEditor { 20 | get => _markupTextEditor; 21 | set => SetAndRaise(MarkupTextEditorProperty, ref _markupTextEditor, value); 22 | } 23 | 24 | protected override void OnAttachedToVisualTree() { 25 | base.OnAttachedToVisualTree(); 26 | if (_isTextAssigned) return; 27 | MarkupTextEditor.Text = LocateXamlDisplay().XamlText; 28 | _isTextAssigned = true; 29 | } 30 | 31 | private XamlDisplay LocateXamlDisplay() => 32 | AssociatedObject!.FindLogicalAncestorOfType()!; 33 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayAvaloniaEditThemeBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Logging; 4 | using Avalonia.LogicalTree; 5 | using Avalonia.VisualTree; 6 | using Avalonia.Xaml.Interactivity; 7 | using AvaloniaEdit; 8 | using AvaloniaEdit.TextMate; 9 | using TextMateSharp.Grammars; 10 | 11 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit; 12 | 13 | public class XamlDisplayAvaloniaEditThemeBehavior : Behavior { 14 | private IDisposable? _disposable; 15 | private RegistryOptions? _registryOptions; 16 | private TextMate.Installation? _textMateInstallation; 17 | 18 | /// 19 | protected override void OnAttachedToVisualTree() { 20 | base.OnAttachedToVisualTree(); 21 | 22 | var xamlDisplay = AssociatedObject.FindLogicalAncestorOfType()!; 23 | var themeName = xamlDisplay.GetValue(XamlDisplayAvaloniaEdit.CodeHighlightThemeNameProperty); 24 | 25 | try 26 | { 27 | _registryOptions = new RegistryOptions(themeName); 28 | _textMateInstallation = AssociatedObject!.InstallTextMate(_registryOptions); 29 | _textMateInstallation.SetGrammar(_registryOptions.GetScopeByLanguageId("xml")); 30 | 31 | _disposable = xamlDisplay.GetObservable(XamlDisplayAvaloniaEdit.CodeHighlightThemeNameProperty) 32 | .Subscribe(name => { 33 | _textMateInstallation.SetTheme(_registryOptions.LoadTheme(name)); 34 | }); 35 | } 36 | catch (Exception e) 37 | { 38 | if (Logger.TryGet(LogEventLevel.Warning, "ShowMeTheXaml.AvaloniaEdit", out var logger)) 39 | { 40 | logger.Log(this, "TextMate highlighting can't be loaded. {Exception}", e); 41 | } 42 | } 43 | } 44 | 45 | /// 46 | protected override void OnDetachedFromVisualTree() { 47 | base.OnDetachedFromVisualTree(); 48 | _disposable?.Dispose(); 49 | _textMateInstallation?.Dispose(); 50 | } 51 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayStyles.axaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 10 | 11 | 12 | 13 | 42 | 43 | 44 | 101 | 102 | 133 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayStyles.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Styling; 2 | 3 | namespace ShowMeTheXaml.Avalonia.AvaloniaEdit { 4 | public class XamlDisplayStyles : Styles { } 5 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | ## Visual Studio Code specific files and folder 7 | .vscode/* 8 | !.vscode/settings.json 9 | !.vscode/tasks.json 10 | !.vscode/launch.json 11 | !.vscode/extensions.jsons 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUNIT 47 | *.VisualState.xml 48 | TestResult.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | **/Properties/launchSettings.json 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_i.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | 214 | # Visual Studio cache files 215 | # files ending in .cache can be ignored 216 | *.[Cc]ache 217 | # but keep track of directories ending in .cache 218 | !*.[Cc]ache/ 219 | 220 | # Others 221 | ClientBin/ 222 | ~$* 223 | *~ 224 | *.dbmdl 225 | *.dbproj.schemaview 226 | *.jfm 227 | *.pfx 228 | *.publishsettings 229 | orleans.codegen.cs 230 | 231 | # Including strong name files can present a security risk 232 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 233 | #*.snk 234 | 235 | # Since there are multiple workflows, uncomment next line to ignore bower_components 236 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 237 | #bower_components/ 238 | 239 | # RIA/Silverlight projects 240 | Generated_Code/ 241 | 242 | # Backup & report files from converting an old project file 243 | # to a newer Visual Studio version. Backup files are not needed, 244 | # because we have git ;-) 245 | _UpgradeReport_Files/ 246 | Backup*/ 247 | UpgradeLog*.XML 248 | UpgradeLog*.htm 249 | ServiceFabricBackup/ 250 | *.rptproj.bak 251 | 252 | # SQL Server files 253 | *.mdf 254 | *.ldf 255 | *.ndf 256 | 257 | # Business Intelligence projects 258 | *.rdl.data 259 | *.bim.layout 260 | *.bim_*.settings 261 | *.rptproj.rsuser 262 | 263 | # Microsoft Fakes 264 | FakesAssemblies/ 265 | 266 | # GhostDoc plugin setting file 267 | *.GhostDoc.xml 268 | 269 | # Node.js Tools for Visual Studio 270 | .ntvs_analysis.dat 271 | node_modules/ 272 | 273 | # Visual Studio 6 build log 274 | *.plg 275 | 276 | # Visual Studio 6 workspace options file 277 | *.opt 278 | 279 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 280 | *.vbw 281 | 282 | # Visual Studio LightSwitch build output 283 | **/*.HTMLClient/GeneratedArtifacts 284 | **/*.DesktopClient/GeneratedArtifacts 285 | **/*.DesktopClient/ModelManifest.xml 286 | **/*.Server/GeneratedArtifacts 287 | **/*.Server/ModelManifest.xml 288 | _Pvt_Extensions 289 | 290 | # Paket dependency manager 291 | .paket/paket.exe 292 | paket-files/ 293 | 294 | # FAKE - F# Make 295 | .fake/ 296 | 297 | # JetBrains Rider 298 | .idea/ 299 | *.sln.iml 300 | 301 | # CodeRush 302 | .cr/ 303 | 304 | # Python Tools for Visual Studio (PTVS) 305 | __pycache__/ 306 | *.pyc 307 | 308 | # Cake - Uncomment if you are using it 309 | # tools/** 310 | # !tools/packages.config 311 | 312 | # Tabs Studio 313 | *.tss 314 | 315 | # Telerik's JustMock configuration file 316 | *.jmconfig 317 | 318 | # BizTalk build output 319 | *.btp.cs 320 | *.btm.cs 321 | *.odx.cs 322 | *.xsd.cs 323 | 324 | # OpenCover UI analysis results 325 | OpenCover/ 326 | 327 | # Azure Stream Analytics local run output 328 | ASALocalRun/ 329 | 330 | # MSBuild Binary and Structured Log 331 | *.binlog 332 | 333 | # NVidia Nsight GPU debugger configuration file 334 | *.nvuser 335 | 336 | # MFractors (Xamarin productivity tool) working folder 337 | .mfractor/ 338 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls.ApplicationLifetimes; 4 | using Avalonia.Markup.Xaml; 5 | using Avalonia.Markup.Xaml.Styling; 6 | using Avalonia.Styling; 7 | using Avalonia.Themes.Fluent; 8 | using Avalonia.Themes.Simple; 9 | using ShowMeTheXaml.Avalonia.Demo.ViewModels; 10 | using ShowMeTheXaml.Avalonia.Demo.Views; 11 | 12 | namespace ShowMeTheXaml.Avalonia.Demo { 13 | public class App : Application { 14 | public override void Initialize() { 15 | AvaloniaXamlLoader.Load(this); 16 | } 17 | 18 | public override void OnFrameworkInitializationCompleted() { 19 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { 20 | desktop.MainWindow = new MainWindow { 21 | DataContext = new MainWindowViewModel(), 22 | }; 23 | } 24 | 25 | base.OnFrameworkInitializationCompleted(); 26 | } 27 | 28 | private static readonly Uri BaseUri 29 | = new Uri("avares://ShowMeTheXaml.Avalonia.Demo/App.xaml"); 30 | 31 | public static readonly StyleInclude XamlDisplayAvaloniaEditStyles 32 | = new StyleInclude(BaseUri) { Source = new Uri("avares://ShowMeTheXaml.Avalonia.AvaloniaEdit/XamlDisplayStyles.axaml") }; 33 | 34 | public static readonly StyleInclude XamlDisplayDefaultStyles 35 | = new StyleInclude(BaseUri) { Source = new Uri("avares://ShowMeTheXaml.Avalonia/XamlDisplay.xaml") }; 36 | 37 | public static readonly FluentTheme Fluent 38 | = new FluentTheme(); 39 | 40 | public static SimpleTheme Simple 41 | = new SimpleTheme(); 42 | } 43 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AvaloniaUtils/ShowMeTheXaml.Avalonia/f99a80770b941efa2b6a18431b5e388db397b9b7/ShowMeTheXaml.Avalonia.Demo/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/Models/CatalogTheme.cs: -------------------------------------------------------------------------------- 1 | namespace ShowMeTheXaml.Avalonia.Demo.Models { 2 | public enum CatalogTheme { 3 | Fluent, 4 | Simple 5 | } 6 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.ReactiveUI; 3 | 4 | namespace ShowMeTheXaml.Avalonia.Demo { 5 | class Program { 6 | // Initialization code. Don't use any Avalonia, third-party APIs or any 7 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 8 | // yet and stuff might break. 9 | public static void Main(string[] args) => BuildAvaloniaApp() 10 | .StartWithClassicDesktopLifetime(args); 11 | 12 | // Avalonia configuration, don't remove; also used by visual designer. 13 | public static AppBuilder BuildAvaloniaApp() 14 | => AppBuilder.Configure() 15 | .UsePlatformDetect() 16 | .LogToTrace() 17 | .UseXamlDisplay() 18 | .UseReactiveUI(); 19 | } 20 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/ShowMeTheXaml.Avalonia.Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0 5 | false 6 | enable 7 | 8 | 9 | 10 | 11 | %(Filename) 12 | 13 | 14 | Designer 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 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/ViewLocator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) The Avalonia Project. All rights reserved. 2 | // Licensed under the MIT license. See licence.md file in the project root for full license information. 3 | 4 | using System; 5 | using Avalonia.Controls; 6 | using Avalonia.Controls.Templates; 7 | using ShowMeTheXaml.Avalonia.Demo.ViewModels; 8 | 9 | namespace ShowMeTheXaml.Avalonia.Demo { 10 | public class ViewLocator : IDataTemplate { 11 | public bool SupportsRecycling => false; 12 | 13 | public Control Build(object data) { 14 | var name = data.GetType().FullName!.Replace("ViewModel", "View"); 15 | var type = Type.GetType(name); 16 | 17 | if (type != null) { 18 | return (Control) Activator.CreateInstance(type); 19 | } 20 | else { 21 | return new TextBlock {Text = "Not Found: " + name}; 22 | } 23 | } 24 | 25 | public bool Match(object data) { 26 | return data is ViewModelBase; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace ShowMeTheXaml.Avalonia.Demo.ViewModels { 2 | public class MainWindowViewModel : ViewModelBase { 3 | public string Greeting => "Hello World!"; 4 | } 5 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace ShowMeTheXaml.Avalonia.Demo.ViewModels { 4 | public class ViewModelBase : ReactiveObject { } 5 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 20 | AvaloniaEdit Style 21 | Default Style 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | using Avalonia.Diagnostics; 6 | using Avalonia.Input; 7 | using Avalonia.Interactivity; 8 | using Avalonia.Markup.Xaml; 9 | using Avalonia.Markup.Xaml.Styling; 10 | using ShowMeTheXaml.Avalonia.Demo.Models; 11 | 12 | namespace ShowMeTheXaml.Avalonia.Demo.Views { 13 | public partial class MainWindow : Window { 14 | public MainWindow() { 15 | InitializeComponent(); 16 | } 17 | 18 | private void StyleSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { 19 | var styleSource = ((ComboBox)sender).SelectedIndex == 0 20 | ? App.XamlDisplayAvaloniaEditStyles 21 | : App.XamlDisplayDefaultStyles; 22 | Application.Current!.Styles[1] = styleSource; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Demo/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/Data/XamlDisplayContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis.Text; 4 | 5 | namespace ShowMeTheXaml.Avalonia.Infrastructure.Data { 6 | public class XamlDisplayContainer { 7 | private const string TargetClrNamespace = "clr-namespace:ShowMeTheXaml;assembly=ShowMeTheXaml.Avalonia"; 8 | public XamlDisplayContainer(SourceText sourceText, Dictionary namespaceAliases, IReadOnlyCollection xamlDisplayInfos) { 9 | SourceText = sourceText; 10 | XamlDisplayInfos = xamlDisplayInfos; 11 | NamespaceAliases = namespaceAliases; 12 | } 13 | 14 | public SourceText SourceText { get; } 15 | public Dictionary NamespaceAliases { get; } 16 | public IReadOnlyCollection XamlDisplayInfos { get; } 17 | public string TargetClrNamespacePrefix => NamespaceAliases.First(pair => pair.Value == TargetClrNamespace).Key; 18 | } 19 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/Data/XamlDisplayInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using XamlX.Ast; 3 | 4 | namespace ShowMeTheXaml.Avalonia.Infrastructure.Data { 5 | public struct XamlDisplayInfo { 6 | public string? UniqueId { get; set; } 7 | public LinePosition LinePosition { get; set; } 8 | public XamlAstObjectNode XamlDisplayNode { get; set; } 9 | public string AstText { get; set; } 10 | 11 | public TextSpan GetDisplayStartingTagTextSpan(XamlDisplayContainer container, out LinePositionSpan linePositionSpan) { 12 | // prefix + ':DialogHost' length 13 | var tagLength = 1 + 11 + container.TargetClrNamespacePrefix.Length; 14 | var end = new LinePosition(LinePosition.Line, LinePosition.Character + tagLength); 15 | linePositionSpan = new LinePositionSpan(LinePosition, end); 16 | return container.SourceText.Lines.GetTextSpan(linePositionSpan); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/Data/XamlDisplayPosition.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | 3 | namespace ShowMeTheXaml.Avalonia.Infrastructure.Data { 4 | public struct XamlDisplayPosition { 5 | public LinePositionSpan OpeningTag { get; set; } 6 | public LinePositionSpan ClosingTag { get; set; } 7 | public LinePositionSpan XamlDisplaySpan => new LinePositionSpan(OpeningTag.Start, ClosingTag.End); 8 | public LinePositionSpan ContentSpan { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/IInfoResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Text; 2 | using ShowMeTheXaml.Avalonia.Infrastructure.Data; 3 | 4 | namespace ShowMeTheXaml.Avalonia.Infrastructure 5 | { 6 | internal interface IInfoResolver 7 | { 8 | XamlDisplayContainer ResolveInfos(SourceText sourceText); 9 | } 10 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/InfoReceiver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis.Text; 4 | using ShowMeTheXaml.Avalonia.Infrastructure.Data; 5 | using ShowMeTheXaml.Avalonia.Infrastructure.XamlParsers; 6 | using XamlX.Ast; 7 | 8 | namespace ShowMeTheXaml.Avalonia.Infrastructure { 9 | /// 10 | /// This class was written to simplify getting text into XamlDisplay and to contain hacks relevant here 11 | /// DO NOT USE IT IN YOUR PROJECT 12 | /// 13 | internal sealed class InfoReceiver : IXamlAstVisitor { 14 | private readonly List _items = new List(); 15 | 16 | public List DisplayInfos => _items; 17 | 18 | public IXamlAstNode Visit(IXamlAstNode node) { 19 | if (node is XamlAstObjectTextNode objectNode) { 20 | var clrType = objectNode.Type.GetClrType(); 21 | if (clrType.FullName != "ShowMeTheXaml.XamlDisplay") 22 | return node; 23 | 24 | if (_items.Any(displayInfo => displayInfo.XamlDisplayNode == node)) 25 | return node; 26 | 27 | var info = new XamlDisplayInfo {LinePosition = new LinePosition(node.Line - 1, node.Position - 1), XamlDisplayNode = objectNode}; 28 | foreach (var child in objectNode.Children) { 29 | if (child is not XamlAstXamlPropertyValueNode {Property: XamlAstNamePropertyReference {Name: "UniqueId"}} propertyValueNode || 30 | propertyValueNode.Values.Count <= 0 || propertyValueNode.Values[0] is not XamlAstTextNode text) continue; 31 | info.UniqueId = text.Text; 32 | // Save original text (custom implementation) 33 | info.AstText = objectNode.ElementContentText; 34 | break; 35 | } 36 | 37 | _items.Add(info); 38 | 39 | return node; 40 | } 41 | 42 | return node; 43 | } 44 | 45 | public void Push(IXamlAstNode node) { } 46 | 47 | public void Pop() { } 48 | } 49 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/InfoResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Text; 4 | using ShowMeTheXaml.Avalonia.Infrastructure.Data; 5 | using ShowMeTheXaml.Avalonia.Infrastructure.XamlParsers; 6 | using XamlX; 7 | 8 | namespace ShowMeTheXaml.Avalonia.Infrastructure { 9 | internal class InfoResolver : IInfoResolver { 10 | private const string AvaloniaXmlnsAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute"; 11 | private const string TargetClrNamespace = "clr-namespace:ShowMeTheXaml;assembly=ShowMeTheXaml.Avalonia"; 12 | private readonly CSharpCompilation _compilation; 13 | private readonly Dictionary _compatibilityMappings = new() { {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} }; 14 | 15 | public InfoResolver(CSharpCompilation compilation) => _compilation = compilation; 16 | 17 | public XamlDisplayContainer ResolveInfos(SourceText sourceText) { 18 | var parsed = XdXDocumentXamlParser.Parse(sourceText.ToString(), _compatibilityMappings); 19 | 20 | if (!parsed.NamespaceAliases.ContainsValue(TargetClrNamespace)) { 21 | return new XamlDisplayContainer(sourceText, new Dictionary(), new List()); 22 | } 23 | 24 | MiniCompiler.CreateDefault(new RoslynTypeSystem(_compilation), AvaloniaXmlnsAttribute).Transform(parsed); 25 | 26 | var visitor = new InfoReceiver(); 27 | parsed.Root.Visit(visitor); 28 | parsed.Root.VisitChildren(visitor); 29 | return new XamlDisplayContainer(sourceText, parsed.NamespaceAliases, visitor.DisplayInfos); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/MiniCompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using XamlX.Compiler; 5 | using XamlX.Emit; 6 | using XamlX.Transform; 7 | using XamlX.Transform.Transformers; 8 | using XamlX.TypeSystem; 9 | 10 | namespace ShowMeTheXaml.Avalonia.Infrastructure 11 | { 12 | internal sealed class MiniCompiler : XamlCompiler 13 | { 14 | public static MiniCompiler CreateDefault(RoslynTypeSystem typeSystem, params string[] additionalTypes) 15 | { 16 | var mappings = new XamlLanguageTypeMappings(typeSystem); 17 | foreach (var additionalType in additionalTypes) 18 | mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType)); 19 | 20 | var configuration = new TransformerConfiguration( 21 | typeSystem, 22 | typeSystem.Assemblies.First(), 23 | mappings); 24 | return new MiniCompiler(configuration); 25 | } 26 | 27 | private MiniCompiler(TransformerConfiguration configuration) 28 | : base(configuration, new XamlLanguageEmitMappings(), false) 29 | { 30 | Transformers.Add(new KnownDirectivesTransformer()); 31 | Transformers.Add(new XamlIntrinsicsTransformer()); 32 | Transformers.Add(new XArgumentsTransformer()); 33 | Transformers.Add(new TypeReferenceResolver()); 34 | } 35 | 36 | protected override XamlEmitContext InitCodeGen(IFileSource file, Func> createSubType, Func, IXamlTypeBuilder> createDelegateType, object codeGen, XamlRuntimeContext context, bool needContextLocal) 37 | => throw new NotSupportedException(); 38 | } 39 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/RoslynTypeSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using XamlX.TypeSystem; 6 | 7 | namespace ShowMeTheXaml.Avalonia.Infrastructure 8 | { 9 | public class RoslynTypeSystem : IXamlTypeSystem 10 | { 11 | private readonly List _assemblies = new List(); 12 | 13 | public RoslynTypeSystem(CSharpCompilation compilation) 14 | { 15 | _assemblies.Add(new RoslynAssembly(compilation.Assembly)); 16 | 17 | var assemblySymbols = compilation 18 | .References 19 | .Select(compilation.GetAssemblyOrModuleSymbol) 20 | .OfType() 21 | .Select(assembly => new RoslynAssembly(assembly)) 22 | .ToList(); 23 | 24 | _assemblies.AddRange(assemblySymbols); 25 | } 26 | 27 | public IEnumerable Assemblies => _assemblies; 28 | 29 | public IXamlAssembly FindAssembly(string substring) => _assemblies[0]; 30 | 31 | public IXamlType FindType(string name) 32 | { 33 | foreach (var assembly in _assemblies) 34 | { 35 | var type = assembly.FindType(name); 36 | if (type != null) 37 | return type; 38 | } 39 | 40 | return null; 41 | } 42 | 43 | public IXamlType FindType(string name, string assembly) 44 | { 45 | foreach (var assemblyInstance in _assemblies) 46 | { 47 | var type = assemblyInstance.FindType(name); 48 | if (type != null) 49 | return type; 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | 56 | public class RoslynAssembly : IXamlAssembly 57 | { 58 | private readonly IAssemblySymbol _symbol; 59 | 60 | public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol; 61 | 62 | public bool Equals(IXamlAssembly other) => 63 | other is RoslynAssembly roslynAssembly && 64 | SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol); 65 | 66 | public string Name => _symbol.Name; 67 | 68 | public IReadOnlyList CustomAttributes => 69 | _symbol.GetAttributes() 70 | .Select(data => new RoslynAttribute(data, this)) 71 | .ToList(); 72 | 73 | public IXamlType FindType(string fullName) 74 | { 75 | var type = _symbol.GetTypeByMetadataName(fullName); 76 | return type is null ? null : new RoslynType(type, this); 77 | } 78 | } 79 | 80 | public class RoslynAttribute : IXamlCustomAttribute 81 | { 82 | private readonly AttributeData _data; 83 | private readonly RoslynAssembly _assembly; 84 | 85 | public RoslynAttribute(AttributeData data, RoslynAssembly assembly) 86 | { 87 | _data = data; 88 | _assembly = assembly; 89 | } 90 | 91 | public bool Equals(IXamlCustomAttribute other) => 92 | other is RoslynAttribute attribute && 93 | _data == attribute._data; 94 | 95 | public IXamlType Type => new RoslynType(_data.AttributeClass, _assembly); 96 | 97 | public List Parameters => 98 | _data.ConstructorArguments 99 | .Select(argument => argument.Value) 100 | .ToList(); 101 | 102 | public Dictionary Properties => 103 | _data.NamedArguments.ToDictionary( 104 | pair => pair.Key, 105 | pair => pair.Value.Value); 106 | } 107 | 108 | public class RoslynType : IXamlType 109 | { 110 | private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat( 111 | typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, 112 | genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | 113 | SymbolDisplayGenericsOptions.IncludeTypeConstraints | 114 | SymbolDisplayGenericsOptions.IncludeVariance); 115 | 116 | private readonly RoslynAssembly _assembly; 117 | private readonly INamedTypeSymbol _symbol; 118 | 119 | public RoslynType(INamedTypeSymbol symbol, RoslynAssembly assembly) 120 | { 121 | _symbol = symbol; 122 | _assembly = assembly; 123 | } 124 | 125 | public bool Equals(IXamlType other) => 126 | other is RoslynType roslynType && 127 | SymbolEqualityComparer.Default.Equals(_symbol, roslynType._symbol); 128 | 129 | public object Id => _symbol; 130 | 131 | public string Name => _symbol.Name; 132 | 133 | public string Namespace => _symbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat); 134 | 135 | public string FullName => $"{Namespace}.{Name}"; 136 | 137 | public IXamlAssembly Assembly => _assembly; 138 | 139 | public IReadOnlyList Properties => 140 | _symbol.GetMembers() 141 | .Where(member => member.Kind == SymbolKind.Property) 142 | .OfType() 143 | .Select(property => new RoslynProperty(property, _assembly)) 144 | .ToList(); 145 | 146 | public IReadOnlyList Events { get; } = new List(); 147 | 148 | public IReadOnlyList Fields { get; } = new List(); 149 | 150 | public IReadOnlyList Methods { get; } = new List(); 151 | 152 | public IReadOnlyList Constructors => 153 | _symbol.Constructors 154 | .Select(method => new RoslynConstructor(method, _assembly)) 155 | .ToList(); 156 | 157 | public IReadOnlyList CustomAttributes { get; } = new List(); 158 | 159 | public IReadOnlyList GenericArguments { get; } = new List(); 160 | 161 | public bool IsAssignableFrom(IXamlType type) => type == this; 162 | 163 | public IXamlType MakeGenericType(IReadOnlyList typeArguments) => this; 164 | 165 | public IXamlType GenericTypeDefinition => this; 166 | 167 | public bool IsArray => false; 168 | 169 | public IXamlType ArrayElementType { get; } = null; 170 | 171 | public IXamlType MakeArrayType(int dimensions) => null; 172 | 173 | public IXamlType BaseType => _symbol.BaseType == null ? null : new RoslynType(_symbol.BaseType, _assembly); 174 | 175 | public bool IsValueType { get; } = false; 176 | 177 | public bool IsEnum { get; } = false; 178 | 179 | public IReadOnlyList Interfaces => 180 | _symbol.AllInterfaces 181 | .Select(abstraction => new RoslynType(abstraction, _assembly)) 182 | .ToList(); 183 | 184 | public bool IsInterface => _symbol.IsAbstract; 185 | 186 | public IXamlType GetEnumUnderlyingType() => null; 187 | 188 | public IReadOnlyList GenericParameters { get; } = new List(); 189 | } 190 | 191 | public class RoslynConstructor : IXamlConstructor 192 | { 193 | private readonly IMethodSymbol _symbol; 194 | private readonly RoslynAssembly _assembly; 195 | 196 | public RoslynConstructor(IMethodSymbol symbol, RoslynAssembly assembly) 197 | { 198 | _symbol = symbol; 199 | _assembly = assembly; 200 | } 201 | 202 | public bool Equals(IXamlConstructor other) => 203 | other is RoslynConstructor roslynConstructor && 204 | SymbolEqualityComparer.Default.Equals(_symbol, roslynConstructor._symbol); 205 | 206 | public bool IsPublic => true; 207 | 208 | public bool IsStatic => false; 209 | 210 | public IReadOnlyList Parameters => 211 | _symbol.Parameters 212 | .Select(parameter => parameter.Type) 213 | .OfType() 214 | .Select(type => new RoslynType(type, _assembly)) 215 | .ToList(); 216 | } 217 | 218 | public class RoslynProperty : IXamlProperty 219 | { 220 | private readonly IPropertySymbol _symbol; 221 | private readonly RoslynAssembly _assembly; 222 | 223 | public RoslynProperty(IPropertySymbol symbol, RoslynAssembly assembly) 224 | { 225 | _symbol = symbol; 226 | _assembly = assembly; 227 | } 228 | 229 | public bool Equals(IXamlProperty other) => 230 | other is RoslynProperty roslynProperty && 231 | SymbolEqualityComparer.Default.Equals(_symbol, roslynProperty._symbol); 232 | 233 | public string Name => _symbol.Name; 234 | 235 | public IXamlType PropertyType => 236 | _symbol.Type is INamedTypeSymbol namedTypeSymbol 237 | ? new RoslynType(namedTypeSymbol, _assembly) 238 | : null; 239 | 240 | public IXamlMethod Getter => _symbol.GetMethod == null ? null : new RoslynMethod(_symbol.GetMethod, _assembly); 241 | 242 | public IXamlMethod Setter => _symbol.SetMethod == null ? null : new RoslynMethod(_symbol.SetMethod, _assembly); 243 | 244 | public IReadOnlyList CustomAttributes { get; } = new List(); 245 | 246 | public IReadOnlyList IndexerParameters { get; } = new List(); 247 | } 248 | 249 | public class RoslynMethod : IXamlMethod 250 | { 251 | private readonly IMethodSymbol _symbol; 252 | private readonly RoslynAssembly _assembly; 253 | 254 | public RoslynMethod(IMethodSymbol symbol, RoslynAssembly assembly) 255 | { 256 | _symbol = symbol; 257 | _assembly = assembly; 258 | } 259 | 260 | public bool Equals(IXamlMethod other) => 261 | other is RoslynMethod roslynMethod && 262 | SymbolEqualityComparer.Default.Equals(roslynMethod._symbol, _symbol); 263 | 264 | public string Name => _symbol.Name; 265 | 266 | public bool IsPublic => true; 267 | 268 | public bool IsStatic => false; 269 | 270 | public IXamlType ReturnType => new RoslynType((INamedTypeSymbol) _symbol.ReturnType, _assembly); 271 | 272 | public IReadOnlyList Parameters => 273 | _symbol.Parameters.Select(parameter => parameter.Type) 274 | .OfType() 275 | .Select(type => new RoslynType(type, _assembly)) 276 | .ToList(); 277 | 278 | public IXamlType DeclaringType => new RoslynType((INamedTypeSymbol)_symbol.ReceiverType, _assembly); 279 | 280 | public IXamlMethod MakeGenericMethod(IReadOnlyList typeArguments) => null; 281 | 282 | public IReadOnlyList CustomAttributes { get; } = new List(); 283 | } 284 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/XamlParsers/XamlAstObjectTextNode.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Xml.Linq; 3 | using XamlX.Ast; 4 | 5 | namespace ShowMeTheXaml.Avalonia.Infrastructure.XamlParsers; 6 | 7 | public class XamlAstObjectTextNode : XamlAstObjectNode { 8 | public XElement _element; 9 | private string? _elementText; 10 | public XamlAstObjectTextNode(IXamlLineInfo lineInfo, IXamlAstTypeReference type, XElement element) : base(lineInfo, type) { 11 | _element = element; 12 | } 13 | 14 | public string ElementContentText => _elementText ??= ProvideElementText(); 15 | private string ProvideElementText() { 16 | var dialogHostElement = RemoveAllNamespaces(_element); 17 | var elementText = dialogHostElement.Elements().FirstOrDefault()?.ToString().Replace("___", ":"); 18 | if (elementText == null) { 19 | return dialogHostElement.FirstNode is XText 20 | ? dialogHostElement.FirstNode.ToString().Trim() 21 | : string.Empty; 22 | } 23 | 24 | var idx = elementText.LastIndexOf('\n'); 25 | // Let's remove unnecessary indentation 26 | if (idx != -1) { 27 | var result = elementText.Substring(idx + 1); 28 | var whitespacesCount = result.TakeWhile(char.IsWhiteSpace).Count(); 29 | if (whitespacesCount != 0) return elementText.Replace("\n" + new string(' ', whitespacesCount), "\n"); 30 | } 31 | else { 32 | return elementText.Trim(); 33 | } 34 | return elementText; 35 | } 36 | 37 | private static XElement RemoveAllNamespaces(XElement e) { 38 | var content = e.Nodes() 39 | .Where(node => node is not XComment) 40 | .Select(node => node is XElement xElement ? RemoveAllNamespaces(xElement) : node); 41 | var newElement = new XElement(GetNameWithNamespace(e), content); 42 | newElement.Add(e.Attributes()); 43 | return newElement; 44 | } 45 | 46 | private static string GetNameWithNamespace(XElement e) { 47 | var prefixOfNamespace = e.GetPrefixOfNamespace(e.Name.Namespace); 48 | // This is done cuz xml doesn't allow to use : in name 49 | // WE TEMPORARY REPLACE ACTUAL NAMESPACES WITH ___ PREFIXES 50 | // To avoid namespaces definitions in XElement.ToString calls 51 | return prefixOfNamespace == null ? e.Name.LocalName : $"{prefixOfNamespace}___{e.Name.LocalName}"; 52 | } 53 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/Infrastructure/XamlParsers/XdXDocumentXamlParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml; 6 | using System.Xml.Linq; 7 | using XamlX; 8 | using XamlX.Ast; 9 | using XamlX.Parsers; 10 | using XamlX.Parsers.SystemXamlMarkupExtensionParser; 11 | 12 | namespace ShowMeTheXaml.Avalonia.Infrastructure.XamlParsers; 13 | 14 | internal class XdXDocumentXamlParser { 15 | public static XamlDocument Parse(string s, Dictionary compatibilityMappings = null) { 16 | return Parse(new StringReader(s), compatibilityMappings); 17 | } 18 | 19 | public static XamlDocument Parse(TextReader reader, Dictionary compatibilityMappings = null) { 20 | var xr = XmlReader.Create(reader, new XmlReaderSettings { 21 | DtdProcessing = DtdProcessing.Ignore 22 | }); 23 | xr = new CompatibleXmlReader(xr, compatibilityMappings ?? new Dictionary()); 24 | 25 | var root = XDocument.Load(xr, LoadOptions.SetLineInfo).Root; 26 | 27 | var doc = new XamlDocument { 28 | Root = new ParserContext(root).Parse() 29 | }; 30 | 31 | foreach (var attr in root.Attributes()) 32 | if (attr.Name.NamespaceName == "http://www.w3.org/2000/xmlns/" || 33 | (attr.Name.NamespaceName == "" && attr.Name.LocalName == "xmlns")) { 34 | var name = attr.Name.NamespaceName == "" ? "" : attr.Name.LocalName; 35 | doc.NamespaceAliases[name] = attr.Value; 36 | } 37 | 38 | return doc; 39 | } 40 | 41 | 42 | class ParserContext { 43 | private readonly XElement _root; 44 | 45 | public ParserContext(XElement root) { 46 | _root = root; 47 | } 48 | 49 | 50 | XamlAstXmlTypeReference GetTypeReference(XElement el) => 51 | new XamlAstXmlTypeReference(el.AsLi(), el.Name.NamespaceName, el.Name.LocalName); 52 | 53 | 54 | static XamlAstXmlTypeReference ParseTypeName(IXamlLineInfo info, string typeName, XElement xel) 55 | => ParseTypeName(info, typeName, 56 | ns => string.IsNullOrWhiteSpace(ns) 57 | ? xel.GetDefaultNamespace().NamespaceName 58 | : xel.GetNamespaceOfPrefix(ns)?.NamespaceName ?? ""); 59 | 60 | static XamlAstXmlTypeReference ParseTypeName(IXamlLineInfo info, string typeName, Func prefixResolver) { 61 | var pair = typeName.Trim().Split(new[] { ':' }, 2); 62 | string xmlns, name; 63 | if (pair.Length == 1) { 64 | xmlns = prefixResolver(""); 65 | name = pair[0]; 66 | } 67 | else { 68 | xmlns = prefixResolver(pair[0]); 69 | if (xmlns == null) 70 | throw new XamlParseException($"Namespace '{pair[0]}' is not recognized", info); 71 | name = pair[1]; 72 | } 73 | return new XamlAstXmlTypeReference(info, xmlns, name); 74 | } 75 | 76 | static List ParseTypeArguments(string args, XElement xel, IXamlLineInfo info) { 77 | try { 78 | XamlAstXmlTypeReference Parse(CommaSeparatedParenthesesTreeParser.Node node) { 79 | var rv = ParseTypeName(info, node.Value, xel); 80 | 81 | if (node.Children.Count != 0) 82 | rv.GenericArguments = node.Children.Select(Parse).ToList(); 83 | return rv; 84 | } 85 | 86 | var tree = CommaSeparatedParenthesesTreeParser.Parse(args); 87 | return tree.Select(Parse).ToList(); 88 | } 89 | catch (CommaSeparatedParenthesesTreeParser.ParseException e) { 90 | throw new XamlParseException(e.Message, info); 91 | } 92 | } 93 | 94 | static IXamlAstValueNode ParseTextValueOrMarkupExtension(string ext, XElement xel, IXamlLineInfo info) { 95 | if (ext.StartsWith("{") || ext.StartsWith(@"\{")) { 96 | if (ext.StartsWith("{}")) 97 | ext = ext.Substring(2); 98 | else { 99 | try { 100 | 101 | return SystemXamlMarkupExtensionParser.Parse(info, ext, 102 | t => ParseTypeName(info, t, xel)); 103 | } 104 | catch (MeScannerParseException parseEx) { 105 | throw new XamlParseException(parseEx.Message, info); 106 | } 107 | } 108 | } 109 | 110 | // Do not apply XAML whitespace normalization to attribute values 111 | return new XamlAstTextNode(info, ext, true); 112 | } 113 | 114 | XamlAstObjectNode ParseNewInstance(XElement el, bool root, XmlSpace spaceMode) { 115 | var declaredMode = el.GetDeclaredWhitespaceMode(); 116 | if (declaredMode != XmlSpace.None) { 117 | spaceMode = declaredMode; 118 | } 119 | 120 | if (el.Name.LocalName.Contains(".")) 121 | throw ParseError(el, "Dots aren't allowed in type names"); 122 | var type = GetTypeReference(el); 123 | var i = new XamlAstObjectTextNode(el.AsLi(), type, el); 124 | foreach (var attr in el.Attributes()) { 125 | if (attr.Name.NamespaceName == "http://www.w3.org/2000/xmlns/" || 126 | (attr.Name.NamespaceName == "" && attr.Name.LocalName == "xmlns")) { 127 | if (!root) 128 | throw ParseError(attr, 129 | "xmlns declarations are only allowed on the root element to preserve memory"); 130 | } 131 | else if (attr.Name.NamespaceName.StartsWith("http://www.w3.org")) { 132 | // Silently ignore all xml-parser related attributes 133 | } 134 | // Parse type arguments 135 | else if (attr.Name.NamespaceName == XamlNamespaces.Xaml2006 && 136 | attr.Name.LocalName == "TypeArguments") 137 | type.GenericArguments = ParseTypeArguments(attr.Value, el, attr.AsLi()); 138 | // Parse as a directive 139 | else if (attr.Name.NamespaceName != "" && !attr.Name.LocalName.Contains(".")) 140 | i.Children.Add(new XamlAstXmlDirective(el.AsLi(), 141 | attr.Name.NamespaceName, attr.Name.LocalName, new[] { 142 | ParseTextValueOrMarkupExtension(attr.Value, el, attr.AsLi()) 143 | } 144 | )); 145 | // Parse as a property 146 | else { 147 | var pname = attr.Name.LocalName; 148 | var ptype = i.Type; 149 | 150 | if (pname.Contains(".")) { 151 | var parts = pname.Split(new[] { '.' }, 2); 152 | pname = parts[1]; 153 | var ns = attr.Name.Namespace == "" ? el.GetDefaultNamespace().NamespaceName : attr.Name.NamespaceName; 154 | ptype = new XamlAstXmlTypeReference(el.AsLi(), ns, parts[0]); 155 | } 156 | 157 | i.Children.Add(new XamlAstXamlPropertyValueNode(el.AsLi(), 158 | new XamlAstNamePropertyReference(el.AsLi(), ptype, pname, type), 159 | ParseTextValueOrMarkupExtension(attr.Value, el, attr.AsLi()))); 160 | } 161 | } 162 | 163 | 164 | foreach (var node in el.Nodes()) { 165 | if (node is XElement elementNode && elementNode.Name.LocalName.Contains(".")) { 166 | if (elementNode.HasAttributes) 167 | throw ParseError(node, "Attributes aren't allowed on element properties"); 168 | var pair = elementNode.Name.LocalName.Split(new[] { '.' }, 2); 169 | i.Children.Add(new XamlAstXamlPropertyValueNode(el.AsLi(), new XamlAstNamePropertyReference 170 | ( 171 | el.AsLi(), 172 | new XamlAstXmlTypeReference(el.AsLi(), elementNode.Name.NamespaceName, 173 | pair[0]), pair[1], type 174 | ), 175 | ParseValueNodeChildren(elementNode, spaceMode) 176 | )); 177 | } 178 | else { 179 | var parsed = ParseValueNode(node, spaceMode); 180 | if (parsed != null) 181 | i.Children.Add(parsed); 182 | } 183 | 184 | } 185 | 186 | return i; 187 | } 188 | 189 | IXamlAstValueNode ParseValueNode(XNode node, XmlSpace spaceMode) { 190 | if (node is XElement el) 191 | return ParseNewInstance(el, false, spaceMode); 192 | if (node is XText text) { 193 | var preserveWhitespace = spaceMode == XmlSpace.Preserve; 194 | return new XamlAstTextNode(node.AsLi(), text.Value, preserveWhitespace); 195 | } 196 | 197 | return null; 198 | } 199 | 200 | List ParseValueNodeChildren(XElement parent, XmlSpace spaceMode) { 201 | var lst = new List(); 202 | foreach (var n in parent.Nodes()) { 203 | var parsed = ParseValueNode(n, spaceMode); 204 | if (parsed != null) 205 | lst.Add(parsed); 206 | } 207 | return lst; 208 | } 209 | 210 | Exception ParseError(IXmlLineInfo line, string message) => 211 | new XamlParseException(message, line.LineNumber, line.LinePosition); 212 | 213 | public XamlAstObjectNode Parse() => ParseNewInstance(_root, true, XmlSpace.Default); 214 | } 215 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/ShowMeTheXaml.Avalonia.Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | preview 6 | true 7 | false 8 | true 9 | ShowMeTheXaml.Avalonia 10 | ShowMeTheXaml.Avalonia.Generator 11 | SKProCH 12 | Xaml code collector for ShowMeTheXaml.Avalonia using Net5 Source Generators 13 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia 14 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia/blob/master/LICENSE 15 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia.git 16 | Git 17 | avalonia avaloniaui csharp-sourcegenerator 18 | enable 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | true 42 | build\;buildTransitive\ 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/ShowMeTheXaml.Avalonia.Generator.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/ShowMeTheXamlCodeTemplatesGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ShowMeTheXaml.Avalonia; 7 | 8 | public static class ShowMeTheXamlCodeTemplatesGenerator { 9 | private const string XamlDisplayInternalDataTemplate = """ 10 | using System.Collections.Generic; 11 | using Avalonia; 12 | using Avalonia.Controls; 13 | using ShowMeTheXaml; 14 | 15 | namespace ShowMeTheXaml {{ 16 | public static class XamlDisplayInternalData {{ 17 | // 18 | public static Dictionary Data {{ get; }} = new Dictionary() {{ 19 | {0} 20 | }}; 21 | 22 | // > 23 | public static Dictionary> NamespaceAliases = new Dictionary>() {{ 24 | {1} 25 | }}; 26 | 27 | /// 28 | /// Loads data for xaml displays 29 | /// 30 | public static AppBuilder UseXamlDisplay(this AppBuilder builder) 31 | {{ 32 | RegisterXamlDisplayData(); 33 | return builder; 34 | }} 35 | 36 | /// 37 | /// Loads data for xaml displays 38 | /// 39 | public static void RegisterXamlDisplayData() 40 | {{ 41 | XamlDisplay.DisplayContent = Data; 42 | XamlDisplay.XamlFilesNamespaceAliases = NamespaceAliases; 43 | }} 44 | }} 45 | }} 46 | """; 47 | 48 | private const string DataEntryTemplate = """ 49 | {{ {0}, new XamlDisplayInstanceData({1}, {2}) }}, 50 | """; 51 | 52 | private const string NamespaceAliasesEntryTemplate = """ 53 | {{ {0}, new Dictionary {{ 54 | {1} 55 | }} 56 | }}, 57 | """; 58 | 59 | private const string AliasEntryTemplate = """ 60 | {{ {0}, {1} }}, 61 | """; 62 | 63 | public static string GenerateXamlDisplayInternalData(Dictionary Aliases)> data) { 64 | string FormatDataEntry(KeyValuePair Aliases)> pair) 65 | => string.Format(DataEntryTemplate, ToLiteral(pair.Key), ToLiteral(pair.Value.XamlText), ToLiteral(pair.Value.FileName)); 66 | 67 | string FormatNamespaceAliasEntry(IGrouping Aliases)>> pair) 68 | => string.Format(NamespaceAliasesEntryTemplate, ToLiteral(pair.Key), string.Join(Environment.NewLine, pair.First().Value.Aliases.Select(FormatAliasEntry))); 69 | 70 | string FormatAliasEntry(KeyValuePair valuePair) 71 | => string.Format(AliasEntryTemplate, ToLiteral(valuePair.Key), ToLiteral(valuePair.Value)); 72 | 73 | var dataText = string.Join(Environment.NewLine, data.Select(FormatDataEntry)); 74 | var namespaceAliasesText = string.Join(Environment.NewLine, data.GroupBy(pair => pair.Value.FileName).Select(FormatNamespaceAliasEntry)); 75 | return string.Format(XamlDisplayInternalDataTemplate, dataText, namespaceAliasesText); 76 | } 77 | 78 | private static string ToLiteral(string input) { 79 | var literal = new StringBuilder(input.Length + 2); 80 | literal.Append("\""); 81 | foreach (var c in input) { 82 | switch (c) { 83 | case '\'': 84 | literal.Append(@"\'"); 85 | break; 86 | case '\"': 87 | literal.Append("\\\""); 88 | break; 89 | case '\\': 90 | literal.Append(@"\\"); 91 | break; 92 | case '\0': 93 | literal.Append(@"\0"); 94 | break; 95 | case '\a': 96 | literal.Append(@"\a"); 97 | break; 98 | case '\b': 99 | literal.Append(@"\b"); 100 | break; 101 | case '\f': 102 | literal.Append(@"\f"); 103 | break; 104 | case '\n': 105 | literal.Append(@"\n"); 106 | break; 107 | case '\r': 108 | literal.Append(@"\r"); 109 | break; 110 | case '\t': 111 | literal.Append(@"\t"); 112 | break; 113 | case '\v': 114 | literal.Append(@"\v"); 115 | break; 116 | default: 117 | // ASCII printable character 118 | if (c >= 0x20 && c <= 0x7e) { 119 | literal.Append(c); 120 | // As UTF16 escaped character 121 | } 122 | else { 123 | literal.Append(@"\u"); 124 | literal.Append(((int)c).ToString("x4")); 125 | } 126 | 127 | break; 128 | } 129 | } 130 | 131 | literal.Append("\""); 132 | return literal.ToString(); 133 | } 134 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.Generator/ShowMeTheXamlGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.Text; 10 | using ShowMeTheXaml.Avalonia.Infrastructure; 11 | 12 | namespace ShowMeTheXaml.Avalonia { 13 | [Generator] 14 | public class ShowMeTheXamlGenerator : ISourceGenerator { 15 | public void Initialize(GeneratorInitializationContext context) { } 16 | 17 | public void Execute(GeneratorExecutionContext context) { 18 | try { 19 | ExecuteInternal(context); 20 | } 21 | catch (Exception e) { 22 | context.ReportDiagnostic(Diagnostic.Create( 23 | new DiagnosticDescriptor( 24 | "XD0000", 25 | $"ShowMeTheXaml.Generator exited with exception", 26 | $"ShowMeTheXaml.Generator throw exception. Report it on GitHub and attach project. Exception type: {e}", 27 | "General", 28 | DiagnosticSeverity.Error, 29 | true 30 | ), 31 | Location.None)); 32 | } 33 | } 34 | private void ExecuteInternal(GeneratorExecutionContext context) { 35 | var codeDictionary = new Dictionary Aliases)>(); 36 | var files = context.AdditionalFiles.Where(text => text.Path.EndsWith(".xaml") || text.Path.EndsWith(".axaml")).ToList(); 37 | if (files.Count == 0) { 38 | context.ReportDiagnostic(Diagnostic.Create( 39 | new DiagnosticDescriptor( 40 | "XD0003", 41 | "No xaml files detected. Consider read \"Getting started\" at our github.", 42 | "Add all xaml and axaml files as AdditionalFiles in csproj", 43 | "Usage", 44 | DiagnosticSeverity.Error, 45 | true 46 | ), 47 | Location.None)); 48 | } 49 | 50 | foreach (var markupFile in files) { 51 | var infoResolver = new InfoResolver((CSharpCompilation)context.Compilation); 52 | var sources = markupFile.GetText() ?? throw new ArgumentNullException("markupFile.GetText()"); 53 | var xamlDisplayContainer = infoResolver.ResolveInfos(sources); 54 | var xamlDisplayInfos = xamlDisplayContainer.XamlDisplayInfos 55 | .OrderByDescending(info => info.LinePosition.Line) 56 | .ThenByDescending(info => info.LinePosition.Line) 57 | .ToList(); 58 | 59 | foreach (var info in xamlDisplayInfos) { 60 | if (info.UniqueId == null) { 61 | context.ReportDiagnostic(Diagnostic.Create( 62 | new DiagnosticDescriptor( 63 | "XD0001", 64 | "UniqueId not set", 65 | "Each XamlDisplay must have a UniqueId property", 66 | "Usage", 67 | DiagnosticSeverity.Error, 68 | true 69 | ), 70 | Location.Create(markupFile.Path, info.GetDisplayStartingTagTextSpan(xamlDisplayContainer, out var linePositionSpan), linePositionSpan) 71 | )); 72 | } 73 | else if (codeDictionary.ContainsKey(info.UniqueId)) { 74 | context.ReportDiagnostic(Diagnostic.Create( 75 | new DiagnosticDescriptor( 76 | "XD0002", 77 | "UniqueId duplicate", 78 | "Each XamlDisplay must have a unique value in UniqueId property", 79 | "Usage", 80 | DiagnosticSeverity.Error, 81 | true 82 | ), 83 | Location.Create(markupFile.Path, info.GetDisplayStartingTagTextSpan(xamlDisplayContainer, out var linePositionSpan), linePositionSpan) 84 | )); 85 | } 86 | else { 87 | codeDictionary.Add(info.UniqueId, (info.AstText, Path.GetFileName(markupFile.Path), xamlDisplayContainer.NamespaceAliases)); 88 | } 89 | } 90 | } 91 | 92 | var generatedCode = ShowMeTheXamlCodeTemplatesGenerator.GenerateXamlDisplayInternalData(codeDictionary); 93 | var sourceText = SourceText.From(generatedCode, Encoding.UTF8); 94 | context.AddSource("XamlDisplayInternalData.g.cs", sourceText); 95 | } 96 | 97 | internal static void CallDebugger() { 98 | if (Debugger.IsAttached) { 99 | Debugger.Break(); 100 | } 101 | else { 102 | Debugger.Launch(); 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShowMeTheXaml.Avalonia.Generator", "ShowMeTheXaml.Avalonia.Generator\ShowMeTheXaml.Avalonia.Generator.csproj", "{459038B3-41A3-476C-B882-5EE89D69AA34}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShowMeTheXaml.Avalonia", "ShowMeTheXaml.Avalonia\ShowMeTheXaml.Avalonia.csproj", "{1D7DC36A-D0E2-40F8-9AF8-04684A87FCA9}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShowMeTheXaml.Avalonia.Demo", "ShowMeTheXaml.Avalonia.Demo\ShowMeTheXaml.Avalonia.Demo.csproj", "{8ED944AB-4F06-4576-8E1E-82F4B4D54F30}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShowMeTheXaml.Avalonia.AvaloniaEdit", "ShowMeTheXaml.Avalonia.AvaloniaEdit\ShowMeTheXaml.Avalonia.AvaloniaEdit.csproj", "{F85FF56D-60F3-48F3-BD77-72ABD63065C8}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {459038B3-41A3-476C-B882-5EE89D69AA34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {459038B3-41A3-476C-B882-5EE89D69AA34}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {459038B3-41A3-476C-B882-5EE89D69AA34}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {459038B3-41A3-476C-B882-5EE89D69AA34}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {1D7DC36A-D0E2-40F8-9AF8-04684A87FCA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {1D7DC36A-D0E2-40F8-9AF8-04684A87FCA9}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {1D7DC36A-D0E2-40F8-9AF8-04684A87FCA9}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {1D7DC36A-D0E2-40F8-9AF8-04684A87FCA9}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {8ED944AB-4F06-4576-8E1E-82F4B4D54F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {8ED944AB-4F06-4576-8E1E-82F4B4D54F30}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {8ED944AB-4F06-4576-8E1E-82F4B4D54F30}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {8ED944AB-4F06-4576-8E1E-82F4B4D54F30}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {F85FF56D-60F3-48F3-BD77-72ABD63065C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {F85FF56D-60F3-48F3-BD77-72ABD63065C8}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {F85FF56D-60F3-48F3-BD77-72ABD63065C8}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {F85FF56D-60F3-48F3-BD77-72ABD63065C8}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/AlignmentYConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data; 4 | using Avalonia.Data.Converters; 5 | using Avalonia.Layout; 6 | using Avalonia.Media; 7 | 8 | // ReSharper disable once CheckNamespace 9 | namespace ShowMeTheXaml { 10 | public class AlignmentYConverter : IValueConverter { 11 | public static AlignmentYConverter Instance { get; } = new AlignmentYConverter(); 12 | 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 14 | if (value is AlignmentY alignmentY) { 15 | return alignmentY switch { 16 | AlignmentY.Top => VerticalAlignment.Top, 17 | AlignmentY.Center => VerticalAlignment.Center, 18 | AlignmentY.Bottom => VerticalAlignment.Bottom, 19 | _ => throw new ArgumentOutOfRangeException() 20 | }; 21 | } 22 | 23 | return BindingOperations.DoNothing; 24 | } 25 | 26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { 27 | throw new NotSupportedException(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/AvaloniaRuntimeXamlLoaderHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml; 5 | using Avalonia.Controls; 6 | using Avalonia.Markup.Xaml; 7 | 8 | namespace ShowMeTheXaml; 9 | 10 | /// 11 | /// Experimental AvaloniaRuntimeXamlLoader helper, allows to compile xaml with separated namespace declarations 12 | /// 13 | public static class AvaloniaRuntimeXamlLoaderHelper { 14 | private const string XamlTemplate = """ 15 | 16 | {1} 17 | 18 | """; 19 | 20 | public static object Parse(string content, Dictionary namespaces) { 21 | string FormatNamespace(KeyValuePair pair) 22 | => string.IsNullOrEmpty(pair.Key) ? $"xmlns=\"{pair.Value}\"" : $"xmlns:{pair.Key}=\"{pair.Value}\""; 23 | 24 | var namespacesString = string.Join(" ", namespaces.Select(FormatNamespace)); 25 | return Parse(content, namespacesString); 26 | } 27 | 28 | public static object Parse(string content, string namespaces) { 29 | try { 30 | // Try to insert namespaces before closing first tag 31 | var endTagIndex = content.IndexOf('>'); 32 | // No closing symbol (>), this is just a string probably 33 | if (endTagIndex == -1) return content; 34 | 35 | var slashAndMoreThenIndex = content.IndexOf("/>", StringComparison.InvariantCulture); 36 | // This is a probably one tag without content 37 | if (slashAndMoreThenIndex != -1) endTagIndex = Math.Min(endTagIndex, slashAndMoreThenIndex); 38 | var finalXaml = content.Insert(endTagIndex, " " + namespaces); 39 | return AvaloniaRuntimeXamlLoader.Parse(finalXaml); 40 | } 41 | catch (Exception) { 42 | // Falling back to old method 43 | return ContentControlWrapperParse(content, namespaces); 44 | } 45 | } 46 | 47 | private static object ContentControlWrapperParse(string content, string namespaces) { 48 | var finalXaml = string.Format(XamlTemplate, namespaces, content); 49 | try { 50 | var contentControl = AvaloniaRuntimeXamlLoader.Parse(finalXaml); 51 | return contentControl.Content; 52 | } 53 | catch (XmlException e) { 54 | // cut line info 55 | var lastIndexOfDot = e.Message.LastIndexOf('.', e.Message.Length - 2); 56 | var meaningfulMessage = lastIndexOfDot == -1 57 | ? e.Message 58 | : e.Message.Substring(0, lastIndexOfDot + 1); 59 | 60 | throw new XmlException(meaningfulMessage, e.InnerException, e.LineNumber - 1, e.LinePosition); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/ShowMeTheXaml.Avalonia.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | preview 5 | enable 6 | true 7 | ShowMeTheXaml.Avalonia 8 | SKProCH 9 | Package contains Avalonia control that can display xaml code from ShowMeTheXaml.Avalonia.Generator 10 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia 11 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia/blob/master/LICENSE 12 | https://github.com/AvaloniaUtils/ShowMeTheXaml.Avalonia.git 13 | Git 14 | avalonia avaloniaui 15 | ShowMeTheXaml 16 | netstandard2.1;netstandard2.0 17 | 18 | 19 | 20 | 21 | %(Filename) 22 | 23 | 24 | Designer 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/XamlDisplay.xaml: -------------------------------------------------------------------------------- 1 | 4 | 37 | 38 | 39 | 92 | 93 | 113 | -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/XamlDisplay.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using Avalonia; 5 | using Avalonia.Controls; 6 | using Avalonia.Controls.Primitives; 7 | using Avalonia.Input; 8 | using Avalonia.Interactivity; 9 | using Avalonia.LogicalTree; 10 | using Avalonia.Media; 11 | using Avalonia.Metadata; 12 | 13 | // ReSharper disable once CheckNamespace 14 | namespace ShowMeTheXaml 15 | { 16 | public class XamlDisplay : TemplatedControl 17 | { 18 | public static readonly StyledProperty XamlTextProperty = 19 | AvaloniaProperty.Register(nameof(XamlText)); 20 | 21 | public static readonly StyledProperty ContentProperty = 22 | ContentControl.ContentProperty.AddOwner(); 23 | 24 | public static readonly StyledProperty XamlButtonAlignmentProperty = 25 | AvaloniaProperty.Register(nameof(XamlButtonAlignment), AlignmentY.Bottom); 26 | 27 | public static readonly StyledProperty IsEditableProperty = 28 | AvaloniaProperty.Register("IsEditable", true); 29 | 30 | private IDisposable? _buttonClickHandler; 31 | private Popup? _popup; 32 | private string _uniqueId = null!; 33 | 34 | public XamlDisplay() 35 | { 36 | #if NETSTANDARD2_1_OR_GREATER 37 | IsEditable = RuntimeFeature.IsDynamicCodeSupported; 38 | #else 39 | IsEditable = false; 40 | #endif 41 | } 42 | 43 | public bool IsEditable 44 | { 45 | get => GetValue(IsEditableProperty); 46 | set => SetValue(IsEditableProperty, value); 47 | } 48 | 49 | public string UniqueId 50 | { 51 | get => _uniqueId; 52 | set 53 | { 54 | _uniqueId = value; 55 | Reset(); 56 | } 57 | } 58 | 59 | public string? XamlText 60 | { 61 | get => GetValue(XamlTextProperty); 62 | set => SetValue(XamlTextProperty, value); 63 | } 64 | 65 | [Content] 66 | public object? Content 67 | { 68 | get => GetValue(ContentProperty); 69 | set 70 | { 71 | if (GetValue(ContentProperty) is ILogical oldLogical) LogicalChildren.Remove(oldLogical); 72 | SetValue(ContentProperty, value); 73 | if (value is ILogical newLogical) LogicalChildren.Add(newLogical); 74 | } 75 | } 76 | 77 | public AlignmentY XamlButtonAlignment 78 | { 79 | get => GetValue(XamlButtonAlignmentProperty); 80 | set => SetValue(XamlButtonAlignmentProperty, value); 81 | } 82 | 83 | public Dictionary CurrentFileNamespaceAliases => 84 | XamlFilesNamespaceAliases[DisplayContent[UniqueId].FileName]; 85 | 86 | private void SourceXamlButtonOnPointerPressed(object sender, PointerPressedEventArgs e) 87 | { 88 | if (_popup != null) 89 | { 90 | _popup.IsOpen = !_popup.IsOpen; 91 | } 92 | } 93 | 94 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 95 | { 96 | base.OnApplyTemplate(e); 97 | _buttonClickHandler?.Dispose(); 98 | _popup = e.NameScope.Find("XamlPopup"); 99 | _buttonClickHandler = e.NameScope.Find("SourceXamlButton") 100 | .AddDisposableHandler(PointerPressedEvent, SourceXamlButtonOnPointerPressed); 101 | } 102 | 103 | public void Reset() 104 | { 105 | if (!DisplayContent.TryGetValue(UniqueId, out var xamlDisplayInstanceData)) return; 106 | if (!string.IsNullOrEmpty(XamlText)) 107 | { 108 | Content = AvaloniaRuntimeXamlLoaderHelper.Parse(xamlDisplayInstanceData.Data, 109 | CurrentFileNamespaceAliases); 110 | } 111 | 112 | XamlText = xamlDisplayInstanceData.Data; 113 | } 114 | 115 | #region ShowMeTheXaml static data 116 | 117 | private static Dictionary? _displayContent; 118 | private static Dictionary>? _xamlFilesNamespaceAliases; 119 | 120 | public static Dictionary DisplayContent 121 | { 122 | get => _displayContent 123 | ?? throw new NullReferenceException( 124 | "Install ShowMeTheXaml.Avalonia.Generator and call XamlDisplayInternalData.RegisterXamlDisplayData" + 125 | "Also check \"Getting started\" on our Github"); 126 | set => _displayContent = value; 127 | } 128 | 129 | public static Dictionary> XamlFilesNamespaceAliases 130 | { 131 | get => _xamlFilesNamespaceAliases 132 | ?? throw new NullReferenceException( 133 | "Install ShowMeTheXaml.Avalonia.Generator and call XamlDisplayInternalData.RegisterXamlDisplayData" + 134 | "Also check \"Getting started\" on our Github"); 135 | set => _xamlFilesNamespaceAliases = value; 136 | } 137 | 138 | #endregion 139 | } 140 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/XamlDisplayInstanceData.cs: -------------------------------------------------------------------------------- 1 | namespace ShowMeTheXaml; 2 | 3 | public readonly struct XamlDisplayInstanceData { 4 | public XamlDisplayInstanceData(string data, string fileName) { 5 | Data = data; 6 | FileName = fileName; 7 | } 8 | public string Data { get; } 9 | public string FileName { get; } 10 | } -------------------------------------------------------------------------------- /ShowMeTheXaml.Avalonia/XamlDisplayPopupBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Linq; 4 | using System.Threading; 5 | using System.Xml; 6 | using Avalonia; 7 | using Avalonia.Controls; 8 | using Avalonia.Controls.Primitives; 9 | using Avalonia.Interactivity; 10 | using Avalonia.LogicalTree; 11 | using Avalonia.Threading; 12 | using Avalonia.VisualTree; 13 | using Avalonia.Xaml.Interactivity; 14 | 15 | namespace ShowMeTheXaml; 16 | 17 | public class XamlDisplayPopupBehavior : Behavior { 18 | public static readonly DirectProperty ApplyButtonProperty 19 | = AvaloniaProperty.RegisterDirect("ApplyButton", 20 | o => o.ApplyButton, 21 | (o, v) => o.ApplyButton = v); 22 | public static readonly DirectProperty ResetButtonProperty 23 | = AvaloniaProperty.RegisterDirect("ResetButton", 24 | o => o.ResetButton, 25 | (o, v) => o.ResetButton = v); 26 | public static readonly DirectProperty MarkupErrorsTextBoxProperty 27 | = AvaloniaProperty.RegisterDirect("MarkupErrorsTextBlock", 28 | o => o.MarkupErrorsTextBox, 29 | (o, v) => o.MarkupErrorsTextBox = v); 30 | public static readonly DirectProperty MarkupTextBoxProperty 31 | = AvaloniaProperty.RegisterDirect("MarkupTextBox", 32 | o => o.MarkupTextBox, 33 | (o, v) => o.MarkupTextBox = v); 34 | private Button _applyButton = null!; 35 | private TextBox _markupErrorsTextBlock = null!; 36 | private TextBox _markupTextBox = null!; 37 | private Button _resetButton = null!; 38 | private Dictionary? _cachedNamespaceAliases; 39 | private IDisposable? _previewErrorsObservable; 40 | 41 | public Button ApplyButton { 42 | get => _applyButton; 43 | set => SetAndRaise(ApplyButtonProperty, ref _applyButton, value); 44 | } 45 | 46 | public Button ResetButton { 47 | get => _resetButton; 48 | set => SetAndRaise(ResetButtonProperty, ref _resetButton, value); 49 | } 50 | 51 | public TextBox MarkupErrorsTextBox { 52 | get => _markupErrorsTextBlock; 53 | set => SetAndRaise(MarkupErrorsTextBoxProperty, ref _markupErrorsTextBlock, value); 54 | } 55 | 56 | public TextBox MarkupTextBox { 57 | get => _markupTextBox; 58 | set => SetAndRaise(MarkupTextBoxProperty, ref _markupTextBox, value); 59 | } 60 | 61 | protected override void OnAttachedToVisualTree() { 62 | base.OnAttachedToVisualTree(); 63 | _previewErrorsObservable = MarkupTextBox.GetObservable(TextBox.TextProperty) 64 | .Throttle(TimeSpan.FromMilliseconds(500)) 65 | .ObserveOn(SynchronizationContext.Current) 66 | .Subscribe(s => LoadMarkupOrPrintErrors(s)); 67 | ResetButton.Click += ResetButtonOnClick; 68 | ApplyButton.Click += ApplyButtonOnClick; 69 | } 70 | 71 | protected override void OnDetachedFromVisualTree() { 72 | base.OnDetachedFromVisualTree(); 73 | _previewErrorsObservable?.Dispose(); 74 | ResetButton.Click -= ResetButtonOnClick; 75 | ApplyButton.Click -= ApplyButtonOnClick; 76 | } 77 | 78 | private void ApplyButtonOnClick(object sender, RoutedEventArgs e) { 79 | var result = LoadMarkupOrPrintErrors(MarkupTextBox.Text); 80 | if (result != null) { 81 | var xamlDisplay = LocateXamlDisplay(); 82 | xamlDisplay.Content = result; 83 | xamlDisplay.XamlText = MarkupTextBox.Text; 84 | } 85 | } 86 | 87 | private void ResetButtonOnClick(object sender, RoutedEventArgs e) { 88 | var xamlDisplay = LocateXamlDisplay(); 89 | xamlDisplay.Reset(); 90 | // Force reset text 91 | MarkupTextBox.Text = xamlDisplay.XamlText; 92 | LoadMarkupOrPrintErrors(xamlDisplay.XamlText!); 93 | } 94 | 95 | private object? LoadMarkupOrPrintErrors(string xaml) { 96 | try { 97 | _cachedNamespaceAliases ??= LocateXamlDisplay().CurrentFileNamespaceAliases; 98 | var result = AvaloniaRuntimeXamlLoaderHelper.Parse(xaml, _cachedNamespaceAliases); 99 | MarkupErrorsTextBox.Text = string.Empty; 100 | return result; 101 | } 102 | catch (XmlException e) { 103 | MarkupErrorsTextBox.Text = e.Message; 104 | } 105 | catch (Exception e) { 106 | MarkupErrorsTextBox.Text = e.Message; 107 | } 108 | return null; 109 | } 110 | 111 | private XamlDisplay LocateXamlDisplay() => 112 | AssociatedObject.FindLogicalAncestorOfType(); 113 | } --------------------------------------------------------------------------------