Browse Source

Merge branch 'master' of https://github.com/AvaloniaUI/Avalonia into feature/flowDirectionImpl

pull/7810/head
daniel mayost 5 years ago
parent
commit
2d4d279bfe
  1. 27
      Avalonia.sln
  2. 3
      native/Avalonia.Native/src/OSX/controlhost.mm
  3. 9
      native/Avalonia.Native/src/OSX/window.mm
  4. 4
      packages/Avalonia/Avalonia.csproj
  5. 3
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  6. 3
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  7. 16
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  8. 11
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  9. 10
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  10. 52
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  11. 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  12. 10
      src/Avalonia.Base/StyledPropertyBase.cs
  13. 70
      src/Avalonia.Base/Utilities/UriExtensions.cs
  14. 2
      src/Avalonia.Controls/Button.cs
  15. 9
      src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
  16. 10
      src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs
  17. 53
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  18. 9
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  19. 2
      src/Avalonia.Controls/ProgressBar.cs
  20. 5
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  21. 5
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
  22. 43
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  23. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  24. 62
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  25. 24
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
  26. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
  27. 13
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  28. 2
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  29. 241
      src/Avalonia.PlatformSupport/AssetLoader.cs
  30. 5
      src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj
  31. 42
      src/Avalonia.PlatformSupport/Internal/AssemblyDescriptor.cs
  32. 42
      src/Avalonia.PlatformSupport/Internal/AssemblyDescriptorResolver.cs
  33. 52
      src/Avalonia.PlatformSupport/Internal/AssetDescriptor.cs
  34. 6
      src/Avalonia.PlatformSupport/Internal/Constants.cs
  35. 59
      src/Avalonia.PlatformSupport/Internal/SlicedStream.cs
  36. 13
      src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs
  37. 55
      src/Avalonia.Styling/Controls/Metadata/TemplatePartAttribute.cs
  38. 2
      src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml
  39. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  40. 3
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  41. 2
      src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
  42. 150
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  43. 26
      src/Avalonia.X11/X11Window.cs
  44. 8
      src/Windows/Avalonia.Win32/WindowImpl.cs
  45. 109
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  46. 29
      tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs
  47. 23
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  48. 62
      tests/Avalonia.PlatformSupport.UnitTests/AssetLoaderTests.cs
  49. 24
      tests/Avalonia.PlatformSupport.UnitTests/Avalonia.PlatformSupport.UnitTests.csproj
  50. 5
      tests/Avalonia.UnitTests/MockAssetLoader.cs
  51. 45
      tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs

27
Avalonia.sln

@ -234,6 +234,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.PlatformSupport.UnitTests", "tests\Avalonia.PlatformSupport.UnitTests\Avalonia.PlatformSupport.UnitTests.csproj", "{CE910927-CE5A-456F-BC92-E4C757354A5C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -2212,6 +2214,30 @@ Global
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhone.Build.0 = Release|Any CPU {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhone.Build.0 = Release|Any CPU
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|iPhone.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|iPhone.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|Any CPU.Build.0 = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhone.ActiveCfg = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhone.Build.0 = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -2276,6 +2302,7 @@ Global
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CE910927-CE5A-456F-BC92-E4C757354A5C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

3
native/Avalonia.Native/src/OSX/controlhost.mm

@ -36,7 +36,10 @@ public:
virtual void DestroyDefaultChild(void* child) override virtual void DestroyDefaultChild(void* child) override
{ {
// ARC will release the object for us // ARC will release the object for us
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"
(__bridge_transfer NSView*) child; (__bridge_transfer NSView*) child;
#pragma clang diagnostic pop
} }
}; };

9
native/Avalonia.Native/src/OSX/window.mm

@ -457,7 +457,8 @@ public:
} }
point = ConvertPointY(point); point = ConvertPointY(point);
auto viewPoint = [Window convertScreenToBase:ToNSPoint(point)]; NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
auto viewPoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
*ret = [View translateLocalPoint:ToAvnPoint(viewPoint)]; *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)];
@ -477,7 +478,8 @@ public:
} }
auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]); auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]);
auto cocoaScreenPoint = [Window convertBaseToScreen:cocoaViewPoint]; NSRect convertRect = [Window convertRectToScreen:NSMakeRect(cocoaViewPoint.x, cocoaViewPoint.y, 0.0, 0.0)];
auto cocoaScreenPoint = NSPointFromCGPoint(NSMakePoint(convertRect.origin.x, convertRect.origin.y));
*ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint)); *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint));
return S_OK; return S_OK;
@ -573,7 +575,8 @@ public:
if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited) if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
|| (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged)))
{ {
auto nspoint = [Window convertBaseToScreen: ToNSPoint(point)]; NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
CGPoint cgpoint = NSPointToCGPoint(nspoint); CGPoint cgpoint = NSPointToCGPoint(nspoint);
auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft); auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
nsevent = [NSEvent eventWithCGEvent: cgevent]; nsevent = [NSEvent eventWithCGEvent: cgevent];

4
packages/Avalonia/Avalonia.csproj

@ -8,7 +8,9 @@
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" /> <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" > <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" >
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
</ProjectReference>
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

3
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -21,8 +21,9 @@
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''"> <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
<RunAOTCompilation>False</RunAOTCompilation> <RunAOTCompilation>True</RunAOTCompilation>
<EnableLLVM>True</EnableLLVM> <EnableLLVM>True</EnableLLVM>
<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

3
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -157,7 +157,8 @@ namespace ControlCatalog.Pages
(button = new Button (button = new Button
{ {
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
Content = "Click to close" Content = "Click to close",
IsDefault = true
}) })
} }
}, },

