Browse Source

Generate asset resource info with paths

Nikita Tsukanov 7 years ago
parent
commit
343905ebb3
57 changed files with 759 additions and 124 deletions
  1. 27 0
      Avalonia.sln
  2. 2 1
      Avalonia.sln.DotSettings
  3. 8 0
      build/BuildTargets.targets
  4. 16 6
      packages/Avalonia/Avalonia.csproj
  5. 3 0
      packages/Avalonia/Avalonia.props
  6. 3 0
      packages/Avalonia/Avalonia.targets
  7. 3 0
      packages/Avalonia/AvaloniaBuildTasks.props
  8. 39 0
      packages/Avalonia/AvaloniaBuildTasks.targets
  9. 6 4
      samples/ControlCatalog/App.xaml
  10. 5 3
      samples/ControlCatalog/ControlCatalog.csproj
  11. 4 2
      samples/ControlCatalog/DecoratedWindow.xaml
  12. 2 1
      samples/ControlCatalog/MainView.xaml
  13. 5 3
      samples/ControlCatalog/MainWindow.xaml
  14. 3 1
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  15. 3 1
      samples/ControlCatalog/Pages/BorderPage.xaml
  16. 2 1
      samples/ControlCatalog/Pages/ButtonPage.xaml
  17. 2 1
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  18. 2 1
      samples/ControlCatalog/Pages/CalendarPage.xaml
  19. 3 1
      samples/ControlCatalog/Pages/CanvasPage.xaml
  20. 6 4
      samples/ControlCatalog/Pages/CarouselPage.xaml
  21. 2 1
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  22. 4 2
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  23. 2 1
      samples/ControlCatalog/Pages/DatePickerPage.xaml
  24. 3 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  25. 3 1
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  26. 3 1
      samples/ControlCatalog/Pages/DropDownPage.xaml
  27. 3 1
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  28. 8 6
      samples/ControlCatalog/Pages/ImagePage.xaml
  29. 3 2
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
  30. 3 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  31. 4 2
      samples/ControlCatalog/Pages/MenuPage.xaml
  32. 2 1
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  33. 4 2
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  34. 3 2
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  35. 4 2
      samples/ControlCatalog/Pages/SliderPage.xaml
  36. 13 2
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  37. 4 2
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  38. 4 2
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  39. 2 1
      samples/ControlCatalog/SideBar.xaml
  40. 3 0
      src/Avalonia.Base/Avalonia.Base.csproj
  41. 1 1
      src/Avalonia.Base/Platform/IAssetLoader.cs
  42. 58 0
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  43. 20 0
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  44. 19 0
      src/Avalonia.Build.Tasks/Extensions.cs
  45. 158 0
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  46. 20 0
      src/Avalonia.Build.Tasks/XamlFileInfo.cs
  47. 3 0
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  48. 6 16
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  49. 2 0
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  50. 2 1
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  51. 29 9
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  52. 25 0
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs
  53. 8 10
      src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs
  54. 9 13
      src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs
  55. 12 0
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs
  56. 164 8
      src/Shared/PlatformSupport/AssetLoader.cs
  57. 2 3
      tests/Avalonia.UnitTests/MockAssetLoader.cs

+ 27 - 0
Avalonia.sln

@@ -147,6 +147,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\Splat.props = build\Splat.props
 		build\System.Memory.props = build\System.Memory.props
 		build\XUnit.props = build\XUnit.props
