diff --git a/Avalonia.sln b/Avalonia.sln
index a989fb828d..a792774d94 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -234,6 +234,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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|iPhoneSimulator.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2276,6 +2302,7 @@ Global
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {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
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm
index f8e9a3b6d1..5683a5a975 100644
--- a/native/Avalonia.Native/src/OSX/controlhost.mm
+++ b/native/Avalonia.Native/src/OSX/controlhost.mm
@@ -36,7 +36,10 @@ public:
virtual void DestroyDefaultChild(void* child) override
{
// ARC will release the object for us
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunused-value"
(__bridge_transfer NSView*) child;
+ #pragma clang diagnostic pop
}
};
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 9b703c4838..620b750a40 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -457,7 +457,8 @@ public:
}
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)];
@@ -477,7 +478,8 @@ public:
}
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));
return S_OK;
@@ -573,7 +575,8 @@ public:
if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
|| (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);
auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
nsevent = [NSEvent eventWithCGEvent: cgevent];
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 4b28527465..4d0ed866a3 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -8,7 +8,9 @@
all
-
+ true
+ TargetFramework=netstandard2.0
+
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index 516acfe4b9..9777bb46c3 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -21,8 +21,9 @@
True
- False
+ True
True
+ True
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
index e96b7aff08..3cadc7243a 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
@@ -157,7 +157,8 @@ namespace ControlCatalog.Pages
(button = new Button
{
HorizontalAlignment = HorizontalAlignment.Center,
- Content = "Click to close"
+ Content = "Click to close",
+ IsDefault = true
})
}
},
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index 34784612f1..5343b57251 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
@@ -2,18 +2,22 @@ using System;
using Android.Content;
using Android.Graphics;
using Android.OS;
+using Android.Runtime;
using Android.Util;
using Android.Views;
+using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Platform;
namespace Avalonia.Android
{
- public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle
+ public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{
bool _invalidateQueued;
readonly object _lock = new object();
private readonly Handler _handler;
-
+
+ IntPtr IPlatformHandle.Handle =>
+ AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, Holder.Surface.Handle);
public InvalidationAwareSurfaceView(Context context) : base(context)
{
@@ -25,7 +29,7 @@ namespace Avalonia.Android
{
lock (_lock)
{
- if(_invalidateQueued)
+ if (_invalidateQueued)
return;
_handler.Post(() =>
{
@@ -70,7 +74,7 @@ namespace Avalonia.Android
public void SurfaceDestroyed(ISurfaceHolder holder)
{
Log.Info("AVALONIA", "Surface Destroyed");
-
+
}
protected void DoDraw()
@@ -83,5 +87,9 @@ namespace Avalonia.Android
}
protected abstract void Draw();
public string HandleDescriptor => "SurfaceView";
+
+ public PixelSize Size => new PixelSize(Holder.SurfaceFrame.Width(), Holder.SurfaceFrame.Height());
+
+ public double Scaling => Resources.DisplayMetrics.Density;
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 0afb1db141..8a475676a5 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
-using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL;
@@ -38,11 +37,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_keyboardHelper = new AndroidKeyboardEventsHelper(this);
_touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot,
GetAvaloniaPointFromEvent);
-
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
- RenderScaling = (int)_view.Resources.DisplayMetrics.Density;
+ RenderScaling = (int)_view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
@@ -77,7 +75,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view;
- public IEnumerable
-
+
+
+
+ Resources\drawable\Icon.png
+
+
+
True
True
True
-
+ True
diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
index 471b982d9e..8f4beb2737 100644
--- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
+++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
@@ -1,9 +1,10 @@
using System;
using Android.App;
using Android.Content.PM;
-using Android.OS;
using Avalonia.Android;
using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Input.TextInput;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
@@ -14,22 +15,15 @@ namespace Avalonia.AndroidTestApplication
[Activity(Label = "Main",
MainLauncher = true,
Icon = "@drawable/icon",
- Theme = "@style/Theme.AppCompat.NoActionBar",
- ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize,
+ Theme = "@style/Theme.AppCompat.NoActionBar",
+ ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize,
LaunchMode = LaunchMode.SingleInstance/*,
ScreenOrientation = ScreenOrientation.Landscape*/)]
- public class MainBaseActivity : AvaloniaActivity
+ public class MainActivity : AvaloniaActivity
{
- protected override void OnCreate(Bundle savedInstanceState)
+ protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
- if (Avalonia.Application.Current == null)
- {
- AppBuilder.Configure()
- .UseAndroid()
- .SetupWithoutStarting();
- }
- base.OnCreate(savedInstanceState);
- Content = App.CreateSimpleWindow();
+ return base.CustomizeAppBuilder(builder);
}
}
@@ -37,13 +31,17 @@ namespace Avalonia.AndroidTestApplication
{
public override void Initialize()
{
- Styles.Add(new DefaultTheme());
-
- var baseLight = (IStyle)AvaloniaXamlLoader.Load(
- new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
- Styles.Add(baseLight);
+ Styles.Add(new SimpleTheme(new Uri("avares://Avalonia.AndroidTestApplication")));
+ }
+ 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
@@ -76,12 +74,12 @@ namespace Avalonia.AndroidTestApplication
Foreground = Brushes.Black
},
- CreateTextBox(Input.TextInput.TextInputContentType.Normal),
- CreateTextBox(Input.TextInput.TextInputContentType.Password),
- CreateTextBox(Input.TextInput.TextInputContentType.Email),
- CreateTextBox(Input.TextInput.TextInputContentType.Url),
- CreateTextBox(Input.TextInput.TextInputContentType.Phone),
- CreateTextBox(Input.TextInput.TextInputContentType.Number),
+ CreateTextBox(TextInputContentType.Normal),
+ CreateTextBox(TextInputContentType.Password),
+ CreateTextBox(TextInputContentType.Email),
+ CreateTextBox(TextInputContentType.Url),
+ CreateTextBox(TextInputContentType.Digits),
+ CreateTextBox(TextInputContentType.Number),
}
}
};
@@ -89,16 +87,16 @@ namespace Avalonia.AndroidTestApplication
return window;
}
- private static TextBox CreateTextBox(Input.TextInput.TextInputContentType contentType)
+ private static TextBox CreateTextBox(TextInputContentType contentType)
{
var textBox = new TextBox()
{
Margin = new Thickness(20, 10),
Watermark = contentType.ToString(),
BorderThickness = new Thickness(3),
- FontSize = 20
+ FontSize = 20,
+ [TextInputOptions.ContentTypeProperty] = contentType
};
- textBox.TextInputOptionsQuery += (s, e) => { e.ContentType = contentType; };
return textBox;
}
diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs
index 053c7a7547..9ffb5872f0 100644
--- a/src/Avalonia.Base/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs
@@ -11,4 +11,5 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Visuals, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
+[assembly: InternalsVisibleTo("Avalonia.PlatformSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs
index 16ba571a5a..535a826c1e 100644
--- a/src/Avalonia.Base/StyledPropertyBase.cs
+++ b/src/Avalonia.Base/StyledPropertyBase.cs
@@ -159,9 +159,9 @@ namespace Avalonia
}
///
- public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
+ public override void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
{
- vistor.Visit(this, ref data);
+ visitor.Visit(this, ref data);
}
///
@@ -242,11 +242,5 @@ namespace Avalonia
_ = type ?? throw new ArgumentNullException(nameof(type));
return GetMetadata(type).DefaultValue;
}
-
- [DebuggerHidden]
- private Func Cast(Func validate)
- {
- return (o, v) => validate((THost)o, v);
- }
}
}
diff --git a/src/Avalonia.Base/Utilities/UriExtensions.cs b/src/Avalonia.Base/Utilities/UriExtensions.cs
new file mode 100644
index 0000000000..c706f72a63
--- /dev/null
+++ b/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 "";
+ }
+}
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 72495bdcb3..899521536f 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -539,6 +539,7 @@ namespace Avalonia.Controls
if (e.Key == Key.Enter && IsVisible && IsEnabled)
{
OnClick();
+ e.Handled = true;
}
}
@@ -552,6 +553,7 @@ namespace Avalonia.Controls
if (e.Key == Key.Escape && IsVisible && IsEnabled)
{
OnClick();
+ e.Handled = true;
}
}
diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
index bcd859100a..6a7da87387 100644
--- a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
+++ b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
@@ -8,15 +8,6 @@ namespace Avalonia.Controls
{
public class MenuFlyoutPresenter : MenuBase
{
- public static readonly StyledProperty CornerRadiusProperty =
- Border.CornerRadiusProperty.AddOwner();
-
- public CornerRadius CornerRadius
- {
- get => GetValue(CornerRadiusProperty);
- set => SetValue(CornerRadiusProperty, value);
- }
-
public MenuFlyoutPresenter()
:base(new DefaultMenuInteractionHandler(true))
{
diff --git a/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs b/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs
new file mode 100644
index 0000000000..264f5e4667
--- /dev/null
+++ b/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; }
+ }
+}
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 9886dd913a..93acd88fb1 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -107,9 +107,6 @@ namespace Avalonia.Controls.Presenters
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsArrange(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
AffectsMeasure(BorderThicknessProperty, PaddingProperty);
- ContentProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e));
- ContentTemplateProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e));
- TemplatedParentProperty.Changed.AddClassHandler((x, e) => x.TemplatedParentChanged(e));
}
public ContentPresenter()
@@ -240,6 +237,21 @@ namespace Avalonia.Controls.Presenters
}
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ switch (change.Property.Name)
+ {
+ case nameof(Content):
+ case nameof(ContentTemplate):
+ ContentChanged(change);
+ break;
+ case nameof(TemplatedParent):
+ TemplatedParentChanged(change);
+ break;
+ }
+ }
+
///
/// Updates the control based on the control's .
///
@@ -254,8 +266,14 @@ namespace Avalonia.Controls.Presenters
public void UpdateChild()
{
var content = Content;
+ UpdateChild(content);
+ }
+
+ private void UpdateChild(object? content)
+ {
+ var contentTemplate = ContentTemplate;
var oldChild = Child;
- var newChild = CreateChild();
+ var newChild = CreateChild(content, oldChild, contentTemplate);
var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
// 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.
- if (!(content is IControl))
+ if (contentTemplate is { } || !(content is IControl))
{
DataContext = content;
}
@@ -299,6 +317,7 @@ namespace Avalonia.Controls.Presenters
}
_createdChild = true;
+
}
///
@@ -325,18 +344,23 @@ namespace Avalonia.Controls.Presenters
{
var content = Content;
var oldChild = Child;
+ return CreateChild(content, oldChild, ContentTemplate);
+ }
+
+ private IControl? CreateChild(object? content, IControl? oldChild, IDataTemplate? template)
+ {
var newChild = content as IControl;
// 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,
// otherwise it will blow up every ContentPresenter without Content set.
- if (newChild == null
- && (content != null || ContentTemplate != null))
+ if ((newChild == null
+ && (content != null || template != null)) || (newChild is { } && template is { }))
{
- var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ??
+ var dataTemplate = this.FindDataTemplate(content, template) ??
(
- RecognizesAccessKey
- ? FuncDataTemplate.Access
+ RecognizesAccessKey
+ ? FuncDataTemplate.Access
: FuncDataTemplate.Default
);
@@ -446,7 +470,14 @@ namespace Avalonia.Controls.Presenters
if (((ILogical)this).IsAttachedToLogicalTree)
{
- UpdateChild();
+ if (e.Property.Name == nameof(Content))
+ {
+ UpdateChild(e.NewValue);
+ }
+ else
+ {
+ UpdateChild();
+ }
}
else if (Child != null)
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 6a0408d6d1..468879edd1 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -23,7 +23,14 @@ namespace Avalonia.Controls.Primitives
}
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)
{
// We are saving it here since child controls might need to know the entire size of the overlay
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index 017a053c48..1d8d6aef45 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
+using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
@@ -137,6 +138,7 @@ namespace Avalonia.Controls
static ProgressBar()
{
+ ValueProperty.OverrideMetadata(new DirectPropertyMetadata(defaultBindingMode: BindingMode.OneWay));
ValueProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MinimumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e));
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index 46ee8e686c..5672641602 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -41,5 +41,10 @@ namespace Avalonia.Diagnostics
/// Default handler is
public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler;
+
+ ///
+ /// Gets or sets whether DevTools should use the dark mode theme
+ ///
+ public bool UseDarkMode { get; set; }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
index de3e56e514..16973a96ef 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
@@ -14,11 +14,13 @@ namespace Avalonia.Diagnostics.ViewModels
case Binding binding:
Path = binding.Path;
Tint = Brushes.CornflowerBlue;
+ ValueTypeTooltip = "Reflection Binding";
break;
case CompiledBindingExtension binding:
Path = binding.Path.ToString();
Tint = Brushes.DarkGreen;
+ ValueTypeTooltip = "Compiled Binding";
break;
case TemplateBinding binding:
@@ -32,6 +34,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
Tint = Brushes.OrangeRed;
+ ValueTypeTooltip = "Template Binding";
break;
default:
@@ -40,6 +43,8 @@ namespace Avalonia.Diagnostics.ViewModels
}
public IBrush Tint { get; }
+
+ public string ValueTypeTooltip { get; }
public string Path { get; }
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index 701947d2ab..a1fd8ed028 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -155,31 +155,19 @@ namespace Avalonia.Diagnostics.ViewModels
public object? SelectedEntity
{
get => _selectedEntity;
- set
- {
- RaiseAndSetIfChanged(ref _selectedEntity, value);
-
- }
+ set => RaiseAndSetIfChanged(ref _selectedEntity, value);
}
public string? SelectedEntityName
{
get => _selectedEntityName;
- set
- {
- RaiseAndSetIfChanged(ref _selectedEntityName, value);
-
- }
+ set => RaiseAndSetIfChanged(ref _selectedEntityName, value);
}
public string? SelectedEntityType
{
get => _selectedEntityType;
- set
- {
- RaiseAndSetIfChanged(ref _selectedEntityType, value);
-
- }
+ set => RaiseAndSetIfChanged(ref _selectedEntityType, value);
}
public PropertyViewModel? SelectedProperty
@@ -502,6 +490,31 @@ namespace Avalonia.Diagnostics.ViewModels
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)
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
index e93dc7361b..5202ac963e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
@@ -7,11 +7,14 @@ namespace Avalonia.Diagnostics.ViewModels
public object Key { get; }
public IBrush Tint { get; }
+
+ public string ValueTypeTooltip { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue)
{
Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown;
+ ValueTypeTooltip = isDynamic ? "Dynamic Resource" : "Static Resource";
}
public void CopyResourceKey()
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index ec97b213a9..cc392853be 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -44,7 +44,9 @@
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}"/>
-
+
+
+
+
+
+
+
+
+
+
@@ -138,6 +160,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -147,28 +180,17 @@
-
+
{
-
+
}
-
+
-
-
-
-
-
-
-
-
-
-
-
@@ -179,15 +201,15 @@
-
+
(
-
+
)
-
+
@@ -200,11 +222,11 @@
-
+
-
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
index 78919a2105..08ffe2c081 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
@@ -7,9 +7,13 @@ namespace Avalonia.Diagnostics.Views
{
internal class ControlDetailsView : UserControl
{
+ private DataGrid _dataGrid;
+
public ControlDetailsView()
{
InitializeComponent();
+
+ _dataGrid = this.GetControl("DataGrid");
}
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);
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
index af6c84a76a..db48aaaa8e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
@@ -15,6 +15,7 @@
+
@@ -46,6 +47,7 @@
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
index a3cff7f3d3..1e152dcdd4 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -11,6 +11,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
+using Avalonia.Themes.Default;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
@@ -242,7 +243,17 @@ namespace Avalonia.Diagnostics.Views
private void RootClosed(object? sender, EventArgs e) => Close();
- public void SetOptions(DevToolsOptions options) =>
+ public void SetOptions(DevToolsOptions options)
+ {
(DataContext as MainViewModel)?.SetOptions(options);
+
+ if (options.UseDarkMode)
+ {
+ if (Styles[0] is SimpleTheme st)
+ {
+ st.Mode = SimpleThemeMode.Dark;
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
index b796e173c4..d7f39f6642 100644
--- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
+++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
@@ -10,6 +10,8 @@
false
all
+ true
+ TargetFramework=netstandard2.0
diff --git a/src/Avalonia.PlatformSupport/AssetLoader.cs b/src/Avalonia.PlatformSupport/AssetLoader.cs
index 7220694d7b..fb03ec2f6e 100644
--- a/src/Avalonia.PlatformSupport/AssetLoader.cs
+++ b/src/Avalonia.PlatformSupport/AssetLoader.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Platform;
+using Avalonia.PlatformSupport.Internal;
using Avalonia.Utilities;
namespace Avalonia.PlatformSupport
@@ -13,12 +14,16 @@ namespace Avalonia.PlatformSupport
///
public class AssetLoader : IAssetLoader
{
- private const string AvaloniaResourceName = "!AvaloniaResources";
- private static readonly Dictionary AssemblyNameCache
- = new Dictionary();
+ private static AssemblyDescriptorResolver s_assemblyDescriptorResolver = new();
private AssemblyDescriptor? _defaultResmAssembly;
+ ///
+ /// Introduced for tests.
+ ///
+ internal static void SetAssemblyDescriptorResolver(AssemblyDescriptorResolver resolver) =>
+ s_assemblyDescriptorResolver = resolver;
+
///
/// Initializes a new instance of the class.
///
@@ -109,17 +114,18 @@ namespace Avalonia.PlatformSupport
/// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset
public IEnumerable GetAssets(Uri uri, Uri? baseUri)
{
- if (uri.IsAbsoluteUri && uri.Scheme == "resm")
+ if (uri.IsAbsoluteResm())
{
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}")) ??
Enumerable.Empty();
}
- uri = EnsureAbsolute(uri, baseUri);
- if (uri.Scheme == "avares")
+ uri = uri.EnsureAbsolute(baseUri);
+ if (uri.IsAvares())
{
var (asm, path) = GetResAsmAndPath(uri);
if (asm == null)
@@ -129,33 +135,23 @@ namespace Avalonia.PlatformSupport
"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();
- 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}"));
}
return Enumerable.Empty();
}
-
- 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)
{
- if (uri.IsAbsoluteUri && uri.Scheme == "resm")
+ if (uri.IsAbsoluteResm())
{
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
@@ -172,9 +168,9 @@ namespace Avalonia.PlatformSupport
return rv;
}
- uri = EnsureAbsolute(uri, baseUri);
+ uri = uri.EnsureAbsolute(baseUri);
- if (uri.Scheme == "avares")
+ if (uri.IsAvares())
{
var (asm, path) = GetResAsmAndPath(uri);
if (asm.AvaloniaResources == null)
@@ -188,8 +184,8 @@ namespace Avalonia.PlatformSupport
private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
{
- var asm = GetAssembly(uri.Authority);
- return (asm, uri.AbsolutePath);
+ var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority);
+ return (asm, uri.GetUnescapeAbsolutePath());
}
private AssemblyDescriptor? GetAssembly(Uri? uri)
@@ -198,197 +194,20 @@ namespace Avalonia.PlatformSupport
{
if (!uri.IsAbsoluteUri)
return null;
- if (uri.Scheme == "avares")
+ if (uri.IsAvares())
return GetResAsmAndPath(uri).asm;
- if (uri.Scheme == "resm")
+ if (uri.IsResm())
{
- var qs = ParseQueryString(uri);
-
- if (qs.TryGetValue("assembly", out var assemblyName))
- {
- return GetAssembly(assemblyName);
- }
+ var assemblyName = uri.GetAssemblyNameFromQuery();
+ if (assemblyName.Length > 0)
+ return s_assemblyDescriptorResolver.GetAssembly(assemblyName);
}
}
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 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? Resources { get; }
- public Dictionary? AvaloniaResources { get; }
- public string? Name { get; }
- }
-
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
diff --git a/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj b/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj
index e08dc5e194..420ac0796c 100644
--- a/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj
+++ b/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj
@@ -16,4 +16,9 @@
+
+
+
+
+
diff --git a/src/Avalonia.PlatformSupport/Internal/AssemblyDescriptor.cs b/src/Avalonia.PlatformSupport/Internal/AssemblyDescriptor.cs
new file mode 100644
index 0000000000..a3de7f2b8a
--- /dev/null
+++ b/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? Resources { get; }
+ public Dictionary? AvaloniaResources { get; }
+ public string? Name { get; }
+ private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
+ r.Path![0] == '/' ? r.Path : '/' + r.Path;
+}
diff --git a/src/Avalonia.PlatformSupport/Internal/AssemblyDescriptorResolver.cs b/src/Avalonia.PlatformSupport/Internal/AssemblyDescriptorResolver.cs
new file mode 100644
index 0000000000..a78051a9c4
--- /dev/null
+++ b/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 _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;
+ }
+}
diff --git a/src/Avalonia.PlatformSupport/Internal/AssetDescriptor.cs b/src/Avalonia.PlatformSupport/Internal/AssetDescriptor.cs
new file mode 100644
index 0000000000..baae1f99e7
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/Avalonia.PlatformSupport/Internal/Constants.cs b/src/Avalonia.PlatformSupport/Internal/Constants.cs
new file mode 100644
index 0000000000..c8a0f7b1ce
--- /dev/null
+++ b/src/Avalonia.PlatformSupport/Internal/Constants.cs
@@ -0,0 +1,6 @@
+namespace Avalonia.PlatformSupport.Internal;
+
+internal static class Constants
+{
+ public static string AvaloniaResourceName => "!AvaloniaResources";
+}
diff --git a/src/Avalonia.PlatformSupport/Internal/SlicedStream.cs b/src/Avalonia.PlatformSupport/Internal/SlicedStream.cs
new file mode 100644
index 0000000000..e310db964a
--- /dev/null
+++ b/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();
+}
diff --git a/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs b/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs
index 0060767565..30e8661e89 100644
--- a/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs
+++ b/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs
@@ -5,14 +5,27 @@ using System.Collections.Generic;
namespace Avalonia.Controls.Metadata
{
+ ///
+ /// Defines all pseudoclasses by name referenced and implemented by a control.
+ ///
+ ///
+ /// This is currently used for code-completion in certain IDEs.
+ ///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class PseudoClassesAttribute : Attribute
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The list of pseudoclass names.
public PseudoClassesAttribute(params string[] pseudoClasses)
{
PseudoClasses = pseudoClasses;
}
+ ///
+ /// Gets the list of pseudoclass names.
+ ///
public IReadOnlyList PseudoClasses { get; }
}
}
diff --git a/src/Avalonia.Styling/Controls/Metadata/TemplatePartAttribute.cs b/src/Avalonia.Styling/Controls/Metadata/TemplatePartAttribute.cs
new file mode 100644
index 0000000000..3b8f971713
--- /dev/null
+++ b/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
+{
+ ///
+ /// Defines a control template part referenced by name in code.
+ /// Template part names should begin with the "PART_" prefix.
+ ///
+ ///
+ /// 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.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public sealed class TemplatePartAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TemplatePartAttribute()
+ {
+ Name = string.Empty;
+ Type = typeof(object);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The part name used by the class to identify a required element in the style.
+ /// The type of the element that should be used as a part with name.
+ public TemplatePartAttribute(string name, Type type)
+ {
+ Name = name;
+ Type = type;
+ }
+
+ ///
+ /// 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.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the type of the element that should be used as a part with name specified
+ /// in .
+ ///
+ public Type Type { get; set; }
+ }
+}
diff --git a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml
index 305f1b1814..2c831cf360 100644
--- a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml
@@ -254,7 +254,7 @@