16
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -2,18 +2,22 @@ using System;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Runtime;
using Android.Util; using Android.Util;
using Android.Views; using Android.Views;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Android namespace Avalonia.Android
{ {
public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{ {
bool _invalidateQueued; bool _invalidateQueued;
readonly object _lock = new object(); readonly object _lock = new object();
private readonly Handler _handler; private readonly Handler _handler;
IntPtr IPlatformHandle.Handle =>
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, Holder.Surface.Handle);
public InvalidationAwareSurfaceView(Context context) : base(context) public InvalidationAwareSurfaceView(Context context) : base(context)
{ {
@ -25,7 +29,7 @@ namespace Avalonia.Android
{ {
lock (_lock) lock (_lock)
{ {
if(_invalidateQueued) if (_invalidateQueued)
return; return;
_handler.Post(() => _handler.Post(() =>
{ {
@ -70,7 +74,7 @@ namespace Avalonia.Android
public void SurfaceDestroyed(ISurfaceHolder holder) public void SurfaceDestroyed(ISurfaceHolder holder)
{ {
Log.Info("AVALONIA", "Surface Destroyed"); Log.Info("AVALONIA", "Surface Destroyed");
} }
protected void DoDraw() protected void DoDraw()
@ -83,5 +87,9 @@ namespace Avalonia.Android
} }
protected abstract void Draw(); protected abstract void Draw();
public string HandleDescriptor => "SurfaceView"; public string HandleDescriptor => "SurfaceView";
public PixelSize Size => new PixelSize(Holder.SurfaceFrame.Width(), Holder.SurfaceFrame.Height());
public double Scaling => Resources.DisplayMetrics.Density;
} }
} }

11
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Avalonia.Android.OpenGL; using Avalonia.Android.OpenGL;
@ -38,11 +37,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
GetAvaloniaPointFromEvent); GetAvaloniaPointFromEvent);
_gl = GlPlatformSurface.TryCreate(this); _gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this); _framebuffer = new FramebufferManager(this);
RenderScaling = (int)_view.Resources.DisplayMetrics.Density; RenderScaling = (int)_view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
@ -77,7 +75,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view; public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer }; public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) => public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseDeferredRendering AndroidPlatform.Options.UseDeferredRendering
@ -216,10 +214,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, _view.Holder.Surface.Handle);
public PixelSize Size => new PixelSize(_view.Holder.SurfaceFrame.Width(), _view.Holder.SurfaceFrame.Height()); public PixelSize Size => _view.Size;
public double Scaling => RenderScaling; public double Scaling => RenderScaling;

10
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -11,12 +11,18 @@
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<DebugType>portable</DebugType> <DebugType>portable</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<AndroidResource Include="..\..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''"> <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
<RunAOTCompilation>True</RunAOTCompilation> <RunAOTCompilation>True</RunAOTCompilation>
<EnableLLVM>True</EnableLLVM> <EnableLLVM>True</EnableLLVM>
<!--<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot>--> <AndroidEnableProfiledAot>True</AndroidEnableProfiledAot>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

52
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -1,9 +1,10 @@
using System; using System;
using Android.App; using Android.App;
using Android.Content.PM; using Android.Content.PM;
using Android.OS;
using Avalonia.Android; using Avalonia.Android;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input.TextInput;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
@ -14,22 +15,15 @@ namespace Avalonia.AndroidTestApplication
[Activity(Label = "Main", [Activity(Label = "Main",
MainLauncher = true, MainLauncher = true,
Icon = "@drawable/icon", Icon = "@drawable/icon",
Theme = "@style/Theme.AppCompat.NoActionBar", Theme = "@style/Theme.AppCompat.NoActionBar",
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize,
LaunchMode = LaunchMode.SingleInstance/*, LaunchMode = LaunchMode.SingleInstance/*,
ScreenOrientation = ScreenOrientation.Landscape*/)] ScreenOrientation = ScreenOrientation.Landscape*/)]
public class MainBaseActivity : AvaloniaActivity public class MainActivity : AvaloniaActivity<App>
{ {
protected override void OnCreate(Bundle savedInstanceState) protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{ {
if (Avalonia.Application.Current == null) return base.CustomizeAppBuilder(builder);
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
}
base.OnCreate(savedInstanceState);
Content = App.CreateSimpleWindow();
} }
} }
@ -37,13 +31,17 @@ namespace Avalonia.AndroidTestApplication
{ {
public override void Initialize() public override void Initialize()
{ {
Styles.Add(new DefaultTheme()); Styles.Add(new SimpleTheme(new Uri("avares://Avalonia.AndroidTestApplication")));
}
var baseLight = (IStyle)AvaloniaXamlLoader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
Styles.Add(baseLight);
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
{
singleViewLifetime.MainView = CreateSimpleWindow();
}
base.OnFrameworkInitializationCompleted();
} }
// This provides a simple UI tree for testing input handling, drawing, etc // This provides a simple UI tree for testing input handling, drawing, etc
@ -76,12 +74,12 @@ namespace Avalonia.AndroidTestApplication
Foreground = Brushes.Black Foreground = Brushes.Black
}, },
CreateTextBox(Input.TextInput.TextInputContentType.Normal), CreateTextBox(TextInputContentType.Normal),
CreateTextBox(Input.TextInput.TextInputContentType.Password), CreateTextBox(TextInputContentType.Password),
CreateTextBox(Input.TextInput.TextInputContentType.Email), CreateTextBox(TextInputContentType.Email),
CreateTextBox(Input.TextInput.TextInputContentType.Url), CreateTextBox(TextInputContentType.Url),
CreateTextBox(Input.TextInput.TextInputContentType.Phone), CreateTextBox(TextInputContentType.Digits),
CreateTextBox(Input.TextInput.TextInputContentType.Number), CreateTextBox(TextInputContentType.Number),
} }
} }
}; };
@ -89,16 +87,16 @@ namespace Avalonia.AndroidTestApplication
return window; return window;
} }
private static TextBox CreateTextBox(Input.TextInput.TextInputContentType contentType) private static TextBox CreateTextBox(TextInputContentType contentType)
{ {
var textBox = new TextBox() var textBox = new TextBox()
{ {
Margin = new Thickness(20, 10), Margin = new Thickness(20, 10),
Watermark = contentType.ToString(), Watermark = contentType.ToString(),
BorderThickness = new Thickness(3), BorderThickness = new Thickness(3),
FontSize = 20 FontSize = 20,
[TextInputOptions.ContentTypeProperty] = contentType
}; };
textBox.TextInputOptionsQuery += (s, e) => { e.ContentType = contentType; };
return textBox; return textBox;
} }

1
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -11,4 +11,5 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Visuals, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Visuals, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.PlatformSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]

10
src/Avalonia.Base/StyledPropertyBase.cs

@ -159,9 +159,9 @@ namespace Avalonia
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data) public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
{ {
vistor.Visit(this, ref data); visitor.Visit(this, ref data);
} }
/// <summary> /// <summary>
@ -242,11 +242,5 @@ namespace Avalonia
_ = type ?? throw new ArgumentNullException(nameof(type)); _ = type ?? throw new ArgumentNullException(nameof(type));
return GetMetadata(type).DefaultValue; return GetMetadata(type).DefaultValue;
} }
[DebuggerHidden]
private Func<IAvaloniaObject, TValue, TValue> Cast<THost>(Func<THost, TValue, TValue> validate)
{
return (o, v) => validate((THost)o, v);
}
} }
} }

70
src/Avalonia.Base/Utilities/UriExtensions.cs

@ -0,0 +1,70 @@
using System;
namespace Avalonia.Utilities;
internal static class UriExtensions
{
public static bool IsAbsoluteResm(this Uri uri) =>
uri.IsAbsoluteUri && uri.IsResm();
public static bool IsResm(this Uri uri) => uri.Scheme == "resm";
public static bool IsAvares(this Uri uri) => uri.Scheme == "avares";
public static Uri EnsureAbsolute(this Uri uri, Uri? baseUri)
{
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.IsResm())
throw new ArgumentException(
$"Relative uris for 'resm' scheme aren't supported; {baseUri} uses resm");
return new Uri(baseUri, uri);
}
public static string GetUnescapeAbsolutePath(this Uri uri) =>
Uri.UnescapeDataString(uri.AbsolutePath);
public static string GetUnescapeAbsoluteUri(this Uri uri) =>
Uri.UnescapeDataString(uri.AbsoluteUri);
public static string GetAssemblyNameFromQuery(this Uri uri)
{
const string assembly = "assembly";
var query = Uri.UnescapeDataString(uri.Query);
// Skip the '?'
var currentIndex = 1;
while (currentIndex < query.Length)
{
var isFind = false;
for (var i = 0; i < assembly.Length; ++currentIndex, ++i)
if (query[currentIndex] == assembly[i])
{
isFind = i == assembly.Length - 1;
}
else
{
break;
}
// Skip the '='
++currentIndex;
var beginIndex = currentIndex;
while (currentIndex < query.Length && query[currentIndex] != '&')
++currentIndex;
if (isFind)
return query.Substring(beginIndex, currentIndex - beginIndex);
++currentIndex;
}
return "";
}
}

2
src/Avalonia.Controls/Button.cs

@ -539,6 +539,7 @@ namespace Avalonia.Controls
if (e.Key == Key.Enter && IsVisible && IsEnabled) if (e.Key == Key.Enter && IsVisible && IsEnabled)
{ {
OnClick(); OnClick();
e.Handled = true;
} }
} }
@ -552,6 +553,7 @@ namespace Avalonia.Controls
if (e.Key == Key.Escape && IsVisible && IsEnabled) if (e.Key == Key.Escape && IsVisible && IsEnabled)
{ {
OnClick(); OnClick();
e.Handled = true;
} }
} }

9
src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs

@ -8,15 +8,6 @@ namespace Avalonia.Controls
{ {
public class MenuFlyoutPresenter : MenuBase public class MenuFlyoutPresenter : MenuBase
{ {
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<FlyoutPresenter>();
public CornerRadius CornerRadius
{
get => GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public MenuFlyoutPresenter() public MenuFlyoutPresenter()
:base(new DefaultMenuInteractionHandler(true)) :base(new DefaultMenuInteractionHandler(true))
{ {

10
src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs

@ -0,0 +1,10 @@
using System;
namespace Avalonia.Platform
{
public interface IPlatformNativeSurfaceHandle : IPlatformHandle
{
PixelSize Size { get; }
double Scaling { get; }
}
}

53
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -107,9 +107,6 @@ namespace Avalonia.Controls.Presenters
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty); AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty); AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
} }
public ContentPresenter() public ContentPresenter()
@ -240,6 +237,21 @@ namespace Avalonia.Controls.Presenters
} }
} }
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(Content):
case nameof(ContentTemplate):
ContentChanged(change);
break;
case nameof(TemplatedParent):
TemplatedParentChanged(change);
break;
}
}
/// <summary> /// <summary>
/// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>. /// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
/// </summary> /// </summary>
@ -254,8 +266,14 @@ namespace Avalonia.Controls.Presenters
public void UpdateChild() public void UpdateChild()
{ {
var content = Content; var content = Content;
UpdateChild(content);
}
private void UpdateChild(object? content)
{
var contentTemplate = ContentTemplate;
var oldChild = Child; var oldChild = Child;
var newChild = CreateChild(); var newChild = CreateChild(content, oldChild, contentTemplate);
var logicalChildren = Host?.LogicalChildren ?? LogicalChildren; var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
// Remove the old child if we're not recycling it. // Remove the old child if we're not recycling it.
@ -271,7 +289,7 @@ namespace Avalonia.Controls.Presenters
} }
// Set the DataContext if the data isn't a control. // Set the DataContext if the data isn't a control.
if (!(content is IControl)) if (contentTemplate is { } || !(content is IControl))
{ {
DataContext = content; DataContext = content;
} }
@ -299,6 +317,7 @@ namespace Avalonia.Controls.Presenters
} }
_createdChild = true; _createdChild = true;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -325,18 +344,23 @@ namespace Avalonia.Controls.Presenters
{ {
var content = Content; var content = Content;
var oldChild = Child; var oldChild = Child;
return CreateChild(content, oldChild, ContentTemplate);
}
private IControl? CreateChild(object? content, IControl? oldChild, IDataTemplate? template)
{
var newChild = content as IControl; var newChild = content as IControl;
// We want to allow creating Child from the Template, if Content is null. // We want to allow creating Child from the Template, if Content is null.
// But it's important to not use DataTemplates, otherwise we will break content presenters in many places, // But it's important to not use DataTemplates, otherwise we will break content presenters in many places,
// otherwise it will blow up every ContentPresenter without Content set. // otherwise it will blow up every ContentPresenter without Content set.
if (newChild == null if ((newChild == null
&& (content != null || ContentTemplate != null)) && (content != null || template != null)) || (newChild is { } && template is { }))
{ {
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? var dataTemplate = this.FindDataTemplate(content, template) ??
( (
RecognizesAccessKey RecognizesAccessKey
? FuncDataTemplate.Access ? FuncDataTemplate.Access
: FuncDataTemplate.Default : FuncDataTemplate.Default
); );
@ -446,7 +470,14 @@ namespace Avalonia.Controls.Presenters
if (((ILogical)this).IsAttachedToLogicalTree) if (((ILogical)this).IsAttachedToLogicalTree)
{ {
UpdateChild(); if (e.Property.Name == nameof(Content))
{
UpdateChild(e.NewValue);
}
else
{
UpdateChild();
}
} }
else if (Child != null) else if (Child != null)
{ {

9
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@ -23,7 +23,14 @@ namespace Avalonia.Controls.Primitives
} }
public bool HitTest(Point point) => Children.HitTestCustom(point); public bool HitTest(Point point) => Children.HitTestCustom(point);
protected override Size MeasureOverride(Size availableSize)
{
foreach (Control child in Children)
child.Measure(availableSize);
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
// We are saving it here since child controls might need to know the entire size of the overlay // We are saving it here since child controls might need to know the entire size of the overlay

2
src/Avalonia.Controls/ProgressBar.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
@ -137,6 +138,7 @@ namespace Avalonia.Controls
static ProgressBar() static ProgressBar()
{ {
ValueProperty.OverrideMetadata<ProgressBar>(new DirectPropertyMetadata<double>(defaultBindingMode: BindingMode.OneWay));
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));

5
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -41,5 +41,10 @@ namespace Avalonia.Diagnostics
/// <remarks>Default handler is <see cref="Screenshots.FilePickerHandler"/></remarks> /// <remarks>Default handler is <see cref="Screenshots.FilePickerHandler"/></remarks>
public IScreenshotHandler ScreenshotHandler { get; set; } public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler; = Convetions.DefaultScreenshotHandler;
/// <summary>
/// Gets or sets whether DevTools should use the dark mode theme
/// </summary>
public bool UseDarkMode { get; set; }
} }
} }

5
src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs

@ -14,11 +14,13 @@ namespace Avalonia.Diagnostics.ViewModels
case Binding binding: case Binding binding:
Path = binding.Path; Path = binding.Path;
Tint = Brushes.CornflowerBlue; Tint = Brushes.CornflowerBlue;
ValueTypeTooltip = "Reflection Binding";
break; break;
case CompiledBindingExtension binding: case CompiledBindingExtension binding:
Path = binding.Path.ToString(); Path = binding.Path.ToString();
Tint = Brushes.DarkGreen; Tint = Brushes.DarkGreen;
ValueTypeTooltip = "Compiled Binding";
break; break;
case TemplateBinding binding: case TemplateBinding binding:
@ -32,6 +34,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
Tint = Brushes.OrangeRed; Tint = Brushes.OrangeRed;
ValueTypeTooltip = "Template Binding";
break; break;
default: default:
@ -40,6 +43,8 @@ namespace Avalonia.Diagnostics.ViewModels
} }
public IBrush Tint { get; } public IBrush Tint { get; }
public string ValueTypeTooltip { get; }
public string Path { get; } public string Path { get; }