+		build\BuildTargets.targets = build\BuildTargets.targets
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@@ -188,6 +189,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalon
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1687,6 +1690,30 @@ Global
 		{3C471044-3640-45E3-B1B2-16D2FF8399EE}.Release|iPhone.Build.0 = Release|Any CPU
 		{3C471044-3640-45E3-B1B2-16D2FF8399EE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{3C471044-3640-45E3-B1B2-16D2FF8399EE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 2 - 1
Avalonia.sln.DotSettings

@@ -35,4 +35,5 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
-	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String></wpf:ResourceDictionary>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 8 - 0
build/BuildTargets.targets

@@ -0,0 +1,8 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
+    <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
+  </PropertyGroup>
+  <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
+  <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>
+</Project>

+ 16 - 6
packages/Avalonia/Avalonia.csproj

@@ -5,6 +5,8 @@
 
   <ItemGroup>
       <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" EmbedReference="false" />
+      <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" />
+
   </ItemGroup>
 
   <PropertyGroup>
@@ -27,14 +29,22 @@
         <Visible>false</Visible>
         <BuildAction>None</BuildAction>
       </_PackageFiles>
-      <_PackageFiles Include="Avalonia.props">
-        <PackagePath>build/Avalonia.props</PackagePath>
-        <Visible>false</Visible>
-        <BuildAction>None</BuildAction>
-      </_PackageFiles>
     </ItemGroup>
   </Target>
-
+  <ItemGroup>
+    <Content Include="*.props">
+       <Pack>true</Pack>
+       <PackagePath>build\</PackagePath>
+    </Content>
+    <Content Include="*.targets">
+      <Pack>true</Pack>
+      <PackagePath>build\</PackagePath>
+    </Content>
+    <Content Include="../../src/Avalonia.Build.Tasks/bin/$(Configuration)/netstandard2.0/Avalonia.Build.Tasks.dll">
+      <Pack>true</Pack>
+      <PackagePath>tools\</PackagePath>
+    </Content>
+  </ItemGroup>
   <Import Project="..\..\build\SharedVersion.props" />
   <Import Project="..\..\build\NetFX.props" />
   <Import Project="..\..\build\CoreLibraries.props" />

+ 3 - 0
packages/Avalonia/Avalonia.props

@@ -2,5 +2,8 @@
   <PropertyGroup>
     <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
     <AvaloniaPreviewerNetFullToolPath>$(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe</AvaloniaPreviewerNetFullToolPath>
+    <AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\tools\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
+    <AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
   </PropertyGroup>
+  <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
 </Project>

+ 3 - 0
packages/Avalonia/Avalonia.targets

@@ -0,0 +1,3 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.targets"/>
+</Project>

+ 3 - 0
packages/Avalonia/AvaloniaBuildTasks.props

@@ -0,0 +1,3 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+
+</Project>

+ 39 - 0
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -0,0 +1,39 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild>
+    <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
+  </PropertyGroup>
+  
+  <UsingTask TaskName="GenerateAvaloniaResourcesTask"
+             AssemblyFile="$(AvaloniaBuildTasksLocation)"
+             />
+
+
+  <Target Name="AddAvaloniaResources" BeforeTargets="ResolveReferences">
+    <PropertyGroup>
+      <AvaloniaResourcesTemporaryFilePath Condition="'$(AvaloniaResourcesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/resources</AvaloniaResourcesTemporaryFilePath>
+    </PropertyGroup>
+    <ItemGroup>
+      <EmbeddedResource Include="$(AvaloniaResourcesTemporaryFilePath)">
+        <LogicalName>!AvaloniaResources</LogicalName>
+      </EmbeddedResource>
+    </ItemGroup>
+  </Target>
+  
+  <Target Name="GenerateAvaloniaResources" 
+          BeforeTargets="CoreCompile"
+          Inputs="@(AvaloniaResource);$(MSBuildProjectFile)"
+          Outputs="$(AvaloniaResourcesTemporaryFilePath)"
+          DependsOnTargets="$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources">
+    <GenerateAvaloniaResourcesTask
+      Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
+      Output="$(AvaloniaResourcesTemporaryFilePath)"
+      Root="$(MSBuildProjectDirectory)"
+      Resources="@(AvaloniaResource)"
+      EmbeddedResources="@(EmbeddedResources)"/>
+    <Exec 
+      Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
+      Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration)"/>
+
+  </Target>
+</Project>

+ 6 - 4
samples/ControlCatalog/App.xaml

@@ -1,7 +1,9 @@
-<Application xmlns="https://github.com/avaloniaui">
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.App">
   <Application.Styles>
-    <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
-    <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
+      <StyleInclude Source="res:asm:Avalonia.Themes.Default/DefaultTheme.xaml"/>
+      <StyleInclude Source="res:asm:Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
       <Setter Property="FontWeight" Value="Medium"/>
@@ -12,6 +14,6 @@
     <Style Selector="TextBlock.h3">
       <Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}"/>
     </Style>
-    <StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
+    <StyleInclude Source="/SideBar.xaml"/>
   </Application.Styles>
 </Application>

+ 5 - 3
samples/ControlCatalog/ControlCatalog.csproj

@@ -6,10 +6,11 @@
     <Compile Update="**\*.xaml.cs">
       <DependentUpon>%(Filename)</DependentUpon>
     </Compile>
-    <EmbeddedResource Include="**\*.xaml">
+    <AvaloniaResource Include="**\*.xaml">
       <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Assets\*" />
+    </AvaloniaResource>
+    <AvaloniaResource Include="Assets\*" />
+    <AvaloniaResource Include="Assets\Fonts\*" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
@@ -24,4 +25,5 @@
   </ItemGroup>
   
   <Import Project="..\..\build\Serilog.props" />
+  <Import Project="..\..\build\BuildTargets.targets" />
 </Project>

+ 4 - 2
samples/ControlCatalog/DecoratedWindow.xaml

@@ -1,6 +1,8 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="ControlCatalog.DecoratedWindow"
         Title="Avalonia Control Gallery"
-        Icon="resm:ControlCatalog.Assets.test_icon.ico"
+        Icon="/Assets/test_icon.ico"
         xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False">
     <Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
         <DockPanel  Grid.Column="1"  Grid.Row="1" >
@@ -30,4 +32,4 @@
         <Border Name="Bottom" Background="Blue" Grid.Row="2" Grid.Column="1"  />
         <Border Name="Left" Background="Blue"  Grid.Row="1" />
     </Grid>
-</Window>
+</Window>

+ 2 - 1
samples/ControlCatalog/MainView.xaml

@@ -1,6 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="ControlCatalog.MainView">
   <TabControl Classes="sidebar" Name="Sidebar">
     <TabControl.PageTransition>
       <CrossFade Duration="0.25"/>

+ 5 - 3
samples/ControlCatalog/MainWindow.xaml

@@ -1,6 +1,8 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
         Title="Avalonia Control Gallery"
-        Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
-        xmlns:local="clr-namespace:ControlCatalog">
+        Icon="/Assets/test_icon.ico?assembly=ControlCatalog"
+        xmlns:local="clr-namespace:ControlCatalog"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="ControlCatalog.MainWindow">
     <local:MainView/>
-</Window>
+</Window>

+ 3 - 1
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.AutoCompleteBoxPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">AutoCompleteBox</TextBlock>
     <TextBlock Classes="h2">A control into which the user can input text</TextBlock>

+ 3 - 1
samples/ControlCatalog/Pages/BorderPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.BorderPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Border</TextBlock>
     <TextBlock Classes="h2">A control which decorates a child with a border and background</TextBlock>

+ 2 - 1
samples/ControlCatalog/Pages/ButtonPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ButtonPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Button</TextBlock>
     <TextBlock Classes="h2">A button control</TextBlock>

+ 2 - 1
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ButtonSpinnerPage">
 
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">ButtonSpinner</TextBlock>

+ 2 - 1
samples/ControlCatalog/Pages/CalendarPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.CalendarPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Calendar</TextBlock>
     <TextBlock Classes="h2">A calendar control for selecting dates</TextBlock>

+ 3 - 1
samples/ControlCatalog/Pages/CanvasPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.CanvasPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Canvas</TextBlock>
     <TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>

+ 6 - 4
samples/ControlCatalog/Pages/CarouselPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.CarouselPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Carousel</TextBlock>
     <TextBlock Classes="h2">An items control that displays its items as pages that fill the control.</TextBlock>
@@ -11,9 +13,9 @@
         <Carousel.PageTransition>
           <PageSlide Duration="0.25" Orientation="Vertical" />
         </Carousel.PageTransition>
-        <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"/>
-        <Image Source="resm:ControlCatalog.Assets.hirsch-899118_640.jpg"/>
-        <Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg"/>
+        <Image Source="/Assets/delicate-arch-896885_640.jpg"/>
+        <Image Source="/Assets/hirsch-899118_640.jpg"/>
+        <Image Source="/Assets/maple-leaf-888807_640.jpg"/>
       </Carousel>
       <Button Name="right" VerticalAlignment="Center" Padding="20">
         <Path Data="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" Fill="Black"/>

+ 2 - 1
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.CheckBoxPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">CheckBox</TextBlock>
     <TextBlock Classes="h2">A check box control</TextBlock>

+ 4 - 2
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ContextMenuPage">
     <StackPanel Orientation="Vertical" Spacing="4">
         <TextBlock Classes="h1">Context Menu</TextBlock>
         <TextBlock Classes="h2">A right click menu that can be applied to any control.</TextBlock>
@@ -19,7 +21,7 @@
                         </MenuItem>
                         <MenuItem Header="Menu Item with _Icon">
                             <MenuItem.Icon>
-                                <Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
+                                <Image Source="/Assets/github_icon.png"/>
                             </MenuItem.Icon>
                         </MenuItem>
                         <MenuItem Header="Menu Item with _Checkbox">

+ 2 - 1
samples/ControlCatalog/Pages/DatePickerPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.DatePickerPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">DatePicker</TextBlock>
     <TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock>

+ 3 - 1
samples/ControlCatalog/Pages/DialogsPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.DialogsPage">
   <StackPanel Orientation="Vertical" Spacing="4" Margin="4">
       <Button Name="OpenFile">Open File</Button>
       <Button Name="SaveFile">Save File</Button>

+ 3 - 1
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.DragAndDropPage">
     <StackPanel Orientation="Vertical" Spacing="4">
         <TextBlock Classes="h1">Drag+Drop</TextBlock>
         <TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>

+ 3 - 1
samples/ControlCatalog/Pages/DropDownPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.DropDownPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">DropDown</TextBlock>
     <TextBlock Classes="h2">A drop-down list.</TextBlock>

+ 3 - 1
samples/ControlCatalog/Pages/ExpanderPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ExpanderPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Expander</TextBlock>
     <TextBlock Classes="h2">Expands to show nested content</TextBlock>

+ 8 - 6
samples/ControlCatalog/Pages/ImagePage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ImagePage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Image</TextBlock>
     <TextBlock Classes="h2">Displays an image</TextBlock>
@@ -9,28 +11,28 @@
                 Spacing="16">
       <StackPanel Orientation="Vertical">
         <TextBlock>No Stretch</TextBlock>
-        <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
+        <Image Source="/Assets/delicate-arch-896885_640.jpg"
                Width="100" Height="200"
                Stretch="None"/>
       </StackPanel>
 
       <StackPanel Orientation="Vertical">
         <TextBlock>Fill</TextBlock>
-        <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
+        <Image Source="/Assets/delicate-arch-896885_640.jpg"
                Width="100" Height="200"
                Stretch="Fill"/>
       </StackPanel>
 
       <StackPanel Orientation="Vertical">
         <TextBlock>Uniform</TextBlock>
-        <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
+        <Image Source="/Assets/delicate-arch-896885_640.jpg"
                 Width="100" Height="200"
                 Stretch="Uniform"/>
       </StackPanel>
 
       <StackPanel Orientation="Vertical">
         <TextBlock>UniformToFill</TextBlock>
-        <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"
+        <Image Source="/Assets/delicate-arch-896885_640.jpg"
                Width="100" Height="200"
                Stretch="UniformToFill"/>
       </StackPanel>
@@ -40,4 +42,4 @@
       <Image Name="Icon" Width="100" Height="200" Stretch="None" />
     </StackPanel>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 3 - 2
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.LayoutTransformControlPage">
   <DockPanel>
     <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto" Margin="16" DockPanel.Dock="Top">
       <TextBlock Grid.Column="0" Grid.Row="0">Rotation</TextBlock>
@@ -23,4 +24,4 @@
       </LayoutTransformControl>
     </Grid>
   </DockPanel>
-</UserControl>
+</UserControl>

+ 3 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ListBoxPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">ListBox</TextBlock>
     <TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock>

+ 4 - 2
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.MenuPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Menu</TextBlock>
     <TextBlock Classes="h2">A window menu</TextBlock>
@@ -19,7 +21,7 @@
                         </MenuItem>
                         <MenuItem Header="Menu Item with _Icon">
                             <MenuItem.Icon>
-                                <Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
+                                <Image Source="/Assets/github_icon.png"/>
                             </MenuItem.Icon>
                         </MenuItem>
                         <MenuItem Header="Menu Item with _Checkbox">

+ 2 - 1
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.NumericUpDownPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
     <TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>

+ 4 - 2
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ProgressBarPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">ProgressBar</TextBlock>
     <TextBlock Classes="h2">A progress bar control</TextBlock>
@@ -21,4 +23,4 @@
       </StackPanel>
     </StackPanel>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 3 - 2
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@@ -1,5 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.RadioButtonPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">RadioButton</TextBlock>
     <TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock>
@@ -37,4 +38,4 @@
       </StackPanel>
     </StackPanel>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 4 - 2
samples/ControlCatalog/Pages/SliderPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.SliderPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Slider</TextBlock>
     <TextBlock Classes="h2">A control that lets the user select from a range of values by moving a Thumb control along a Track.</TextBlock>
@@ -18,4 +20,4 @@
     </StackPanel>
 
   </StackPanel>
-</UserControl>
+</UserControl>

+ 13 - 2
samples/ControlCatalog/Pages/TextBoxPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.TextBoxPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">TextBox</TextBlock>
     <TextBlock Classes="h2">A control into which the user can input text</TextBlock>
@@ -33,11 +35,20 @@
                  Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
       </StackPanel>
         <StackPanel Orientation="Vertical" Spacing="8">
+            <TextBlock Classes="h2">resm fonts</TextBlock>
             <TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
                 <TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
                 <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
                 <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
         </StackPanel>
+        
+        <StackPanel Orientation="Vertical" Spacing="8">
+            <TextBlock Classes="h2">res fonts</TextBlock>
+            <TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="res:asm:ControlCatalog/Assets/Fonts#Source Sans Pro"/>
+            <TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="res:asm:ControlCatalog/Assets/Fonts#Source Sans Pro"/>
+            <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="res:asm:ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
+            <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="res:asm:ControlCatalog/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
+        </StackPanel>
       </StackPanel>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 4 - 2
samples/ControlCatalog/Pages/ToolTipPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ToolTipPage">
     <StackPanel Orientation="Vertical"
                 Spacing="4">
         <TextBlock Classes="h1">ToolTip</TextBlock>
@@ -38,4 +40,4 @@
             </Border>
         </Grid>
     </StackPanel>
-</UserControl>
+</UserControl>

+ 4 - 2
samples/ControlCatalog/Pages/TreeViewPage.xaml

@@ -1,4 +1,6 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.TreeViewPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">TreeView</TextBlock>
     <TextBlock Classes="h2">Displays a hierachical tree of data.</TextBlock>
@@ -16,4 +18,4 @@
       </TreeView>
     </StackPanel>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 2 - 1
samples/ControlCatalog/SideBar.xaml

@@ -1,5 +1,6 @@
 <Styles xmlns="https://github.com/avaloniaui"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="ControlCatalog.SideBar">
   <Style Selector="TabControl.sidebar">
     <Setter Property="Template">
       <ControlTemplate>

+ 3 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -6,6 +6,9 @@
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
     <IsPackable>false</IsPackable>
   </PropertyGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"/>
+  </ItemGroup>
   <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Binding.props" />
   <Import Project="..\..\build\Rx.props" />

+ 1 - 1
src/Avalonia.Base/Platform/IAssetLoader.cs

@@ -66,6 +66,6 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="uri">The URI.</param>
         /// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
-        IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri uri);
+        IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri);
     }
 }