43
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -155,31 +155,19 @@ namespace Avalonia.Diagnostics.ViewModels
public object? SelectedEntity public object? SelectedEntity
{ {
get => _selectedEntity; get => _selectedEntity;
set set => RaiseAndSetIfChanged(ref _selectedEntity, value);
{
RaiseAndSetIfChanged(ref _selectedEntity, value);
}
} }
public string? SelectedEntityName public string? SelectedEntityName
{ {
get => _selectedEntityName; get => _selectedEntityName;
set set => RaiseAndSetIfChanged(ref _selectedEntityName, value);
{
RaiseAndSetIfChanged(ref _selectedEntityName, value);
}
} }
public string? SelectedEntityType public string? SelectedEntityType
{ {
get => _selectedEntityType; get => _selectedEntityType;
set set => RaiseAndSetIfChanged(ref _selectedEntityType, value);
{
RaiseAndSetIfChanged(ref _selectedEntityType, value);
}
} }
public PropertyViewModel? SelectedProperty public PropertyViewModel? SelectedProperty
@ -502,6 +490,31 @@ namespace Avalonia.Diagnostics.ViewModels
inpc2.PropertyChanged += ControlPropertyChanged; inpc2.PropertyChanged += ControlPropertyChanged;
} }
} }
internal void SelectProperty(AvaloniaProperty property)
{
SelectedProperty = null;
if (SelectedEntity != _avaloniaObject)
{
NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString());
}
if (PropertiesView is null)
{
return;
}
foreach (object o in PropertiesView)
{
if (o is AvaloniaPropertyViewModel propertyVm && propertyVm.Property == property)
{
SelectedProperty = propertyVm;
break;
}
}
}
internal void UpdatePropertiesView(bool showImplementedInterfaces) internal void UpdatePropertiesView(bool showImplementedInterfaces)
{ {

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -7,11 +7,14 @@ namespace Avalonia.Diagnostics.ViewModels
public object Key { get; } public object Key { get; }
public IBrush Tint { get; } public IBrush Tint { get; }
public string ValueTypeTooltip { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue) public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue)
{ {
Key = resourceKey; Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown; Tint = isDynamic ? Brushes.Orange : Brushes.Brown;
ValueTypeTooltip = isDynamic ? "Dynamic Resource" : "Static Resource";
} }
public void CopyResourceKey() public void CopyResourceKey()

62
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -44,7 +44,9 @@
UseWholeWordFilter="{Binding UseWholeWordFilter}" UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}"/> UseRegexFilter="{Binding UseRegexFilter}"/>
<DataGrid Items="{Binding PropertiesView}" <DataGrid
x:Name="DataGrid"
Items="{Binding PropertiesView}"
Grid.Row="2" Grid.Row="2"
BorderThickness="0" BorderThickness="0"
RowBackground="Transparent" RowBackground="Transparent"
@ -129,6 +131,26 @@
</Expander.Header> </Expander.Header>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}"> <ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl.Styles>
<Style Selector="TextBlock.property-name">
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="TextBlock.property-name:pointerover">
<Setter Property="TextDecorations" Value="Underline" />
</Style>
<Style Selector="Rectangle.property-inactive">
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Height" Value="1" />
<Setter Property="Fill" Value="#6C6C6C" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ItemsControl.Styles>
<ItemsControl.DataTemplates> <ItemsControl.DataTemplates>
<DataTemplate DataType="IBrush"> <DataTemplate DataType="IBrush">
@ -138,6 +160,17 @@
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="Color">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="vm:BindingSetterViewModel"> <DataTemplate DataType="vm:BindingSetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left"> <Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu> <Panel.ContextMenu>
@ -147,28 +180,17 @@
</ContextMenu> </ContextMenu>
</Panel.ContextMenu> </Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2"> <StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" /> <TextBlock Classes="property-name" PointerPressed="PropertyNamePressed" Text="{Binding Name}" />
<TextBlock Text=":" /> <TextBlock Text=":" />
<TextBlock>{</TextBlock> <TextBlock>{</TextBlock>
<Rectangle Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/> <Rectangle Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}" ToolTip.Tip="{Binding ValueTypeTooltip}"/>
<TextBlock Text="{Binding Path}" /> <TextBlock Text="{Binding Path}" />
<TextBlock>}</TextBlock> <TextBlock>}</TextBlock>
</StackPanel> </StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" /> <Rectangle Classes="property-inactive" IsVisible="{Binding !IsActive}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="Color">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="vm:ResourceSetterViewModel"> <DataTemplate DataType="vm:ResourceSetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left"> <Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu> <Panel.ContextMenu>
@ -179,15 +201,15 @@
</ContextMenu> </ContextMenu>
</Panel.ContextMenu> </Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left"> <StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" /> <TextBlock Classes="property-name" PointerPressed="PropertyNamePressed" Text="{Binding Name}" />
<TextBlock Text=":" /> <TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/> <ContentControl Content="{Binding Value}"/>
<TextBlock>(</TextBlock> <TextBlock>(</TextBlock>
<Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/> <Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}" ToolTip.Tip="{Binding ValueTypeTooltip}"/>
<TextBlock FontStyle="Italic" Text="{Binding Key}" /> <TextBlock FontStyle="Italic" Text="{Binding Key}" />
<TextBlock>)</TextBlock> <TextBlock>)</TextBlock>
</StackPanel> </StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" IsVisible="{Binding !IsActive}" /> <Rectangle Classes="property-inactive" IsVisible="{Binding !IsActive}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>
@ -200,11 +222,11 @@
</ContextMenu> </ContextMenu>
</Panel.ContextMenu> </Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2"> <StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" /> <TextBlock Classes="property-name" PointerPressed="PropertyNamePressed" Text="{Binding Name}" />
<TextBlock Text=":" /> <TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/> <ContentControl Content="{Binding Value}"/>
</StackPanel> </StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" /> <Rectangle Classes="property-inactive" IsVisible="{Binding !IsActive}" />
</Panel> </Panel>
</DataTemplate> </DataTemplate>

24
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs

@ -7,9 +7,13 @@ namespace Avalonia.Diagnostics.Views
{ {
internal class ControlDetailsView : UserControl internal class ControlDetailsView : UserControl
{ {
private DataGrid _dataGrid;
public ControlDetailsView() public ControlDetailsView()
{ {
InitializeComponent(); InitializeComponent();
_dataGrid = this.GetControl<DataGrid>("DataGrid");
} }
private void InitializeComponent() private void InitializeComponent()
@ -25,5 +29,25 @@ namespace Avalonia.Diagnostics.Views
} }
} }
private void PropertyNamePressed(object sender, PointerPressedEventArgs e)
{
var mainVm = (ControlDetailsViewModel?) DataContext;
if (mainVm is null)
{
return;
}
if (sender is Control control && control.DataContext is SetterViewModel setterVm)
{
mainVm.SelectProperty(setterVm.Property);
if (mainVm.SelectedProperty is not null)
{
_dataGrid.ScrollIntoView(mainVm.SelectedProperty, null);
}
}
}
} }
} }

2
src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml

@ -15,6 +15,7 @@
<SolidColorBrush x:Key="PaddingHighlightBrush" Color="#CEDA8E" /> <SolidColorBrush x:Key="PaddingHighlightBrush" Color="#CEDA8E" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" /> <SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<SolidColorBrush x:Key="SizeHighlightBrush" Color="#9ED0DC" /> <SolidColorBrush x:Key="SizeHighlightBrush" Color="#9ED0DC" />
<SolidColorBrush x:Key="ThicknessEditorForeground" Color="Black" />
</UserControl.Resources> </UserControl.Resources>
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -46,6 +47,7 @@
<controls:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top" <controls:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top"
HorizontalAlignment="Center" HorizontalAlignment="Center"
TextBlock.Foreground="{StaticResource ThicknessEditorForeground}"
Background="{StaticResource MarginBackgroundBrush}" Background="{StaticResource MarginBackgroundBrush}"
Highlight="{StaticResource MarginHighlightBrush}" Highlight="{StaticResource MarginHighlightBrush}"
Thickness="{Binding MarginThickness}"> Thickness="{Binding MarginThickness}">

13
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -11,6 +11,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views namespace Avalonia.Diagnostics.Views
@ -242,7 +243,17 @@ namespace Avalonia.Diagnostics.Views
private void RootClosed(object? sender, EventArgs e) => Close(); private void RootClosed(object? sender, EventArgs e) => Close();
public void SetOptions(DevToolsOptions options) => public void SetOptions(DevToolsOptions options)
{
(DataContext as MainViewModel)?.SetOptions(options); (DataContext as MainViewModel)?.SetOptions(options);
if (options.UseDarkMode)
{
if (Styles[0] is SimpleTheme st)
{
st.Mode = SimpleThemeMode.Dark;
}
}
}
} }
} }

2
src/Avalonia.MicroCom/Avalonia.MicroCom.csproj

@ -10,6 +10,8 @@
<ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"> <ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly> <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets> <ExcludeAssets>all</ExcludeAssets>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />

241
src/Avalonia.PlatformSupport/AssetLoader.cs

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.PlatformSupport.Internal;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.PlatformSupport namespace Avalonia.PlatformSupport
@ -13,12 +14,16 @@ namespace Avalonia.PlatformSupport
/// </summary> /// </summary>
public class AssetLoader : IAssetLoader public class AssetLoader : IAssetLoader
{ {
private const string AvaloniaResourceName = "!AvaloniaResources"; private static AssemblyDescriptorResolver s_assemblyDescriptorResolver = new();
private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
= new Dictionary<string, AssemblyDescriptor>();
private AssemblyDescriptor? _defaultResmAssembly; private AssemblyDescriptor? _defaultResmAssembly;
/// <remarks>
/// Introduced for tests.
/// </remarks>
internal static void SetAssemblyDescriptorResolver(AssemblyDescriptorResolver resolver) =>
s_assemblyDescriptorResolver = resolver;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AssetLoader"/> class. /// Initializes a new instance of the <see cref="AssetLoader"/> class.
/// </summary> /// </summary>
@ -109,17 +114,18 @@ namespace Avalonia.PlatformSupport
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns> /// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri) public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
{ {
if (uri.IsAbsoluteUri && uri.Scheme == "resm") if (uri.IsAbsoluteResm())
{ {
var assembly = GetAssembly(uri); var assembly = GetAssembly(uri);
return assembly?.Resources?.Where(x => x.Key.Contains(uri.AbsolutePath)) return assembly?.Resources?
.Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0)
.Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>(); Enumerable.Empty<Uri>();
} }
uri = EnsureAbsolute(uri, baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.Scheme == "avares") if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); var (asm, path) = GetResAsmAndPath(uri);
if (asm == null) if (asm == null)
@ -129,33 +135,23 @@ namespace Avalonia.PlatformSupport
"don't know where to look up for the resource, try specifying assembly explicitly."); "don't know where to look up for the resource, try specifying assembly explicitly.");
} }
if (asm?.AvaloniaResources == null) if (asm.AvaloniaResources == null)
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
path = path.TrimEnd('/') + '/';
return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path)) if (path[path.Length - 1] != '/')
path += '/';
return asm.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
.Select(x => new Uri($"avares://{asm.Name}{x.Key}")); .Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
} }
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
} }
private Uri EnsureAbsolute(Uri uri, Uri? baseUri)
{
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) private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri)
{ {
if (uri.IsAbsoluteUri && uri.Scheme == "resm") if (uri.IsAbsoluteResm())
{ {
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly; var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
@ -172,9 +168,9 @@ namespace Avalonia.PlatformSupport
return rv; return rv;
} }
uri = EnsureAbsolute(uri, baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.Scheme == "avares") if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); var (asm, path) = GetResAsmAndPath(uri);
if (asm.AvaloniaResources == null) if (asm.AvaloniaResources == null)
@ -188,8 +184,8 @@ namespace Avalonia.PlatformSupport
private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
{ {
var asm = GetAssembly(uri.Authority); var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority);
return (asm, uri.AbsolutePath); return (asm, uri.GetUnescapeAbsolutePath());
} }
private AssemblyDescriptor? GetAssembly(Uri? uri) private AssemblyDescriptor? GetAssembly(Uri? uri)
@ -198,197 +194,20 @@ namespace Avalonia.PlatformSupport
{ {
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri)
return null; return null;
if (uri.Scheme == "avares") if (uri.IsAvares())
return GetResAsmAndPath(uri).asm; return GetResAsmAndPath(uri).asm;
if (uri.Scheme == "resm") if (uri.IsResm())
{ {
var qs = ParseQueryString(uri); var assemblyName = uri.GetAssemblyNameFromQuery();
if (assemblyName.Length > 0)
if (qs.TryGetValue("assembly", out var assemblyName)) return s_assemblyDescriptorResolver.GetAssembly(assemblyName);
{
return GetAssembly(assemblyName);
}
} }
} }
return null; return null;
} }
private AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
if (!AssemblyNameCache.TryGetValue(name, out var rv))
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
if (match != null)
{
AssemblyNameCache[name] = rv = new AssemblyDescriptor(match);
}
else
{
// iOS does not support loading assemblies dynamically!
#if NET6_0_OR_GREATER
if (OperatingSystem.IsIOS())
{
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
}
#endif
name = Uri.UnescapeDataString(name);
AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
}
}
return rv;
}
private Dictionary<string, string> ParseQueryString(Uri uri)
{
return uri.Query.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
}
private interface IAssetDescriptor
{
Stream GetStream();
Assembly Assembly { get; }
}
private class AssemblyResourceDescriptor : IAssetDescriptor
{
private readonly Assembly _asm;
private readonly string _name;
public AssemblyResourceDescriptor(Assembly asm, string name)
{
_asm = asm;
_name = name;
}
public Stream GetStream()
{
var s = _asm.GetManifestResourceStream(_name);
return s ?? throw new InvalidOperationException($"Could not find manifest resource stream '{_name}',");
}
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()
{
var s = Assembly.GetManifestResourceStream(AvaloniaResourceName) ??
throw new InvalidOperationException($"Could not find manifest resource stream '{AvaloniaResourceName}',");
return new SlicedStream(s, _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
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (assembly != null)
{
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; }
}
public static void RegisterResUriParsers() public static void RegisterResUriParsers()
{ {
if (!UriParser.IsKnownScheme("avares")) if (!UriParser.IsKnownScheme("avares"))

5
src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj

@ -16,4 +16,9 @@
<Import Project="..\..\build\NetCore.props" /> <Import Project="..\..\build\NetCore.props" />
<Import Project="..\..\build\NetFX.props" /> <Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87" />
</ItemGroup>
</Project> </Project>

42
src/Avalonia.PlatformSupport/Internal/AssemblyDescriptor.cs

@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Utilities;
namespace Avalonia.PlatformSupport.Internal;
internal class AssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (assembly != null)
{
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName))
{
if (resources != null)
{
Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), 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; }
private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
r.Path![0] == '/' ? r.Path : '/' + r.Path;
}

42
src/Avalonia.PlatformSupport/Internal/AssemblyDescriptorResolver.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Avalonia.PlatformSupport.Internal;
internal class AssemblyDescriptorResolver
{
private readonly Dictionary<string, AssemblyDescriptor> _assemblyNameCache = new();
public AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
if (!_assemblyNameCache.TryGetValue(name, out var rv))
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
if (match != null)
{
_assemblyNameCache[name] = rv = new AssemblyDescriptor(match);
}
else
{
// iOS does not support loading assemblies dynamically!
#if NET6_0_OR_GREATER
if (OperatingSystem.IsIOS())
{
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
}
#endif
name = Uri.UnescapeDataString(name);
_assemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
}
}
return rv;
}
}

52
src/Avalonia.PlatformSupport/Internal/AssetDescriptor.cs

@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Reflection;
namespace Avalonia.PlatformSupport.Internal;
internal interface IAssetDescriptor
{
Stream GetStream();
Assembly Assembly { get; }
}
internal class AssemblyResourceDescriptor : IAssetDescriptor
{
private readonly Assembly _asm;
private readonly string _name;
public AssemblyResourceDescriptor(Assembly asm, string name)
{
_asm = asm;
_name = name;
}
public Stream GetStream()
{
var s = _asm.GetManifestResourceStream(_name);
return s ?? throw new InvalidOperationException($"Could not find manifest resource stream '{_name}',");
}
public Assembly Assembly => _asm;
}
internal 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()
{
var s = Assembly.GetManifestResourceStream(Constants.AvaloniaResourceName) ??
throw new InvalidOperationException($"Could not find manifest resource stream '{Constants.AvaloniaResourceName}',");
return new SlicedStream(s, _offset, _length);
}
}

6
src/Avalonia.PlatformSupport/Internal/Constants.cs

@ -0,0 +1,6 @@
namespace Avalonia.PlatformSupport.Internal;
internal static class Constants
{
public static string AvaloniaResourceName => "!AvaloniaResources";
}

59
src/Avalonia.PlatformSupport/Internal/SlicedStream.cs

@ -0,0 +1,59 @@
using System;
using System.IO;
namespace Avalonia.PlatformSupport.Internal;
internal 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();
}

13
src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs

@ -5,14 +5,27 @@ using System.Collections.Generic;
namespace Avalonia.Controls.Metadata namespace Avalonia.Controls.Metadata
{ {
/// <summary>
/// Defines all pseudoclasses by name referenced and implemented by a control.
/// </summary>
/// <remarks>
/// This is currently used for code-completion in certain IDEs.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class PseudoClassesAttribute : Attribute public sealed class PseudoClassesAttribute : Attribute
{ {
/// <summary>
/// Initializes a new instance of the <see cref="PseudoClassesAttribute"/> class.
/// </summary>
/// <param name="pseudoClasses">The list of pseudoclass names.</param>
public PseudoClassesAttribute(params string[] pseudoClasses) public PseudoClassesAttribute(params string[] pseudoClasses)
{ {
PseudoClasses = pseudoClasses; PseudoClasses = pseudoClasses;
} }
/// <summary>
/// Gets the list of pseudoclass names.
/// </summary>
public IReadOnlyList<string> PseudoClasses { get; } public IReadOnlyList<string> PseudoClasses { get; }
} }
} }

55
src/Avalonia.Styling/Controls/Metadata/TemplatePartAttribute.cs

@ -0,0 +1,55 @@
// This source file is adapted from the Windows Presentation Foundation project.
// (https://github.com/dotnet/wpf/)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
#nullable enable
namespace Avalonia.Controls.Metadata
{
/// <summary>
/// Defines a control template part referenced by name in code.
/// Template part names should begin with the "PART_" prefix.
/// </summary>
/// <remarks>
/// Style authors should be able to identify the part type used for styling the specific control.
/// The part is usually required in the style and should have a specific predefined name.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class TemplatePartAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TemplatePartAttribute"/> class.
/// </summary>
public TemplatePartAttribute()
{
Name = string.Empty;
Type = typeof(object);
}
/// <summary>
/// Initializes a new instance of the <see cref="TemplatePartAttribute"/> class.
/// </summary>
/// <param name="name">The part name used by the class to identify a required element in the style.</param>
/// <param name="type">The type of the element that should be used as a part with name.</param>
public TemplatePartAttribute(string name, Type type)
{
Name = name;
Type = type;
}
/// <summary>
/// Gets or sets the part name used by the class to identify a required element in the style.
/// Template part names should begin with the "PART_" prefix.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the type of the element that should be used as a part with name specified
/// in <see cref="Name"/>.
/// </summary>
public Type Type { get; set; }
}
}