+ 58 - 0
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+
+// ReSharper disable AssignNullToNotNullAttribute
+
+namespace Avalonia.Utilities
+{
+    #if !BUILDTASK
+    public 
+    #endif
+        static class AvaloniaResourcesIndexReaderWriter
+    {
+        private const int LastKnownVersion = 1;
+        public static List<AvaloniaResourcesIndexEntry> Read(Stream stream)
+        {
+            var ver = new BinaryReader(stream).ReadInt32();
+            if (ver > LastKnownVersion)
+                throw new Exception("Resources index format version is not known");            
+            var index = (AvaloniaResourcesIndex)
+                new DataContractSerializer(typeof(AvaloniaResourcesIndex)).ReadObject(stream);
+            return index.Entries;
+        }
+
+        public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)
+        {
+            new BinaryWriter(stream).Write(LastKnownVersion);
+            new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream,
+                new AvaloniaResourcesIndex()
+                {
+                    Entries = entries
+                });
+        }
+    }
+
+    [DataContract]
+    public class AvaloniaResourcesIndex
+    {       
+        [DataMember]
+        public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>();
+    }
+
+    [DataContract]
+    public class AvaloniaResourcesIndexEntry
+    {
+        [DataMember]
+        public string Path { get; set; }
+        
+        [DataMember]
+        public int Offset { get; set; }
+        
+        [DataMember]
+        public int Size { get; set; }
+    }
+}

+ 20 - 0
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
+        <DefineConstants>$(DefineConstants);BUILDTASK</DefineConstants>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
+        <Link>Shared/AvaloniaResourcesIndex.cs</Link>
+      </Compile>
+      <Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
+        <Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
+      </Compile>
+      <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" />
+    </ItemGroup>
+  <!-- Disable built-in Pack target -->
+  <Target Name="Pack"/>
+</Project>

+ 19 - 0
src/Avalonia.Build.Tasks/Extensions.cs

@@ -0,0 +1,19 @@
+using Microsoft.Build.Framework;
+
+namespace Avalonia.Build.Tasks
+{
+    public static class Extensions
+    {
+        public static void LogError(this IBuildEngine engine, string file, string message)
+        {
+            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "",
+                "Avalonia"));
+        }
+        
+        public static void LogWarning(this IBuildEngine engine, string file, string message)
+        {
+            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "",
+                "Avalonia"));
+        }
+    }
+}

+ 158 - 0
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Avalonia.Markup.Xaml.PortableXaml;
+using Avalonia.Utilities;
+using Microsoft.Build.Framework;
+using SPath=System.IO.Path;
+namespace Avalonia.Build.Tasks
+{
+    public class GenerateAvaloniaResourcesTask : ITask
+    {
+        [Required]
+        public ITaskItem[] Resources { get; set; }
+        [Required]
+        public string Root { get; set; }
+        [Required]
+        public string Output { get; set; }
+        [Required]
+        public ITaskItem[] EmbeddedResources { get; set; }
+
+        class Source
+        {
+            public string Path { get; set; }
+            public int Size { get; set; }
+            private byte[] _data;
+            private string _sourcePath;
+
+            public Source(string file, string root)
+            {
+                file = SPath.GetFullPath(file);
+                root = SPath.GetFullPath(root);
+                var fileUri = new Uri(file, UriKind.Absolute);
+                var rootUri = new Uri(root, UriKind.Absolute);
+                rootUri = new Uri(rootUri.ToString().TrimEnd('/') + '/');
+                Path = '/' + rootUri.MakeRelativeUri(fileUri).ToString().TrimStart('/');
+                _sourcePath = file;
+                Size = (int)new FileInfo(_sourcePath).Length;
+            }
+
+            public string SystemPath => _sourcePath ?? Path;
+
+            public Source(string path, byte[] data)
+            {
+                Path = path;
+                _data = data;
+                Size = data.Length;
+            }
+
+            public Stream Open()
+            {
+                if (_data != null)
+                    return new MemoryStream(_data, false);
+                return File.OpenRead(_sourcePath);
+            }
+
+            public string ReadAsString()
+            {
+                if (_data != null)
+                    return Encoding.UTF8.GetString(_data);
+                return File.ReadAllText(_sourcePath);
+            }
+        }
+
+        List<Source> BuildResourceSources() => Resources.Select(r => new Source(r.ItemSpec, Root)).ToList();
+
+        void Pack(Stream output, List<Source> sources)
+        {
+            var offsets = new Dictionary<Source, int>();
+            var coffset = 0;
+            foreach (var s in sources)
+            {
+                offsets[s] = coffset;
+                coffset += s.Size;
+            }
+            var index = sources.Select(s => new AvaloniaResourcesIndexEntry
+            {
+                Path = s.Path,
+                Size = s.Size,
+                Offset = offsets[s]
+            }).ToList();
+            var ms = new MemoryStream();
+            AvaloniaResourcesIndexReaderWriter.Write(ms, index);
+            new BinaryWriter(output).Write((int)ms.Length);
+            ms.Position = 0;
+            ms.CopyTo(output);
+            foreach (var s in sources)
+            {
+                using(var input = s.Open())
+                    input.CopyTo(output);
+            }
+        }
+
+        bool PreProcessXamlFiles(List<Source> sources)
+        {
+            var typeToXamlIndex = new Dictionary<string, string>(); 
+            
+            foreach (var s in sources.ToList())
+            {
+                if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml"))
+                {
+                    XamlFileInfo info;
+                    try
+                    {
+                        info = XamlFileInfo.Parse(s.ReadAsString());
+                    }
+                    catch(Exception e)
+                    {
+                        BuildEngine.LogError(s.SystemPath, "File doesn't contain valid XAML: " + e);
+                        return false;
+                    }
+
+                    if (info.XClass != null)
+                    {
+                        if (typeToXamlIndex.ContainsKey(info.XClass))
+                        {
+                            
+                            BuildEngine.LogError(s.SystemPath,
+                                $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
+                            return false;
+                        }
+                        typeToXamlIndex[info.XClass] = s.Path;
+                    }
+                }
+            }
+
+            var xamlInfo = new AvaloniaResourceXamlInfo
+            {
+                ClassToResourcePathIndex = typeToXamlIndex
+            };
+            var ms = new MemoryStream();
+            new DataContractSerializer(typeof(AvaloniaResourceXamlInfo)).WriteObject(ms, xamlInfo);
+            sources.Add(new Source("/!AvaloniaResourceXamlInfo", ms.ToArray()));
+            return true;
+        }
+        
+        public bool Execute()
+        {
+            foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml")))
+                BuildEngine.LogWarning(r.ItemSpec, "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
+            var resources = BuildResourceSources();
+
+            if (!PreProcessXamlFiles(resources))
+                return false;
+            var dir = Path.GetDirectoryName(Output);
+            Directory.CreateDirectory(dir);
+            using (var file = File.Create(Output))
+                Pack(file, resources);
+            return true;
+        }
+
+        public IBuildEngine BuildEngine { get; set; }
+        public ITaskHost HostObject { get; set; }
+    }
+}

+ 20 - 0
src/Avalonia.Build.Tasks/XamlFileInfo.cs

@@ -0,0 +1,20 @@
+using System.Xml.Linq;
+
+namespace Avalonia.Build.Tasks
+{
+    public class XamlFileInfo
+    {
+        public string XClass { get; set; }
+        
+        public static XamlFileInfo Parse(string data)
+        {
+            var xdoc = XDocument.Parse(data);
+            var xclass = xdoc.Root.Attribute(XName.Get("Class", "http://schemas.microsoft.com/winfx/2006/xaml"));
+            return new XamlFileInfo
+            {
+                XClass = xclass?.Value
+            };
+        }
+    }
+    
+}

+ 3 - 0
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@@ -13,7 +13,10 @@
     <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <AvaloniaResource Include="DefaultTheme.xaml"/>
+    <AvaloniaResource Include="Accents/*.xaml"/>
   </ItemGroup>  
   <Import Project="..\..\build\EmbedXaml.props" />
+  <Import Project="..\..\build\BuildTargets.targets"/>
   <Import Project="..\..\build\Rx.props" />
 </Project>

+ 6 - 16
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@@ -32,11 +32,11 @@ namespace Avalonia.Media.Fonts
         /// <returns></returns>
         private static IEnumerable<Uri> GetFontAssetsByLocation(Uri location)
         {
-            var availableAssets = s_assetLoader.GetAssets(location);
+            var availableAssets = s_assetLoader.GetAssets(location, null);
 
-            var matchingAssets = availableAssets.Where(x => x.absolutePath.EndsWith(".ttf") || x.absolutePath.EndsWith(".otf"));
+            var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"));
 
-            return matchingAssets.Select(x => GetAssetUri(x.absolutePath, x.assembly));
+            return matchingAssets;
         }
 
         /// <summary>
@@ -48,25 +48,15 @@ namespace Avalonia.Media.Fonts
         /// <returns></returns>
         private static IEnumerable<Uri> GetFontAssetsByFileName(Uri location, string fileName)
         {
-            var availableResources = s_assetLoader.GetAssets(location);
+            var availableResources = s_assetLoader.GetAssets(location, null);
 
             var compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
 
             var matchingResources =
-                availableResources.Where(x => x.absolutePath.Contains(compareTo) && (x.absolutePath.EndsWith(".ttf") || x.absolutePath.EndsWith(".otf")));
+                availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")));
 
-            return matchingResources.Select(x => GetAssetUri(x.absolutePath, x.assembly));
+            return matchingResources;
         }
 
-        /// <summary>
-        /// Returns a <see cref="Uri"/> for a font asset that follows the resm scheme
-        /// </summary>
-        /// <param name="absolutePath"></param>
-        /// <param name="assembly"></param>
-        /// <returns></returns>
-        private static Uri GetAssetUri(string absolutePath, Assembly assembly)
-        {
-            return new Uri("resm:" + absolutePath + "?assembly=" + assembly.GetName().Name, UriKind.RelativeOrAbsolute);
-        }
     }
 }

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -9,6 +9,7 @@
   </PropertyGroup>
     <ItemGroup>
         <Compile Include="AvaloniaXamlLoader.cs" />