2
src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml

@ -254,7 +254,7 @@
<Style Selector="ToggleSwitch:disabled /template/ Border#OuterBorder"> <Style Selector="ToggleSwitch:disabled /template/ Border#OuterBorder">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffDisabled}"/> <Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffDisabled}"/>
<Setter Property="Background" Value="{DynamicResource ToggleSwitchFillOffPressed}"/> <Setter Property="Background" Value="{DynamicResource ToggleSwitchFillOffDisabled}"/>
</Style> </Style>
<Style Selector="ToggleSwitch:disabled /template/ Ellipse#SwitchKnobOff"> <Style Selector="ToggleSwitch:disabled /template/ Ellipse#SwitchKnobOff">

4
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -3,8 +3,9 @@
x:Class="Avalonia.Themes.Default.DefaultTheme"> x:Class="Avalonia.Themes.Default.DefaultTheme">
<!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) --> <!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ToolTip.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ToolTip.xaml"/>
<!-- PathIcon also needs to be defined early so it can be overriden by other styles (e.g. ComboBox) -->
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/PathIcon.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/DataValidationErrors.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/DataValidationErrors.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/FocusAdorner.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/FocusAdorner.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/Button.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/Button.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/Carousel.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/Carousel.xaml"/>
@ -22,7 +23,6 @@
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ContextMenu.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ContextMenu.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/MenuItem.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/MenuItem.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/PathIcon.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/PopupRoot.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/PopupRoot.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ProgressBar.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ProgressBar.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/RadioButton.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/RadioButton.xaml"/>

3
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@ -3,6 +3,8 @@
x:Class="Avalonia.Themes.Fluent.Controls.FluentControls"> x:Class="Avalonia.Themes.Fluent.Controls.FluentControls">
<!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) --> <!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ToolTip.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ToolTip.xaml"/>
<!-- PathIcon also needs to be defined early so it can be overriden by other styles (e.g. ComboBox) -->
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/PathIcon.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Button.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Button.xaml"/>
@ -21,7 +23,6 @@
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ContextMenu.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ContextMenu.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/MenuItem.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/MenuItem.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/PathIcon.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/PopupRoot.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/PopupRoot.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ProgressBar.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ProgressBar.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/RadioButton.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/RadioButton.xaml"/>

2
src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml

@ -220,7 +220,7 @@
<Style Selector="ToggleSwitch:disabled /template/ Border#OuterBorder"> <Style Selector="ToggleSwitch:disabled /template/ Border#OuterBorder">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffDisabled}"/> <Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffDisabled}"/>
<Setter Property="Background" Value="{DynamicResource ToggleSwitchFillOffPressed}"/> <Setter Property="Background" Value="{DynamicResource ToggleSwitchFillOffDisabled}"/>
</Style> </Style>
<Style Selector="ToggleSwitch:disabled /template/ Ellipse#SwitchKnobOff"> <Style Selector="ToggleSwitch:disabled /template/ Ellipse#SwitchKnobOff">

150
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
{ {
@ -12,18 +13,10 @@ namespace Avalonia.Media.Fonts
/// </summary> /// </summary>
/// <param name="fontFamilyKey"></param> /// <param name="fontFamilyKey"></param>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey) public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey) =>
{ IsFontTtfOrOtf(fontFamilyKey.Source) ?
var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); GetFontAssetsByExpression(fontFamilyKey) :
GetFontAssetsBySource(fontFamilyKey);
if (sourceWithoutArguments.EndsWith(".ttf")
|| sourceWithoutArguments.EndsWith(".otf"))
{
return GetFontAssetsByExpression(fontFamilyKey);
}
return GetFontAssetsBySource(fontFamilyKey);
}
/// <summary> /// <summary>
/// Searches for font assets at a given location and returns a quantity of found assets /// Searches for font assets at a given location and returns a quantity of found assets
@ -34,11 +27,7 @@ namespace Avalonia.Media.Fonts
{ {
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri);
return availableAssets.Where(x => IsFontTtfOrOtf(x));
var matchingAssets =
availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"));
return matchingAssets;
} }
/// <summary> /// <summary>
@ -49,71 +38,124 @@ namespace Avalonia.Media.Fonts
/// <returns></returns> /// <returns></returns>
private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey)
{ {
var (fileNameWithoutExtension, extension) = GetFileName(fontFamilyKey, out var location);
var filePattern = CreateFilePattern(fontFamilyKey, location, fileNameWithoutExtension);
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
var availableResources = assetLoader.GetAssets(location, fontFamilyKey.BaseUri);
var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); return availableResources.Where(x => IsContainsFile(x, filePattern, extension));
}
var availableResources = assetLoader.GetAssets(location, fontFamilyKey.BaseUri); private static (string fileNameWithoutExtension, string extension) GetFileName(
FontFamilyKey fontFamilyKey, out Uri location)
{
if (fontFamilyKey.Source.IsAbsoluteResm())
{
var fileName = GetFileNameAndExtension(fontFamilyKey.Source.GetUnescapeAbsolutePath(), '.');
var uriLocation = fontFamilyKey.Source.GetUnescapeAbsoluteUri()
.Replace("." + fileName.fileNameWithoutExtension + fileName.extension, string.Empty);
location = new Uri(uriLocation, UriKind.RelativeOrAbsolute);
return fileName;
}
string compareTo; var filename = GetFileNameAndExtension(fontFamilyKey.Source.OriginalString);
var fullFilename = filename.fileNameWithoutExtension + filename.extension;
if (fontFamilyKey.Source.IsAbsoluteUri) if (fontFamilyKey.BaseUri != null)
{ {
if (fontFamilyKey.Source.Scheme == "resm") var relativePath = fontFamilyKey.Source.OriginalString
{ .Replace(fullFilename, string.Empty);
compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
} location = new Uri(fontFamilyKey.BaseUri, relativePath);
else
{
compareTo = location.AbsolutePath + fileName.Split('*').First();
}
} }
else else
{ {
compareTo = location.AbsolutePath + fileName.Split('*').First(); var uriString = fontFamilyKey.Source
.GetUnescapeAbsoluteUri()
.Replace(fullFilename, string.Empty);
location = new Uri(uriString);
} }
var matchingResources = availableResources.Where( return filename;
x => x.AbsolutePath.Contains(compareTo) }
&& x.AbsolutePath.EndsWith(fileExtension));
return matchingResources; private static string CreateFilePattern(
FontFamilyKey fontFamilyKey, Uri location, string fileNameWithoutExtension)
{
var path = location.GetUnescapeAbsolutePath();
var file = GetSubString(fileNameWithoutExtension, '*');
return fontFamilyKey.Source.IsAbsoluteResm()
? path + "." + file
: path + file;
} }
private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location) private static bool IsContainsFile(Uri x, string filePattern, string fileExtension)
{ {
if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm") var path = x.GetUnescapeAbsolutePath();
{ return path.IndexOf(filePattern, StringComparison.Ordinal) >= 0
fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault(); && path.EndsWith(fileExtension, StringComparison.Ordinal);
}
var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').Last(); private static bool IsFontTtfOrOtf(Uri uri)
{
var sourceWithoutArguments = GetSubString(uri.OriginalString, '?');
return sourceWithoutArguments.EndsWith(".ttf", StringComparison.Ordinal)
|| sourceWithoutArguments.EndsWith(".otf", StringComparison.Ordinal);
}
location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute); private static (string fileNameWithoutExtension, string extension) GetFileNameAndExtension(
string path, char directorySeparator = '/')
{
var pathAsSpan = path.AsSpan();
pathAsSpan = IsPathRooted(pathAsSpan, directorySeparator) ?
pathAsSpan.Slice(1, path.Length - 1) :
pathAsSpan;
return fileName; var extension = GetFileExtension(pathAsSpan);
} if (extension.Length == pathAsSpan.Length)
return (extension.ToString(), string.Empty);
var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); var fileName = GetFileName(pathAsSpan, directorySeparator, extension.Length);
return (fileName.ToString(), extension.ToString());
}
var fileNameWithExtension = pathSegments.Last(); private static bool IsPathRooted(ReadOnlySpan<char> path, char directorySeparator) =>
path.Length > 0 && path[0] == directorySeparator;
var fileNameSegments = fileNameWithExtension.Split('.'); private static ReadOnlySpan<char> GetFileExtension(ReadOnlySpan<char> path)
{
for (var i = path.Length - 1; i > 0; --i)
{
if (path[i] == '.')
return path.Slice(i, path.Length - i);
}
fileExtension = "." + fileNameSegments.Last(); return path;
}
if (fontFamilyKey.BaseUri != null) private static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path, char directorySeparator, int extensionLength)
{
for (var i = path.Length - extensionLength - 1; i >= 0; --i)
{ {
var relativePath = fontFamilyKey.Source.OriginalString if (path[i] == directorySeparator)
.Replace(fileNameWithExtension, string.Empty); return path.Slice(i + 1, path.Length - i - extensionLength - 1);
location = new Uri(fontFamilyKey.BaseUri, relativePath);
} }
else
return path.Slice(0, path.Length - extensionLength);
}
private static string GetSubString(string path, char separator)
{
for (var i = 0; i < path.Length; i++)
{ {
location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty)); if (path[i] == separator)
return path.Substring(0, i);
} }
return fileNameSegments.First(); return path;
} }
} }
} }