+        <Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
         <Compile Include="Converters\MemberSelectorTypeConverter.cs" />
         <Compile Include="Converters\ParseTypeConverter.cs" />
         <Compile Include="Converters\SetterValueTypeConverter.cs" />
@@ -18,6 +19,7 @@
         <Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
         <Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
         <Compile Include="Parsers\PropertyParser.cs" />
+        <Compile Include="PortableXaml\AvaloniaResourceXamlInfo.cs" />
         <Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
         <Compile Include="PortableXaml\AttributeExtensions.cs" />
         <Compile Include="PortableXaml\AvaloniaMemberAttributeProvider.cs" />

+ 2 - 1
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@@ -37,7 +37,8 @@ namespace Avalonia.Markup.Xaml
             { typeof(Selector), typeof(SelectorTypeConverter) },
             { typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
             { typeof(WindowIcon), typeof(IconTypeConverter) },
-            { typeof(CultureInfo), typeof(CultureInfoConverter)}
+            { typeof(CultureInfo), typeof(CultureInfoConverter)},
+            { typeof(Uri), typeof(AvaloniaUriTypeConverter)}
         };
 
         /// <summary>

+ 29 - 9
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -10,6 +10,8 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
 using System.Text;
 
 namespace Avalonia.Markup.Xaml
@@ -85,7 +87,7 @@ namespace Avalonia.Markup.Xaml
                     "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
             }
 