26
src/Avalonia.X11/X11Window.cs

@ -134,8 +134,8 @@ namespace Avalonia.X11
SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
else else
_renderHandle = _handle; _renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID"); Handle = new SurfacePlatformHandle(this);
_realSize = new PixelSize(defaultWidth, defaultHeight); _realSize = new PixelSize(defaultWidth, defaultHeight);
platform.Windows[_handle] = OnEvent; platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
@ -169,7 +169,9 @@ namespace Avalonia.X11
if (glx != null) if (glx != null)
surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext, surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
new SurfaceInfo(this, _x11.Display, _handle, _renderHandle))); new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
surfaces.Add(Handle);
Surfaces = surfaces.ToArray(); Surfaces = surfaces.ToArray();
UpdateMotifHints(); UpdateMotifHints();
UpdateSizeHints(null); UpdateSizeHints(null);
@ -1142,5 +1144,23 @@ namespace Avalonia.X11
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8);
public bool NeedsManagedDecorations => false; public bool NeedsManagedDecorations => false;
public class SurfacePlatformHandle : IPlatformNativeSurfaceHandle
{
private readonly X11Window _owner;
public PixelSize Size => _owner.ToPixelSize(_owner.ClientSize);
public double Scaling => _owner.RenderScaling;
public SurfacePlatformHandle(X11Window owner)
{
_owner = owner;
}
public IntPtr Handle => _owner._renderHandle;
public string? HandleDescriptor => "XID";
}
} }
} }

8
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -459,7 +459,7 @@ namespace Avalonia.Win32
} }
} }
public IEnumerable<object> Surfaces => new object[] { Handle, _gl, _framebuffer }; public IEnumerable<object> Surfaces => new object[] { (IPlatformNativeSurfaceHandle)Handle, _gl, _framebuffer };
public PixelPoint Position public PixelPoint Position
{ {
@ -1383,12 +1383,16 @@ namespace Avalonia.Win32
public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current; public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current;
private class WindowImplPlatformHandle : IPlatformHandle private class WindowImplPlatformHandle : IPlatformNativeSurfaceHandle
{ {
private readonly WindowImpl _owner; private readonly WindowImpl _owner;
public WindowImplPlatformHandle(WindowImpl owner) => _owner = owner; public WindowImplPlatformHandle(WindowImpl owner) => _owner = owner;
public IntPtr Handle => _owner.Hwnd; public IntPtr Handle => _owner.Hwnd;
public string HandleDescriptor => PlatformConstants.WindowHandleType; public string HandleDescriptor => PlatformConstants.WindowHandleType;
public PixelSize Size => PixelSize.FromSize(_owner.ClientSize, Scaling);
public double Scaling => _owner.RenderScaling;
} }
} }
} }

109
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -11,6 +11,7 @@ using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities;
using Microsoft.Reactive.Testing; using Microsoft.Reactive.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -785,7 +786,7 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled); Assert.False(source.ValueSetterCalled);
} }
[Fact] [Fact]
@ -796,7 +797,7 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled); Assert.False(source.ValueSetterCalled);
} }
[Fact] [Fact]
@ -838,7 +839,7 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled); Assert.False(source.ValueSetterCalled);
} }
[Fact] [Fact]
@ -850,7 +851,36 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled); Assert.False(source.ValueSetterCalled);
}
[Fact]
public void Disposing_a_TwoWay_Binding_Should_Set_Default_Value_On_Binding_Target_But_Not_On_Source()
{
var target = new Class3();
// Create a source class which has a Value set to -1 and a Minimum set to -2
var source = new TestTwoWayBindingViewModel() { Value = -1, Minimum = -2 };
// Reset the setter counter
source.ResetSetterCalled();
// 1. bind the minimum
var disposable_1 = target.Bind(Class3.MinimumProperty, new Binding("Minimum", BindingMode.TwoWay) { Source = source });
// 2. Bind the value
var disposable_2 = target.Bind(Class3.ValueProperty, new Binding("Value", BindingMode.TwoWay) { Source = source });
// Dispose the minimum binding
disposable_1.Dispose();
// Dispose the value binding
disposable_2.Dispose();
// The value setter should be called here as we have disposed minimum fist and the default value of minimum is 0, so this should be changed.
Assert.True(source.ValueSetterCalled);
// The minimum value should not be changed in the source.
Assert.False(source.MinimumSetterCalled);
} }
/// <summary> /// <summary>
@ -888,6 +918,56 @@ namespace Avalonia.Base.UnitTests
AvaloniaProperty.Register<Class2, string>("Bar", "bardefault"); AvaloniaProperty.Register<Class2, string>("Bar", "bardefault");
} }
private class Class3 : AvaloniaObject
{
static Class3()
{
MinimumProperty.Changed.Subscribe(x => OnMinimumChanged(x));
}
private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs<double> e)
{
if (e.Sender is Class3 s)
{
s.SetValue(ValueProperty, MathUtilities.Clamp(s.Value, e.NewValue.Value, double.PositiveInfinity));
}
}
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly StyledProperty<double> ValueProperty =
AvaloniaProperty.Register<Class3, double>(nameof(Value), 0);
/// <summary>
/// Gets or sets the Value property
/// </summary>
public double Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<Class3, double>(nameof(Minimum), 0);
/// <summary>
/// Gets or sets the minimum property
/// </summary>
public double Minimum
{
get { return GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
}
private class TestOneTimeBinding : IBinding private class TestOneTimeBinding : IBinding
{ {
private IObservable<object> _source; private IObservable<object> _source;
@ -952,7 +1032,18 @@ namespace Avalonia.Base.UnitTests
set set
{ {
_value = value; _value = value;
SetterCalled = true; ValueSetterCalled = true;
}
}
private double _minimum;
public double Minimum
{
get => _minimum;
set
{
_minimum = value;
MinimumSetterCalled = true;
} }
} }
@ -962,15 +1053,17 @@ namespace Avalonia.Base.UnitTests
set set
{ {
_value = value; _value = value;
SetterCalled = true; ValueSetterCalled = true;
} }
} }
public bool SetterCalled { get; private set; } public bool ValueSetterCalled { get; private set; }
public bool MinimumSetterCalled { get; private set; }
public void ResetSetterCalled() public void ResetSetterCalled()
{ {
SetterCalled = false; ValueSetterCalled = false;
MinimumSetterCalled = false;
} }
} }
} }

29
tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Base.UnitTests.Utilities;
public class UriExtensionsTests
{
[Fact]
public void Assembly_Name_From_Query_Parsed()
{
const string key = "assembly";
const string value = "Avalonia.Themes.Default";
var uri = new Uri($"resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?{key}={value}");
var name = uri.GetAssemblyNameFromQuery();
Assert.Equal(value, name);
}
[Fact]
public void Assembly_Name_From_Empty_Query_Not_Parsed()
{
var uri = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml");
var name = uri.GetAssemblyNameFromQuery();
Assert.Equal(string.Empty, name);
}
}