-            foreach (var uri in GetUrisFor(type))
+            foreach (var uri in GetUrisFor(assetLocator, type))
             {
                 if (assetLocator.Exists(uri))
                 {
@@ -134,18 +136,14 @@ namespace Avalonia.Markup.Xaml
             var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
             using (var stream = asset.stream)
             {
+                var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
                 try
                 {
-                    return Load(stream, asset.assembly, rootInstance, uri);
+                    return Load(stream, asset.assembly, rootInstance, absoluteUri);
                 }
                 catch (Exception e)
                 {
-                    var uriString = uri.ToString();
-                    if (!uri.IsAbsoluteUri)
-                    {
-                        uriString = new Uri(baseUri, uri).AbsoluteUri;
-                    }
-                    throw new XamlLoadException("Error loading xaml at " + uriString + ": " + e.Message, e);
+                    throw new XamlLoadException("Error loading xaml at " + absoluteUri + ": " + e.Message, e);
                 }
             }
         }
@@ -221,15 +219,37 @@ namespace Avalonia.Markup.Xaml
             return LoadFromReader(reader, null);
         }
 
+
+        private static readonly DataContractSerializer s_xamlInfoSerializer =
+            new DataContractSerializer(typeof(AvaloniaResourceXamlInfo));
         /// <summary>
         /// Gets the URI for a type.
         /// </summary>
+        /// <param name="assetLocator"></param>
         /// <param name="type">The type.</param>
         /// <returns>The URI.</returns>
-        private static IEnumerable<Uri> GetUrisFor(Type type)
+        private static IEnumerable<Uri> GetUrisFor(IAssetLoader assetLocator, Type type)
         {
             var asm = type.GetTypeInfo().Assembly.GetName().Name;
+            var xamlInfoUri = new Uri($"res:asm:{asm}/!AvaloniaResourceXamlInfo");
             var typeName = type.FullName;
+            if (typeName == null)
+                throw new ArgumentException("Type doesn't have a FullName");
+            
+            if (assetLocator.Exists(xamlInfoUri))
+            {
+                using (var xamlInfoStream = assetLocator.Open(xamlInfoUri))
+                {
+                    var xamlInfo = (AvaloniaResourceXamlInfo)s_xamlInfoSerializer.ReadObject(xamlInfoStream);
+                    if (xamlInfo.ClassToResourcePathIndex.TryGetValue(typeName, out var rv) == true)
+                    {
+                        yield return new Uri($"res:asm:{asm}{rv}");
+                        yield break;
+                    }
+                }
+            }
+            
+            
             yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
             yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
         }

+ 25 - 0
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs

@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+    public class AvaloniaUriTypeConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            var s = value as string;
+            if (s == null)
+                return null;
+            //On Unix Uri tries to interpret paths starting with "/" as file Uris
+            if (s.StartsWith("/"))
+                return new Uri(s, UriKind.Relative);
+            return new Uri(s);
+        }
+    }
+}

+ 8 - 10
src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs

@@ -20,18 +20,16 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            var uri = new Uri((string)value, UriKind.RelativeOrAbsolute);
-            var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file";
+            var s = (string)value;
+            var uri = s.StartsWith("/")
+                ? new Uri(s, UriKind.Relative)
+                : new Uri(s, UriKind.RelativeOrAbsolute);
 
-            switch (scheme)
-            {
-                case "file":
-                    return new Bitmap((string)value);
+            if(uri.IsAbsoluteUri && uri.IsFile)
+                return new Bitmap(uri.LocalPath);
 
-                default:
-                    var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
-                    return new Bitmap(assets.Open(uri, context.GetBaseUri()));
-            }
+            var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            return new Bitmap(assets.Open(uri, context.GetBaseUri()));
         }
     }
 }

+ 9 - 13
src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs

@@ -36,20 +36,16 @@ namespace Avalonia.Markup.Xaml.Converters
             throw new NotSupportedException();
         }
 
-        private WindowIcon CreateIconFromPath(ITypeDescriptorContext context, string path)
+        private WindowIcon CreateIconFromPath(ITypeDescriptorContext context, string s)
         {
-            var uri = new Uri(path, UriKind.RelativeOrAbsolute);
-            var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file";
-
-            switch (scheme)
-            {
-                case "file":
-                    return new WindowIcon(path);
-
-                default:
-                    var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
-                    return new WindowIcon(assets.Open(uri, context.GetBaseUri()));
-            }
+            var uri = s.StartsWith("/")
+                ? new Uri(s, UriKind.Relative)
+                : new Uri(s, UriKind.RelativeOrAbsolute);
+            
+            if(uri.IsAbsoluteUri && uri.IsFile)
+                return new WindowIcon(uri.LocalPath);
+            var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            return new WindowIcon(assets.Open(uri, context.GetBaseUri()));
         }
     }
 }

+ 12 - 0
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs

@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace Avalonia.Markup.Xaml.PortableXaml
+{
+    [DataContract]
+    class AvaloniaResourceXamlInfo
+    {
+        [DataMember]
+        public Dictionary<string, string> ClassToResourcePathIndex { get; set; } = new Dictionary<string, string>();
+    }
+}

+ 164 - 8
src/Shared/PlatformSupport/AssetLoader.cs

@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Shared.PlatformSupport
 {
@@ -15,6 +16,7 @@ namespace Avalonia.Shared.PlatformSupport
     /// </summary>
     public class AssetLoader : IAssetLoader
     {
+        private const string AvaloniaResourceName = "!AvaloniaResources";
         private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
             = new Dictionary<string, AssemblyDescriptor>();
 
@@ -99,19 +101,50 @@ namespace Avalonia.Shared.PlatformSupport
         /// Gets all assets of a folder and subfolders that match specified uri.
         /// </summary>
         /// <param name="uri">The URI.</param>
+        /// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
         /// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
-        public IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri uri)
+        public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
         {
-            var assembly = GetAssembly(uri);
+            if (uri.IsAbsoluteUri && uri.Scheme == "resm")
+            {
+                var assembly = GetAssembly(uri);
+
+                return assembly?.Resources.Where(x => x.Key.Contains(uri.AbsolutePath))
+                           .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
+                       Enumerable.Empty<Uri>();
+            }
 
-            return assembly?.Resources.Where(x => x.Key.Contains(uri.AbsolutePath))
-                       .Select(x => (x.Key, x.Value.Assembly)) ??
-                   Enumerable.Empty<(string AbsolutePath, Assembly Assembly)>();
+            uri = EnsureAbsolute(uri, baseUri);
+            if (uri.Scheme == "res")
+            {
+                var (asm, path) = GetResAsmAndPath(uri);
+                if (asm?.AvaloniaResources == null)
+                    return Enumerable.Empty<Uri>();
+                path = path.TrimEnd('/') + '/';
+                return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path))
+                    .Select(x => new Uri($"res:asm:{asm.Name}{x.Key}"));
+            }
+
+            return Enumerable.Empty<Uri>();
         }
 