23
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -353,6 +353,29 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(target.Host); Assert.Null(target.Host);
} }
[Fact]
public void Content_Should_Become_DataContext_When_ControlTemplate_Is_Not_Null()
{
var (target, _) = CreateTarget();
var textBlock = new TextBlock
{
[!TextBlock.TextProperty] = new Binding("Name"),
};
var canvas = new Canvas()
{
Name = "Canvas"
};
target.ContentTemplate = new FuncDataTemplate<Canvas>((_, __) => textBlock);
target.Content = canvas;
Assert.NotNull(target.DataContext);
Assert.Equal(canvas, target.DataContext);
Assert.Equal("Canvas", textBlock.Text);
}
(ContentPresenter presenter, ContentControl templatedParent) CreateTarget() (ContentPresenter presenter, ContentControl templatedParent) CreateTarget()
{ {
var templatedParent = new ContentControl var templatedParent = new ContentControl

62
tests/Avalonia.PlatformSupport.UnitTests/AssetLoaderTests.cs

@ -0,0 +1,62 @@
using System;
using System.Reflection;
using Avalonia.PlatformSupport.Internal;
using Moq;
using Xunit;
namespace Avalonia.PlatformSupport.UnitTests;
public class AssetLoaderTests
{
public class MockAssembly : Assembly {}
private const string AssemblyNameWithWhitespace = "Awesome Library";
private const string AssemblyNameWithNonAscii = "Какое-то-название";
static AssetLoaderTests()
{
var resolver = Mock.Of<AssemblyDescriptorResolver>();
var descriptor = CreateAssemblyDescriptor(AssemblyNameWithWhitespace);
Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithWhitespace)).Returns(descriptor);
descriptor = CreateAssemblyDescriptor(AssemblyNameWithNonAscii);
Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithNonAscii)).Returns(descriptor);
AssetLoader.SetAssemblyDescriptorResolver(resolver);
}
[Fact]
public void AssemblyName_With_Whitespace_Should_Load_Resm()
{
var uri = new Uri($"resm:Avalonia.Base.UnitTests.Assets.something?assembly={AssemblyNameWithWhitespace}");
var loader = new AssetLoader();
var assemblyActual = loader.GetAssembly(uri, null);
Assert.Equal(AssemblyNameWithWhitespace, assemblyActual?.FullName);
}
[Fact]
public void AssemblyName_With_Non_ASCII_Should_Load_Avares()
{
var uri = new Uri($"avares://{AssemblyNameWithNonAscii}/Assets/something");
var loader = new AssetLoader();
var assemblyActual = loader.GetAssembly(uri, null);
Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName);
}
private static AssemblyDescriptor CreateAssemblyDescriptor(string assemblyName)
{
var assembly = Mock.Of<MockAssembly>();
Mock.Get(assembly).Setup(x => x.GetName()).Returns(new AssemblyName(assemblyName));
Mock.Get(assembly).Setup(x => x.FullName).Returns(assemblyName);
var descriptor = Mock.Of<AssemblyDescriptor>();
Mock.Get(descriptor).Setup(x => x.Assembly).Returns(assembly);
return descriptor;
}
}

24
tests/Avalonia.PlatformSupport.UnitTests/Avalonia.PlatformSupport.UnitTests.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\UnitTests.NetFX.props" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
</Project>

5
tests/Avalonia.UnitTests/MockAssetLoader.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.UnitTests namespace Avalonia.UnitTests
{ {
@ -39,7 +40,9 @@ namespace Avalonia.UnitTests
public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri) public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
{ {
return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath)); var absPath = uri.GetUnescapeAbsolutePath();
return _assets.Keys.Where(
x => x.GetUnescapeAbsolutePath().IndexOf(absPath, StringComparison.Ordinal) >= 0);
} }
public void SetDefaultAssembly(Assembly asm) public void SetDefaultAssembly(Assembly asm)

45
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs

@ -12,24 +12,28 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
public class FontFamilyLoaderTests : IDisposable public class FontFamilyLoaderTests : IDisposable
{ {
private const string AssetMyFontRegular = AssetLocation + ".MyFont Regular.ttf" + Assembly + FontName;
private const string FontName = "#MyFont"; private const string FontName = "#MyFont";
private const string Assembly = "?assembly=Avalonia.Visuals.UnitTests"; private const string Assembly = "?assembly=Avalonia.Visuals.UnitTests";
private const string AssetLocation = "resm:Avalonia.Visuals.UnitTests.Assets"; private const string AssetLocation = "resm:Avalonia.Visuals.UnitTests.Assets";
private const string AssetLocationAvares = "avares://Avalonia.Visuals.UnitTests";
private const string AssetYourFileName = "/Assets/YourFont.ttf";
private const string AssetYourFontAvares = AssetLocationAvares + AssetYourFileName;
private readonly IDisposable _testApplication; private readonly IDisposable _testApplication;
public FontFamilyLoaderTests() public FontFamilyLoaderTests()
{ {
const string AssetMyFontRegular = AssetLocation + ".MyFont-Regular.ttf" + Assembly + FontName; const string AssetMyFontBold = AssetLocation + ".MyFont Bold.ttf" + Assembly + FontName;
const string AssetMyFontBold = AssetLocation + ".MyFont-Bold.ttf" + Assembly + FontName;
const string AssetYourFont = AssetLocation + ".YourFont.ttf" + Assembly + FontName; const string AssetYourFont = AssetLocation + ".YourFont.ttf" + Assembly + FontName;
var fontAssets = new[] var fontAssets = new[]
{ {
(AssetMyFontRegular, "AssetData"), (AssetMyFontRegular, "AssetData"),
(AssetMyFontBold, "AssetData"), (AssetMyFontBold, "AssetData"),
(AssetYourFont, "AssetData") (AssetYourFont, "AssetData"),
}; (AssetYourFontAvares, "AssetData")
};
_testApplication = StartWithResources(fontAssets); _testApplication = StartWithResources(fontAssets);
} }
@ -42,10 +46,18 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
[Fact] [Fact]
public void Should_Load_Single_FontAsset() public void Should_Load_Single_FontAsset()
{ {
const string FontAsset = AssetLocation + ".MyFont-Regular.ttf" + Assembly + FontName; var source = new Uri(AssetMyFontRegular, UriKind.RelativeOrAbsolute);
var key = new FontFamilyKey(source);
var source = new Uri(FontAsset, UriKind.RelativeOrAbsolute); var fontAssets = FontFamilyLoader.LoadFontAssets(key);
Assert.Single(fontAssets);
}
[Fact]
public void Should_Load_Single_FontAsset_Avares_Without_BaseUri()
{
var source = new Uri(AssetYourFontAvares);
var key = new FontFamilyKey(source); var key = new FontFamilyKey(source);
var fontAssets = FontFamilyLoader.LoadFontAssets(key); var fontAssets = FontFamilyLoader.LoadFontAssets(key);
@ -54,10 +66,21 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
} }
[Fact] [Fact]
public void Should_Load_Matching_Assets() public void Should_Load_Single_FontAsset_Avares_With_BaseUri()
{ {
var source = new Uri(AssetLocation + ".MyFont-*.ttf" + Assembly + FontName, UriKind.RelativeOrAbsolute); var source = new Uri(AssetYourFileName, UriKind.RelativeOrAbsolute);
var baseUri = new Uri(AssetLocationAvares);
var key = new FontFamilyKey(source, baseUri);
var fontAssets = FontFamilyLoader.LoadFontAssets(key);
Assert.Single(fontAssets);
}
[Fact]
public void Should_Load_Matching_Assets()
{
var source = new Uri(AssetLocation + ".MyFont*.ttf" + Assembly + FontName, UriKind.RelativeOrAbsolute);
var key = new FontFamilyKey(source); var key = new FontFamilyKey(source);
var fontAssets = FontFamilyLoader.LoadFontAssets(key).ToArray(); var fontAssets = FontFamilyLoader.LoadFontAssets(key).ToArray();

Loading…
Cancel
Save