-        private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
+        private Uri EnsureAbsolute(Uri uri, Uri baseUri)
         {
-            if (!uri.IsAbsoluteUri || uri.Scheme == "resm")
+            if (uri.IsAbsoluteUri)
+                return uri;
+            if(baseUri == null)
+                throw new ArgumentException($"Relative uri {uri} without base url");
+            if (!baseUri.IsAbsoluteUri)
+                throw new ArgumentException($"Base uri {baseUri} is relative");
+            if (baseUri.Scheme == "resm")
+                throw new ArgumentException(
+                    $"Relative uris for 'resm' scheme aren't supported; {baseUri} uses resm");
+            return new Uri(baseUri, uri);
+        }
+        
+        private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
+        {           
+            if (uri.IsAbsoluteUri && uri.Scheme == "resm")
             {
                 var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultAssembly;
 
@@ -128,9 +161,44 @@ namespace Avalonia.Shared.PlatformSupport
                 asm.Resources.TryGetValue(resourceKey, out rv);
                 return rv;
             }
-            throw new ArgumentException($"Invalid uri, see https://github.com/AvaloniaUI/Avalonia/issues/282#issuecomment-166982104", nameof(uri));
+
+            uri = EnsureAbsolute(uri, baseUri);
+
+            if (uri.Scheme == "res")
+            {
+                var (asm, path) = GetResAsmAndPath(uri);
+                if (asm.AvaloniaResources == null)
+                    return null;
+                asm.AvaloniaResources.TryGetValue(path, out var desc);
+                return desc;
+            }
+
+            throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
         }
 
+        private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
+        {
+            string path = null, asmPart = null;
+            if (!uri.AbsolutePath.StartsWith("asm:"))
+                path = uri.AbsolutePath;
+            else
+            {
+                var sp = uri.AbsolutePath.Split(new[] {'/'}, 2);
+                asmPart = sp[0].Substring(4);
+                path = '/' + sp[1];
+            }
+
+            var asm = (asmPart == null ? null : GetAssembly(asmPart)) ?? _defaultAssembly;
+            if (asm == null)
+            {
+                throw new ArgumentException(
+                    "No default assembly, entry assembly or explicit assembly specified; " +
+                    "don't know where to look up for the resource, try specifying assembly explicitly.");
+            }
+
+            return (asm, path);
+        }
+        
         private AssemblyDescriptor GetAssembly(Uri uri)
         {
             if (uri != null)
@@ -213,6 +281,80 @@ namespace Avalonia.Shared.PlatformSupport
 
             public Assembly Assembly => _asm;
         }
+        
+        private class AvaloniaResourceDescriptor : IAssetDescriptor
+        {
+            private readonly int _offset;
+            private readonly int _length;
+            public Assembly Assembly { get; }
+
+            public AvaloniaResourceDescriptor(Assembly asm, int offset, int length)
+            {
+                _offset = offset;
+                _length = length;
+                Assembly = asm;
+            }
+            
+            public Stream GetStream()
+            {
+                return new SlicedStream(Assembly.GetManifestResourceStream(AvaloniaResourceName), _offset, _length);
+            }
+        }
+        
+        class SlicedStream : Stream
+            {
+                private readonly Stream _baseStream;
+                private readonly int _from;
+
+                public SlicedStream(Stream baseStream, int from, int length)
+                {
+                    Length = length;
+                    _baseStream = baseStream;
+                    _from = from;
+                    _baseStream.Position = from;
+                }
+                public override void Flush()
+                {
+                }
+
+                public override int Read(byte[] buffer, int offset, int count)
+                {
+                    return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position));
+                }
+
+                public override long Seek(long offset, SeekOrigin origin)
+                {
+                    if (origin == SeekOrigin.Begin)
+                        Position = offset;
+                    if (origin == SeekOrigin.End)
+                        Position = _from + Length + offset;
+                    if (origin == SeekOrigin.Current)
+                        Position = Position + offset;
+                    return Position;
+                }
+
+                public override void SetLength(long value) => throw new NotSupportedException();
+
+                public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+                public override bool CanRead => true;
+                public override bool CanSeek => _baseStream.CanRead;
+                public override bool CanWrite => false;
+                public override long Length { get; }
+                public override long Position
+                {
+                    get => _baseStream.Position - _from;
+                    set => _baseStream.Position = value + _from;
+                }
+
+                protected override void Dispose(bool disposing)
+                {
+                    if (disposing)
+                        _baseStream.Dispose();
+                }
+
+                public override void Close() => _baseStream.Close();
+            }
 
         private class AssemblyDescriptor
         {
@@ -225,11 +367,25 @@ namespace Avalonia.Shared.PlatformSupport
                     Resources = assembly.GetManifestResourceNames()
                         .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
                     Name = assembly.GetName().Name;
+                    using (var resources = assembly.GetManifestResourceStream(AvaloniaResourceName))
+                    {
+                        if (resources != null)
+                        {
+                            Resources.Remove(AvaloniaResourceName);
+
+                            var indexLength = new BinaryReader(resources).ReadInt32();
+                            var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
+                            var baseOffset = indexLength + 4;
+                            AvaloniaResources = index.ToDictionary(r => "/" + r.Path.TrimStart('/'), r => (IAssetDescriptor)
+                                new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
+                        }
+                    }
                 }
             }
 
             public Assembly Assembly { get; }
             public Dictionary<string, IAssetDescriptor> Resources { get; }
+            public Dictionary<string, IAssetDescriptor> AvaloniaResources { get; }
             public string Name { get; }
         }
     }

+ 2 - 3
tests/Avalonia.UnitTests/MockAssetLoader.cs

@@ -32,10 +32,9 @@ namespace Avalonia.UnitTests
             return (Open(uri, baseUri), (Assembly)null);
         }
 
-        public IEnumerable<(string absolutePath, Assembly assembly)> GetAssets(Uri uri)
+        public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
         {
-            return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath))
-                .Select(x => (x.AbsolutePath, Assembly.GetEntryAssembly()));
+            return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath));
         }
 
         public void SetDefaultAssembly(Assembly asm)