diff --git a/.gitmodules b/.gitmodules
index 10c780c09fd..2d11fdfa9e1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,5 +2,5 @@
path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
- path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
- url = https://github.com/kekekeks/XamlIl.git
+ path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
+ url = https://github.com/kekekeks/XamlX.git
diff --git a/Avalonia.sln b/Avalonia.sln
index f6dc039c2f0..ccaa41f552b 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -123,10 +123,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
+ build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
- build\BuildTargets.targets = build\BuildTargets.targets
+ build\CoreLibraries.props = build\CoreLibraries.props
+ build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
+ build\iOSWorkarounds.props = build\iOSWorkarounds.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@@ -136,17 +139,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
build\ReactiveUI.props = build\ReactiveUI.props
+ build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
+ build\SharedVersion.props = build\SharedVersion.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props
+ build\SourceLink.props = build\SourceLink.props
+ build\System.Drawing.Common.props = build\System.Drawing.Common.props
build\System.Memory.props = build\System.Memory.props
+ build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
ProjectSection(SolutionItems) = preProject
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
+ build\BuildTargets.targets = build\BuildTargets.targets
+ build\LegacyProject.targets = build\LegacyProject.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
@@ -207,6 +217,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1826,6 +1842,54 @@ Global
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.Build.0 = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.Build.0 = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -1946,6 +2010,30 @@ Global
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2004,6 +2092,7 @@ Global
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+ {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution
index a2208a9a91d..bef7e45524d 100644
--- a/Avalonia.v3.ncrunchsolution
+++ b/Avalonia.v3.ncrunchsolution
@@ -3,6 +3,7 @@
tests\TestFiles\**.*
src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll
+ src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll
True
.ncrunch
diff --git a/Directory.Build.props b/Directory.Build.props
index 1f26df9bbc1..b41f8c488e3 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,6 @@
$(MSBuildThisFileDirectory)build-intermediate/nuget
+ $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index 873048ef21d..88c4d362827 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 4def44cbd00..a8d9332c57b 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/dirs.proj b/dirs.proj
index 4b3b1183f0c..26c8f54b23f 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -6,7 +6,7 @@
-
+
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 6800ff7d689..9ff6130e5fa 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -205,6 +205,15 @@ enum AvnMenuItemToggleType
Radio
};
+enum AvnExtendClientAreaChromeHints
+{
+ AvnNoChrome = 0,
+ AvnSystemChrome = 0x01,
+ AvnPreferSystemChrome = 0x02,
+ AvnOSXThickTitleBar = 0x08,
+ AvnDefaultChrome = AvnSystemChrome,
+};
+
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
@@ -279,6 +288,10 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
virtual HRESULT TakeFocusFromChildren() = 0;
+ virtual HRESULT SetExtendClientArea (bool enable) = 0;
+ virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) = 0;
+ virtual HRESULT GetExtendTitleBarHeight (double*ret) = 0;
+ virtual HRESULT SetExtendTitleBarHeight (double value) = 0;
};
AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index 278daf9a189..455cfa2e415 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -4,6 +4,14 @@
{
public:
FORWARD_IUNKNOWN()
+
+ private:
+ CGFloat PrimaryDisplayHeight()
+ {
+ return NSMaxY([[[NSScreen screens] firstObject] frame]);
+ }
+
+public:
virtual HRESULT GetScreenCount (int* ret) override
{
@autoreleasepool
@@ -25,15 +33,15 @@ virtual HRESULT GetScreen (int index, AvnScreen* ret) override
auto screen = [[NSScreen screens] objectAtIndex:index];
- ret->Bounds.X = [screen frame].origin.x;
- ret->Bounds.Y = [screen frame].origin.y;
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
+ ret->Bounds.X = [screen frame].origin.x;
+ ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
- ret->WorkingArea.X = [screen visibleFrame].origin.x;
- ret->WorkingArea.Y = [screen visibleFrame].origin.y;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
+ ret->WorkingArea.X = [screen visibleFrame].origin.x;
+ ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h
index bdf3007a289..b1f64bca880 100644
--- a/native/Avalonia.Native/src/OSX/window.h
+++ b/native/Avalonia.Native/src/OSX/window.h
@@ -3,9 +3,6 @@
class WindowBaseImpl;
-@interface AutoFitContentVisualEffectView : NSVisualEffectView
-@end
-
@interface AvnView : NSView
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
@@ -15,6 +12,14 @@ class WindowBaseImpl;
-(AvnPixelSize) getPixelSize;
@end
+@interface AutoFitContentView : NSView
+-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
+-(void) ShowTitleBar: (bool) show;
+-(void) SetTitleBarHeightHint: (double) height;
+-(void) SetContent: (NSView* _Nonnull) content;
+-(void) ShowBlur: (bool) show;
+@end
+
@interface AvnWindow : NSWindow
+(void) closeAll;
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
@@ -27,6 +32,8 @@ class WindowBaseImpl;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(NSMenu* _Nullable)menu;
-(double) getScaling;
+-(double) getExtendedTitleBarHeight;
+-(void) setIsExtended:(bool)value;
@end
struct INSWindowHolder
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 7f8a6e13937..2d0ffbe4f0c 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -6,8 +6,6 @@
#include
#include "rendertarget.h"
-
-
class WindowBaseImpl : public virtual ComSingleObject, public INSWindowHolder
{
private:
@@ -20,7 +18,7 @@
View = NULL;
Window = NULL;
}
- NSVisualEffectView* VisualEffect;
+ AutoFitContentView* StandardContainer;
AvnView* View;
AvnWindow* Window;
ComPtr BaseEvents;
@@ -39,6 +37,7 @@
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
View = [[AvnView alloc] initWithParent:this];
+ StandardContainer = [[AutoFitContentView new] initWithContent:View];
Window = [[AvnWindow alloc] initWithParent:this];
@@ -49,12 +48,8 @@
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
- VisualEffect = [AutoFitContentVisualEffectView new];
- [VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
- [VisualEffect setMaterial:NSVisualEffectMaterialLight];
- [VisualEffect setAutoresizesSubviews:true];
-
- [Window setContentView: View];
+ [Window setOpaque:false];
+ [Window setContentView: StandardContainer];
}
virtual HRESULT ObtainNSWindowHandle(void** ret) override
@@ -410,12 +405,7 @@ virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override
virtual HRESULT SetBlurEnabled (bool enable) override
{
- [Window setContentView: enable ? VisualEffect : View];
-
- if(enable)
- {
- [VisualEffect addSubview:View];
- }
+ [StandardContainer ShowBlur:enable];
return S_OK;
}
@@ -492,6 +482,8 @@ virtual void OnResized ()
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
+ bool _isClientAreaExtended;
+ AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
@@ -505,6 +497,8 @@ virtual void OnResized ()
ComPtr WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
+ _isClientAreaExtended = false;
+ _extendClientHints = AvnDefaultChrome;
_fullScreenActive = false;
_canResize = true;
_decorations = SystemDecorationsFull;
@@ -523,8 +517,20 @@ void HideOrShowTrafficLights ()
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) {
- if ([button isKindOfClass:[NSButton class]]) {
- [button setHidden: (_decorations != SystemDecorationsFull)];
+ if ([button isKindOfClass:[NSButton class]])
+ {
+ if(_isClientAreaExtended)
+ {
+ auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+ [button setHidden: !wantsChrome];
+ }
+ else
+ {
+ [button setHidden: (_decorations != SystemDecorationsFull)];
+ }
+
+ [button setWantsLayer:true];
}
}
}
@@ -600,6 +606,35 @@ void WindowStateChanged () override
if(_lastWindowState != state)
{
+ if(_isClientAreaExtended)
+ {
+ if(_lastWindowState == FullScreen)
+ {
+ // we exited fs.
+ if(_extendClientHints & AvnOSXThickTitleBar)
+ {
+ Window.toolbar = [NSToolbar new];
+ Window.toolbar.showsBaselineSeparator = false;
+ }
+
+ [Window setTitlebarAppearsTransparent:true];
+
+ [StandardContainer setFrameSize: StandardContainer.frame.size];
+ }
+ else if(state == FullScreen)
+ {
+ // we entered fs.
+ if(_extendClientHints & AvnOSXThickTitleBar)
+ {
+ Window.toolbar = nullptr;
+ }
+
+ [Window setTitlebarAppearsTransparent:false];
+
+ [StandardContainer setFrameSize: StandardContainer.frame.size];
+ }
+ }
+
_lastWindowState = state;
WindowEvents->WindowStateChanged(state);
}
@@ -656,8 +691,6 @@ virtual HRESULT SetDecorations(SystemDecorations value) override
return S_OK;
}
- auto currentFrame = [Window frame];
-
UpdateStyle();
HideOrShowTrafficLights();
@@ -790,6 +823,81 @@ virtual HRESULT TakeFocusFromChildren () override
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
+
+ return S_OK;
+ }
+
+ virtual HRESULT SetExtendClientArea (bool enable) override
+ {
+ _isClientAreaExtended = enable;
+
+ if(enable)
+ {
+ Window.titleVisibility = NSWindowTitleHidden;
+
+ [Window setTitlebarAppearsTransparent:true];
+
+ auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+ if (wantsTitleBar)
+ {
+ [StandardContainer ShowTitleBar:true];
+ }
+ else
+ {
+ [StandardContainer ShowTitleBar:false];
+ }
+
+ if(_extendClientHints & AvnOSXThickTitleBar)
+ {
+ Window.toolbar = [NSToolbar new];
+ Window.toolbar.showsBaselineSeparator = false;
+ }
+ else
+ {
+ Window.toolbar = nullptr;
+ }
+ }
+ else
+ {
+ Window.titleVisibility = NSWindowTitleVisible;
+ Window.toolbar = nullptr;
+ [Window setTitlebarAppearsTransparent:false];
+ View.layer.zPosition = 0;
+ }
+
+ [Window setIsExtended:enable];
+
+ HideOrShowTrafficLights();
+
+ UpdateStyle();
+
+ return S_OK;
+ }
+
+ virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
+ {
+ _extendClientHints = hints;
+
+ SetExtendClientArea(_isClientAreaExtended);
+ return S_OK;
+ }
+
+ virtual HRESULT GetExtendTitleBarHeight (double*ret) override
+ {
+ if(ret == nullptr)
+ {
+ return E_POINTER;
+ }
+
+ *ret = [Window getExtendedTitleBarHeight];
+
+ return S_OK;
+ }
+
+ virtual HRESULT SetExtendTitleBarHeight (double value) override
+ {
+ [StandardContainer SetTitleBarHeightHint:value];
return S_OK;
}
@@ -802,8 +910,9 @@ void EnterFullScreenMode ()
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
- [Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
-
+ Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
+ Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
+
[Window toggleFullScreen:nullptr];
}
@@ -951,19 +1060,120 @@ virtual NSWindowStyleMask GetStyle() override
{
s |= NSWindowStyleMaskMiniaturizable;
}
+
+ if(_isClientAreaExtended)
+ {
+ s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
+ }
return s;
}
};
NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
-@implementation AutoFitContentVisualEffectView
+@implementation AutoFitContentView
+{
+ NSVisualEffectView* _titleBarMaterial;
+ NSBox* _titleBarUnderline;
+ NSView* _content;
+ NSVisualEffectView* _blurBehind;
+ double _titleBarHeightHint;
+ bool _settingSize;
+}
+
+-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
+{
+ _titleBarHeightHint = -1;
+ _content = content;
+ _settingSize = false;
+
+ [self setAutoresizesSubviews:true];
+ [self setWantsLayer:true];
+
+ _titleBarMaterial = [NSVisualEffectView new];
+ [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
+ [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
+ [_titleBarMaterial setWantsLayer:true];
+ _titleBarMaterial.hidden = true;
+
+ _titleBarUnderline = [NSBox new];
+ _titleBarUnderline.boxType = NSBoxSeparator;
+ _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
+ _titleBarUnderline.hidden = true;
+
+ [self addSubview:_titleBarMaterial];
+ [self addSubview:_titleBarUnderline];
+
+ _blurBehind = [NSVisualEffectView new];
+ [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+ [_blurBehind setMaterial:NSVisualEffectMaterialLight];
+ [_blurBehind setWantsLayer:true];
+ _blurBehind.hidden = true;
+
+ [self addSubview:_blurBehind];
+ [self addSubview:_content];
+
+ [self setWantsLayer:true];
+ return self;
+}
+
+-(void) ShowBlur:(bool)show
+{
+ _blurBehind.hidden = !show;
+}
+
+-(void) ShowTitleBar: (bool) show
+{
+ _titleBarMaterial.hidden = !show;
+ _titleBarUnderline.hidden = !show;
+}
+
+-(void) SetTitleBarHeightHint: (double) height
+{
+ _titleBarHeightHint = height;
+
+ [self setFrameSize:self.frame.size];
+}
+
-(void)setFrameSize:(NSSize)newSize
{
- [super setFrameSize:newSize];
- if([[self subviews] count] == 0)
+ if(_settingSize)
+ {
return;
- [[self subviews][0] setFrameSize: newSize];
+ }
+
+ _settingSize = true;
+ [super setFrameSize:newSize];
+
+ [_blurBehind setFrameSize:newSize];
+ [_content setFrameSize:newSize];
+
+ auto window = objc_cast([self window]);
+
+ // TODO get actual titlebar size
+
+ double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
+
+ NSRect tbar;
+ tbar.origin.x = 0;
+ tbar.origin.y = newSize.height - height;
+ tbar.size.width = newSize.width;
+ tbar.size.height = height;
+
+ [_titleBarMaterial setFrame:tbar];
+ tbar.size.height = height < 1 ? 0 : 1;
+ [_titleBarUnderline setFrame:tbar];
+ _settingSize = false;
+}
+
+-(void) SetContent: (NSView* _Nonnull) content
+{
+ if(content != nullptr)
+ {
+ [content removeFromSuperview];
+ [self addSubview:content];
+ _content = content;
+ }
}
@end
@@ -1081,10 +1291,15 @@ -(void)setFrameSize:(NSSize)newSize
_parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
- _lastPixelSize.Width = (int)fsize.width;
- _lastPixelSize.Height = (int)fsize.height;
- [self updateRenderTarget];
- _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+
+ if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
+ {
+ _lastPixelSize.Width = (int)fsize.width;
+ _lastPixelSize.Height = (int)fsize.height;
+ [self updateRenderTarget];
+
+ _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+ }
}
- (void)updateLayer
@@ -1523,15 +1738,43 @@ @implementation AvnWindow
bool _canBecomeKeyAndMain;
bool _closed;
bool _isEnabled;
+ bool _isExtended;
AvnMenu* _menu;
double _lastScaling;
}
+-(void) setIsExtended:(bool)value;
+{
+ _isExtended = value;
+}
+
-(double) getScaling
{
return _lastScaling;
}
+-(double) getExtendedTitleBarHeight
+{
+ if(_isExtended)
+ {
+ for (id subview in self.contentView.superview.subviews)
+ {
+ if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
+ {
+ NSView *titlebarView = [subview subviews][0];
+
+ return (double)titlebarView.frame.size.height;
+ }
+ }
+
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+(void)closeAll
{
NSArray* windows = [NSArray arrayWithArray:[NSApp windows]];
@@ -1650,6 +1893,7 @@ -(AvnWindow*) initWithParent: (WindowBaseImpl*) parent
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
[self invalidateShadow];
+ _isExtended = false;
return self;
}
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 890967ae4ff..fe877dc49c3 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -268,6 +268,8 @@ void RunCoreTest(string projectName)
.DependsOn(CreateIntermediateNugetPackages)
.Executes(() =>
{
+ BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
+ Parameters.Version + ".nupkg");
var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
EnsureCleanDirectory(Parameters.NugetRoot);
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
diff --git a/nukebuild/BuildTasksPatcher.cs b/nukebuild/BuildTasksPatcher.cs
new file mode 100644
index 00000000000..44f01da6690
--- /dev/null
+++ b/nukebuild/BuildTasksPatcher.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using ILRepacking;
+using Mono.Cecil;
+
+public class BuildTasksPatcher
+{
+ public static void PatchBuildTasksInPackage(string packagePath)
+ {
+ using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
+ ZipArchiveMode.Update))
+ {
+
+ foreach (var entry in archive.Entries.ToList())
+ {
+ if (entry.Name == "Avalonia.Build.Tasks.dll")
+ {
+ var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
+ var output = temp + ".output";
+ var patched = new MemoryStream();
+ try
+ {
+ entry.ExtractToFile(temp, true);
+ var repack = new ILRepacking.ILRepack(new RepackOptions()
+ {
+ Internalize = true,
+ InputAssemblies = new[]
+ {
+ temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0]
+ .FullyQualifiedName
+ },
+ SearchDirectories = new string[0],
+ OutputFile = output
+ });
+ repack.Repack();
+
+
+ // 'hurr-durr assembly with the same name is already loaded' prevention
+ using (var asm = AssemblyDefinition.ReadAssembly(output,
+ new ReaderParameters { ReadWrite = true, InMemory = true, }))
+ {
+ asm.Name = new AssemblyNameDefinition(
+ "Avalonia.Build.Tasks."
+ + Guid.NewGuid().ToString().Replace("-", ""),
+ new Version(0, 0, 0));
+ asm.Write(patched);
+ patched.Position = 0;
+ }
+ }
+ finally
+ {
+ try
+ {
+ if (File.Exists(temp))
+ File.Delete(temp);
+ if (File.Exists(output))
+ File.Delete(output);
+ }
+ catch
+ {
+ //ignore
+ }
+ }
+
+ var fn = entry.FullName;
+ entry.Delete();
+ var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal);
+ using (var s = newEntry.Open())
+ patched.CopyTo(s);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 584c36d0337..4c64d4ff932 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -14,6 +14,9 @@
+
+
+
diff --git a/readme.md b/readme.md
index 6a04c7e31ec..19a9a8420dc 100644
--- a/readme.md
+++ b/readme.md
@@ -16,7 +16,7 @@ To see the status of some of our features, please see our [Roadmap](https://gith
## 🚀 Getting Started
-The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starer guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
+The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
Avalonia is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/
diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml
index 26a62ebca62..14c371efef9 100644
--- a/samples/BindingDemo/MainWindow.xaml
+++ b/samples/BindingDemo/MainWindow.xaml
@@ -5,7 +5,8 @@
xmlns:local="clr-namespace:BindingDemo"
Title="AvaloniaUI Bindings Test"
Width="800"
- Height="600">
+ Height="600"
+ x:DataType="vm:MainWindowViewModel">
+
@@ -55,6 +55,7 @@
+
@@ -66,6 +67,7 @@
+
@@ -79,7 +81,7 @@
Simple - Light
Simple - Dark
-
+
None
Transparent
Blur
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index d6d4a71ad3c..b0c205246e2 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -58,13 +58,6 @@ public MainView()
if (VisualRoot is Window window)
window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
};
-
- var transparencyLevels = this.Find("TransparencyLevels");
- transparencyLevels.SelectionChanged += (sender, e) =>
- {
- if (VisualRoot is Window window)
- window.TransparencyLevelHint = (WindowTransparencyLevel)transparencyLevels.SelectedIndex;
- };
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index 76422bc130e..97bd88f5e43 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/samples/ControlCatalog/MainWindow.xaml
@@ -7,7 +7,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
- x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}">
+ ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
+ ExtendClientAreaChromeHints="{Binding ChromeHints}"
+ ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
+ TransparencyLevelHint="{Binding TransparencyLevel}"
+ x:Name="MainWindow"
+ x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
@@ -56,20 +61,30 @@
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml
new file mode 100644
index 00000000000..96cfcc5288f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs b/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs
new file mode 100644
index 00000000000..44e7c4b92be
--- /dev/null
+++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs
@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class AcrylicPage : UserControl
+ {
+ public AcrylicPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml
index 8133d0e408c..bfd14cc627d 100644
--- a/samples/ControlCatalog/Pages/BorderPage.xaml
+++ b/samples/ControlCatalog/Pages/BorderPage.xaml
@@ -9,27 +9,27 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
-
+
Border
-
Border and Background
-
Rounded Corners
-
Rounded Corners
-
diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml b/samples/ControlCatalog/Pages/ButtonPage.xaml
index 8b697b79481..a4427c70c51 100644
--- a/samples/ControlCatalog/Pages/ButtonPage.xaml
+++ b/samples/ControlCatalog/Pages/ButtonPage.xaml
@@ -9,10 +9,10 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
-
-
+
+
-
+
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
index 486cc55d445..369f7037186 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
@@ -16,7 +16,7 @@
-
+
Control Items
diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml
index 8f147638b51..8ccd8e97f73 100644
--- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml
+++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml
@@ -9,7 +9,7 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
-
@@ -34,7 +34,7 @@
-
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
index 65a798e53c8..c22cf68b682 100644
--- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
@@ -10,15 +10,15 @@
HorizontalAlignment="Center"
Spacing="16">
-
+
Drag Me
-
+
Drag Me (custom)
-
Drop some text or files here
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
index e600e644af9..304782dbf9d 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
@@ -1,6 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ItemsRepeater
@@ -23,16 +47,8 @@
-
-
-
-
-
-
-
+
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
index cce80a2d3cd..9e898c45369 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
@@ -38,6 +38,12 @@ private void InitializeComponent()
AvaloniaXamlLoader.Load(this);
}
+ public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
+ {
+ var item = (ItemsRepeaterPageViewModel.Item)e.DataContext;
+ e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
+ }
+
private void LayoutChanged(object sender, SelectionChangedEventArgs e)
{
if (_repeater == null)
@@ -79,6 +85,26 @@ private void LayoutChanged(object sender, SelectionChangedEventArgs e)
MinItemHeight = 200,
};
break;
+ case 4:
+ _scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+ _scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
+ _repeater.Layout = new WrapLayout
+ {
+ Orientation = Orientation.Vertical,
+ HorizontalSpacing = 20,
+ VerticalSpacing = 20
+ };
+ break;
+ case 5:
+ _scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
+ _scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
+ _repeater.Layout = new WrapLayout
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalSpacing = 20,
+ VerticalSpacing = 20
+ };
+ break;
}
}
diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
index 446dfd7ce17..8cf3610b472 100644
--- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
+++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
@@ -11,10 +11,10 @@
RowDefinitions="24,Auto,24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
-
-
-
-
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index 47b4ce7151c..f4d81418ac8 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -10,7 +10,13 @@
HorizontalAlignment="Center"
Spacing="16">
-
+
Add
diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml b/samples/ControlCatalog/Pages/RelativePanelPage.axaml
new file mode 100644
index 00000000000..3657d52bd9d
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
new file mode 100644
index 00000000000..11d0a5152e0
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class RelativePanelPage : UserControl
+ {
+ public RelativePanelPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index f73ef9b4fb0..4b8edcf98c3 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -64,51 +64,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
+ StrokeThicknessUnit="Pixel"
+ StrokeThickness="2">
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ StrokeThicknessUnit="Pixel"
+ StrokeThickness="1">
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ StrokeThicknessUnit="Pixel"
+ StrokeThickness="2">
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml
index 73d83e08f1a..ec073d48a9b 100644
--- a/samples/ControlCatalog/Pages/ToolTipPage.xaml
+++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml
@@ -12,7 +12,7 @@
HorizontalAlignment="Center">
@@ -26,7 +26,7 @@
diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
new file mode 100644
index 00000000000..b90f43c3b6c
--- /dev/null
+++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ None
+ Transparent
+ Blur
+ AcrylicBlur
+
+
+
diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
new file mode 100644
index 00000000000..d8d4f3f371e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class WindowCustomizationsPage : UserControl
+ {
+ public WindowCustomizationsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index 1ee6bf7a293..7c911e91e9e 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -12,7 +12,7 @@
diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
index 73aaeff9944..f893a6e28e9 100644
--- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
@@ -62,13 +62,9 @@ private ObservableCollection- CreateItems()
public class Item : ReactiveObject
{
private double _height = double.NaN;
- private int _index;
-
- public Item(int index)
- {
- _index = index;
- }
+ public Item(int index) => Index = index;
+ public int Index { get; }
public string Text { get; set; }
public double Height
@@ -76,8 +72,6 @@ public double Height
get => _height;
set => this.RaiseAndSetIfChanged(ref _height, value);
}
-
- public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat;
}
}
}
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
index 0257b4ce663..4356a032fad 100644
--- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -3,6 +3,8 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
+using Avalonia.Platform;
+using System;
using ReactiveUI;
namespace ControlCatalog.ViewModels
@@ -14,6 +16,12 @@ class MainWindowViewModel : ReactiveObject
private bool _isMenuItemChecked = true;
private WindowState _windowState;
private WindowState[] _windowStates;
+ private int _transparencyLevel;
+ private ExtendClientAreaChromeHints _chromeHints;
+ private bool _extendClientAreaEnabled;
+ private bool _systemTitleBarEnabled;
+ private bool _preferSystemChromeEnabled;
+ private double _titleBarHeight;
public MainWindowViewModel(IManagedNotificationManager notificationManager)
{
@@ -62,6 +70,63 @@ public MainWindowViewModel(IManagedNotificationManager notificationManager)
WindowState.Maximized,
WindowState.FullScreen,
};
+
+ this.WhenAnyValue(x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeEnabled)
+ .Subscribe(x =>
+ {
+ var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
+
+ if(x.Item1)
+ {
+ hints |= ExtendClientAreaChromeHints.SystemChrome;
+ }
+
+ if(x.Item2)
+ {
+ hints |= ExtendClientAreaChromeHints.PreferSystemChrome;
+ }
+
+ ChromeHints = hints;
+ });
+
+ SystemTitleBarEnabled = true;
+ TitleBarHeight = -1;
+ }
+
+ public int TransparencyLevel
+ {
+ get { return _transparencyLevel; }
+ set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); }
+ }
+
+ public ExtendClientAreaChromeHints ChromeHints
+ {
+ get { return _chromeHints; }
+ set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
+ }
+
+ public bool ExtendClientAreaEnabled
+ {
+ get { return _extendClientAreaEnabled; }
+ set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
+ }
+
+ public bool SystemTitleBarEnabled
+ {
+ get { return _systemTitleBarEnabled; }
+ set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
+ }
+
+ public bool PreferSystemChromeEnabled
+ {
+ get { return _preferSystemChromeEnabled; }
+ set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
+ }
+
+ public double TitleBarHeight
+ {
+ get { return _titleBarHeight; }
+ set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); }
}
public WindowState WindowState
diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml
index ccc3f54cc0b..61e4d2385b1 100644
--- a/samples/RenderDemo/App.xaml
+++ b/samples/RenderDemo/App.xaml
@@ -3,8 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.App">
-
-
+
-
\ No newline at end of file
+
diff --git a/samples/RenderDemo/Pages/ClippingPage.xaml b/samples/RenderDemo/Pages/ClippingPage.xaml
index 8920b8a1235..10225f7c491 100644
--- a/samples/RenderDemo/Pages/ClippingPage.xaml
+++ b/samples/RenderDemo/Pages/ClippingPage.xaml
@@ -43,7 +43,7 @@
C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344
C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
-
+
@@ -53,4 +53,4 @@
Apply Geometry Clip
-
\ No newline at end of file
+
diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml
index d6bde0b1466..fd23067f611 100644
--- a/samples/RenderDemo/SideBar.xaml
+++ b/samples/RenderDemo/SideBar.xaml
@@ -1,65 +1,68 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
index c623ae68b5a..cc831ef8aeb 100644
--- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
+++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 69fd2a9f135..71dce93ce7a 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -126,7 +126,7 @@ public virtual void Show()
_view.Visibility = ViewStates.Visible;
}
- public double Scaling => 1;
+ public double RenderScaling => 1;
void Draw()
{
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 09480f2701b..a873d5fd428 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Data;
+using Avalonia.Data.Core;
using Avalonia.Utilities;
namespace Avalonia
@@ -9,7 +10,7 @@ namespace Avalonia
///
/// Base class for avalonia properties.
///
- public abstract class AvaloniaProperty : IEquatable
+ public abstract class AvaloniaProperty : IEquatable, IPropertyInfo
{
///
/// Represents an unset property value.
@@ -582,6 +583,11 @@ private PropertyMetadata GetMetadataWithOverrides(Type type)
return _defaultMetadata;
}
+
+ bool IPropertyInfo.CanGet => true;
+ bool IPropertyInfo.CanSet => true;
+ object IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
+ void IPropertyInfo.Set(object target, object value) => ((AvaloniaObject)target).SetValue(this, value);
}
///
diff --git a/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
new file mode 100644
index 00000000000..f66411c2c2d
--- /dev/null
+++ b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Avalonia.Data.Core
+{
+ public class ClrPropertyInfo : IPropertyInfo
+ {
+ private readonly Func
-
-
-
-
- $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
index f94f10f7927..5a2c74e16f5 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
@@ -5,7 +5,7 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
-using XamlIl.TypeSystem;
+using XamlX.TypeSystem;
namespace Avalonia.Build.Tasks
{
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 30e8f120d7c..c1cc0e7bf0b 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -7,17 +7,18 @@
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
-using XamlIl.TypeSystem;
using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Parsers;
-using XamlIl.Transform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Parsers;
+using XamlX.Transform;
+using XamlX.TypeSystem;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;
+using XamlX.IL;
namespace Avalonia.Build.Tasks
{
@@ -50,23 +51,32 @@ public static CompileResult Compile(IBuildEngine engine, string input, string[]
if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
// Nothing to do
return new CompileResult(true);
-
- var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
- var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
+
+ var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ asm.MainModule.Types.Add(clrPropertiesDef);
+ var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ asm.MainModule.Types.Add(indexerAccessorClosure);
+
+ var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
+ var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
- XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
- AvaloniaXamlIlLanguage.CustomValueConverter);
+ XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
+ AvaloniaXamlIlLanguage.CustomValueConverter,
+ new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
+ new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
- var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
- xamlLanguage);
+ var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
+ xamlLanguage, emitConfig);
- var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl };
+ var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl };
var editorBrowsableAttribute = typeSystem
.GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
@@ -126,35 +136,35 @@ bool CompileGroup(IResourceGroup group)
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
- var parsed = XDocumentXamlIlParser.Parse(xaml);
+ var parsed = XDocumentXamlParser.Parse(xaml);
- var initialRoot = (XamlIlAstObjectNode)parsed.Root;
+ var initialRoot = (XamlAstObjectNode)parsed.Root;
- var precompileDirective = initialRoot.Children.OfType()
+ var precompileDirective = initialRoot.Children.OfType()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
if (precompileDirective != null)
{
- var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim()
+ var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
.ToLowerInvariant();
if (precompileText == "false")
continue;
if (precompileText != "true")
- throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective);
+ throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
}
- var classDirective = initialRoot.Children.OfType()
+ var classDirective = initialRoot.Children.OfType()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
- IXamlIlType classType = null;
+ IXamlType classType = null;
if (classDirective != null)
{
- if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
- throw new XamlIlParseException("x:Class should have a string value", classDirective);
+ if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
+ throw new XamlParseException("x:Class should have a string value", classDirective);
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
- throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
+ throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
compiler.OverrideRootType(parsed,
- new XamlIlAstClrTypeReference(classDirective, classType, false));
+ new XamlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}
@@ -323,7 +333,7 @@ bool CompileGroup(IResourceGroup group)
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
- if (e is XamlIlParseException xe)
+ if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index 7e893d131ec..7e921944eae 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -3920,6 +3920,14 @@ void SetValidationStatus(ICellEditBinding binding)
dataGridColumn: CurrentColumn,
dataGridRow: EditingRow,
dataGridCell: editingCell);
+
+ EditingRow.InvalidateDesiredHeight();
+ var column = editingCell.OwningColumn;
+ if (column.Width.IsSizeToCells || column.Width.IsAuto)
+ {// Invalidate desired width and force recalculation
+ column.SetWidthDesiredValue(0);
+ EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width);
+ }
}
// We're done, so raise the CellEditEnded event
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index 830eff1102f..d5115c983ac 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
@@ -767,6 +767,11 @@ internal void InvalidateHorizontalArrange()
}
}
+ internal void InvalidateDesiredHeight()
+ {
+ _cellsElement?.InvalidateDesiredHeight();
+ }
+
internal void ResetGridLine()
{
_bottomGridLine = null;
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
index 6e0703c90ff..c5fe9f0cb2a 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
@@ -299,6 +299,11 @@ internal void Recycle()
DesiredHeight = 0;
}
+ internal void InvalidateDesiredHeight()
+ {
+ DesiredHeight = 0;
+ }
+
private bool ShouldDisplayCell(DataGridColumn column, double frozenLeftEdge, double scrollingLeftEdge)
{
if (!column.IsVisible)
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
index 17e7ecba433..738732f6710 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
@@ -188,7 +188,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml
index 95bd9550a50..ae5b902ae8c 100644
--- a/src/Avalonia.Themes.Default/ComboBox.xaml
+++ b/src/Avalonia.Themes.Default/ComboBox.xaml
@@ -1,10 +1,29 @@
+
+
+
+
+ Item 1
+ Item 2
+
+
+ Item 1
+ Item 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml
index 88266ac9798..ded121f5f6b 100644
--- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml
@@ -44,7 +44,7 @@
-
+
diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml
index 0e7ec428563..1856eec2065 100644
--- a/src/Avalonia.Themes.Default/Window.xaml
+++ b/src/Avalonia.Themes.Default/Window.xaml
@@ -1,21 +1,25 @@
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
index 88040a69e74..6a2a04f7326 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
@@ -261,9 +261,7 @@
1
32
0,0
-
12,0,0,0
-
12,4,12,4
@@ -273,31 +271,12 @@
-
-
-
@@ -307,40 +286,6 @@
1
-
-
-
-
-
-
@@ -490,6 +435,15 @@
+
+
+
+
+
+
+
+
+
1
@@ -725,74 +679,33 @@
-
- XamlAutoFontFamily
- XamlAutoFontFamily
- 24
- 40
- 14
- -25
- 12,0,12,0
- 12,0,12,0
- 12,14,0,13
- 0
- 0,6,0,0
- 12,14,0,13
- SemiLight
- Bold
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ XamlAutoFontFamily
+ 24
+ 40
+ -25
+ 12,0,12,0
+ 12,0,12,0
+ SemiLight
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0
@@ -888,5 +801,11 @@
32
+
+
+
+
+
+ 1
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml
index d29db79e7bc..9ef92a44d5a 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml
@@ -1,9 +1,9 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml
index 43867f6e973..8c920401224 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml
@@ -1,9 +1,9 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf
new file mode 100644
index 00000000000..38f809991d8
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf
new file mode 100644
index 00000000000..5fd9dd9eedf
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf
new file mode 100644
index 00000000000..113d545eea2
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf
new file mode 100644
index 00000000000..98c4538462d
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf
new file mode 100644
index 00000000000..c1c3700fe55
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf
new file mode 100644
index 00000000000..04b15402d93
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml
index 44e7962e170..0d5d733cd9c 100644
--- a/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml
+++ b/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml
@@ -22,8 +22,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
index 84bf799d8d0..c2a1359e8a2 100644
--- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
+++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
@@ -11,13 +11,10 @@
-
-
-
-
-
-
+
+
+
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Fluent/Button.xaml b/src/Avalonia.Themes.Fluent/Button.xaml
index 345a74512c0..34bcd0f0c14 100644
--- a/src/Avalonia.Themes.Fluent/Button.xaml
+++ b/src/Avalonia.Themes.Fluent/Button.xaml
@@ -19,8 +19,7 @@
-
-
+
+
+
+
-
+
-
+
-
+
-
-
-
+
-
+
-
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml b/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml
index c26dec3d34b..66ccea99b52 100644
--- a/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml
+++ b/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml
@@ -1,17 +1,19 @@
-
-
+
+
Item 1
- Item 2
-
-
-
- Item 1
- Item 2
-
-
+ Item 2 long
+ Item 3
+ Item 4
+
+
@@ -19,12 +21,11 @@
-
-
+ Padding="{TemplateBinding Padding}" />
+
+
+
+
+
+
+
+
-
+
-
diff --git a/src/Avalonia.Themes.Fluent/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/ContextMenu.xaml
index 44783a8dea0..a6b61569440 100644
--- a/src/Avalonia.Themes.Fluent/ContextMenu.xaml
+++ b/src/Avalonia.Themes.Fluent/ContextMenu.xaml
@@ -1,7 +1,7 @@
-
-
diff --git a/src/Avalonia.Themes.Fluent/NotificationCard.xaml b/src/Avalonia.Themes.Fluent/NotificationCard.xaml
index 47d5988e8c3..5a0fa326577 100644
--- a/src/Avalonia.Themes.Fluent/NotificationCard.xaml
+++ b/src/Avalonia.Themes.Fluent/NotificationCard.xaml
@@ -1,92 +1,104 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
index 08de50c6e32..cc5e2234f8b 100644
--- a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
+++ b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
@@ -25,8 +25,7 @@
-
-
+
@@ -44,7 +43,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/ProgressBar.xaml
index ac5dd7c0edf..036595b61c1 100644
--- a/src/Avalonia.Themes.Fluent/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Fluent/ProgressBar.xaml
@@ -142,35 +142,15 @@
diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/RadioButton.xaml
index 23bcfc616a1..acde4ea0be4 100644
--- a/src/Avalonia.Themes.Fluent/RadioButton.xaml
+++ b/src/Avalonia.Themes.Fluent/RadioButton.xaml
@@ -17,8 +17,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/RepeatButton.xaml
index 12ba38d614a..5afaf05e9f4 100644
--- a/src/Avalonia.Themes.Fluent/RepeatButton.xaml
+++ b/src/Avalonia.Themes.Fluent/RepeatButton.xaml
@@ -17,8 +17,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/TabStripItem.xaml b/src/Avalonia.Themes.Fluent/TabStripItem.xaml
index d45f705a400..628ab8ddddd 100644
--- a/src/Avalonia.Themes.Fluent/TabStripItem.xaml
+++ b/src/Avalonia.Themes.Fluent/TabStripItem.xaml
@@ -14,11 +14,11 @@
diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml
index 49fc4b59b07..0327e776e37 100644
--- a/src/Avalonia.Themes.Fluent/TextBox.xaml
+++ b/src/Avalonia.Themes.Fluent/TextBox.xaml
@@ -8,74 +8,67 @@
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -103,7 +97,6 @@
diff --git a/src/Avalonia.Themes.Fluent/TimePicker.xaml b/src/Avalonia.Themes.Fluent/TimePicker.xaml
index 4a411796d8f..f82e194a73d 100644
--- a/src/Avalonia.Themes.Fluent/TimePicker.xaml
+++ b/src/Avalonia.Themes.Fluent/TimePicker.xaml
@@ -31,8 +31,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/ToggleButton.xaml
index a3c300f6f58..f2f07d3e2ac 100644
--- a/src/Avalonia.Themes.Fluent/ToggleButton.xaml
+++ b/src/Avalonia.Themes.Fluent/ToggleButton.xaml
@@ -20,8 +20,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
index 88266ac9798..4309edefe3f 100644
--- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
@@ -44,7 +44,7 @@
-
+
@@ -156,8 +156,7 @@
-
-
-
@@ -264,10 +259,6 @@
-
-
diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml
index 47ad494bbfd..f7a1ebbc6b9 100644
--- a/src/Avalonia.Themes.Fluent/ToolTip.xaml
+++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml
@@ -45,8 +45,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Window.xaml
index aee15347eb4..1856eec2065 100644
--- a/src/Avalonia.Themes.Fluent/Window.xaml
+++ b/src/Avalonia.Themes.Fluent/Window.xaml
@@ -1,21 +1,25 @@
+
-
+
-
+
-
+
-
+
diff --git a/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs
new file mode 100644
index 00000000000..34cede40185
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs
@@ -0,0 +1,20 @@
+namespace Avalonia.Media
+{
+ ///
+ /// Background Sources for Acrylic.
+ ///
+ public enum AcrylicBackgroundSource
+ {
+ ///
+ /// The acrylic has no background.
+ ///
+ None,
+
+ ///
+ /// Cuts through all render layers to reveal the window background.
+ /// This means if your window is transparent or blurred it
+ /// will be blended with the material.
+ ///
+ Digger
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index 052ee5e1b7e..16b4f90d579 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -89,6 +89,11 @@ public static Color FromUInt32(uint value)
/// The .
public static Color Parse(string s)
{
+ if (s is null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
if (TryParse(s, out Color color))
{
return color;
@@ -120,14 +125,16 @@ public static Color Parse(ReadOnlySpan s)
/// The status of the operation.
public static bool TryParse(string s, out Color color)
{
- if (s == null)
+ color = default;
+
+ if (s is null)
{
- throw new ArgumentNullException(nameof(s));
+ return false;
}
if (s.Length == 0)
{
- throw new FormatException();
+ return false;
}
if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color))
@@ -144,8 +151,6 @@ public static bool TryParse(string s, out Color color)
return true;
}
- color = default;
-
return false;
}
diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs
new file mode 100644
index 00000000000..6b699acd2e2
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs
@@ -0,0 +1,345 @@
+using System;
+
+namespace Avalonia.Media
+{
+ public class ExperimentalAcrylicMaterial : AvaloniaObject, IMutableExperimentalAcrylicMaterial
+ {
+ private Color _effectiveTintColor;
+ private Color _effectiveLuminosityColor;
+
+ static ExperimentalAcrylicMaterial()
+ {
+ AffectsRender(
+ TintColorProperty,
+ BackgroundSourceProperty,
+ TintOpacityProperty,
+ MaterialOpacityProperty,
+ PlatformTransparencyCompensationLevelProperty);
+
+ TintColorProperty.Changed.AddClassHandler((b, e) =>
+ {
+ b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+ b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+ });
+
+ TintOpacityProperty.Changed.AddClassHandler((b, e) =>
+ {
+ b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+ b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+ });
+
+ MaterialOpacityProperty.Changed.AddClassHandler((b, e) =>
+ {
+ b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+ b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+ });
+
+ PlatformTransparencyCompensationLevelProperty.Changed.AddClassHandler((b, e) =>
+ {
+ b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+ b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+ });
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TintColorProperty =
+ AvaloniaProperty.Register(nameof(TintColor));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty BackgroundSourceProperty =
+ AvaloniaProperty.Register(nameof(BackgroundSource));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TintOpacityProperty =
+ AvaloniaProperty.Register(nameof(TintOpacity), 0.8);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaterialOpacityProperty =
+ AvaloniaProperty.Register(nameof(MaterialOpacity), 0.5);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlatformTransparencyCompensationLevelProperty =
+ AvaloniaProperty.Register(nameof(PlatformTransparencyCompensationLevel), 0.0);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty FallbackColorProperty =
+ AvaloniaProperty.Register(nameof(FallbackColor));
+
+ ///
+ public event EventHandler Invalidated;
+
+ ///
+ /// Gets or Sets the BackgroundSource .
+ ///
+ public AcrylicBackgroundSource BackgroundSource
+ {
+ get => GetValue(BackgroundSourceProperty);
+ set => SetValue(BackgroundSourceProperty, value);
+ }
+
+ ///
+ /// Gets or Sets the TintColor.
+ ///
+ public Color TintColor
+ {
+ get => GetValue(TintColorProperty);
+ set => SetValue(TintColorProperty, value);
+ }
+
+ ///
+ /// Gets or Sets the Tint Opacity.
+ ///
+ public double TintOpacity
+ {
+ get => GetValue(TintOpacityProperty);
+ set => SetValue(TintOpacityProperty, value);
+ }
+
+ ///
+ /// Gets or Sets the Fallback Color.
+ /// This is used on rendering plaforms that dont support acrylic.
+ ///
+ public Color FallbackColor
+ {
+ get => GetValue(FallbackColorProperty);
+ set => SetValue(FallbackColorProperty, value);
+ }
+
+ ///
+ /// Gets or Sets the MaterialOpacity.
+ /// This makes the material more or less opaque.
+ ///
+ public double MaterialOpacity
+ {
+ get => GetValue(MaterialOpacityProperty);
+ set => SetValue(MaterialOpacityProperty, value);
+ }
+
+ ///
+ /// Gets or Sets the PlatformTransparencyCompensationLevel.
+ /// This value defines the minimum that can be used.
+ /// It means material opacity is re-scaled from this value to 1.
+ ///
+ public double PlatformTransparencyCompensationLevel
+ {
+ get => GetValue(PlatformTransparencyCompensationLevelProperty);
+ set => SetValue(PlatformTransparencyCompensationLevelProperty, value);
+ }
+
+ Color IExperimentalAcrylicMaterial.MaterialColor => _effectiveLuminosityColor;
+
+ Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor;
+
+ private struct HsvColor
+ {
+ public float Hue { get; set; }
+ public float Saturation { get; set; }
+ public float Value { get; set; }
+ }
+
+ private static HsvColor RgbToHsv(Color color)
+ {
+ var r = color.R / 255.0f;
+ var g = color.G / 255.0f;
+ var b = color.B / 255.0f;
+ var max = Math.Max(r, Math.Max(g, b));
+ var min = Math.Min(r, Math.Min(g, b));
+
+ float h, s, v;
+ h = v = max;
+
+ var d = max - min;
+ s = max == 0 ? 0 : d / max;
+
+ if (max == min)
+ {
+ h = 0; // achromatic
+ }
+ else
+ {
+ if (max == r)
+ {
+ h = (g - b) / d + (g < b ? 6 : 0);
+ }
+ else if (max == g)
+ {
+ h = (b - r) / d + 2;
+ }
+ else if (max == b)
+ {
+ h = (r - g) / d + 4;
+ }
+
+ h /= 6;
+ }
+
+ return new HsvColor { Hue = h, Saturation = s, Value = v };
+ }
+
+ private static Color GetEffectiveTintColor(Color tintColor, double tintOpacity)
+ {
+ // Update tintColor's alpha with the combined opacity value
+ double tintOpacityModifier = GetTintOpacityModifier(tintColor);
+
+ return new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B);
+ }
+
+ private static double GetTintOpacityModifier(Color tintColor)
+ {
+ // This method supresses the maximum allowable tint opacity depending on the luminosity and saturation of a color by
+ // compressing the range of allowable values - for example, a user-defined value of 100% will be mapped to 45% for pure
+ // white (100% luminosity), 85% for pure black (0% luminosity), and 90% for pure gray (50% luminosity). The intensity of
+ // the effect increases linearly as luminosity deviates from 50%. After this effect is calculated, we cancel it out
+ // linearly as saturation increases from zero.
+
+ const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning.
+
+ const double whiteMaxOpacity = 0.2; // 100% luminosity
+ const double midPointMaxOpacity = 0.45; // 50% luminosity
+ const double blackMaxOpacity = 0.45; // 0% luminosity
+
+ var hsv = RgbToHsv(tintColor);
+
+ double opacityModifier = midPointMaxOpacity;
+
+ if (hsv.Value != midPoint)
+ {
+ // Determine maximum suppression amount
+ double lowestMaxOpacity = midPointMaxOpacity;
+ double maxDeviation = midPoint;
+
+ if (hsv.Value > midPoint)
+ {
+ lowestMaxOpacity = whiteMaxOpacity; // At white (100% hsvV)
+ maxDeviation = 1 - maxDeviation;
+ }
+ else if (hsv.Value < midPoint)
+ {
+ lowestMaxOpacity = blackMaxOpacity; // At black (0% hsvV)
+ }
+
+ double maxOpacitySuppression = midPointMaxOpacity - lowestMaxOpacity;
+
+ // Determine normalized deviation from the midpoint
+ double deviation = Math.Abs(hsv.Value - midPoint);
+ double normalizedDeviation = deviation / maxDeviation;
+
+ // If we have saturation, reduce opacity suppression to allow that color to come through more
+ if (hsv.Saturation > 0)
+ {
+ // Dampen opacity suppression based on how much saturation there is
+ maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0);
+ }
+
+ double opacitySuppression = maxOpacitySuppression * normalizedDeviation;
+
+ opacityModifier = midPointMaxOpacity - opacitySuppression;
+ }
+
+ return opacityModifier;
+ }
+
+ private Color GetEffectiveLuminosityColor()
+ {
+ double? luminosityOpacity = MaterialOpacity;
+
+ return GetLuminosityColor(luminosityOpacity);
+ }
+
+ private static byte Trim(double value)
+ {
+ value = Math.Min(Math.Floor(value * 256), 255);
+
+ if (value < 0)
+ {
+ return 0;
+ }
+ else if (value > 255)
+ {
+ return 255;
+ }
+
+ return (byte)value;
+ }
+
+ private static float RGBMax(Color color)
+ {
+ if (color.R > color.G)
+ return (color.R > color.B) ? color.R : color.B;
+ else
+ return (color.G > color.B) ? color.G : color.B;
+ }
+
+ private static float RGBMin(Color color)
+ {
+ if (color.R < color.G)
+ return (color.R < color.B) ? color.R : color.B;
+ else
+ return (color.G < color.B) ? color.G : color.B;
+ }
+
+ // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity
+ private Color GetLuminosityColor(double? luminosityOpacity)
+ {
+ // Calculate the HSL lightness value of the color.
+ var max = (float)RGBMax(TintColor) / 255.0f;
+ var min = (float)RGBMin(TintColor) / 255.0f;
+
+ var lightness = (max + min) / 2.0;
+
+ lightness = 1 - ((1 - lightness) * luminosityOpacity.Value);
+
+ lightness = 0.13 + (lightness * 0.74);
+
+ var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness));
+
+ var compensationMultiplier = 1 - PlatformTransparencyCompensationLevel;
+ return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + (luminosityOpacity.Value * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B);
+ }
+
+ ///
+ /// Marks a property as affecting the brush's visual representation.
+ ///
+ /// The properties.
+ ///
+ /// After a call to this method in a brush's static constructor, any change to the
+ /// property will cause the event to be raised on the brush.
+ ///
+ protected static void AffectsRender(params AvaloniaProperty[] properties)
+ where T : ExperimentalAcrylicMaterial
+ {
+ static void Invalidate(AvaloniaPropertyChangedEventArgs e)
+ {
+ (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
+ }
+
+ foreach (var property in properties)
+ {
+ property.Changed.Subscribe(e => Invalidate(e));
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The event args.
+ protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
+
+ public IExperimentalAcrylicMaterial ToImmutable()
+ {
+ return new ImmutableExperimentalAcrylicMaterial(this);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs
index f9410afe6a6..ad3fee7eb7f 100644
--- a/src/Avalonia.Visuals/Media/FontManager.cs
+++ b/src/Avalonia.Visuals/Media/FontManager.cs
@@ -79,12 +79,13 @@ public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = fa
/// Returns a new typeface, or an existing one if a matching typeface exists.
///
/// The font family.
- /// The font weight.
/// The font style.
+ /// The font weight.
///
/// The typeface.
///
- public Typeface GetOrAddTypeface(FontFamily fontFamily, FontWeight fontWeight = FontWeight.Normal, FontStyle fontStyle = FontStyle.Normal)
+ public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
+ FontWeight fontWeight = FontWeight.Normal)
{
while (true)
{
@@ -93,14 +94,14 @@ public Typeface GetOrAddTypeface(FontFamily fontFamily, FontWeight fontWeight =
fontFamily = _defaultFontFamily;
}
- var key = new FontKey(fontFamily.Name, fontWeight, fontStyle);
+ var key = new FontKey(fontFamily.Name, fontStyle, fontWeight);
if (_typefaceCache.TryGetValue(key, out var typeface))
{
return typeface;
}
- typeface = new Typeface(fontFamily, fontWeight, fontStyle);
+ typeface = new Typeface(fontFamily, fontStyle, fontWeight);
if (_typefaceCache.TryAdd(key, typeface))
{
@@ -121,15 +122,16 @@ public Typeface GetOrAddTypeface(FontFamily fontFamily, FontWeight fontWeight =
/// Returns null if no fallback was found.
///
/// The codepoint to match against.
- /// The font weight.
/// The font style.
+ /// The font weight.
/// The font family. This is optional and used for fallback lookup.
/// The culture.
///
/// The matched typeface.
///
- public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = FontWeight.Normal,
+ public Typeface MatchCharacter(int codepoint,
FontStyle fontStyle = FontStyle.Normal,
+ FontWeight fontWeight = FontWeight.Normal,
FontFamily fontFamily = null, CultureInfo culture = null)
{
foreach (var cachedTypeface in _typefaceCache.Values)
@@ -142,8 +144,8 @@ public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = FontWeight
}
}
- var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture, out var key) ?
- _typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Weight, key.Style)) :
+ var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
+ _typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;
return matchedTypeface;
diff --git a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
index a8d81648baf..b330db84628 100644
--- a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
+++ b/src/Avalonia.Visuals/Media/Fonts/FontKey.cs
@@ -4,7 +4,7 @@ namespace Avalonia.Media.Fonts
{
public readonly struct FontKey : IEquatable
{
- public FontKey(string familyName, FontWeight weight, FontStyle style)
+ public FontKey(string familyName, FontStyle style, FontWeight weight)
{
FamilyName = familyName;
Style = style;
diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs
index 29c9d93560e..a32a3e1b6c7 100644
--- a/src/Avalonia.Visuals/Media/GlyphRun.cs
+++ b/src/Avalonia.Visuals/Media/GlyphRun.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media
{
@@ -205,13 +205,16 @@ public double GetDistanceFromCharacterHit(CharacterHit characterHit)
var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex);
- var currentCluster = _glyphClusters[glyphIndex];
-
- if (characterHit.TrailingLength > 0)
+ if (!GlyphClusters.IsEmpty)
{
- while (glyphIndex < _glyphClusters.Length && _glyphClusters[glyphIndex] == currentCluster)
+ var currentCluster = GlyphClusters[glyphIndex];
+
+ if (characterHit.TrailingLength > 0)
{
- glyphIndex++;
+ while (glyphIndex < GlyphClusters.Length && GlyphClusters[glyphIndex] == currentCluster)
+ {
+ glyphIndex++;
+ }
}
}
@@ -302,7 +305,7 @@ public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInsi
}
}
- var characterHit = FindNearestCharacterHit(GlyphClusters[index], out var width);
+ var characterHit = FindNearestCharacterHit(GlyphClusters.IsEmpty ? index : GlyphClusters[index], out var width);
var offset = GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
@@ -370,26 +373,31 @@ public int Compare(T x, T y)
///
public int FindGlyphIndex(int characterIndex)
{
+ if (GlyphClusters.IsEmpty)
+ {
+ return characterIndex;
+ }
+
if (IsLeftToRight)
{
- if (characterIndex < _glyphClusters[0])
+ if (characterIndex < GlyphClusters[0])
{
return 0;
}
- if (characterIndex > _glyphClusters[_glyphClusters.Length - 1])
+ if (characterIndex > GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
}
}
else
{
- if (characterIndex < _glyphClusters[_glyphClusters.Length - 1])
+ if (characterIndex < GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
}
- if (characterIndex > _glyphClusters[0])
+ if (characterIndex > GlyphClusters[0])
{
return 0;
}
@@ -397,7 +405,7 @@ public int FindGlyphIndex(int characterIndex)
var comparer = IsLeftToRight ? s_ascendingComparer : s_descendingComparer;
- var clusters = _glyphClusters.Buffer.Span;
+ var clusters = GlyphClusters.Buffer.Span;
// Find the start of the cluster at the character index.
var start = clusters.BinarySearch((ushort)characterIndex, comparer);
@@ -418,9 +426,19 @@ public int FindGlyphIndex(int characterIndex)
}
}
- while (start > 0 && clusters[start - 1] == clusters[start])
+ if (IsLeftToRight)
{
- start--;
+ while (start > 0 && clusters[start - 1] == clusters[start])
+ {
+ start--;
+ }
+ }
+ else
+ {
+ while (start + 1 < clusters.Length && clusters[start + 1] == clusters[start])
+ {
+ start++;
+ }
}
return start;
@@ -440,34 +458,74 @@ public CharacterHit FindNearestCharacterHit(int index, out double width)
var start = FindGlyphIndex(index);
- var currentCluster = _glyphClusters[start];
+ if (GlyphClusters.IsEmpty)
+ {
+ width = GetGlyphWidth(index);
+
+ return new CharacterHit(start, 1);
+ }
- var trailingLength = 0;
+ var cluster = GlyphClusters[start];
- while (start < _glyphClusters.Length && _glyphClusters[start] == currentCluster)
+ var nextCluster = cluster;
+
+ var currentIndex = start;
+
+ while (nextCluster == cluster)
{
- if (GlyphAdvances.IsEmpty)
+ width += GetGlyphWidth(currentIndex);
+
+ if (IsLeftToRight)
{
- var glyph = GlyphIndices[start];
+ currentIndex++;
- width += GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
+ if (currentIndex == GlyphClusters.Length)
+ {
+ break;
+ }
}
else
{
- width += GlyphAdvances[start];
+ currentIndex--;
+
+ if (currentIndex < 0)
+ {
+ break;
+ }
}
- trailingLength++;
- start++;
+ nextCluster = GlyphClusters[currentIndex];
+ }
+
+ int trailingLength;
+
+ if (nextCluster == cluster)
+ {
+ trailingLength = Characters.Start + Characters.Length - cluster;
+ }
+ else
+ {
+ trailingLength = nextCluster - cluster;
}
- if (start == _glyphClusters.Length &&
- currentCluster + trailingLength != Characters.Start + Characters.Length)
+ return new CharacterHit(cluster, trailingLength);
+ }
+
+ ///
+ /// Gets a glyph's width.
+ ///
+ /// The glyph index.
+ /// The glyph's width.
+ private double GetGlyphWidth(int index)
+ {
+ if (GlyphAdvances.IsEmpty)
{
- trailingLength = Characters.Start + Characters.Length - currentCluster;
+ var glyph = GlyphIndices[index];
+
+ return GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
}
- return new CharacterHit(currentCluster, trailingLength);
+ return GlyphAdvances[index];
}
///
diff --git a/src/Avalonia.Visuals/Media/IBrush.cs b/src/Avalonia.Visuals/Media/IBrush.cs
index 7756e94598b..15b7681be4a 100644
--- a/src/Avalonia.Visuals/Media/IBrush.cs
+++ b/src/Avalonia.Visuals/Media/IBrush.cs
@@ -13,4 +13,4 @@ public interface IBrush
///
double Opacity { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs
new file mode 100644
index 00000000000..e71584258a7
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs
@@ -0,0 +1,33 @@
+namespace Avalonia.Media
+{
+ ///
+ /// Experimental Interface for producing Acrylic-like materials.
+ ///
+ public interface IExperimentalAcrylicMaterial
+ {
+ ///
+ /// Gets the of the material.
+ ///
+ AcrylicBackgroundSource BackgroundSource { get; }
+
+ ///
+ /// Gets the TintColor of the material.
+ ///
+ Color TintColor { get; }
+
+ ///
+ /// Gets the TintOpacity of the material.
+ ///
+ double TintOpacity { get; }
+
+ ///
+ /// Gets the effective material color.
+ ///
+ Color MaterialColor { get; }
+
+ ///
+ /// Gets the fallback color.
+ ///
+ Color FallbackColor { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs
new file mode 100644
index 00000000000..fcfe4631a60
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs
@@ -0,0 +1,14 @@
+namespace Avalonia.Media
+{
+ ///
+ /// Represents a mutable brush which can return an immutable clone of itself.
+ ///
+ public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender
+ {
+ ///
+ /// Creates an immutable clone of the brush.
+ ///
+ /// The immutable clone.
+ IExperimentalAcrylicMaterial ToImmutable();
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs
new file mode 100644
index 00000000000..f46d76cf3f5
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs
@@ -0,0 +1,73 @@
+using System;
+
+namespace Avalonia.Media
+{
+ public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable
+ {
+ public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush)
+ {
+ BackgroundSource = brush.BackgroundSource;
+ TintColor = brush.TintColor;
+ TintOpacity = brush.TintOpacity;
+ FallbackColor = brush.FallbackColor;
+ MaterialColor = brush.MaterialColor;
+ }
+
+ public AcrylicBackgroundSource BackgroundSource { get; }
+
+ public Color TintColor { get; }
+
+ public Color MaterialColor { get; }
+
+ public double TintOpacity { get; }
+
+ public Color FallbackColor { get; }
+
+ public bool Equals(ImmutableExperimentalAcrylicMaterial other)
+ {
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
+ return
+ TintColor == other.TintColor &&
+ TintOpacity == other.TintOpacity &&
+ BackgroundSource == other.BackgroundSource &&
+ FallbackColor == other.FallbackColor && MaterialColor == other.MaterialColor;
+
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is ImmutableExperimentalAcrylicMaterial other && Equals(other);
+ }
+
+ public Color GetEffectiveTintColor()
+ {
+ return TintColor;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+
+ hash = (hash * 23) + TintColor.GetHashCode();
+ hash = (hash * 23) + TintOpacity.GetHashCode();
+ hash = (hash * 23) + BackgroundSource.GetHashCode();
+ hash = (hash * 23) + FallbackColor.GetHashCode();
+ hash = (hash * 23) + MaterialColor.GetHashCode();
+
+ return hash;
+ }
+ }
+
+ public static bool operator ==(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/MaterialExtensions.cs b/src/Avalonia.Visuals/Media/MaterialExtensions.cs
new file mode 100644
index 00000000000..c0b445c357b
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/MaterialExtensions.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Avalonia.Media
+{
+ public static class MaterialExtensions
+ {
+ ///
+ /// Converts a brush to an immutable brush.
+ ///
+ /// The brush.
+ ///
+ /// The result of calling if the brush is mutable,
+ /// otherwise .
+ ///
+ public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material)
+ {
+ Contract.Requires(material != null);
+
+ return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextDecoration.cs b/src/Avalonia.Visuals/Media/TextDecoration.cs
index a83555946ba..681fc5d4994 100644
--- a/src/Avalonia.Visuals/Media/TextDecoration.cs
+++ b/src/Avalonia.Visuals/Media/TextDecoration.cs
@@ -1,4 +1,5 @@
-using Avalonia.Media.Immutable;
+using Avalonia.Collections;
+using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
@@ -14,28 +15,52 @@ public class TextDecoration : AvaloniaObject
AvaloniaProperty.Register(nameof(Location));
///
- /// Defines the property.
+ /// Defines the property.
///
- public static readonly StyledProperty PenProperty =
- AvaloniaProperty.Register(nameof(Pen));
+ public static readonly StyledProperty StrokeProperty =
+ AvaloniaProperty.Register(nameof(Stroke));
///
- /// Defines the property.
+ /// Defines the property.
///
- public static readonly StyledProperty PenThicknessUnitProperty =
- AvaloniaProperty.Register(nameof(PenThicknessUnit));
+ public static readonly StyledProperty StrokeThicknessUnitProperty =
+ AvaloniaProperty.Register(nameof(StrokeThicknessUnit));
///
- /// Defines the property.
+ /// Defines the property.
///
- public static readonly StyledProperty PenOffsetProperty =
- AvaloniaProperty.Register(nameof(PenOffset));
+ public static readonly StyledProperty> StrokeDashArrayProperty =
+ AvaloniaProperty.Register>(nameof(StrokeDashArray));
///
- /// Defines the property.
+ /// Defines the property.
///
- public static readonly StyledProperty PenOffsetUnitProperty =
- AvaloniaProperty.Register(nameof(PenOffsetUnit));
+ public static readonly StyledProperty StrokeDashOffsetProperty =
+ AvaloniaProperty.Register(nameof(StrokeDashOffset));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StrokeThicknessProperty =
+ AvaloniaProperty.Register(nameof(StrokeThickness), 1);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StrokeLineCapProperty =
+ AvaloniaProperty.Register(nameof(StrokeLineCap));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StrokeOffsetProperty =
+ AvaloniaProperty.Register(nameof(StrokeOffset));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StrokeOffsetUnitProperty =
+ AvaloniaProperty.Register(nameof(StrokeOffsetUnit));
///
/// Gets or sets the location.
@@ -50,54 +75,139 @@ public TextDecorationLocation Location
}
///
- /// Gets or sets the pen.
+ /// Gets or sets the that specifies how the is painted.
///
- ///
- /// The pen.
- ///
- public IPen Pen
+ public IBrush Stroke
+ {
+ get { return GetValue(StrokeProperty); }
+ set { SetValue(StrokeProperty, value); }
+ }
+
+ ///
+ /// Gets the units in which the thickness of the is expressed.
+ ///
+ public TextDecorationUnit StrokeThicknessUnit
+ {
+ get => GetValue(StrokeThicknessUnitProperty);
+ set => SetValue(StrokeThicknessUnitProperty, value);
+ }
+
+ ///
+ /// Gets or sets a collection of values that indicate the pattern of dashes and gaps
+ /// that is used to draw the .
+ ///
+ public AvaloniaList StrokeDashArray
+ {
+ get { return GetValue(StrokeDashArrayProperty); }
+ set { SetValue(StrokeDashArrayProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the distance within the dash pattern where a dash begins.
+ ///
+ public double StrokeDashOffset
+ {
+ get { return GetValue(StrokeDashOffsetProperty); }
+ set { SetValue(StrokeDashOffsetProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the thickness of the .
+ ///
+ public double StrokeThickness
{
- get => GetValue(PenProperty);
- set => SetValue(PenProperty, value);
+ get { return GetValue(StrokeThicknessProperty); }
+ set { SetValue(StrokeThicknessProperty, value); }
}
///
- /// Gets the units in which the Thickness of the text decoration's is expressed.
+ /// Gets or sets a enumeration value that describes the shape at the ends of a line.
///
- public TextDecorationUnit PenThicknessUnit
+ public PenLineCap StrokeLineCap
{
- get => GetValue(PenThicknessUnitProperty);
- set => SetValue(PenThicknessUnitProperty, value);
+ get { return GetValue(StrokeLineCapProperty); }
+ set { SetValue(StrokeLineCapProperty, value); }
}
///
- /// Gets or sets the pen offset.
+ /// The stroke's offset.
///
///
/// The pen offset.
///
- public double PenOffset
+ public double StrokeOffset
{
- get => GetValue(PenOffsetProperty);
- set => SetValue(PenOffsetProperty, value);
+ get => GetValue(StrokeOffsetProperty);
+ set => SetValue(StrokeOffsetProperty, value);
}
///
- /// Gets the units in which the value is expressed.
+ /// Gets the units in which the value is expressed.
///
- public TextDecorationUnit PenOffsetUnit
+ public TextDecorationUnit StrokeOffsetUnit
{
- get => GetValue(PenOffsetUnitProperty);
- set => SetValue(PenOffsetUnitProperty, value);
+ get => GetValue(StrokeOffsetUnitProperty);
+ set => SetValue(StrokeOffsetUnitProperty, value);
}
///
- /// Creates an immutable clone of the .
+ /// Draws the at given origin.
///
- /// The immutable clone.
- public ImmutableTextDecoration ToImmutable()
+ /// The drawing context.
+ /// The shaped characters that are decorated.
+ /// The origin.
+ internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters, Point origin)
{
- return new ImmutableTextDecoration(Location, Pen?.ToImmutable(), PenThicknessUnit, PenOffset, PenOffsetUnit);
+ var fontRenderingEmSize = shapedTextCharacters.Properties.FontRenderingEmSize;
+ var fontMetrics = shapedTextCharacters.FontMetrics;
+ var thickness = StrokeThickness;
+
+ switch (StrokeThicknessUnit)
+ {
+ case TextDecorationUnit.FontRecommended:
+ switch (Location)
+ {
+ case TextDecorationLocation.Underline:
+ thickness = fontMetrics.UnderlineThickness;
+ break;
+ case TextDecorationLocation.Strikethrough:
+ thickness = fontMetrics.StrikethroughThickness;
+ break;
+ }
+
+ break;
+ case TextDecorationUnit.FontRenderingEmSize:
+ thickness = fontRenderingEmSize * thickness;
+ break;
+ }
+
+ switch (Location)
+ {
+ case TextDecorationLocation.Overline:
+ origin += new Point(0, fontMetrics.Ascent);
+ break;
+ case TextDecorationLocation.Strikethrough:
+ origin += new Point(0, -fontMetrics.StrikethroughPosition);
+ break;
+ case TextDecorationLocation.Underline:
+ origin += new Point(0, -fontMetrics.UnderlinePosition);
+ break;
+ }
+
+ switch (StrokeOffsetUnit)
+ {
+ case TextDecorationUnit.FontRenderingEmSize:
+ origin += new Point(0, StrokeOffset * fontRenderingEmSize);
+ break;
+ case TextDecorationUnit.Pixel:
+ origin += new Point(0, StrokeOffset);
+ break;
+ }
+
+ var pen = new Pen(Stroke ?? shapedTextCharacters.Properties.ForegroundBrush, thickness,
+ new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
+
+ drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Bounds.Width, 0));
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextDecorationCollection.cs b/src/Avalonia.Visuals/Media/TextDecorationCollection.cs
index 21e2e2484c5..2dced2252e1 100644
--- a/src/Avalonia.Visuals/Media/TextDecorationCollection.cs
+++ b/src/Avalonia.Visuals/Media/TextDecorationCollection.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
-using Avalonia.Media.Immutable;
using Avalonia.Utilities;
namespace Avalonia.Media
@@ -11,22 +10,6 @@ namespace Avalonia.Media
///
public class TextDecorationCollection : AvaloniaList
{
- ///
- /// Creates an immutable clone of the .
- ///
- /// The immutable clone.
- public ImmutableTextDecoration[] ToImmutable()
- {
- var immutable = new ImmutableTextDecoration[Count];
-
- for (var i = 0; i < Count; i++)
- {
- immutable[i] = this[i].ToImmutable();
- }
-
- return immutable;
- }
-
///
/// Parses a string.
///
diff --git a/src/Avalonia.Visuals/Media/TextDecorationUnit.cs b/src/Avalonia.Visuals/Media/TextDecorationUnit.cs
index dde425bb94c..a61983e8d59 100644
--- a/src/Avalonia.Visuals/Media/TextDecorationUnit.cs
+++ b/src/Avalonia.Visuals/Media/TextDecorationUnit.cs
@@ -1,7 +1,7 @@
namespace Avalonia.Media
{
///
- /// Specifies the unit type of either a or a thickness value.
+ /// Specifies the unit type of either a or a value.
///
public enum TextDecorationUnit
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
index 4903342cea8..56790cc0db2 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
@@ -1,6 +1,4 @@
-using Avalonia.Platform;
-
-namespace Avalonia.Media.TextFormatting
+namespace Avalonia.Media.TextFormatting
{
///
/// A text run that supports drawing content.
@@ -17,6 +15,6 @@ public abstract class DrawableTextRun : TextRun
///
/// The drawing context.
/// The origin.
- public abstract void Draw(IDrawingContextImpl drawingContext, Point origin);
+ public abstract void Draw(DrawingContext drawingContext, Point origin);
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
new file mode 100644
index 00000000000..8e7d934bca4
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
@@ -0,0 +1,56 @@
+namespace Avalonia.Media.TextFormatting
+{
+ public class GenericTextParagraphProperties : TextParagraphProperties
+ {
+ private TextAlignment _textAlignment;
+ private TextWrapping _textWrapping;
+ private double _lineHeight;
+
+ public GenericTextParagraphProperties(
+ TextRunProperties defaultTextRunProperties,
+ TextAlignment textAlignment = TextAlignment.Left,
+ TextWrapping textWrapping = TextWrapping.NoWrap,
+ double lineHeight = 0)
+ {
+ DefaultTextRunProperties = defaultTextRunProperties;
+
+ _textAlignment = textAlignment;
+
+ _textWrapping = textWrapping;
+
+ _lineHeight = lineHeight;
+ }
+
+ public override TextRunProperties DefaultTextRunProperties { get; }
+
+ public override TextAlignment TextAlignment => _textAlignment;
+
+ public override TextWrapping TextWrapping => _textWrapping;
+
+ public override double LineHeight => _lineHeight;
+
+ ///
+ /// Set text alignment
+ ///
+ internal void SetTextAlignment(TextAlignment textAlignment)
+ {
+ _textAlignment = textAlignment;
+ }
+
+ ///
+ /// Set text wrap
+ ///
+ internal void SetTextWrapping(TextWrapping textWrapping)
+ {
+ _textWrapping = textWrapping;
+ }
+
+ ///
+ /// Set line height
+ ///
+ internal void SetLineHeight(double lineHeight)
+ {
+ _lineHeight = lineHeight;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs
new file mode 100644
index 00000000000..3db35894986
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs
@@ -0,0 +1,40 @@
+using System.Globalization;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Generic implementation of TextRunProperties
+ ///
+ public class GenericTextRunProperties : TextRunProperties
+ {
+ public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = 12,
+ TextDecorationCollection textDecorations = null, IBrush foregroundBrush = null, IBrush backgroundBrush = null,
+ CultureInfo cultureInfo = null)
+ {
+ Typeface = typeface;
+ FontRenderingEmSize = fontRenderingEmSize;
+ TextDecorations = textDecorations;
+ ForegroundBrush = foregroundBrush;
+ BackgroundBrush = backgroundBrush;
+ CultureInfo = cultureInfo;
+ }
+
+ ///
+ public override Typeface Typeface { get; }
+
+ ///
+ public override double FontRenderingEmSize { get; }
+
+ ///
+ public override TextDecorationCollection TextDecorations { get; }
+
+ ///
+ public override IBrush ForegroundBrush { get; }
+
+ ///
+ public override IBrush BackgroundBrush { get; }
+
+ ///
+ public override CultureInfo CultureInfo { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs
new file mode 100644
index 00000000000..0c6c7229414
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs
@@ -0,0 +1,23 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// A group of characters that can be shaped.
+ ///
+ public sealed class ShapeableTextCharacters : TextRun
+ {
+ public ShapeableTextCharacters(ReadOnlySlice text, TextRunProperties properties)
+ {
+ TextSourceLength = text.Length;
+ Text = text;
+ Properties = properties;
+ }
+
+ public override int TextSourceLength { get; }
+
+ public override ReadOnlySlice Text { get; }
+
+ public override TextRunProperties Properties { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
new file mode 100644
index 00000000000..9e67a03f452
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -0,0 +1,154 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// A text run that holds shaped characters.
+ ///
+ public sealed class ShapedTextCharacters : DrawableTextRun
+ {
+ public ShapedTextCharacters(GlyphRun glyphRun, TextRunProperties properties)
+ {
+ Text = glyphRun.Characters;
+ Properties = properties;
+ TextSourceLength = Text.Length;
+ FontMetrics = new FontMetrics(Properties.Typeface, Properties.FontRenderingEmSize);
+ GlyphRun = glyphRun;
+ }
+
+ ///
+ public override ReadOnlySlice Text { get; }
+
+ ///
+ public override TextRunProperties Properties { get; }
+
+ ///
+ public override int TextSourceLength { get; }
+
+ ///
+ public override Rect Bounds => GlyphRun.Bounds;
+
+ ///
+ /// Gets the font metrics.
+ ///
+ ///
+ /// The font metrics.
+ ///
+ public FontMetrics FontMetrics { get; }
+
+ ///
+ /// Gets the glyph run.
+ ///
+ ///
+ /// The glyphs.
+ ///
+ public GlyphRun GlyphRun { get; }
+
+ ///
+ public override void Draw(DrawingContext drawingContext, Point origin)
+ {
+ if (GlyphRun.GlyphIndices.Length == 0)
+ {
+ return;
+ }
+
+ if (Properties.Typeface == null)
+ {
+ return;
+ }
+
+ if (Properties.ForegroundBrush == null)
+ {
+ return;
+ }
+
+ if (Properties.BackgroundBrush != null)
+ {
+ drawingContext.DrawRectangle(Properties.BackgroundBrush, null,
+ new Rect(origin.X, origin.Y + FontMetrics.Ascent, Bounds.Width, Bounds.Height));
+ }
+
+ drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun, origin);
+
+ if (Properties.TextDecorations == null)
+ {
+ return;
+ }
+
+ foreach (var textDecoration in Properties.TextDecorations)
+ {
+ textDecoration.Draw(drawingContext, this, origin);
+ }
+ }
+
+ ///
+ /// Splits the at specified length.
+ ///
+ /// The length.
+ /// The split result.
+ public SplitTextCharactersResult Split(int length)
+ {
+ var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length);
+
+ if (GlyphRun.Characters.Length == length)
+ {
+ return new SplitTextCharactersResult(this, null);
+ }
+
+ if (GlyphRun.GlyphIndices.Length == glyphCount)
+ {
+ return new SplitTextCharactersResult(this, null);
+ }
+
+ var firstGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Take(glyphCount),
+ GlyphRun.GlyphAdvances.Take(glyphCount),
+ GlyphRun.GlyphOffsets.Take(glyphCount),
+ GlyphRun.Characters.Take(length),
+ GlyphRun.GlyphClusters.Take(glyphCount));
+
+ var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
+
+ var secondGlyphRun = new GlyphRun(
+ Properties.Typeface.GlyphTypeface,
+ Properties.FontRenderingEmSize,
+ GlyphRun.GlyphIndices.Skip(glyphCount),
+ GlyphRun.GlyphAdvances.Skip(glyphCount),
+ GlyphRun.GlyphOffsets.Skip(glyphCount),
+ GlyphRun.Characters.Skip(length),
+ GlyphRun.GlyphClusters.Skip(glyphCount));
+
+ var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
+
+ return new SplitTextCharactersResult(firstTextRun, secondTextRun);
+ }
+
+ public readonly struct SplitTextCharactersResult
+ {
+ public SplitTextCharactersResult(ShapedTextCharacters first, ShapedTextCharacters second)
+ {
+ First = first;
+
+ Second = second;
+ }
+
+ ///
+ /// Gets the first text run.
+ ///
+ ///
+ /// The first text run.
+ ///
+ public ShapedTextCharacters First { get; }
+
+ ///
+ /// Gets the second text run.
+ ///
+ ///
+ /// The second text run.
+ ///
+ public ShapedTextCharacters Second { get; }
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs
deleted file mode 100644
index 00f9b918cb1..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextRun.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-using Avalonia.Media.Immutable;
-using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Platform;
-using Avalonia.Utility;
-
-namespace Avalonia.Media.TextFormatting
-{
- ///
- /// A text run that holds a shaped glyph run.
- ///
- public sealed class ShapedTextRun : DrawableTextRun
- {
- public ShapedTextRun(ReadOnlySlice text, TextStyle style) : this(
- TextShaper.Current.ShapeText(text, style.TextFormat), style)
- {
- }
-
- public ShapedTextRun(GlyphRun glyphRun, TextStyle style)
- {
- Text = glyphRun.Characters;
- Style = style;
- GlyphRun = glyphRun;
- }
-
- ///
- public override Rect Bounds => GlyphRun.Bounds;
-
- ///
- /// Gets the glyph run.
- ///
- ///
- /// The glyphs.
- ///
- public GlyphRun GlyphRun { get; }
-
- ///
- public override void Draw(IDrawingContextImpl drawingContext, Point origin)
- {
- if (GlyphRun.GlyphIndices.Length == 0)
- {
- return;
- }
-
- if (Style.TextFormat.Typeface == null)
- {
- return;
- }
-
- if (Style.Foreground == null)
- {
- return;
- }
-
- drawingContext.DrawGlyphRun(Style.Foreground, GlyphRun, origin);
-
- if (Style.TextDecorations == null)
- {
- return;
- }
-
- foreach (var textDecoration in Style.TextDecorations)
- {
- DrawTextDecoration(drawingContext, textDecoration, origin);
- }
- }
-
- ///
- /// Draws the at given origin.
- ///
- /// The drawing context.
- /// The text decoration.
- /// The origin.
- private void DrawTextDecoration(IDrawingContextImpl drawingContext, ImmutableTextDecoration textDecoration, Point origin)
- {
- var textFormat = Style.TextFormat;
-
- var fontMetrics = Style.TextFormat.FontMetrics;
-
- var thickness = textDecoration.Pen?.Thickness ?? 1.0;
-
- switch (textDecoration.PenThicknessUnit)
- {
- case TextDecorationUnit.FontRecommended:
- switch (textDecoration.Location)
- {
- case TextDecorationLocation.Underline:
- thickness = fontMetrics.UnderlineThickness;
- break;
- case TextDecorationLocation.Strikethrough:
- thickness = fontMetrics.StrikethroughThickness;
- break;
- }
- break;
- case TextDecorationUnit.FontRenderingEmSize:
- thickness = textFormat.FontRenderingEmSize * thickness;
- break;
- }
-
- switch (textDecoration.Location)
- {
- case TextDecorationLocation.Overline:
- origin += new Point(0, textFormat.FontMetrics.Ascent);
- break;
- case TextDecorationLocation.Strikethrough:
- origin += new Point(0, -textFormat.FontMetrics.StrikethroughPosition);
- break;
- case TextDecorationLocation.Underline:
- origin += new Point(0, -textFormat.FontMetrics.UnderlinePosition);
- break;
- }
-
- switch (textDecoration.PenOffsetUnit)
- {
- case TextDecorationUnit.FontRenderingEmSize:
- origin += new Point(0, textDecoration.PenOffset * textFormat.FontRenderingEmSize);
- break;
- case TextDecorationUnit.Pixel:
- origin += new Point(0, textDecoration.PenOffset);
- break;
- }
-
- var pen = new ImmutablePen(
- textDecoration.Pen?.Brush ?? Style.Foreground.ToImmutable(),
- thickness,
- textDecoration.Pen?.DashStyle?.ToImmutable(),
- textDecoration.Pen?.LineCap ?? default,
- textDecoration.Pen?.LineJoin ?? PenLineJoin.Miter,
- textDecoration.Pen?.MiterLimit ?? 10.0);
-
- drawingContext.DrawLine(pen, origin, origin + new Point(GlyphRun.Bounds.Width, 0));
- }
-
- ///
- /// Splits the at specified length.
- ///
- /// The length.
- /// The split result.
- public SplitTextCharactersResult Split(int length)
- {
- var glyphCount = 0;
-
- var firstCharacters = GlyphRun.Characters.Take(length);
-
- var codepointEnumerator = new CodepointEnumerator(firstCharacters);
-
- while (codepointEnumerator.MoveNext())
- {
- glyphCount++;
- }
-
- if (GlyphRun.Characters.Length == length)
- {
- return new SplitTextCharactersResult(this, null);
- }
-
- if (GlyphRun.GlyphIndices.Length == glyphCount)
- {
- return new SplitTextCharactersResult(this, null);
- }
-
- var firstGlyphRun = new GlyphRun(
- Style.TextFormat.Typeface.GlyphTypeface,
- Style.TextFormat.FontRenderingEmSize,
- GlyphRun.GlyphIndices.Take(glyphCount),
- GlyphRun.GlyphAdvances.Take(glyphCount),
- GlyphRun.GlyphOffsets.Take(glyphCount),
- GlyphRun.Characters.Take(length),
- GlyphRun.GlyphClusters.Take(length));
-
- var firstTextRun = new ShapedTextRun(firstGlyphRun, Style);
-
- var secondGlyphRun = new GlyphRun(
- Style.TextFormat.Typeface.GlyphTypeface,
- Style.TextFormat.FontRenderingEmSize,
- GlyphRun.GlyphIndices.Skip(glyphCount),
- GlyphRun.GlyphAdvances.Skip(glyphCount),
- GlyphRun.GlyphOffsets.Skip(glyphCount),
- GlyphRun.Characters.Skip(length),
- GlyphRun.GlyphClusters.Skip(length));
-
- var secondTextRun = new ShapedTextRun(secondGlyphRun, Style);
-
- return new SplitTextCharactersResult(firstTextRun, secondTextRun);
- }
-
- public readonly struct SplitTextCharactersResult
- {
- public SplitTextCharactersResult(ShapedTextRun first, ShapedTextRun second)
- {
- First = first;
-
- Second = second;
- }
-
- ///
- /// Gets the first text run.
- ///
- ///
- /// The first text run.
- ///
- public ShapedTextRun First { get; }
-
- ///
- /// Gets the second text run.
- ///
- ///
- /// The second text run.
- ///
- public ShapedTextRun Second { get; }
- }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs b/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
deleted file mode 100644
index f84e45d4c6f..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
+++ /dev/null
@@ -1,395 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Platform;
-using Avalonia.Utility;
-
-namespace Avalonia.Media.TextFormatting
-{
- internal class SimpleTextFormatter : TextFormatter
- {
- private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
-
- ///
- public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
- TextParagraphProperties paragraphProperties)
- {
- var textTrimming = paragraphProperties.TextTrimming;
- var textWrapping = paragraphProperties.TextWrapping;
- TextLine textLine;
-
- var textRuns = FormatTextRuns(textSource, firstTextSourceIndex, out var textPointer);
-
- if (textTrimming != TextTrimming.None)
- {
- textLine = PerformTextTrimming(textPointer, textRuns, paragraphWidth, paragraphProperties);
- }
- else
- {
- if (textWrapping == TextWrapping.Wrap)
- {
- textLine = PerformTextWrapping(textPointer, textRuns, paragraphWidth, paragraphProperties);
- }
- else
- {
- var textLineMetrics =
- TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment);
-
- textLine = new SimpleTextLine(textPointer, textRuns, textLineMetrics);
- }
- }
-
- return textLine;
- }
-
- ///
- /// Formats text runs with optional text style overrides.
- ///
- /// The text source.
- /// The first text source index.
- /// The text pointer that covers the formatted text runs.
- ///
- /// The formatted text runs.
- ///
- private List FormatTextRuns(ITextSource textSource, int firstTextSourceIndex, out TextPointer textPointer)
- {
- var start = -1;
- var length = 0;
-
- var textRuns = new List();
-
- while (true)
- {
- var textRun = textSource.GetTextRun(firstTextSourceIndex + length);
-
- if (start == -1)
- {
- start = textRun.Text.Start;
- }
-
- if (textRun is TextEndOfLine)
- {
- break;
- }
-
- switch (textRun)
- {
- case TextCharacters textCharacters:
-
- var runText = textCharacters.Text;
-
- while (!runText.IsEmpty)
- {
- var shapableTextStyleRun = CreateShapableTextStyleRun(runText, textRun.Style);
-
- var shapedRun = new ShapedTextRun(runText.Take(shapableTextStyleRun.TextPointer.Length),
- shapableTextStyleRun.Style);
-
- textRuns.Add(shapedRun);
-
- runText = runText.Skip(shapedRun.Text.Length);
- }
-
- break;
- default:
- throw new NotSupportedException("Run type not supported by the formatter.");
- }
-
- length += textRun.Text.Length;
- }
-
- textPointer = new TextPointer(start, length);
-
- return textRuns;
- }
-
- ///
- /// Performs text trimming and returns a trimmed line.
- ///
- /// A value that specifies the width of the paragraph that the line fills.
- /// A value that represents paragraph properties,
- /// such as TextWrapping, TextAlignment, or TextStyle.
- /// The text runs to perform the trimming on.
- /// The text that was used to construct the text runs.
- ///
- private static TextLine PerformTextTrimming(TextPointer text, IReadOnlyList textRuns,
- double paragraphWidth, TextParagraphProperties paragraphProperties)
- {
- var textTrimming = paragraphProperties.TextTrimming;
- var availableWidth = paragraphWidth;
- var currentWidth = 0.0;
- var runIndex = 0;
-
- while (runIndex < textRuns.Count)
- {
- var currentRun = textRuns[runIndex];
-
- currentWidth += currentRun.GlyphRun.Bounds.Width;
-
- if (currentWidth > availableWidth)
- {
- var ellipsisRun = CreateEllipsisRun(currentRun.Style);
-
- var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);
-
- if (textTrimming == TextTrimming.WordEllipsis)
- {
- if (measuredLength < text.End)
- {
- var currentBreakPosition = 0;
-
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
- {
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
-
- if (nextBreakPosition == 0)
- {
- break;
- }
-
- if (nextBreakPosition > measuredLength)
- {
- break;
- }
-
- currentBreakPosition = nextBreakPosition;
- }
-
- measuredLength = currentBreakPosition;
- }
- }
-
- var splitResult = SplitTextRuns(textRuns, measuredLength);
-
- var trimmedRuns = new List(splitResult.First.Count + 1);
-
- trimmedRuns.AddRange(splitResult.First);
-
- trimmedRuns.Add(ellipsisRun);
-
- var textLineMetrics =
- TextLineMetrics.Create(trimmedRuns, paragraphWidth, paragraphProperties.TextAlignment);
-
- return new SimpleTextLine(text.Take(measuredLength), trimmedRuns, textLineMetrics);
- }
-
- availableWidth -= currentRun.GlyphRun.Bounds.Width;
-
- runIndex++;
- }
-
- return new SimpleTextLine(text, textRuns,
- TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment));
- }
-
- ///
- /// Performs text wrapping returns a list of text lines.
- ///
- /// The text paragraph properties.
- /// The text run'S.
- /// The text to analyze for break opportunities.
- ///
- ///
- private static TextLine PerformTextWrapping(TextPointer text, IReadOnlyList textRuns,
- double paragraphWidth, TextParagraphProperties paragraphProperties)
- {
- var availableWidth = paragraphWidth;
- var currentWidth = 0.0;
- var runIndex = 0;
- var length = 0;
-
- while (runIndex < textRuns.Count)
- {
- var currentRun = textRuns[runIndex];
-
- if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
- {
- var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
-
- if (measuredLength < currentRun.Text.Length)
- {
- var currentBreakPosition = -1;
-
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
- {
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
-
- if (nextBreakPosition == 0)
- {
- break;
- }
-
- if (nextBreakPosition > measuredLength)
- {
- break;
- }
-
- currentBreakPosition = nextBreakPosition;
- }
-
- if (currentBreakPosition != -1)
- {
- measuredLength = currentBreakPosition;
- }
- }
-
- length += measuredLength;
-
- var splitResult = SplitTextRuns(textRuns, length);
-
- var textLineMetrics =
- TextLineMetrics.Create(splitResult.First, paragraphWidth, paragraphProperties.TextAlignment);
-
- return new SimpleTextLine(text.Take(length), splitResult.First, textLineMetrics);
- }
-
- currentWidth += currentRun.GlyphRun.Bounds.Width;
-
- length += currentRun.GlyphRun.Characters.Length;
-
- runIndex++;
- }
-
- return new SimpleTextLine(text, textRuns,
- TextLineMetrics.Create(textRuns, paragraphWidth, paragraphProperties.TextAlignment));
- }
-
- ///
- /// Measures the number of characters that fits into available width.
- ///
- /// The text run.
- /// The available width.
- ///
- private static int MeasureText(ShapedTextRun textRun, double availableWidth)
- {
- var glyphRun = textRun.GlyphRun;
-
- var characterHit = glyphRun.GetCharacterHitFromDistance(availableWidth, out _);
-
- return characterHit.FirstCharacterIndex + characterHit.TrailingLength - textRun.Text.Start;
- }
-
- ///
- /// Creates an ellipsis.
- ///
- /// The text style.
- ///
- private static ShapedTextRun CreateEllipsisRun(TextStyle textStyle)
- {
- var formatterImpl = AvaloniaLocator.Current.GetService();
-
- var glyphRun = formatterImpl.ShapeText(s_ellipsis, textStyle.TextFormat);
-
- return new ShapedTextRun(glyphRun, textStyle);
- }
-
- private readonly struct SplitTextRunsResult
- {
- public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second)
- {
- First = first;
-
- Second = second;
- }
-
- ///
- /// Gets the first text runs.
- ///
- ///
- /// The first text runs.
- ///
- public IReadOnlyList First { get; }
-
- ///
- /// Gets the second text runs.
- ///
- ///
- /// The second text runs.
- ///
- public IReadOnlyList Second { get; }
- }
-
- ///
- /// Split a sequence of runs into two segments at specified length.
- ///
- /// The text run's.
- /// The length to split at.
- ///
- private static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length)
- {
- var currentLength = 0;
-
- for (var i = 0; i < textRuns.Count; i++)
- {
- var currentRun = textRuns[i];
-
- if (currentLength + currentRun.GlyphRun.Characters.Length < length)
- {
- currentLength += currentRun.GlyphRun.Characters.Length;
- continue;
- }
-
- var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
-
- var first = new ShapedTextRun[firstCount];
-
- if (firstCount > 1)
- {
- for (var j = 0; j < i; j++)
- {
- first[j] = textRuns[j];
- }
- }
-
- var secondCount = textRuns.Count - firstCount;
-
- if (currentLength + currentRun.GlyphRun.Characters.Length == length)
- {
- var second = new ShapedTextRun[secondCount];
-
- var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
-
- if (secondCount > 0)
- {
- for (var j = 0; j < secondCount; j++)
- {
- second[j] = textRuns[i + j + offset];
- }
- }
-
- first[i] = currentRun;
-
- return new SplitTextRunsResult(first, second);
- }
- else
- {
- secondCount++;
-
- var second = new ShapedTextRun[secondCount];
-
- if (secondCount > 0)
- {
- for (var j = 1; j < secondCount; j++)
- {
- second[j] = textRuns[i + j];
- }
- }
-
- var split = currentRun.Split(length - currentLength);
-
- first[i] = split.First;
-
- second[0] = split.Second;
-
- return new SplitTextRunsResult(first, second);
- }
- }
-
- return new SplitTextRunsResult(textRuns, null);
- }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs
deleted file mode 100644
index 11d241bc348..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextLine.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Platform;
-
-namespace Avalonia.Media.TextFormatting
-{
- internal class SimpleTextLine : TextLine
- {
- private readonly IReadOnlyList _textRuns;
-
- public SimpleTextLine(TextPointer textPointer, IReadOnlyList textRuns, TextLineMetrics lineMetrics)
- {
- Text = textPointer;
- _textRuns = textRuns;
- LineMetrics = lineMetrics;
- }
-
- ///
- public override TextPointer Text { get; }
-
- ///
- public override IReadOnlyList TextRuns => _textRuns;
-
- ///
- public override TextLineMetrics LineMetrics { get; }
-
- ///
- public override void Draw(IDrawingContextImpl drawingContext, Point origin)
- {
- var currentX = origin.X;
-
- foreach (var textRun in _textRuns)
- {
- var baselineOrigin = new Point(currentX + LineMetrics.BaselineOrigin.X,
- origin.Y + LineMetrics.BaselineOrigin.Y);
-
- textRun.Draw(drawingContext, baselineOrigin);
-
- currentX += textRun.Bounds.Width;
- }
- }
-
- ///
- public override CharacterHit GetCharacterHitFromDistance(double distance)
- {
- if (distance < 0)
- {
- // hit happens before the line, return the first position
- return new CharacterHit(Text.Start);
- }
-
- // process hit that happens within the line
- var characterHit = new CharacterHit();
-
- foreach (var run in _textRuns)
- {
- characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
-
- if (distance <= run.Bounds.Width)
- {
- break;
- }
-
- distance -= run.Bounds.Width;
- }
-
- return characterHit;
- }
-
- ///
- public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
- {
- return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
- }
-
- ///
- public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
- {
- int nextVisibleCp;
- bool navigableCpFound;
-
- if (characterHit.TrailingLength == 0)
- {
- navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex, out nextVisibleCp);
-
- if (navigableCpFound)
- {
- // Move from leading to trailing edge
- return new CharacterHit(nextVisibleCp, 1);
- }
- }
-
- navigableCpFound = FindNextCodepointIndex(characterHit.FirstCharacterIndex + 1, out nextVisibleCp);
-
- if (navigableCpFound)
- {
- // Move from trailing edge of current character to trailing edge of next
- return new CharacterHit(nextVisibleCp, 1);
- }
-
- // Can't move, we're after the last character
- return characterHit;
- }
-
- ///
- public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
- {
- int previousCodepointIndex;
- bool codepointIndexFound;
-
- var cpHit = characterHit.FirstCharacterIndex;
- var trailingHit = characterHit.TrailingLength != 0;
-
- // Input can be right after the end of the current line. Snap it to be at the end of the line.
- if (cpHit >= Text.Start + Text.Length)
- {
- cpHit = Text.Start + Text.Length - 1;
-
- trailingHit = true;
- }
-
- if (trailingHit)
- {
- codepointIndexFound = FindPreviousCodepointIndex(cpHit, out previousCodepointIndex);
-
- if (codepointIndexFound)
- {
- // Move from trailing to leading edge
- return new CharacterHit(previousCodepointIndex, 0);
- }
- }
-
- codepointIndexFound = FindPreviousCodepointIndex(cpHit - 1, out previousCodepointIndex);
-
- if (codepointIndexFound)
- {
- // Move from leading edge of current character to leading edge of previous
- return new CharacterHit(previousCodepointIndex, 0);
- }
-
- // Can't move, we're before the first character
- return characterHit;
- }
-
- ///
- public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
- {
- // same operation as move-to-previous
- return GetPreviousCaretCharacterHit(characterHit);
- }
-
- ///
- /// Get distance from line start to the specified codepoint index
- ///
- private double DistanceFromCodepointIndex(int codepointIndex)
- {
- var currentDistance = 0.0;
-
- foreach (var textRun in _textRuns)
- {
- if (codepointIndex > textRun.Text.End)
- {
- currentDistance += textRun.Bounds.Width;
-
- continue;
- }
-
- return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
- }
-
- return currentDistance;
- }
-
- ///
- /// Search forward from the given codepoint index (inclusive) to find the next navigable codepoint index.
- /// Return true if one such codepoint index is found, false otherwise.
- ///
- private bool FindNextCodepointIndex(int codepointIndex, out int nextCodepointIndex)
- {
- nextCodepointIndex = codepointIndex;
-
- if (codepointIndex >= Text.Start + Text.Length)
- {
- return false; // Cannot go forward anymore
- }
-
- GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var cpRunStart);
-
- while (runIndex < TextRuns.Count)
- {
- // When navigating forward, only the trailing edge of visible content is
- // navigable.
- if (runIndex < TextRuns.Count)
- {
- nextCodepointIndex = Math.Max(cpRunStart, codepointIndex);
- return true;
- }
-
- cpRunStart += TextRuns[runIndex++].Text.Length;
- }
-
- return false;
- }
-
- ///
- /// Search backward from the given codepoint index (inclusive) to find the previous navigable codepoint index.
- /// Return true if one such codepoint is found, false otherwise.
- ///
- private bool FindPreviousCodepointIndex(int codepointIndex, out int previousCodepointIndex)
- {
- previousCodepointIndex = codepointIndex;
-
- if (codepointIndex < Text.Start)
- {
- return false; // Cannot go backward anymore.
- }
-
- // Position the cpRunEnd at the end of the span that contains the given cp
- GetRunIndexAtCodepointIndex(codepointIndex, out var runIndex, out var codepointIndexAtRunEnd);
-
- codepointIndexAtRunEnd += TextRuns[runIndex].Text.End;
-
- while (runIndex >= 0)
- {
- // Visible content has caret stops at its leading edge.
- if (runIndex + 1 < TextRuns.Count)
- {
- previousCodepointIndex = Math.Min(codepointIndexAtRunEnd, codepointIndex);
- return true;
- }
-
- // Newline sequence has caret stops at its leading edge.
- if (runIndex == TextRuns.Count)
- {
- // Get the cp index at the beginning of the newline sequence.
- previousCodepointIndex = codepointIndexAtRunEnd - TextRuns[runIndex].Text.Length + 1;
- return true;
- }
-
- codepointIndexAtRunEnd -= TextRuns[runIndex--].Text.Length;
- }
-
- return false;
- }
-
- private void GetRunIndexAtCodepointIndex(int codepointIndex, out int runIndex, out int codepointIndexAtRunStart)
- {
- codepointIndexAtRunStart = Text.Start;
- runIndex = 0;
-
- // Find the span that contains the given cp
- while (runIndex < TextRuns.Count &&
- codepointIndexAtRunStart + TextRuns[runIndex].Text.Length <= codepointIndex)
- {
- codepointIndexAtRunStart += TextRuns[runIndex++].Text.Length;
- }
- }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
index d9b27958ab9..47e716982c4 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
@@ -1,4 +1,6 @@
-using Avalonia.Utility;
+using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@@ -7,15 +9,182 @@ namespace Avalonia.Media.TextFormatting
///
public class TextCharacters : TextRun
{
- protected TextCharacters()
+ public TextCharacters(ReadOnlySlice text, TextRunProperties properties)
{
-
+ TextSourceLength = text.Length;
+ Text = text;
+ Properties = properties;
}
- public TextCharacters(ReadOnlySlice text, TextStyle style)
+ ///
+ public override int TextSourceLength { get; }
+
+ ///
+ public override ReadOnlySlice Text { get; }
+
+ ///
+ public override TextRunProperties Properties { get; }
+
+ ///
+ /// Gets a list of .
+ ///
+ /// The shapeable text characters.
+ internal IList GetShapeableCharacters()
{
- Text = text;
- Style = style;
+ var shapeableCharacters = new List(2);
+
+ var runText = Text;
+
+ while (!runText.IsEmpty)
+ {
+ var shapeableRun = CreateShapeableRun(runText, Properties);
+
+ shapeableCharacters.Add(shapeableRun);
+
+ runText = runText.Skip(shapeableRun.Text.Length);
+ }
+
+ return shapeableCharacters;
+ }
+
+ ///
+ /// Creates a shapeable text run with unique properties.
+ ///
+ /// The text to create text runs from.
+ /// The default text run properties.
+ /// A list of shapeable text runs.
+ private ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, TextRunProperties defaultProperties)
+ {
+ var defaultTypeface = defaultProperties.Typeface;
+
+ var currentTypeface = defaultTypeface;
+
+ if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count))
+ {
+ return new ShapeableTextCharacters(text.Take(count),
+ new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
+ defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
+
+ }
+
+ var codepoint = Codepoint.ReadAt(text, count, out _);
+
+ //ToDo: Fix FontFamily fallback
+ currentTypeface =
+ FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
+
+ if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
+ {
+ //Fallback found
+ return new ShapeableTextCharacters(text.Take(count),
+ new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
+ defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
+ }
+
+ // no fallback found
+ currentTypeface = defaultTypeface;
+
+ var glyphTypeface = currentTypeface.GlyphTypeface;
+
+ var enumerator = new GraphemeEnumerator(text);
+
+ while (enumerator.MoveNext())
+ {
+ var grapheme = enumerator.Current;
+
+ if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
+ {
+ break;
+ }
+
+ count += grapheme.Text.Length;
+ }
+
+ return new ShapeableTextCharacters(text.Take(count),
+ new GenericTextRunProperties(currentTypeface, defaultProperties.FontRenderingEmSize,
+ defaultProperties.TextDecorations, defaultProperties.ForegroundBrush));
+ }
+
+ ///
+ /// Tries to get run properties.
+ ///
+ ///
+ ///
+ /// The typeface that is used to find matching characters.
+ ///
+ ///
+ protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface, Typeface defaultTypeface,
+ out int count)
+ {
+ if (text.Length == 0)
+ {
+ count = 0;
+ return false;
+ }
+
+ var isFallback = typeface != defaultTypeface;
+
+ count = 0;
+ var script = Script.Common;
+ //var direction = BiDiClass.LeftToRight;
+
+ var font = typeface.GlyphTypeface;
+ var defaultFont = defaultTypeface.GlyphTypeface;
+
+ var enumerator = new GraphemeEnumerator(text);
+
+ while (enumerator.MoveNext())
+ {
+ var grapheme = enumerator.Current;
+
+ var currentScript = grapheme.FirstCodepoint.Script;
+
+ //var currentDirection = grapheme.FirstCodepoint.BiDiClass;
+
+ //// ToDo: Implement BiDi algorithm
+ //if (currentScript.HorizontalDirection != direction)
+ //{
+ // if (!UnicodeUtility.IsWhiteSpace(grapheme.FirstCodepoint))
+ // {
+ // break;
+ // }
+ //}
+
+ if (currentScript != script)
+ {
+ if (currentScript != Script.Inherited && currentScript != Script.Common)
+ {
+ if (script == Script.Inherited || script == Script.Common)
+ {
+ script = currentScript;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ if (isFallback)
+ {
+ if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _))
+ {
+ break;
+ }
+ }
+
+ if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _))
+ {
+ if (!grapheme.FirstCodepoint.IsWhiteSpace)
+ {
+ break;
+ }
+ }
+
+ count += grapheme.Text.Length;
+ }
+
+ return count > 0;
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
new file mode 100644
index 00000000000..ffd65423a30
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
@@ -0,0 +1,23 @@
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Properties of text collapsing
+ ///
+ public abstract class TextCollapsingProperties
+ {
+ ///
+ /// Gets the width in which the collapsible range is constrained to
+ ///
+ public abstract double Width { get; }
+
+ ///
+ /// Gets the text run that is used as collapsing symbol
+ ///
+ public abstract TextRun Symbol { get; }
+
+ ///
+ /// Gets the style of collapsing
+ ///
+ public abstract TextCollapsingStyle Style { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
new file mode 100644
index 00000000000..1523cc4d9aa
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
@@ -0,0 +1,18 @@
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Text collapsing style
+ ///
+ public enum TextCollapsingStyle
+ {
+ ///
+ /// Collapse trailing characters
+ ///
+ TrailingCharacter,
+
+ ///
+ /// Collapse trailing words
+ ///
+ TrailingWord,
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormat.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormat.cs
deleted file mode 100644
index 18dd6c7c109..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormat.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-
-namespace Avalonia.Media.TextFormatting
-{
- ///
- /// Unique text formatting properties that are used by the .
- ///
- public readonly struct TextFormat : IEquatable
- {
- public TextFormat(Typeface typeface, double fontRenderingEmSize)
- {
- Typeface = typeface;
- FontRenderingEmSize = fontRenderingEmSize;
- FontMetrics = new FontMetrics(typeface, fontRenderingEmSize);
- }
-
- ///
- /// Gets the typeface.
- ///
- ///
- /// The typeface.
- ///
- public Typeface Typeface { get; }
-
- ///
- /// Gets the font rendering em size.
- ///
- ///
- /// The em rendering size of the font.
- ///
- public double FontRenderingEmSize { get; }
-
- ///
- /// Gets the font metrics.
- ///
- ///
- /// The metrics of the font.
- ///
- public FontMetrics FontMetrics { get; }
-
- public static bool operator ==(TextFormat self, TextFormat other)
- {
- return self.Equals(other);
- }
-
- public static bool operator !=(TextFormat self, TextFormat other)
- {
- return !(self == other);
- }
-
- public bool Equals(TextFormat other)
- {
- return Typeface.Equals(other.Typeface) && FontRenderingEmSize.Equals(other.FontRenderingEmSize);
- }
-
- public override bool Equals(object obj)
- {
- return obj is TextFormat other && Equals(other);
- }
-
- public override int GetHashCode()
- {
- unchecked
- {
- var hashCode = (Typeface != null ? Typeface.GetHashCode() : 0);
- hashCode = (hashCode * 397) ^ FontRenderingEmSize.GetHashCode();
- return hashCode;
- }
- }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs
index 7da39dc5dc9..e4c898e2b86 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs
@@ -1,5 +1,4 @@
using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting
{
@@ -22,7 +21,7 @@ public static TextFormatter Current
return current;
}
- current = new SimpleTextFormatter();
+ current = new TextFormatterImpl();
AvaloniaLocator.CurrentMutable.Bind().ToConstant(current);
@@ -38,149 +37,10 @@ public static TextFormatter Current
/// A value that specifies the width of the paragraph that the line fills.
/// A value that represents paragraph properties,
/// such as TextWrapping, TextAlignment, or TextStyle.
+ /// A value that specifies the text formatter state,
+ /// in terms of where the previous line in the paragraph was broken by the text formatting process.
/// The formatted line.
public abstract TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
- TextParagraphProperties paragraphProperties);
-
- ///
- /// Creates a text style run with unique properties.
- ///
- /// The text to create text runs from.
- ///
- /// A list of text runs.
- protected TextStyleRun CreateShapableTextStyleRun(ReadOnlySlice text, TextStyle defaultStyle)
- {
- var defaultTypeface = defaultStyle.TextFormat.Typeface;
-
- var currentTypeface = defaultTypeface;
-
- if (TryGetRunProperties(text, currentTypeface, defaultTypeface, out var count))
- {
- return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
- defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
-
- }
-
- var codepoint = Codepoint.ReadAt(text, count, out _);
-
- //ToDo: Fix FontFamily fallback
- currentTypeface =
- FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Weight, defaultTypeface.Style, defaultStyle.TextFormat.Typeface.FontFamily);
-
- if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
- {
- //Fallback found
- return new TextStyleRun(new TextPointer(text.Start, count), new TextStyle(currentTypeface,
- defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
-
- }
-
- // no fallback found
- currentTypeface = defaultTypeface;
-
- var glyphTypeface = currentTypeface.GlyphTypeface;
-
- var enumerator = new GraphemeEnumerator(text);
-
- while (enumerator.MoveNext())
- {
- var grapheme = enumerator.Current;
-
- if (!grapheme.FirstCodepoint.IsWhiteSpace && glyphTypeface.TryGetGlyph(grapheme.FirstCodepoint, out _))
- {
- break;
- }
-
- count += grapheme.Text.Length;
- }
-
- return new TextStyleRun(new TextPointer(text.Start, count),
- new TextStyle(currentTypeface, defaultStyle.TextFormat.FontRenderingEmSize,
- defaultStyle.Foreground, defaultStyle.TextDecorations));
- }
-
- ///
- /// Tries to get run properties.
- ///
- ///
- ///
- /// The typeface that is used to find matching characters.
- ///
- ///
- protected bool TryGetRunProperties(ReadOnlySlice text, Typeface typeface, Typeface defaultTypeface,
- out int count)
- {
- if (text.Length == 0)
- {
- count = 0;
- return false;
- }
-
- var isFallback = typeface != defaultTypeface;
-
- count = 0;
- var script = Script.Common;
- //var direction = BiDiClass.LeftToRight;
-
- var font = typeface.GlyphTypeface;
- var defaultFont = defaultTypeface.GlyphTypeface;
-
- var enumerator = new GraphemeEnumerator(text);
-
- while (enumerator.MoveNext())
- {
- var grapheme = enumerator.Current;
-
- var currentScript = grapheme.FirstCodepoint.Script;
-
- //var currentDirection = grapheme.FirstCodepoint.BiDiClass;
-
- //// ToDo: Implement BiDi algorithm
- //if (currentScript.HorizontalDirection != direction)
- //{
- // if (!UnicodeUtility.IsWhiteSpace(grapheme.FirstCodepoint))
- // {
- // break;
- // }
- //}
-
- if (currentScript != script)
- {
- if (currentScript != Script.Inherited && currentScript != Script.Common)
- {
- if (script == Script.Inherited || script == Script.Common)
- {
- script = currentScript;
- }
- else
- {
- break;
- }
- }
- }
-
- if (isFallback)
- {
- if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _))
- {
- break;
- }
- }
-
- if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _))
- {
- if (!grapheme.FirstCodepoint.IsWhiteSpace)
- {
- break;
- }
- }
-
- count += grapheme.Text.Length;
- }
-
- return count > 0;
- }
+ TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null);
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
new file mode 100644
index 00000000000..9318fcc68e5
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -0,0 +1,496 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+
+namespace Avalonia.Media.TextFormatting
+{
+ internal class TextFormatterImpl : TextFormatter
+ {
+ ///
+ public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
+ TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
+ {
+ var textWrapping = paragraphProperties.TextWrapping;
+
+ var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);
+
+ var textRange = GetTextRange(textRuns);
+
+ TextLine textLine;
+
+ switch (textWrapping)
+ {
+ case TextWrapping.NoWrap:
+ {
+ var textLineMetrics =
+ TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
+
+ textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
+ break;
+ }
+ case TextWrapping.WrapWithOverflow:
+ case TextWrapping.Wrap:
+ {
+ textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
+ break;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return textLine;
+ }
+
+ ///
+ /// Measures the number of characters that fits into available width.
+ ///
+ /// The text run.
+ /// The available width.
+ ///
+ internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
+ {
+ var glyphRun = textCharacters.GlyphRun;
+
+ if (glyphRun.Bounds.Width < availableWidth)
+ {
+ return glyphRun.Characters.Length;
+ }
+
+ var glyphCount = 0;
+
+ var currentWidth = 0.0;
+
+ if (glyphRun.GlyphAdvances.IsEmpty)
+ {
+ var glyphTypeface = glyphRun.GlyphTypeface;
+
+ for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+ {
+ var glyph = glyphRun.GlyphIndices[i];
+
+ var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
+ }
+ }
+ else
+ {
+ foreach (var advance in glyphRun.GlyphAdvances)
+ {
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
+ }
+ }
+
+ if (glyphCount == glyphRun.GlyphIndices.Length)
+ {
+ return glyphRun.Characters.Length;
+ }
+
+ if (glyphRun.GlyphClusters.IsEmpty)
+ {
+ return glyphCount;
+ }
+
+ var firstCluster = glyphRun.GlyphClusters[0];
+
+ var lastCluster = glyphRun.GlyphClusters[glyphCount];
+
+ return lastCluster - firstCluster;
+ }
+
+ ///
+ /// Split a sequence of runs into two segments at specified length.
+ ///
+ /// The text run's.
+ /// The length to split at.
+ /// The split text runs.
+ internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length)
+ {
+ var currentLength = 0;
+
+ for (var i = 0; i < textRuns.Count; i++)
+ {
+ var currentRun = textRuns[i];
+
+ if (currentLength + currentRun.GlyphRun.Characters.Length < length)
+ {
+ currentLength += currentRun.GlyphRun.Characters.Length;
+ continue;
+ }
+
+ var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
+
+ var first = new ShapedTextCharacters[firstCount];
+
+ if (firstCount > 1)
+ {
+ for (var j = 0; j < i; j++)
+ {
+ first[j] = textRuns[j];
+ }
+ }
+
+ var secondCount = textRuns.Count - firstCount;
+
+ if (currentLength + currentRun.GlyphRun.Characters.Length == length)
+ {
+ var second = new ShapedTextCharacters[secondCount];
+
+ var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
+
+ if (secondCount > 0)
+ {
+ for (var j = 0; j < secondCount; j++)
+ {
+ second[j] = textRuns[i + j + offset];
+ }
+ }
+
+ first[i] = currentRun;
+
+ return new SplitTextRunsResult(first, second);
+ }
+ else
+ {
+ secondCount++;
+
+ var second = new ShapedTextCharacters[secondCount];
+
+ if (secondCount > 0)
+ {
+ for (var j = 1; j < secondCount; j++)
+ {
+ second[j] = textRuns[i + j];
+ }
+ }
+
+ var split = currentRun.Split(length - currentLength);
+
+ first[i] = split.First;
+
+ second[0] = split.Second;
+
+ return new SplitTextRunsResult(first, second);
+ }
+ }
+
+ return new SplitTextRunsResult(textRuns, null);
+ }
+
+ ///
+ /// Fetches text runs.
+ ///
+ /// The text source.
+ /// The first text source index.
+ /// Previous line break. Can be null.
+ /// Next line break. Can be null.
+ ///
+ /// The formatted text runs.
+ ///
+ private static IReadOnlyList FetchTextRuns(ITextSource textSource,
+ int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak)
+ {
+ nextLineBreak = default;
+
+ var currentLength = 0;
+
+ var textRuns = new List();
+
+ if (previousLineBreak != null)
+ {
+ foreach (var shapedCharacters in previousLineBreak.RemainingCharacters)
+ {
+ if (shapedCharacters == null)
+ {
+ continue;
+ }
+
+ textRuns.Add(shapedCharacters);
+
+ if (TryGetLineBreak(shapedCharacters, out var runLineBreak))
+ {
+ var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
+
+ nextLineBreak = new TextLineBreak(splitResult.Second);
+
+ return splitResult.First;
+ }
+
+ currentLength += shapedCharacters.Text.Length;
+ }
+ }
+
+ firstTextSourceIndex += currentLength;
+
+ var textRunEnumerator = new TextRunEnumerator(textSource, firstTextSourceIndex);
+
+ while (textRunEnumerator.MoveNext())
+ {
+ var textRun = textRunEnumerator.Current;
+
+ switch (textRun)
+ {
+ case TextCharacters textCharacters:
+ {
+ var shapeableRuns = textCharacters.GetShapeableCharacters();
+
+ foreach (var run in shapeableRuns)
+ {
+ var glyphRun = TextShaper.Current.ShapeText(run.Text, run.Properties.Typeface,
+ run.Properties.FontRenderingEmSize, run.Properties.CultureInfo);
+
+ var shapedCharacters = new ShapedTextCharacters(glyphRun, run.Properties);
+
+ textRuns.Add(shapedCharacters);
+ }
+
+ break;
+ }
+ }
+
+ if (TryGetLineBreak(textRun, out var runLineBreak))
+ {
+ var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap);
+
+ nextLineBreak = new TextLineBreak(splitResult.Second);
+
+ return splitResult.First;
+ }
+
+ currentLength += textRun.Text.Length;
+ }
+
+ return textRuns;
+ }
+
+ private static bool TryGetLineBreak(TextRun textRun, out LineBreak lineBreak)
+ {
+ lineBreak = default;
+
+ if (textRun.Text.IsEmpty)
+ {
+ return false;
+ }
+
+ var lineBreakEnumerator = new LineBreakEnumerator(textRun.Text);
+
+ while (lineBreakEnumerator.MoveNext())
+ {
+ if (!lineBreakEnumerator.Current.Required)
+ {
+ continue;
+ }
+
+ lineBreak = lineBreakEnumerator.Current;
+
+ if (lineBreak.PositionWrap >= textRun.Text.Length)
+ {
+ return true;
+ }
+
+ //The line breaker isn't treating \n\r as a pair so we have to fix that here.
+ if (textRun.Text[lineBreak.PositionMeasure] == '\n'
+ && textRun.Text[lineBreak.PositionWrap] == '\r')
+ {
+ lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1,
+ lineBreak.Required);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Performs text wrapping returns a list of text lines.
+ ///
+ /// The text run's.
+ /// The text range that is covered by the text runs.
+ /// The paragraph width.
+ /// The text paragraph properties.
+ /// The wrapped text line.
+ private static TextLine PerformTextWrapping(IReadOnlyList textRuns, TextRange textRange,
+ double paragraphWidth, TextParagraphProperties paragraphProperties)
+ {
+ var availableWidth = paragraphWidth;
+ var currentWidth = 0.0;
+ var runIndex = 0;
+ var currentLength = 0;
+
+ while (runIndex < textRuns.Count)
+ {
+ var currentRun = textRuns[runIndex];
+
+ if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
+ {
+ var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
+
+ var breakFound = false;
+
+ var currentBreakPosition = 0;
+
+ if (measuredLength < currentRun.Text.Length)
+ {
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
+
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ {
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+ if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
+ {
+ break;
+ }
+
+ breakFound = lineBreaker.Current.Required ||
+ lineBreaker.Current.PositionWrap != currentRun.Text.Length;
+
+ currentBreakPosition = nextBreakPosition;
+ }
+ }
+
+ if (breakFound)
+ {
+ measuredLength = currentBreakPosition;
+ }
+ else
+ {
+ if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
+ {
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition));
+
+ if (lineBreaker.MoveNext())
+ {
+ measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap;
+ }
+ }
+ }
+
+ currentLength += measuredLength;
+
+ var splitResult = SplitTextRuns(textRuns, currentLength);
+
+ var textLineMetrics = TextLineMetrics.Create(splitResult.First,
+ new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);
+
+ var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
+ new TextLineBreak(splitResult.Second) :
+ null;
+
+ return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak);
+ }
+
+ currentWidth += currentRun.GlyphRun.Bounds.Width;
+
+ currentLength += currentRun.GlyphRun.Characters.Length;
+
+ runIndex++;
+ }
+
+ return new TextLineImpl(textRuns,
+ TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
+ }
+
+ ///
+ /// Gets the text range that is covered by the text runs.
+ ///
+ /// The text runs.
+ /// The text range that is covered by the text runs.
+ private static TextRange GetTextRange(IReadOnlyList textRuns)
+ {
+ if (textRuns is null || textRuns.Count == 0)
+ {
+ return new TextRange();
+ }
+
+ var firstTextRun = textRuns[0];
+
+ if (textRuns.Count == 1)
+ {
+ return new TextRange(firstTextRun.Text.Start, firstTextRun.Text.Length);
+ }
+
+ var start = firstTextRun.Text.Start;
+
+ var end = textRuns[textRuns.Count - 1].Text.End + 1;
+
+ return new TextRange(start, end - start);
+ }
+
+ internal readonly struct SplitTextRunsResult
+ {
+ public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second)
+ {
+ First = first;
+
+ Second = second;
+ }
+
+ ///
+ /// Gets the first text runs.
+ ///
+ ///
+ /// The first text runs.
+ ///
+ public IReadOnlyList First { get; }
+
+ ///
+ /// Gets the second text runs.
+ ///
+ ///
+ /// The second text runs.
+ ///
+ public IReadOnlyList Second { get; }
+ }
+
+ private struct TextRunEnumerator
+ {
+ private readonly ITextSource _textSource;
+ private int _pos;
+
+ public TextRunEnumerator(ITextSource textSource, int firstTextSourceIndex)
+ {
+ _textSource = textSource;
+ _pos = firstTextSourceIndex;
+ Current = null;
+ }
+
+ // ReSharper disable once MemberHidesStaticFromOuterClass
+ public TextRun Current { get; private set; }
+
+ public bool MoveNext()
+ {
+ Current = _textSource.GetTextRun(_pos);
+
+ if (Current is null)
+ {
+ return false;
+ }
+
+ if (Current.TextSourceLength == 0)
+ {
+ return false;
+ }
+
+ _pos += Current.TextSourceLength;
+
+ return !(Current is TextEndOfLine);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
index 0292398782b..14602a25605 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
@@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Media.Immutable;
-using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Platform;
using Avalonia.Utilities;
-using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting
{
@@ -14,11 +10,12 @@ namespace Avalonia.Media.TextFormatting
///
public class TextLayout
{
- private static readonly ReadOnlySlice s_empty = new ReadOnlySlice(new[] { '\u200B' });
+ private static readonly char[] s_empty = { '\u200B' };
private readonly ReadOnlySlice _text;
private readonly TextParagraphProperties _paragraphProperties;
- private readonly IReadOnlyList _textStyleOverrides;
+ private readonly IReadOnlyList> _textStyleOverrides;
+ private readonly TextTrimming _textTrimming;
///
/// Initializes a new instance of the class.
@@ -33,6 +30,7 @@ public class TextLayout
/// The text decorations.
/// The maximum width.
/// The maximum height.
+ /// The height of each line of text.
/// The maximum number of text lines.
/// The text style overrides.
public TextLayout(
@@ -46,18 +44,24 @@ public TextLayout(
TextDecorationCollection textDecorations = null,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
+ double lineHeight = double.NaN,
int maxLines = 0,
- IReadOnlyList textStyleOverrides = null)
+ IReadOnlyList> textStyleOverrides = null)
{
_text = string.IsNullOrEmpty(text) ?
new ReadOnlySlice() :
new ReadOnlySlice(text.AsMemory());
_paragraphProperties =
- CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming, textDecorations?.ToImmutable());
+ CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
+ textDecorations, lineHeight);
+
+ _textTrimming = textTrimming;
_textStyleOverrides = textStyleOverrides;
+ LineHeight = lineHeight;
+
MaxWidth = maxWidth;
MaxHeight = maxHeight;
@@ -67,22 +71,29 @@ public TextLayout(
UpdateLayout();
}
+ ///
+ /// Gets or sets the height of each line of text.
+ ///
+ ///
+ /// A value of NaN (equivalent to an attribute value of "Auto") indicates that the line height
+ /// is determined automatically from the current font characteristics. The default is NaN.
+ ///
+ public double LineHeight { get; }
+
///
/// Gets the maximum width.
///
public double MaxWidth { get; }
-
///
/// Gets the maximum height.
///
public double MaxHeight { get; }
-
///
/// Gets the maximum number of text lines.
///
- public double MaxLines { get; }
+ public int MaxLines { get; }
///
/// Gets the text lines.
@@ -93,19 +104,19 @@ public TextLayout(
public IReadOnlyList TextLines { get; private set; }
///
- /// Gets the bounds of the layout.
+ /// Gets the size of the layout.
///
///
/// The bounds.
///
- public Rect Bounds { get; private set; }
+ public Size Size { get; private set; }
///
/// Draws the text layout.
///
/// The drawing context.
/// The origin.
- public void Draw(IDrawingContextImpl context, Point origin)
+ public void Draw(DrawingContext context, Point origin)
{
if (!TextLines.Any())
{
@@ -116,7 +127,10 @@ public void Draw(IDrawingContextImpl context, Point origin)
foreach (var textLine in TextLines)
{
- textLine.Draw(context, new Point(origin.X, currentY));
+ var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width,
+ _paragraphProperties.TextAlignment);
+
+ textLine.Draw(context, new Point(origin.X + offsetX, currentY));
currentY += textLine.LineMetrics.Size.Height;
}
@@ -130,38 +144,32 @@ public void Draw(IDrawingContextImpl context, Point origin)
/// The foreground.
/// The text alignment.
/// The text wrapping.
- /// The text trimming.
/// The text decorations.
+ /// The height of each line of text.
///
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
- IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming,
- ImmutableTextDecoration[] textDecorations)
+ IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping,
+ TextDecorationCollection textDecorations, double lineHeight)
{
- var textRunStyle = new TextStyle(typeface, fontSize, foreground, textDecorations);
+ var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
- return new TextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming);
+ return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, lineHeight);
}
///
/// Updates the current bounds.
///
/// The text line.
- /// The left.
- /// The right.
- /// The bottom.
- private static void UpdateBounds(TextLine textLine, ref double left, ref double right, ref double bottom)
+ /// The current width.
+ /// The current height.
+ private static void UpdateBounds(TextLine textLine, ref double width, ref double height)
{
- if (right < textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width)
- {
- right = textLine.LineMetrics.BaselineOrigin.X + textLine.LineMetrics.Size.Width;
- }
-
- if (left < textLine.LineMetrics.BaselineOrigin.X)
+ if (width < textLine.LineMetrics.Size.Width)
{
- left = textLine.LineMetrics.BaselineOrigin.X;
+ width = textLine.LineMetrics.Size.Width;
}
- bottom += textLine.LineMetrics.Size.Height;
+ height += textLine.LineMetrics.Size.Height;
}
///
@@ -170,14 +178,15 @@ private static void UpdateBounds(TextLine textLine, ref double left, ref double
/// The empty text line.
private TextLine CreateEmptyTextLine(int startingIndex)
{
- var textFormat = _paragraphProperties.DefaultTextStyle.TextFormat;
+ var properties = _paragraphProperties.DefaultTextRunProperties;
- var glyphRun = TextShaper.Current.ShapeText(s_empty, textFormat);
+ var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice(s_empty, startingIndex, 1),
+ properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo);
- var textRuns = new[] { new ShapedTextRun(glyphRun, _paragraphProperties.DefaultTextStyle) };
+ var textRuns = new[] { new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) };
- return new SimpleTextLine(new TextPointer(startingIndex, 0), textRuns,
- TextLineMetrics.Create(textRuns, MaxWidth, _paragraphProperties.TextAlignment));
+ return new TextLineImpl(textRuns,
+ TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties));
}
///
@@ -191,110 +200,112 @@ private void UpdateLayout()
TextLines = new List { textLine };
- Bounds = new Rect(textLine.LineMetrics.BaselineOrigin.X, 0, 0, textLine.LineMetrics.Size.Height);
+ Size = new Size(0, textLine.LineMetrics.Size.Height);
}
else
{
var textLines = new List();
- double left = 0.0, right = 0.0, bottom = 0.0;
-
- var lineBreaker = new LineBreakEnumerator(_text);
+ double width = 0.0, height = 0.0;
var currentPosition = 0;
- while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines))
- {
- int length;
+ var textSource = new FormattedTextSource(_text,
+ _paragraphProperties.DefaultTextRunProperties, _textStyleOverrides);
- if (lineBreaker.MoveNext())
- {
- if (!lineBreaker.Current.Required)
- {
- continue;
- }
+ TextLine previousLine = null;
- length = lineBreaker.Current.PositionWrap - currentPosition;
+ while (currentPosition < _text.Length)
+ {
+ var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
+ _paragraphProperties, previousLine?.LineBreak);
+
+ currentPosition += textLine.TextRange.Length;
- if (currentPosition + length < _text.Length)
+ if (textLines.Count > 0)
+ {
+ if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) &&
+ height + textLine.LineMetrics.Size.Height > MaxHeight)
{
- //The line breaker isn't treating \n\r as a pair so we have to fix that here.
- if (_text[lineBreaker.Current.PositionMeasure] == '\n'
- && _text[lineBreaker.Current.PositionWrap] == '\r')
+ if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None)
{
- length++;
+ var collapsedLine =
+ previousLine.Collapse(GetCollapsingProperties(MaxWidth));
+
+ textLines[textLines.Count - 1] = collapsedLine;
}
+
+ break;
}
}
- else
- {
- length = _text.Length - currentPosition;
- }
- var remainingLength = length;
+ var hasOverflowed = textLine.LineMetrics.HasOverflowed;
- while (remainingLength > 0 && (MaxLines == 0 || textLines.Count < MaxLines))
+ if (hasOverflowed && _textTrimming != TextTrimming.None)
{
- var textSlice = _text.AsSlice(currentPosition, remainingLength);
-
- var textSource = new FormattedTextSource(textSlice, _paragraphProperties.DefaultTextStyle, _textStyleOverrides);
-
- var textLine = TextFormatter.Current.FormatLine(textSource, 0, MaxWidth, _paragraphProperties);
-
- UpdateBounds(textLine, ref left, ref right, ref bottom);
-
- textLines.Add(textLine);
-
- if (!double.IsPositiveInfinity(MaxHeight) && bottom + textLine.LineMetrics.Size.Height > MaxHeight)
- {
- currentPosition = _text.Length;
- break;
- }
+ textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth));
+ }
- if (_paragraphProperties.TextTrimming != TextTrimming.None)
- {
- currentPosition += remainingLength;
+ textLines.Add(textLine);
- break;
- }
+ UpdateBounds(textLine, ref width, ref height);
- remainingLength -= textLine.Text.Length;
+ previousLine = textLine;
- currentPosition += textLine.Text.Length;
+ if (currentPosition != _text.Length || textLine.LineBreak == null)
+ {
+ continue;
}
- }
- if (lineBreaker.Current.Required && currentPosition == _text.Length)
- {
var emptyTextLine = CreateEmptyTextLine(currentPosition);
- UpdateBounds(emptyTextLine, ref left, ref right, ref bottom);
-
textLines.Add(emptyTextLine);
}
- Bounds = new Rect(left, 0, right, bottom);
+ Size = new Size(width, height);
TextLines = textLines;
}
}
- private struct FormattedTextSource : ITextSource
+ ///
+ /// Gets the for current text trimming mode.
+ ///
+ /// The collapsing width.
+ /// The .
+ private TextCollapsingProperties GetCollapsingProperties(double width)
+ {
+ return _textTrimming switch
+ {
+ TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width,
+ _paragraphProperties.DefaultTextRunProperties),
+ TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width,
+ _paragraphProperties.DefaultTextRunProperties),
+ _ => throw new ArgumentOutOfRangeException(),
+ };
+ }
+
+ private readonly struct FormattedTextSource : ITextSource
{
private readonly ReadOnlySlice _text;
- private readonly TextStyle _defaultStyle;
- private readonly IReadOnlyList _textStyleOverrides;
+ private readonly TextRunProperties _defaultProperties;
+ private readonly IReadOnlyList> _textModifier;
- public FormattedTextSource(ReadOnlySlice text, TextStyle defaultStyle,
- IReadOnlyList textStyleOverrides)
+ public FormattedTextSource(ReadOnlySlice text, TextRunProperties defaultProperties,
+ IReadOnlyList> textModifier)
{
_text = text;
- _defaultStyle = defaultStyle;
- _textStyleOverrides = textStyleOverrides;
+ _defaultProperties = defaultProperties;
+ _textModifier = textModifier;
}
public TextRun GetTextRun(int textSourceIndex)
{
+ if (textSourceIndex > _text.End)
+ {
+ return new TextEndOfLine();
+ }
+
var runText = _text.Skip(textSourceIndex);
if (runText.IsEmpty)
@@ -302,30 +313,29 @@ public TextRun GetTextRun(int textSourceIndex)
return new TextEndOfLine();
}
- var textStyleRun = CreateTextStyleRunWithOverride(runText, _defaultStyle, _textStyleOverrides);
+ var textStyleRun = CreateTextStyleRun(runText, _defaultProperties, _textModifier);
- return new TextCharacters(runText.Take(textStyleRun.TextPointer.Length), textStyleRun.Style);
+ return new TextCharacters(runText.Take(textStyleRun.Length), textStyleRun.Value);
}
///
- /// Creates a text style run that has overrides applied. Only overrides with equal TextStyle.
- /// If optimizeForShaping is true Foreground is ignored.
+ /// Creates a span of text run properties that has modifier applied.
///
- /// The text to create the run for.
- /// The default text style for segments that don't have an override.
- /// The text style overrides.
+ /// The text to create the properties for.
+ /// The default text properties.
+ /// The text properties modifier.
///
/// The created text style run.
///
- private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice text,
- TextStyle defaultTextStyle, IReadOnlyList textStyleOverrides)
+ private static ValueSpan CreateTextStyleRun(ReadOnlySlice text,
+ TextRunProperties defaultProperties, IReadOnlyList> textModifier)
{
- if(textStyleOverrides == null || textStyleOverrides.Count == 0)
+ if (textModifier == null || textModifier.Count == 0)
{
- return new TextStyleRun(new TextPointer(text.Start, text.Length), defaultTextStyle);
+ return new ValueSpan(text.Start, text.Length, defaultProperties);
}
- var currentTextStyle = defaultTextStyle;
+ var currentProperties = defaultProperties;
var hasOverride = false;
@@ -333,35 +343,34 @@ private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice t
var length = 0;
- for (; i < textStyleOverrides.Count; i++)
+ for (; i < textModifier.Count; i++)
{
- var styleOverride = textStyleOverrides[i];
+ var propertiesOverride = textModifier[i];
- var textPointer = styleOverride.TextPointer;
+ var textRange = new TextRange(propertiesOverride.Start, propertiesOverride.Length);
- if (textPointer.End < text.Start)
+ if (textRange.End < text.Start)
{
continue;
}
- if (textPointer.Start > text.End)
+ if (textRange.Start > text.End)
{
length = text.Length;
break;
}
- if (textPointer.Start > text.Start)
+ if (textRange.Start > text.Start)
{
- if (styleOverride.Style.TextFormat != currentTextStyle.TextFormat ||
- !currentTextStyle.Foreground.Equals(styleOverride.Style.Foreground))
+ if (propertiesOverride.Value != currentProperties)
{
- length = Math.Min(Math.Abs(textPointer.Start - text.Start), text.Length);
+ length = Math.Min(Math.Abs(textRange.Start - text.Start), text.Length);
break;
}
}
- length += Math.Min(text.Length - length, textPointer.Length);
+ length += Math.Min(text.Length - length, textRange.Length);
if (hasOverride)
{
@@ -370,13 +379,12 @@ private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice t
hasOverride = true;
- currentTextStyle = styleOverride.Style;
+ currentProperties = propertiesOverride.Value;
}
- if (length < text.Length && i == textStyleOverrides.Count)
+ if (length < text.Length && i == textModifier.Count)
{
- if (currentTextStyle.Foreground.Equals(defaultTextStyle.Foreground) &&
- currentTextStyle.TextFormat == defaultTextStyle.TextFormat)
+ if (currentProperties == defaultProperties)
{
length = text.Length;
}
@@ -387,7 +395,7 @@ private static TextStyleRun CreateTextStyleRunWithOverride(ReadOnlySlice t
text = text.Take(length);
}
- return new TextStyleRun(new TextPointer(text.Start, length), currentTextStyle);
+ return new ValueSpan(text.Start, length, currentProperties);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
index a0f7b44882e..423ca9fb7f3 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
@@ -9,12 +8,12 @@ namespace Avalonia.Media.TextFormatting
public abstract class TextLine
{
///
- /// Gets the text.
+ /// Gets the text range that is covered by the line.
///
///
- /// The text pointer.
+ /// The text range that is covered by the line.
///
- public abstract TextPointer Text { get; }
+ public abstract TextRange TextRange { get; }
///
/// Gets the text runs.
@@ -32,48 +31,73 @@ public abstract class TextLine
///
public abstract TextLineMetrics LineMetrics { get; }
+ ///
+ /// Gets the state of the line when broken by line breaking process.
+ ///
+ ///
+ /// A value that represents the line break.
+ ///
+ public abstract TextLineBreak LineBreak { get; }
+
+ ///
+ /// Gets a value that indicates whether the line is collapsed.
+ ///
+ ///
+ /// true, if the line is collapsed; otherwise, false.
+ ///
+ public abstract bool HasCollapsed { get; }
+
///
/// Draws the at the given origin.
///
/// The drawing context.
/// The origin.
- public abstract void Draw(IDrawingContextImpl drawingContext, Point origin);
+ public abstract void Draw(DrawingContext drawingContext, Point origin);
///
- /// Client to get the character hit corresponding to the specified
- /// distance from the beginning of the line.
+ /// Create a collapsed line based on collapsed text properties.
///
- /// distance in text flow direction from the beginning of the line
- /// The
+ /// A list of
+ /// objects that represent the collapsed text properties.
+ ///
+ /// A value that represents a collapsed line that can be displayed.
+ ///
+ public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
+
+ ///
+ /// Gets the character hit corresponding to the specified distance from the beginning of the line.
+ ///
+ /// A value that represents the distance from the beginning of the line.
+ /// The object at the specified distance from the beginning of the line.
public abstract CharacterHit GetCharacterHitFromDistance(double distance);
///
- /// Client to get the distance from the beginning of the line from the specified
+ /// Gets the distance from the beginning of the line to the specified character hit.
/// .
///
- /// of the character to query the distance.
- /// Distance in text flow direction from the beginning of the line.
+ /// The object whose distance you want to query.
+ /// A that represents the distance from the beginning of the line.
public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit);
///
- /// Client to get the next for caret navigation.
+ /// Gets the next character hit for caret navigation.
///
/// The current .
/// The next .
public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit);
///
- /// Client to get the previous character hit for caret navigation
+ /// Gets the previous character hit for caret navigation.
///
- /// the current character hit
- /// The previous
+ /// The current .
+ /// The previous .
public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit);
///
- /// Client to get the previous character hit after backspacing
+ /// Gets the previous character hit after backspacing.
///
- /// the current character hit
- /// The after backspacing
+ /// The current .
+ /// The after backspacing.
public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit);
///
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs
new file mode 100644
index 00000000000..c24454cb764
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Media.TextFormatting
+{
+ public class TextLineBreak
+ {
+ public TextLineBreak(IReadOnlyList remainingCharacters)
+ {
+ RemainingCharacters = remainingCharacters;
+ }
+
+ ///
+ /// Get the remaining shaped characters that were split up by the during the formatting process.
+ ///
+ public IReadOnlyList RemainingCharacters { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
new file mode 100644
index 00000000000..f73a7be759a
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -0,0 +1,377 @@
+using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.TextFormatting
+{
+ internal class TextLineImpl : TextLine
+ {
+ private readonly IReadOnlyList _textRuns;
+
+ public TextLineImpl(IReadOnlyList textRuns, TextLineMetrics lineMetrics,
+ TextLineBreak lineBreak = null, bool hasCollapsed = false)
+ {
+ _textRuns = textRuns;
+ LineMetrics = lineMetrics;
+ LineBreak = lineBreak;
+ HasCollapsed = hasCollapsed;
+ }
+
+ ///
+ public override TextRange TextRange => LineMetrics.TextRange;
+
+ ///
+ public override IReadOnlyList TextRuns => _textRuns;
+
+ ///
+ public override TextLineMetrics LineMetrics { get; }
+
+ ///
+ public override TextLineBreak LineBreak { get; }
+
+ ///
+ public override bool HasCollapsed { get; }
+
+ ///
+ public override void Draw(DrawingContext drawingContext, Point origin)
+ {
+ var currentX = origin.X;
+
+ foreach (var textRun in _textRuns)
+ {
+ var baselineOrigin = new Point(currentX, origin.Y + LineMetrics.TextBaseline);
+
+ textRun.Draw(drawingContext, baselineOrigin);
+
+ currentX += textRun.Bounds.Width;
+ }
+ }
+
+ ///
+ public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
+ {
+ if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0)
+ {
+ return this;
+ }
+
+ var collapsingProperties = collapsingPropertiesList[0];
+ var runIndex = 0;
+ var currentWidth = 0.0;
+ var textRange = TextRange;
+ var collapsedLength = 0;
+ TextLineMetrics textLineMetrics;
+
+ var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol);
+
+ var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width;
+
+ while (runIndex < _textRuns.Count)
+ {
+ var currentRun = _textRuns[runIndex];
+
+ currentWidth += currentRun.GlyphRun.Bounds.Width;
+
+ if (currentWidth > availableWidth)
+ {
+ var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
+
+ var currentBreakPosition = 0;
+
+ if (measuredLength < textRange.End)
+ {
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
+
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ {
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+ if (nextBreakPosition == 0)
+ {
+ break;
+ }
+
+ if (nextBreakPosition > measuredLength)
+ {
+ break;
+ }
+
+ currentBreakPosition = nextBreakPosition;
+ }
+ }
+
+ if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
+ {
+ measuredLength = currentBreakPosition;
+ }
+
+ collapsedLength += measuredLength;
+
+ var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);
+
+ var shapedTextCharacters = new List(splitResult.First.Count + 1);
+
+ shapedTextCharacters.AddRange(splitResult.First);
+
+ shapedTextCharacters.Add(shapedSymbol);
+
+ textRange = new TextRange(textRange.Start, collapsedLength);
+
+ var shapedWidth = GetShapedWidth(shapedTextCharacters);
+
+ textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
+ LineMetrics.TextBaseline, textRange, false);
+
+ return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
+ }
+
+ availableWidth -= currentRun.GlyphRun.Bounds.Width;
+
+ collapsedLength += currentRun.GlyphRun.Characters.Length;
+
+ runIndex++;
+ }
+
+ textLineMetrics =
+ new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width),
+ LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed);
+
+ return new TextLineImpl(new List(_textRuns) { shapedSymbol }, textLineMetrics, null,
+ true);
+ }
+
+ ///
+ public override CharacterHit GetCharacterHitFromDistance(double distance)
+ {
+ if (distance < 0)
+ {
+ // hit happens before the line, return the first position
+ return new CharacterHit(TextRange.Start);
+ }
+
+ // process hit that happens within the line
+ var characterHit = new CharacterHit();
+
+ foreach (var run in _textRuns)
+ {
+ characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
+
+ if (distance <= run.Bounds.Width)
+ {
+ break;
+ }
+
+ distance -= run.Bounds.Width;
+ }
+
+ return characterHit;
+ }
+
+ ///
+ public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
+ {
+ return DistanceFromCodepointIndex(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0));
+ }
+
+ ///
+ public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
+ {
+ if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit))
+ {
+ return nextCharacterHit;
+ }
+
+ return characterHit; // Can't move, we're after the last character
+ }
+
+ ///
+ public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
+ {
+ if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit))
+ {
+ return previousCharacterHit;
+ }
+
+ return characterHit; // Can't move, we're before the first character
+ }
+
+ ///
+ public override CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit)
+ {
+ // same operation as move-to-previous
+ return GetPreviousCaretCharacterHit(characterHit);
+ }
+
+ ///
+ /// Get distance from line start to the specified codepoint index.
+ ///
+ private double DistanceFromCodepointIndex(int codepointIndex)
+ {
+ var currentDistance = 0.0;
+
+ foreach (var textRun in _textRuns)
+ {
+ if (codepointIndex > textRun.Text.End)
+ {
+ currentDistance += textRun.Bounds.Width;
+
+ continue;
+ }
+
+ return currentDistance + textRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(codepointIndex));
+ }
+
+ return currentDistance;
+ }
+
+ ///
+ /// Tries to find the next character hit.
+ ///
+ /// The current character hit.
+ /// The next character hit.
+ ///
+ private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit)
+ {
+ nextCharacterHit = characterHit;
+
+ var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+
+ if (codepointIndex >= TextRange.Start + TextRange.Length)
+ {
+ return false; // Cannot go forward anymore
+ }
+
+ var runIndex = GetRunIndexAtCodepointIndex(codepointIndex);
+
+ while (runIndex < TextRuns.Count)
+ {
+ var run = _textRuns[runIndex];
+
+ var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
+
+ nextCharacterHit = characterHit.TrailingLength != 0 ?
+ foundCharacterHit :
+ new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
+
+ if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
+ {
+ return true;
+ }
+
+ runIndex++;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to find the previous character hit.
+ ///
+ /// The current character hit.
+ /// The previous character hit.
+ ///
+ private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit)
+ {
+ previousCharacterHit = characterHit;
+
+ var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+
+ if (codepointIndex < TextRange.Start)
+ {
+ return false; // Cannot go backward anymore.
+ }
+
+ var runIndex = GetRunIndexAtCodepointIndex(codepointIndex);
+
+ while (runIndex >= 0)
+ {
+ var run = _textRuns[runIndex];
+
+ var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+
+ previousCharacterHit = characterHit.TrailingLength != 0 ?
+ foundCharacterHit :
+ new CharacterHit(foundCharacterHit.FirstCharacterIndex);
+
+ if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex)
+ {
+ return true;
+ }
+
+ runIndex--;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the run index of the specified codepoint index.
+ ///
+ /// The codepoint index.
+ /// The text run index.
+ private int GetRunIndexAtCodepointIndex(int codepointIndex)
+ {
+ if (codepointIndex >= TextRange.End)
+ {
+ return _textRuns.Count - 1;
+ }
+
+ if (codepointIndex <= 0)
+ {
+ return 0;
+ }
+
+ var runIndex = 0;
+
+ while (runIndex < _textRuns.Count)
+ {
+ var run = _textRuns[runIndex];
+
+ if (run.Text.End > codepointIndex)
+ {
+ return runIndex;
+ }
+
+ runIndex++;
+ }
+
+ return runIndex;
+ }
+
+ ///
+ /// Creates a shaped symbol.
+ ///
+ /// The symbol run to shape.
+ ///
+ /// The shaped symbol.
+ ///
+ internal static ShapedTextCharacters CreateShapedSymbol(TextRun textRun)
+ {
+ var formatterImpl = AvaloniaLocator.Current.GetService();
+
+ var glyphRun = formatterImpl.ShapeText(textRun.Text, textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize,
+ textRun.Properties.CultureInfo);
+
+ return new ShapedTextCharacters(glyphRun, textRun.Properties);
+ }
+
+ ///
+ /// Gets the shaped width of specified shaped text characters.
+ ///
+ /// The shaped text characters.
+ ///
+ /// The shaped width.
+ ///
+ private static double GetShapedWidth(IReadOnlyList shapedTextCharacters)
+ {
+ var shapedWidth = 0.0;
+
+ for (var i = 0; i < shapedTextCharacters.Count; i++)
+ {
+ shapedWidth += shapedTextCharacters[i].Bounds.Width;
+ }
+
+ return shapedWidth;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
index 096305c09c0..6875cc1c04a 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@@ -8,38 +9,21 @@ namespace Avalonia.Media.TextFormatting
///
public readonly struct TextLineMetrics
{
- public TextLineMetrics(double width, double xOrigin, double ascent, double descent, double lineGap)
+ public TextLineMetrics(Size size, double textBaseline, TextRange textRange, bool hasOverflowed)
{
- Ascent = ascent;
- Descent = descent;
- LineGap = lineGap;
- Size = new Size(width, descent - ascent + lineGap);
- BaselineOrigin = new Point(xOrigin, -ascent);
+ Size = size;
+ TextBaseline = textBaseline;
+ TextRange = textRange;
+ HasOverflowed = hasOverflowed;
}
///
- /// Gets the overall recommended distance above the baseline.
+ /// Gets the text range that is covered by the text line.
///
///
- /// The ascent.
+ /// The text range that is covered by the text line.
///
- public double Ascent { get; }
-
- ///
- /// Gets the overall recommended distance under the baseline.
- ///
- ///
- /// The descent.
- ///
- public double Descent { get; }
-
- ///
- /// Gets the overall recommended additional space between two lines of text.
- ///
- ///
- /// The leading.
- ///
- public double LineGap { get; }
+ public TextRange TextRange { get; }
///
/// Gets the size of the text line.
@@ -50,21 +34,26 @@ public TextLineMetrics(double width, double xOrigin, double ascent, double desce
public Size Size { get; }
///
- /// Gets the baseline origin.
+ /// Gets the distance from the top to the baseline of the line of text.
///
- ///
- /// The baseline origin.
- ///
- public Point BaselineOrigin { get; }
+ public double TextBaseline { get; }
+
+ ///
+ /// Gets a boolean value that indicates whether content of the line overflows
+ /// the specified paragraph width.
+ ///
+ public bool HasOverflowed { get; }
///
/// Creates the text line metrics.
///
/// The text runs.
+ /// The text range that is covered by the text line.
/// The paragraph width.
- /// The text alignment.
+ /// The text alignment.
///
- public static TextLineMetrics Create(IEnumerable textRuns, double paragraphWidth, TextAlignment textAlignment)
+ public static TextLineMetrics Create(IEnumerable textRuns, TextRange textRange, double paragraphWidth,
+ TextParagraphProperties paragraphProperties)
{
var lineWidth = 0.0;
var ascent = 0.0;
@@ -73,31 +62,35 @@ public static TextLineMetrics Create(IEnumerable textRuns, double parag
foreach (var textRun in textRuns)
{
- var shapedRun = (ShapedTextRun)textRun;
+ var shapedRun = (ShapedTextCharacters)textRun;
- lineWidth += shapedRun.Bounds.Width;
+ var fontMetrics =
+ new FontMetrics(shapedRun.Properties.Typeface, shapedRun.Properties.FontRenderingEmSize);
- var textFormat = textRun.Style.TextFormat;
+ lineWidth += shapedRun.Bounds.Width;
- if (ascent > textRun.Style.TextFormat.FontMetrics.Ascent)
+ if (ascent > fontMetrics.Ascent)
{
- ascent = textFormat.FontMetrics.Ascent;
+ ascent = fontMetrics.Ascent;
}
- if (descent < textFormat.FontMetrics.Descent)
+ if (descent < fontMetrics.Descent)
{
- descent = textFormat.FontMetrics.Descent;
+ descent = fontMetrics.Descent;
}
- if (lineGap < textFormat.FontMetrics.LineGap)
+ if (lineGap < fontMetrics.LineGap)
{
- lineGap = textFormat.FontMetrics.LineGap;
+ lineGap = fontMetrics.LineGap;
}
}
- var xOrigin = TextLine.GetParagraphOffsetX(lineWidth, paragraphWidth, textAlignment);
+ var size = new Size(lineWidth,
+ double.IsNaN(paragraphProperties.LineHeight) || MathUtilities.IsZero(paragraphProperties.LineHeight) ?
+ descent - ascent + lineGap :
+ paragraphProperties.LineHeight);
- return new TextLineMetrics(lineWidth, xOrigin, ascent, descent, lineGap);
+ return new TextLineMetrics(size, -ascent, textRange, size.Width > paragraphWidth);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
index 1368f1777aa..3ecd1aafd93 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
@@ -3,38 +3,32 @@
///
/// Provides a set of properties that are used during the paragraph layout.
///
- public readonly struct TextParagraphProperties
+ public abstract class TextParagraphProperties
{
- public TextParagraphProperties(
- TextStyle defaultTextStyle,
- TextAlignment textAlignment = TextAlignment.Left,
- TextWrapping textWrapping = TextWrapping.NoWrap,
- TextTrimming textTrimming = TextTrimming.None)
- {
- DefaultTextStyle = defaultTextStyle;
- TextAlignment = textAlignment;
- TextWrapping = textWrapping;
- TextTrimming = textTrimming;
- }
+ ///
+ /// Gets the text alignment.
+ ///
+ public abstract TextAlignment TextAlignment { get; }
///
/// Gets the default text style.
///
- public TextStyle DefaultTextStyle { get; }
+ public abstract TextRunProperties DefaultTextRunProperties { get; }
///
- /// Gets the text alignment.
+ /// If not null, text decorations to apply to all runs in the line. This is in addition
+ /// to any text decorations specified by the TextRunProperties for individual text runs.
///
- public TextAlignment TextAlignment { get; }
+ public virtual TextDecorationCollection TextDecorations => null;
///
/// Gets the text wrapping.
///
- public TextWrapping TextWrapping { get; }
+ public abstract TextWrapping TextWrapping { get; }
///
- /// Gets the text trimming.
+ /// Paragraph's line height
///
- public TextTrimming TextTrimming { get; }
+ public abstract double LineHeight { get; }
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextPointer.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRange.cs
similarity index 73%
rename from src/Avalonia.Visuals/Media/TextFormatting/TextPointer.cs
rename to src/Avalonia.Visuals/Media/TextFormatting/TextRange.cs
index 65d5c04b4c3..1177c758f44 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextPointer.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRange.cs
@@ -5,9 +5,9 @@ namespace Avalonia.Media.TextFormatting
///
/// References a portion of a text buffer.
///
- public readonly struct TextPointer
+ public readonly struct TextRange
{
- public TextPointer(int start, int length)
+ public TextRange(int start, int length)
{
Start = start;
Length = length;
@@ -41,30 +41,30 @@ public TextPointer(int start, int length)
/// Returns a specified number of contiguous elements from the start of the slice.
///
/// The number of elements to return.
- /// A that contains the specified number of elements from the start of this slice.
- public TextPointer Take(int length)
+ /// A that contains the specified number of elements from the start of this slice.
+ public TextRange Take(int length)
{
if (length > Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
- return new TextPointer(Start, length);
+ return new TextRange(Start, length);
}
///
/// Bypasses a specified number of elements in the slice and then returns the remaining elements.
///
/// The number of elements to skip before returning the remaining elements.
- /// A that contains the elements that occur after the specified index in this slice.
- public TextPointer Skip(int length)
+ /// A that contains the elements that occur after the specified index in this slice.
+ public TextRange Skip(int length)
{
if (length > Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
- return new TextPointer(Start + length, Length - length);
+ return new TextRange(Start + length, Length - length);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs
index 28b83333b9d..c15a771755a 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs
@@ -1,5 +1,5 @@
using System.Diagnostics;
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@@ -9,15 +9,22 @@ namespace Avalonia.Media.TextFormatting
[DebuggerTypeProxy(typeof(TextRunDebuggerProxy))]
public abstract class TextRun
{
+ public static readonly int DefaultTextSourceLength = 1;
+
+ ///
+ /// Gets the text source length.
+ ///
+ public virtual int TextSourceLength => DefaultTextSourceLength;
+
///
/// Gets the text run's text.
///
- public ReadOnlySlice Text { get; protected set; }
+ public virtual ReadOnlySlice Text => default;
///
- /// Gets the text run's style.
+ /// A set of properties shared by every characters in the run
///
- public TextStyle Style { get; protected set; }
+ public virtual TextRunProperties Properties => null;
private class TextRunDebuggerProxy
{
@@ -42,7 +49,7 @@ public string Text
}
}
- public TextStyle Style => _textRun.Style;
+ public TextRunProperties Properties => _textRun.Properties;
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
new file mode 100644
index 00000000000..c4f9443c3d5
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Globalization;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Provides a set of properties, such as typeface or foreground brush, that can be applied to a TextRun object. This is an abstract class.
+ ///
+ ///
+ /// The text layout client provides a concrete implementation of this abstract class.
+ /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store.
+ ///
+ public abstract class TextRunProperties : IEquatable
+ {
+ ///
+ /// Run typeface
+ ///
+ public abstract Typeface Typeface { get; }
+
+ ///
+ /// Em size of font used to format and display text
+ ///
+ public abstract double FontRenderingEmSize { get; }
+
+ ///
+ /// Run TextDecorations.
+ ///
+ public abstract TextDecorationCollection TextDecorations { get; }
+
+ ///
+ /// Brush used to fill text.
+ ///
+ public abstract IBrush ForegroundBrush { get; }
+
+ ///
+ /// Brush used to paint background of run.
+ ///
+ public abstract IBrush BackgroundBrush { get; }
+
+ ///
+ /// Run text culture.
+ ///
+ public abstract CultureInfo CultureInfo { get; }
+
+ public bool Equals(TextRunProperties other)
+ {
+ if (ReferenceEquals(null, other))
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+
+ return Typeface.Equals(other.Typeface) &&
+ FontRenderingEmSize.Equals(other.FontRenderingEmSize)
+ && Equals(TextDecorations, other.TextDecorations) &&
+ Equals(ForegroundBrush, other.ForegroundBrush) &&
+ Equals(BackgroundBrush, other.BackgroundBrush) &&
+ Equals(CultureInfo, other.CultureInfo);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ReferenceEquals(this, obj) || obj is TextRunProperties other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = (Typeface != null ? Typeface.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ FontRenderingEmSize.GetHashCode();
+ hashCode = (hashCode * 397) ^ (TextDecorations != null ? TextDecorations.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (ForegroundBrush != null ? ForegroundBrush.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (BackgroundBrush != null ? BackgroundBrush.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (CultureInfo != null ? CultureInfo.GetHashCode() : 0);
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(TextRunProperties left, TextRunProperties right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(TextRunProperties left, TextRunProperties right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
index eb3a4129bcc..a02ace408fc 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
@@ -1,6 +1,7 @@
using System;
+using System.Globalization;
using Avalonia.Platform;
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
@@ -44,9 +45,10 @@ public static TextShaper Current
}
///
- public GlyphRun ShapeText(ReadOnlySlice text, TextFormat textFormat)
+ public GlyphRun ShapeText(ReadOnlySlice text, Typeface typeface, double fontRenderingEmSize,
+ CultureInfo culture)
{
- return _platformImpl.ShapeText(text, textFormat);
+ return _platformImpl.ShapeText(text, typeface, fontRenderingEmSize, culture);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextStyle.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextStyle.cs
deleted file mode 100644
index cf52c3ca177..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextStyle.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Avalonia.Media.Immutable;
-
-namespace Avalonia.Media.TextFormatting
-{
- ///
- /// Unique text formatting properties that effect the styling of a text.
- ///
- public readonly struct TextStyle
- {
- public TextStyle(Typeface typeface, double fontRenderingEmSize = 12, IBrush foreground = null,
- ImmutableTextDecoration[] textDecorations = null)
- : this(new TextFormat(typeface, fontRenderingEmSize), foreground, textDecorations)
- {
- }
-
- public TextStyle(TextFormat textFormat, IBrush foreground = null,
- ImmutableTextDecoration[] textDecorations = null)
- {
- TextFormat = textFormat;
- Foreground = foreground;
- TextDecorations = textDecorations;
- }
-
- ///
- /// Gets the text format.
- ///
- public TextFormat TextFormat { get; }
-
- ///
- /// Gets the foreground.
- ///
- public IBrush Foreground { get; }
-
- ///
- /// Gets the text decorations.
- ///
- public ImmutableTextDecoration[] TextDecorations { get; }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextStyleRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextStyleRun.cs
deleted file mode 100644
index 55f89991829..00000000000
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextStyleRun.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Avalonia.Media.TextFormatting
-{
- ///
- /// Represents a text run's style and is used during the layout process of the .
- ///
- public readonly struct TextStyleRun
- {
- public TextStyleRun(TextPointer textPointer, TextStyle style)
- {
- TextPointer = textPointer;
- Style = style;
- }
-
- ///
- /// Gets the text pointer.
- ///
- public TextPointer TextPointer { get; }
-
- ///
- /// Gets the text style.
- ///
- public TextStyle Style { get; }
- }
-}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
new file mode 100644
index 00000000000..4bd46e8c751
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
@@ -0,0 +1,33 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// a collapsing properties to collapse whole line toward the end
+ /// at character granularity and with ellipsis being the collapsing symbol
+ ///
+ public class TextTrailingCharacterEllipsis : TextCollapsingProperties
+ {
+ private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
+
+ ///
+ /// Construct a text trailing character ellipsis collapsing properties
+ ///
+ /// width in which collapsing is constrained to
+ /// text run properties of ellispis symbol
+ public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties)
+ {
+ Width = width;
+ Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+ }
+
+ ///
+ public sealed override double Width { get; }
+
+ ///
+ public sealed override TextRun Symbol { get; }
+
+ ///
+ public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter;
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
new file mode 100644
index 00000000000..9dffddd207d
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
@@ -0,0 +1,37 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// a collapsing properties to collapse whole line toward the end
+ /// at word granularity and with ellipsis being the collapsing symbol
+ ///
+ public class TextTrailingWordEllipsis : TextCollapsingProperties
+ {
+ private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
+
+ ///
+ /// Construct a text trailing word ellipsis collapsing properties
+ ///
+ /// width in which collapsing is constrained to
+ /// text run properties of ellispis symbol
+ public TextTrailingWordEllipsis(
+ double width,
+ TextRunProperties textRunProperties
+ )
+ {
+ Width = width;
+ Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+ }
+
+
+ ///
+ public sealed override double Width { get; }
+
+ ///
+ public sealed override TextRun Symbol { get; }
+
+ ///
+ public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord;
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
index 94171b73244..2f46fdd9d05 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
@@ -1,4 +1,4 @@
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
@@ -112,7 +112,7 @@ public static Codepoint ReadAt(ReadOnlySlice text, int index, out int coun
{
count = 1;
- if (index > text.End)
+ if (index > text.Length)
{
return ReplacementCodepoint;
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs
index 2ff4952cab1..9e1f748ebb5 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/CodepointEnumerator.cs
@@ -1,4 +1,4 @@
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs
index a6791b4a534..f268340eb98 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Grapheme.cs
@@ -1,4 +1,4 @@
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs
index fd7831dfe60..1e4ac8fe0fd 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeEnumerator.cs
@@ -4,7 +4,7 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System.Runtime.InteropServices;
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index 25a32bb1a39..76bb9ac44f4 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
@@ -16,7 +16,7 @@
// Ported from: https://github.com/foliojs/linebreak
// Copied from: https://github.com/toptensoftware/RichTextKit
-using Avalonia.Utility;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
@@ -109,7 +109,6 @@ public bool MoveNext()
{
case PairBreakType.DI: // Direct break
shouldBreak = true;
- _lastPos = _pos;
break;
case PairBreakType.IN: // possible indirect break
diff --git a/src/Avalonia.Visuals/Media/TextWrapping.cs b/src/Avalonia.Visuals/Media/TextWrapping.cs
index 56df3670bda..b7915e5612d 100644
--- a/src/Avalonia.Visuals/Media/TextWrapping.cs
+++ b/src/Avalonia.Visuals/Media/TextWrapping.cs
@@ -13,6 +13,13 @@ public enum TextWrapping
///
/// Text can wrap.
///
- Wrap
+ Wrap,
+
+ ///
+ /// Line-breaking occurs if the line overflows the available block width.
+ /// However, a line may overflow the block width if the line breaking algorithm
+ /// cannot determine a break opportunity, as in the case of a very long word.
+ ///
+ WrapWithOverflow
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs
index 7618598a3fb..677e9308049 100644
--- a/src/Avalonia.Visuals/Media/Typeface.cs
+++ b/src/Avalonia.Visuals/Media/Typeface.cs
@@ -16,11 +16,11 @@ public class Typeface : IEquatable
/// Initializes a new instance of the class.
///
/// The font family.
- /// The font weight.
/// The font style.
+ /// The font weight.
public Typeface([NotNull]FontFamily fontFamily,
- FontWeight weight = FontWeight.Normal,
- FontStyle style = FontStyle.Normal)
+ FontStyle style = FontStyle.Normal,
+ FontWeight weight = FontWeight.Normal)
{
if (weight <= 0)
{
@@ -39,9 +39,9 @@ public Typeface([NotNull]FontFamily fontFamily,
/// The font style.
/// The font weight.
public Typeface(string fontFamilyName,
- FontWeight weight = FontWeight.Normal,
- FontStyle style = FontStyle.Normal)
- : this(new FontFamily(fontFamilyName), weight, style)
+ FontStyle style = FontStyle.Normal,
+ FontWeight weight = FontWeight.Normal)
+ : this(new FontFamily(fontFamilyName), style, weight)
{
}
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs b/src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs
new file mode 100644
index 00000000000..a3cf0298dd4
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs
@@ -0,0 +1,9 @@
+using Avalonia.Media;
+
+namespace Avalonia.Platform
+{
+ public interface IDrawingContextWithAcrylicLikeSupport
+ {
+ void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect);
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
index 3d0bea8c802..59b08aae0a2 100644
--- a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
@@ -22,15 +22,16 @@ public interface IFontManagerImpl
/// Tries to match a specified character to a typeface that supports specified font properties.
///
/// The codepoint to match against.
- /// The font weight.
/// The font style.
+ /// The font weight.
/// The font family. This is optional and used for fallback lookup.
/// The culture.
/// The matching font key.
///
/// True, if the could match the character to specified parameters, False otherwise.
///
- bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle,
+ bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
+ FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey);
///
diff --git a/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs b/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs
index 4d770a6c6e6..d915da26034 100644
--- a/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs
+++ b/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs
@@ -1,6 +1,6 @@
-using Avalonia.Media;
-using Avalonia.Media.TextFormatting;
-using Avalonia.Utility;
+using System.Globalization;
+using Avalonia.Media;
+using Avalonia.Utilities;
namespace Avalonia.Platform
{
@@ -13,8 +13,10 @@ public interface ITextShaperImpl
/// Shapes the specified region within the text and returns a resulting glyph run.
///
/// The text.
- /// The text format.
+ /// The typeface.
+ /// The font rendering em size.
+ /// The culture.
/// A shaped glyph run.
- GlyphRun ShapeText(ReadOnlySlice text, TextFormat textFormat);
+ GlyphRun ShapeText(ReadOnlySlice text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index dfb21a02891..4a364998fdb 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// A drawing context which builds a scene graph.
///
- internal class DeferredDrawingContextImpl : IDrawingContextImpl
+ internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
{
private readonly ISceneBuilder _sceneBuilder;
private VisualNode _node;
@@ -164,6 +164,21 @@ public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect,
}
}
+ ///
+ public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
+ {
+ var next = NextDrawAs();
+
+ if (next == null || !next.Item.Equals(Transform, material, rect))
+ {
+ Add(new ExperimentalAcrylicNode(Transform, material, rect));
+ }
+ else
+ {
+ ++_drawOperationindex;
+ }
+ }
+
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs();
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
new file mode 100644
index 00000000000..336d11e3fd6
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
@@ -0,0 +1,94 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ ///
+ /// A node in the scene graph which represents a rectangle draw.
+ ///
+ internal class ExperimentalAcrylicNode : DrawOperation
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform.
+ /// The rectangle to draw.
+ /// The box shadow parameters
+ /// Child scenes for drawing visual brushes.
+ public ExperimentalAcrylicNode(
+ Matrix transform,
+ IExperimentalAcrylicMaterial material,
+ RoundedRect rect)
+ : base(rect.Rect, transform)
+ {
+ Transform = transform;
+ Material = material?.ToImmutable();
+ Rect = rect;
+ }
+
+ ///
+ /// Gets the transform with which the node will be drawn.
+ ///
+ public Matrix Transform { get; }
+
+ public IExperimentalAcrylicMaterial Material { get; }
+
+ ///
+ /// Gets the rectangle to draw.
+ ///
+ public RoundedRect Rect { get; }
+
+ ///
+ /// Determines if this draw operation equals another.
+ ///
+ /// The transform of the other draw operation.
+ /// The fill of the other draw operation.
+ /// The rectangle of the other draw operation.
+ /// True if the draw operations are the same, otherwise false.
+ ///
+ /// The properties of the other draw operation are passed in as arguments to prevent
+ /// allocation of a not-yet-constructed draw operation object.
+ ///
+ public bool Equals(Matrix transform, IExperimentalAcrylicMaterial material, RoundedRect rect)
+ {
+ return transform == Transform &&
+ Equals(material, Material) &&
+ rect.Equals(Rect);
+ }
+
+ ///
+ public override void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+
+ if(context is IDrawingContextWithAcrylicLikeSupport idc)
+ {
+ idc.DrawRectangle(Material, Rect);
+ }
+ else
+ {
+ context.DrawRectangle(new ImmutableSolidColorBrush(Material.FallbackColor), null, Rect);
+ }
+ }
+
+ ///
+ public override bool HitTest(Point p)
+ {
+ // TODO: This doesn't respect CornerRadius yet.
+ if (Transform.HasInverse)
+ {
+ p *= Transform.Invert();
+
+ if (Material != null)
+ {
+ var rect = Rect.Rect;
+ return rect.Contains(p);
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
index 5059a6d0428..ec1a7753b1d 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -19,7 +19,7 @@ internal class RectangleNode : BrushDrawOperation
/// The fill brush.
/// The stroke pen.
/// The rectangle to draw.
- /// The box shadow parameters
+ /// The box shadow parameters
/// Child scenes for drawing visual brushes.
public RectangleNode(
Matrix transform,
diff --git a/src/Avalonia.Visuals/Utility/ReadOnlySlice.cs b/src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs
similarity index 98%
rename from src/Avalonia.Visuals/Utility/ReadOnlySlice.cs
rename to src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs
index ff2b3b93633..5feaa88e26c 100644
--- a/src/Avalonia.Visuals/Utility/ReadOnlySlice.cs
+++ b/src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs
@@ -2,9 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using Avalonia.Utilities;
-namespace Avalonia.Utility
+namespace Avalonia.Utilities
{
///
/// ReadOnlySlice enables the ability to work with a sequence within a region of memory and retains the position in within that region.
@@ -47,7 +46,7 @@ public ReadOnlySlice(ReadOnlyMemory buffer, int start, int length)
public int Length { get; }
///
- /// Gets a value that indicates whether this instance of is Empty.
+ /// Gets a value that indicates whether this instance of is Empty.
///
public bool IsEmpty => Length == 0;
diff --git a/src/Avalonia.Visuals/Utilities/ValueSpan.cs b/src/Avalonia.Visuals/Utilities/ValueSpan.cs
new file mode 100644
index 00000000000..7a10d865ef4
--- /dev/null
+++ b/src/Avalonia.Visuals/Utilities/ValueSpan.cs
@@ -0,0 +1,30 @@
+namespace Avalonia.Utilities
+{
+ ///
+ /// Pairing of value and positions sharing that value.
+ ///
+ public readonly struct ValueSpan
+ {
+ public ValueSpan(int start, int length, T value)
+ {
+ Start = start;
+ Length = length;
+ Value = value;
+ }
+
+ ///
+ /// Get's the start of the span.
+ ///
+ public int Start { get; }
+
+ ///
+ /// Get's the length of the span.
+ ///
+ public int Length { get; }
+
+ ///
+ /// Get's the value of the span.
+ ///
+ public T Value { get; }
+ }
+}
diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs
index 23fb27f72b1..6c4eb81c84e 100644
--- a/src/Avalonia.X11/X11NativeControlHost.cs
+++ b/src/Avalonia.X11/X11NativeControlHost.cs
@@ -167,7 +167,7 @@ public void HideWithSize(Size size)
XUnmapWindow(_display, _holder.Handle);
}
- size *= _attachedTo.Window.Scaling;
+ size *= _attachedTo.Window.RenderScaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
}
@@ -179,7 +179,7 @@ public void ShowInBounds(Rect bounds)
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
- bounds *= _attachedTo.Window.Scaling;
+ bounds *= _attachedTo.Window.RenderScaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs
index e247a4241a0..af6d2674b64 100644
--- a/src/Avalonia.X11/X11Screens.cs
+++ b/src/Avalonia.X11/X11Screens.cs
@@ -48,9 +48,13 @@ static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens)
var pwa = (IntPtr*)prop;
var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
-
- foreach (var s in screens)
+
+ foreach (var s in screens)
+ {
s.WorkingArea = s.Bounds.Intersect(wa);
+ if (s.WorkingArea.Width <= 0 || s.WorkingArea.Height <= 0)
+ s.WorkingArea = s.Bounds;
+ }
XFree(prop);
return screens;
@@ -134,8 +138,14 @@ public FallbackScreensImpl(X11Info info, X11ScreensUserSettings settings)
settings.GlobalScaleFactor)
});
}
-
- Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
+ else
+ {
+ Screens = new[]
+ {
+ new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null,
+ settings.GlobalScaleFactor)
+ };
+ }
}
public X11Screen[] Screens { get; }
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 499fe5a67ae..c24abcd2302 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -163,7 +163,7 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent)
var surfaces = new List
{
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
- depth, () => Scaling)
+ depth, () => RenderScaling)
};
if (egl != null)
@@ -217,7 +217,7 @@ public PixelSize Size
}
}
- public double Scaling => _window.Scaling;
+ public double Scaling => _window.RenderScaling;
}
void UpdateMotifHints()
@@ -284,9 +284,9 @@ void UpdateSizeHints(PixelSize? preResize)
XSetWMNormalHints(_x11.Display, _handle, ref hints);
}
- public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
+ public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling);
- public double Scaling
+ public double RenderScaling
{
get
{
@@ -296,6 +296,8 @@ public double Scaling
}
private set => _scaling = value;
}
+
+ public double DesktopScaling => RenderScaling;
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
@@ -312,7 +314,15 @@ public Action TransparencyLevelChanged
{
get => _transparencyHelper.TransparencyLevelChanged;
set => _transparencyHelper.TransparencyLevelChanged = value;
- }
+ }
+
+ public Action ExtendClientAreaToDecorationsChanged { get; set; }
+
+ public Thickness ExtendedMargins { get; } = new Thickness();
+
+ public Thickness OffScreenMargin { get; } = new Thickness();
+
+ public bool IsClientAreaExtendedToDecorations { get; }
public Action Closed { get; set; }
public Action PositionChanged { get; set; }
@@ -530,14 +540,14 @@ private bool UpdateScaling(bool skipResize = false)
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
- newScaling = monitor?.PixelDensity ?? Scaling;
+ newScaling = monitor?.PixelDensity ?? RenderScaling;
}
- if (Scaling != newScaling)
+ if (RenderScaling != newScaling)
{
var oldScaledSize = ClientSize;
- Scaling = newScaling;
- ScalingChanged?.Invoke(Scaling);
+ RenderScaling = newScaling;
+ ScalingChanged?.Invoke(RenderScaling);
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
Resize(oldScaledSize, true);
@@ -699,9 +709,9 @@ public void ScheduleXI2Input(RawInputEventArgs args)
private void ScheduleInput(RawInputEventArgs args)
{
if (args is RawPointerEventArgs mouse)
- mouse.Position = mouse.Position / Scaling;
+ mouse.Position = mouse.Position / RenderScaling;
if (args is RawDragEvent drag)
- drag.Location = drag.Location / Scaling;
+ drag.Location = drag.Location / RenderScaling;
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
@@ -752,11 +762,7 @@ public void SetInputRoot(IInputRoot inputRoot)
public void Dispose()
{
- if (_handle != IntPtr.Zero)
- {
- XDestroyWindow(_x11.Display, _handle);
- Cleanup();
- }
+ Cleanup();
}
void Cleanup()
@@ -779,8 +785,7 @@ void Cleanup()
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)
- {
- XDestroyWindow(_x11.Display, _renderHandle);
+ {
_renderHandle = IntPtr.Zero;
}
}
@@ -813,11 +818,11 @@ public void Show()
public void Hide() => XUnmapWindow(_x11.Display, _handle);
- public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
+ public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling);
public PixelPoint PointToScreen(Point point) => new PixelPoint(
- (int)(point.X * Scaling + Position.X),
- (int)(point.Y * Scaling + Position.Y));
+ (int)(point.X * RenderScaling + Position.X),
+ (int)(point.Y * RenderScaling + Position.Y));
public void SetSystemDecorations(SystemDecorations enabled)
{
@@ -837,7 +842,7 @@ private void MoveResize(PixelPoint position, Size size, double scaling)
Resize(size, true);
}
- PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
+ PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
void Resize(Size clientSize, bool force)
{
@@ -1017,13 +1022,13 @@ public void SetMinMaxSize(Size minSize, Size maxSize)
{
_scaledMinMaxSize = (minSize, maxSize);
var min = new PixelSize(
- (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
- (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
+ (int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling),
+ (int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling));
const int maxDim = MaxWindowDimension;
var max = new PixelSize(
- (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
- (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
+ (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)),
+ (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling)));
_minMaxSize = (min, max);
UpdateSizeHints(null);
@@ -1039,6 +1044,18 @@ public void SetEnabled(bool enable)
_disabled = !enable;
}
+ public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
+ {
+ }
+
+ public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
+ {
+ }
+
+ public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
+ {
+ }
+
public Action GotInputWhenDisabled { get; set; }
public void SetIcon(IWindowIconImpl icon)
@@ -1107,5 +1124,9 @@ public void SetWindowManagerAddShadowHint(bool enabled)
}
public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel;
+
+ public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8);
+
+ public bool NeedsManagedDecorations => false;
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index 21533443633..0a101eec7a9 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -65,7 +65,7 @@ public void SetCursor(IPlatformHandle cursor)
public IMouseDevice MouseDevice => new MouseDevice();
public IPopupImpl CreatePopup() => null;
- public double Scaling => _outputBackend.Scaling;
+ public double RenderScaling => _outputBackend.Scaling;
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
@@ -77,10 +77,12 @@ public void SetCursor(IPlatformHandle cursor)
public Action Closed { get; set; }
public Action LostFocus { get; set; }
- public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
+ public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
+ public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
new file mode 100644
index 00000000000..768545eb7e7
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
new file mode 100644
index 00000000000..4569970d010
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl;
+// ReSharper disable CheckNamespace
+
+namespace Avalonia.Markup.Xaml
+{
+ public static class AvaloniaRuntimeXamlLoader
+ {
+ ///
+ /// Loads XAML from a string.
+ ///
+ /// The string containing the XAML.
+ /// Default assembly for clr-namespace:
+ ///
+ /// The optional instance into which the XAML should be loaded.
+ ///
+ /// The loaded object.
+ public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false)
+ {
+ Contract.Requires(xaml != null);
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+ {
+ return Load(stream, localAssembly, rootInstance, uri, designMode);
+ }
+ }
+
+ public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
+ bool designMode = false)
+ => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode);
+
+ public static object Parse(string xaml, Assembly localAssembly = null)
+ => Load(xaml, localAssembly);
+
+ public static T Parse(string xaml, Assembly localAssembly = null)
+ => (T)Parse(xaml, localAssembly);
+
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
similarity index 85%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
index 5a5da518d0f..d9b4cd70daa 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
@@ -10,12 +10,15 @@
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
-using XamlIl.Transform;
-using XamlIl.TypeSystem;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.IL;
+using XamlX.Emit;
#if RUNTIME_XAML_CECIL
using TypeAttributes = Mono.Cecil.TypeAttributes;
using Mono.Cecil;
-using XamlIl.Ast;
+using XamlX.Ast;
+using XamlX.IL.Cecil;
#endif
namespace Avalonia.Markup.Xaml.XamlIl
{
@@ -24,9 +27,10 @@ static class AvaloniaXamlIlRuntimeCompiler
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
private static ModuleBuilder _sreBuilder;
- private static IXamlIlType _sreContextType;
- private static XamlIlLanguageTypeMappings _sreMappings;
- private static XamlIlXmlnsMappings _sreXmlns;
+ private static IXamlType _sreContextType;
+ private static XamlLanguageTypeMappings _sreMappings;
+ private static XamlLanguageEmitMappings _sreEmitMappings;
+ private static XamlXmlnsMappings _sreXmlns;
private static AssemblyBuilder _sreAsm;
private static bool _sreCanSave;
@@ -82,13 +86,14 @@ static void InitializeSre()
}
if (_sreMappings == null)
- _sreMappings = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
+ (_sreMappings, _sreEmitMappings) = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
if (_sreXmlns == null)
- _sreXmlns = XamlIlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
+ _sreXmlns = XamlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
if (_sreContextType == null)
- _sreContextType = XamlIlContextDefinition.GenerateContextClass(
+ _sreContextType = XamlILContextDefinition.GenerateContextClass(
_sreTypeSystem.CreateTypeBuilder(
- _sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings);
+ _sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings,
+ _sreEmitMappings);
}
@@ -114,13 +119,19 @@ static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstan
InitializeSre();
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
+ var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
+ var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
+ var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
- var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm,
- _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
+ var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
+ _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
+ new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
+ new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))),
+ _sreEmitMappings,
_sreContextType) { EnableIlVerification = true };
- var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
+
- IXamlIlType overrideType = null;
+ IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _sreTypeSystem.GetType(rootInstance.GetType());
@@ -129,6 +140,7 @@ static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstan
compiler.IsDesignMode = isDesignMode;
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
+ clrPropertyBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);
}
@@ -203,6 +215,7 @@ public static object Load(Stream stream, Assembly localAssembly, object rootInst
private static string _cecilEmitDir;
private static CecilTypeSystem _cecilTypeSystem;
private static XamlIlLanguageTypeMappings _cecilMappings;
+ private static XamlLanguageEmitMappings _cecilEmitMappings;
private static XamlIlXmlnsMappings _cecilXmlns;
private static bool _cecilInitialized;
@@ -215,7 +228,7 @@ static void InitializeCecil()
Directory.CreateDirectory(_cecilEmitDir);
var refs = new[] {path}.Concat(File.ReadAllLines(path + ".refs"));
_cecilTypeSystem = new CecilTypeSystem(refs);
- _cecilMappings = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
+ (_cecilMappings, _cecilEmitMappings) = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
_cecilXmlns = XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings);
_cecilInitialized = true;
}
@@ -226,7 +239,7 @@ static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance
if (uri == null)
throw new InvalidOperationException("Please, go away");
InitializeCecil();
- IXamlIlType overrideType = null;
+ IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
@@ -261,6 +274,7 @@ static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name),
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings),
AvaloniaXamlIlLanguage.CustomValueConverter),
+ _cecilEmitMappings,
_cecilTypeSystem.CreateTypeBuilder(contextDef));
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
new file mode 100644
index 00000000000..abff763bb14
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -0,0 +1,163 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Parsers;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+ class AvaloniaXamlIlCompiler : XamlILCompiler
+ {
+ private readonly TransformerConfiguration _configuration;
+ private readonly IXamlType _contextType;
+ private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
+ private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
+
+ private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings)
+ : base(configuration, emitMappings, true)
+ {
+ _configuration = configuration;
+
+ void InsertAfter(params IXamlAstTransformer[] t)
+ => Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
+
+ void InsertBefore(params IXamlAstTransformer[] t)
+ => Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
+
+
+ // Before everything else
+
+ Transformers.Insert(0, new XNameTransformer());
+ Transformers.Insert(1, new IgnoredDirectivesTransformer());
+ Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
+ Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
+
+
+ // Targeted
+ InsertBefore(
+ new AvaloniaXamlIlTransformInstanceAttachedProperties(),
+ new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
+ InsertAfter(
+ new AvaloniaXamlIlAvaloniaPropertyResolver());
+
+ InsertBefore(
+ new AvaloniaXamlIlBindingPathParser(),
+ new AvaloniaXamlIlSelectorTransformer(),
+ new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
+ new AvaloniaXamlIlPropertyPathTransformer(),
+ new AvaloniaXamlIlSetterTransformer(),
+ new AvaloniaXamlIlConstructorServiceProviderTransformer(),
+ new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
+ new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
+ );
+
+ // After everything else
+ InsertBefore(
+ new AddNameScopeRegistration(),
+ new AvaloniaXamlIlDataContextTypeTransformer(),
+ new AvaloniaXamlIlBindingPathTransformer(),
+ new AvaloniaXamlIlCompiledBindingsMetadataRemover()
+ );
+
+ Transformers.Add(new AvaloniaXamlIlMetadataRemover());
+ Transformers.Add(new AvaloniaXamlIlRootObjectScope());
+
+ Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
+ Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
+ }
+ public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
+ XamlLanguageEmitMappings emitMappings,
+ IXamlTypeBuilder contextTypeBuilder)
+ : this(configuration, emitMappings)
+ {
+ _contextType = CreateContextType(contextTypeBuilder);
+ }
+
+
+ public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
+ XamlLanguageEmitMappings emitMappings,
+ IXamlType contextType) : this(configuration, emitMappings)
+ {
+ _contextType = contextType;
+ }
+
+ public const string PopulateName = "__AvaloniaXamlIlPopulate";
+ public const string BuildName = "__AvaloniaXamlIlBuild";
+
+ public bool IsDesignMode
+ {
+ get => _designTransformer.IsDesignMode;
+ set => _designTransformer.IsDesignMode = value;
+ }
+
+ public bool DefaultCompileBindings
+ {
+ get => _bindingTransformer.CompileBindingsByDefault;
+ set => _bindingTransformer.CompileBindingsByDefault = value;
+ }
+
+ public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder tb, IXamlType overrideRootType)
+ {
+ var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary
+ {
+ {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
+ });
+
+ var rootObject = (XamlAstObjectNode)parsed.Root;
+
+ var classDirective = rootObject.Children
+ .OfType().FirstOrDefault(x =>
+ x.Namespace == XamlNamespaces.Xaml2006
+ && x.Name == "Class");
+
+ var rootType =
+ classDirective != null ?
+ new XamlAstClrTypeReference(classDirective,
+ _configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
+ false) :
+ TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
+ (XamlAstXmlTypeReference)rootObject.Type, true);
+
+
+ if (overrideRootType != null)
+ {
+ if (!rootType.Type.IsAssignableFrom(overrideRootType))
+ throw new XamlX.XamlLoadException(
+ $"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject);
+ rootType = new XamlAstClrTypeReference(rootObject, overrideRootType, false);
+ }
+
+ OverrideRootType(parsed, rootType);
+
+ Transform(parsed);
+ Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
+
+ }
+
+ public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)
+ {
+ var root = (XamlAstObjectNode)doc.Root;
+ var oldType = root.Type;
+ if (oldType.Equals(newType))
+ return;
+
+ root.Type = newType;
+ foreach (var child in root.Children.OfType())
+ {
+ if (child.Property is XamlAstNamePropertyReference prop)
+ {
+ if (prop.DeclaringType.Equals(oldType))
+ prop.DeclaringType = newType;
+ if (prop.TargetType.Equals(oldType))
+ prop.TargetType = newType;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
new file mode 100644
index 00000000000..0c0dcb16346
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
@@ -0,0 +1,26 @@
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+ class AvaloniaXamlIlCompilerConfiguration : TransformerConfiguration
+ {
+ public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
+ public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
+
+ public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem,
+ IXamlAssembly defaultAssembly,
+ XamlLanguageTypeMappings typeMappings,
+ XamlXmlnsMappings xmlnsMappings,
+ XamlValueConverter customValueConverter,
+ XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
+ XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter)
+ : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)
+ {
+ ClrPropertyEmitter = clrPropertyEmitter;
+ AccessorFactoryEmitter = accessorFactoryEmitter;
+ AddExtra(ClrPropertyEmitter);
+ AddExtra(AccessorFactoryEmitter);
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
similarity index 71%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
index a1fe6976b72..99ec3744bf3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
@@ -2,10 +2,12 @@
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.TypeSystem;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
@@ -17,12 +19,12 @@ ONLY use types from netstandard and XamlIl. NO dependencies on Avalonia are allo
class AvaloniaXamlIlLanguage
{
- public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem)
+ public static (XamlLanguageTypeMappings language, XamlLanguageEmitMappings emit) Configure(IXamlTypeSystem typeSystem)
{
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var assignBindingAttribute = typeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
var bindingType = typeSystem.GetType("Avalonia.Data.IBinding");
- var rv = new XamlIlLanguageTypeMappings(typeSystem)
+ var rv = new XamlLanguageTypeMappings(typeSystem)
{
SupportInitialize = typeSystem.GetType("System.ComponentModel.ISupportInitialize"),
XmlnsAttributes =
@@ -51,18 +53,22 @@ public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem)
},
InnerServiceProviderFactoryMethod =
runtimeHelpers.FindMethod(m => m.Name == "CreateInnerServiceProviderV1"),
- ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
- rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
- return rv;
+
+ var emit = new XamlLanguageEmitMappings
+ {
+ ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.EmitProvideValueTarget,
+ ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c)
+ };
+ return (rv, emit);
}
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
- private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
- IXamlIlTypeSystem typeSystem,
- IXamlIlTypeBuilder typebuilder, IXamlIlEmitter constructor)
+ private static void EmitNameScopeField(XamlLanguageTypeMappings mappings,
+ IXamlTypeSystem typeSystem,
+ IXamlTypeBuilder typebuilder, IXamlILEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
@@ -78,23 +84,23 @@ private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
}
- class AttributeResolver : IXamlIlCustomAttributeResolver
+ class AttributeResolver : IXamlCustomAttributeResolver
{
- private readonly IXamlIlType _typeConverterAttribute;
+ private readonly IXamlType _typeConverterAttribute;
- private readonly List> _converters =
- new List>();
+ private readonly List> _converters =
+ new List>();
- private readonly IXamlIlType _avaloniaList;
- private readonly IXamlIlType _avaloniaListConverter;
+ private readonly IXamlType _avaloniaList;
+ private readonly IXamlType _avaloniaListConverter;
- public AttributeResolver(IXamlIlTypeSystem typeSystem, XamlIlLanguageTypeMappings mappings)
+ public AttributeResolver(IXamlTypeSystem typeSystem, XamlLanguageTypeMappings mappings)
{
_typeConverterAttribute = mappings.TypeConverterAttributes.First();
- void AddType(IXamlIlType type, IXamlIlType conv)
- => _converters.Add(new KeyValuePair(type, conv));
+ void AddType(IXamlType type, IXamlType conv)
+ => _converters.Add(new KeyValuePair(type, conv));
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
@@ -113,7 +119,7 @@ void Add(string type, string conv)
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1");
}
- IXamlIlType LookupConverter(IXamlIlType type)
+ IXamlType LookupConverter(IXamlType type)
{
foreach(var p in _converters)
if (p.Key.Equals(type))
@@ -123,15 +129,15 @@ IXamlIlType LookupConverter(IXamlIlType type)
return null;
}
- class ConstructedAttribute : IXamlIlCustomAttribute
+ class ConstructedAttribute : IXamlCustomAttribute
{
- public bool Equals(IXamlIlCustomAttribute other) => false;
+ public bool Equals(IXamlCustomAttribute other) => false;
- public IXamlIlType Type { get; }
+ public IXamlType Type { get; }
public List Parameters { get; }
public Dictionary Properties { get; }
- public ConstructedAttribute(IXamlIlType type, List parameters, Dictionary properties)
+ public ConstructedAttribute(IXamlType type, List parameters, Dictionary properties)
{
Type = type;
Parameters = parameters ?? new List();
@@ -139,7 +145,7 @@ public ConstructedAttribute(IXamlIlType type, List parameters, Dictionar
}
}
- public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType attributeType)
+ public IXamlCustomAttribute GetCustomAttribute(IXamlType type, IXamlType attributeType)
{
if (attributeType.Equals(_typeConverterAttribute))
{
@@ -151,25 +157,25 @@ public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType a
return null;
}
- public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlProperty property, IXamlIlType attributeType)
+ public IXamlCustomAttribute GetCustomAttribute(IXamlProperty property, IXamlType attributeType)
{
return null;
}
}
- public static bool CustomValueConverter(XamlIlAstTransformationContext context,
- IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result)
+ public static bool CustomValueConverter(AstTransformationContext context,
+ IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result)
{
if (type.FullName == "System.TimeSpan"
- && node is XamlIlAstTextNode tn
+ && node is XamlAstTextNode tn
&& !tn.Text.Contains(":"))
{
var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture);
- result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn,
+ result = new XamlStaticOrTargetedReturnMethodCallNode(tn,
type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double),
new[]
{
- new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
+ new XamlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
});
return true;
}
@@ -178,9 +184,9 @@ public static bool CustomValueConverter(XamlIlAstTransformationContext context,
{
var scope = context.ParentNodes().OfType().FirstOrDefault();
if (scope == null)
- throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
- if (!(node is XamlIlAstTextNode text))
- throw new XamlIlLoadException("Property should be a text node", node);
+ throw new XamlX.XamlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
+ if (!(node is XamlAstTextNode text))
+ throw new XamlX.XamlLoadException("Property should be a text node", node);
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text);
return true;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
new file mode 100644
index 00000000000..cb36851bab8
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.Emit;
+using XamlX.IL;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AddNameScopeRegistration : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlPropertyAssignmentNode pa)
+ {
+ if (pa.Property.Name == "Name"
+ && pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
+ {
+ if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
+ && mg.Children.OfType().Any())
+ return node;
+
+ IXamlAstValueNode value = null;
+ for (var c = 0; c < pa.Values.Count; c++)
+ if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
+ {
+ value = pa.Values[c];
+ if (!(value is XamlAstTextNode))
+ {
+ var local = new XamlAstCompilerLocalNode(value);
+ // Wrap original in local initialization
+ pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
+ // Use local
+ value = local;
+ }
+
+ break;
+ }
+
+ if (value != null)
+ {
+ var objectType = context.ParentNodes().OfType().FirstOrDefault()?.Type.GetClrType();
+ return new XamlManipulationGroupNode(pa)
+ {
+ Children =
+ {
+ pa,
+ new AvaloniaNameScopeRegistrationXamlIlNode(value, objectType)
+ }
+ };
+ }
+ }
+ else if (pa.Property.CustomAttributes.Select(attr => attr.Type).Intersect(context.Configuration.TypeMappings.DeferredContentPropertyAttributes).Any())
+ {
+ pa.Values[pa.Values.Count - 1] =
+ new NestedScopeMetadataNode(pa.Values[pa.Values.Count - 1]);
+ }
+ }
+
+ return node;
+ }
+ }
+
+ class NestedScopeMetadataNode : XamlValueWithSideEffectNodeBase
+ {
+ public NestedScopeMetadataNode(IXamlAstValueNode value) : base(value, value)
+ {
+ }
+ }
+
+ class AvaloniaNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
+ {
+ public IXamlAstValueNode Name { get; set; }
+ public IXamlType TargetType { get; }
+
+ public AvaloniaNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name)
+ {
+ TargetType = targetType;
+ Name = name;
+ }
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ => Name = (IXamlAstValueNode)Name.Visit(visitor);
+ }
+
+ class AvaloniaNameScopeRegistrationXamlIlNodeEmitter : IXamlAstLocalsNodeEmitter
+ {
+ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals context, IXamlILEmitter codeGen)
+ {
+ if (node is AvaloniaNameScopeRegistrationXamlIlNode registration)
+ {
+
+ var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+ f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
+
+ using (var targetLoc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object))
+ {
+
+ codeGen
+ // var target = {pop}
+ .Stloc(targetLoc.Local)
+ // _context.NameScope.Register(Name, target)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(scopeField);
+
+ context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
+
+ codeGen
+ .Ldloc(targetLoc.Local)
+ .EmitCall(context.GetAvaloniaTypes().INameScopeRegister, true);
+ }
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ return default;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
new file mode 100644
index 00000000000..de2c0eab962
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
@@ -0,0 +1,75 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+
+using XamlParseException = XamlX.XamlParseException;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
+ {
+ public bool CompileBindingsByDefault { get; set; }
+
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlCompileBindingsNode)
+ {
+ return node;
+ }
+
+ if (node is XamlAstObjectNode obj)
+ {
+ foreach (var item in obj.Children)
+ {
+ if (item is XamlAstXmlDirective directive)
+ {
+ if (directive.Namespace == XamlNamespaces.Xaml2006
+ && directive.Name == "CompileBindings"
+ && directive.Values.Count == 1)
+ {
+ if (!(directive.Values[0] is XamlAstTextNode text
+ && bool.TryParse(text.Text, out var compileBindings)))
+ {
+ throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+ }
+
+ obj.Children.Remove(directive);
+
+ return new AvaloniaXamlIlCompileBindingsNode(obj, compileBindings);
+ }
+ }
+ }
+ }
+
+ // Convert the tag to either a CompiledBinding or ReflectionBinding tag.
+
+ if (node is XamlAstXmlTypeReference tref
+ && tref.Name == "Binding"
+ && tref.XmlNamespace == "https://github.com/avaloniaui")
+ {
+ tref.IsMarkupExtension = true;
+
+ var compileBindings = context.ParentNodes()
+ .OfType()
+ .FirstOrDefault()
+ ?.CompileBindings ?? CompileBindingsByDefault;
+
+ tref.Name = compileBindings ? "CompiledBinding" : "ReflectionBinding";
+ }
+ return node;
+ }
+ }
+
+ internal class AvaloniaXamlIlCompileBindingsNode : XamlValueWithSideEffectNodeBase
+ {
+ public AvaloniaXamlIlCompileBindingsNode(IXamlAstValueNode value, bool compileBindings)
+ : base(value, value)
+ {
+ CompileBindings = compileBindings;
+ }
+
+ public bool CompileBindings { get; }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
similarity index 64%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
index 74f5c29f6a9..9219a39ddc4 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
@@ -1,14 +1,14 @@
using System.Linq;
-using XamlIl.Ast;
-using XamlIl.Transform;
+using XamlX.Ast;
+using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer
+ class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstClrProperty prop)
+ if (node is XamlAstClrProperty prop)
{
var n = prop.Name + "Property";
var field =
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
new file mode 100644
index 00000000000..7944d8b5692
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Avalonia.Markup.Parsers;
+using Avalonia.Utilities;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+using XamlParseException = XamlX.XamlParseException;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlBindingPathParser : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+ {
+ var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding);
+
+ if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText)
+ {
+ var reader = new CharacterReader(bindingPathText.Text.AsSpan());
+ var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
+
+ if (convertedNode != null)
+ {
+ nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
+ }
+
+ binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
+ }
+ else
+ {
+ var bindingPathAssignment = binding.Children.OfType()
+ .FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path");
+
+ if (bindingPathAssignment != null && bindingPathAssignment.Values[0] is XamlAstTextNode pathValue)
+ {
+ var reader = new CharacterReader(pathValue.Text.AsSpan());
+ var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
+
+ if (convertedNode != null)
+ {
+ nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
+ }
+
+ bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
+ }
+ }
+ }
+
+ return node;
+ }
+
+ private static BindingExpressionGrammar.INode ConvertLongFormPropertiesToBindingExpressionNode(
+ AstTransformationContext context,
+ XamlAstObjectNode binding)
+ {
+ BindingExpressionGrammar.INode convertedNode = null;
+
+ var syntheticCompiledBindingProperties = binding.Children.OfType()
+ .Where(v => v.Property is AvaloniaSyntheticCompiledBindingProperty)
+ .ToList();
+
+ var elementNameProperty = syntheticCompiledBindingProperties
+ .FirstOrDefault(v =>
+ v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+ && prop.Name == SyntheticCompiledBindingPropertyName.ElementName);
+
+ var sourceProperty = syntheticCompiledBindingProperties
+ .FirstOrDefault(v =>
+ v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+ && prop.Name == SyntheticCompiledBindingPropertyName.Source);
+
+ var relativeSourceProperty = syntheticCompiledBindingProperties
+ .FirstOrDefault(v =>
+ v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+ && prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource);
+
+ if (elementNameProperty?.Values[0] is XamlAstTextNode elementName)
+ {
+ convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text };
+ }
+ else if (elementNameProperty != null)
+ {
+ throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
+ }
+
+ if (sourceProperty?.Values[0] != null)
+ {
+ if (convertedNode != null)
+ {
+ throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+ }
+
+ convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]);
+ }
+
+ if (GetRelativeSourceObjectFromAssignment(
+ context,
+ relativeSourceProperty,
+ out var relativeSourceObject))
+ {
+ if (convertedNode != null)
+ {
+ throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+ }
+
+ var mode = relativeSourceObject.Children
+ .OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode")
+ ?.Values[0] is XamlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null;
+ if (relativeSourceObject.Arguments.Count == 0 && mode == null)
+ {
+ mode = "FindAncestor";
+ }
+
+ if (mode == "FindAncestor")
+ {
+ var ancestorLevel = relativeSourceObject.Children
+ .OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "FindAncestor")
+ ?.Values[0] is XamlAstTextNode ancestorLevelText ? int.Parse(ancestorLevelText.Text) - 1 : 0;
+
+ var treeType = relativeSourceObject.Children
+ .OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
+ ?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
+
+ var ancestorTypeName = relativeSourceObject.Children
+ .OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
+ ?.Values[0] as XamlAstTextNode;
+
+ IXamlType ancestorType = null;
+ if (ancestorTypeName is null)
+ {
+ if (treeType == "Visual")
+ {
+ throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
+ }
+ else if (treeType == "Logical")
+ {
+ var styledElementType = context.GetAvaloniaTypes().StyledElement;
+ ancestorType = context
+ .ParentNodes()
+ .OfType()
+ .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
+ .ElementAtOrDefault(ancestorLevel)
+ ?.Type.GetClrType();
+
+ if (ancestorType is null)
+ {
+ throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
+ }
+ }
+ }
+ else
+ {
+ ancestorType = TypeReferenceResolver.ResolveType(
+ context,
+ ancestorTypeName.Text,
+ false,
+ ancestorTypeName,
+ true).GetClrType();
+ }
+
+ if (treeType == "Visual")
+ {
+ convertedNode = new VisualAncestorBindingExpressionNode
+ {
+ Type = ancestorType,
+ Level = ancestorLevel
+ };
+ }
+ else if (treeType == "Logical")
+ {
+ convertedNode = new LogicalAncestorBindingExpressionNode
+ {
+ Type = ancestorType,
+ Level = ancestorLevel
+ };
+ }
+ else
+ {
+ throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
+ }
+ }
+ else if (mode == "DataContext")
+ {
+ convertedNode = null;
+ }
+ else if (mode == "Self")
+ {
+ convertedNode = new BindingExpressionGrammar.SelfNode();
+ }
+ else if (mode == "TemplatedParent")
+ {
+ var parentType = context.ParentNodes().OfType()
+ .FirstOrDefault(x =>
+ x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
+ ?.TargetType.GetClrType();
+
+ if (parentType is null)
+ {
+ throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
+ }
+
+ convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
+ }
+ else
+ {
+ throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
+ }
+ }
+
+ if (elementNameProperty != null)
+ {
+ binding.Children.Remove(elementNameProperty);
+ }
+ if (sourceProperty != null)
+ {
+ binding.Children.Remove(sourceProperty);
+ }
+ if (relativeSourceProperty != null)
+ {
+ binding.Children.Remove(relativeSourceProperty);
+ }
+
+ return convertedNode;
+ }
+
+ private static bool GetRelativeSourceObjectFromAssignment(
+ AstTransformationContext context,
+ XamlAstXamlPropertyValueNode relativeSourceProperty,
+ out XamlAstObjectNode relativeSourceObject)
+ {
+ relativeSourceObject = null;
+ if (relativeSourceProperty is null)
+ {
+ return false;
+ }
+
+ if (relativeSourceProperty.Values[0] is XamlMarkupExtensionNode me)
+ {
+ if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
+ {
+ throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
+ }
+
+ relativeSourceObject = (XamlAstObjectNode)me.Value;
+ return true;
+ }
+
+ if (relativeSourceProperty.Values[0] is XamlAstObjectNode on)
+ {
+ if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
+ {
+ throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
+ }
+
+ relativeSourceObject = on;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ class ParsedBindingPathNode : XamlAstNode, IXamlAstValueNode
+ {
+ public ParsedBindingPathNode(IXamlLineInfo lineInfo, IXamlType compiledBindingType, IList path)
+ : base(lineInfo)
+ {
+ Type = new XamlAstClrTypeReference(lineInfo, compiledBindingType, false);
+ Path = path;
+ }
+
+ public IXamlAstTypeReference Type { get; }
+
+ public IList Path { get; }
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ {
+ for (int i = 0; i < Path.Count; i++)
+ {
+ if (Path[i] is IXamlAstNode ast)
+ {
+ Path[i] = (BindingExpressionGrammar.INode)ast.Visit(visitor);
+ }
+ }
+ }
+ }
+
+ class VisualAncestorBindingExpressionNode : BindingExpressionGrammar.INode
+ {
+ public IXamlType Type { get; set; }
+ public int Level { get; set; }
+ }
+
+ class LogicalAncestorBindingExpressionNode : BindingExpressionGrammar.INode
+ {
+ public IXamlType Type { get; set; }
+ public int Level { get; set; }
+ }
+
+ class TemplatedParentBindingExpressionNode : BindingExpressionGrammar.INode
+ {
+ public IXamlType Type { get; set; }
+ }
+
+ class RawSourceBindingExpressionNode : XamlAstNode, BindingExpressionGrammar.INode
+ {
+ public RawSourceBindingExpressionNode(IXamlAstValueNode rawSource)
+ : base(rawSource)
+ {
+ RawSource = rawSource;
+ }
+
+ public IXamlAstValueNode RawSource { get; private set; }
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ {
+ RawSource = (IXamlAstValueNode)RawSource.Visit(visitor);
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
new file mode 100644
index 00000000000..fc320846877
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlBindingPathTransformer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstConstructableObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+ {
+ IXamlType startType;
+ var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
+ if (parentDataContextNode is null)
+ {
+ throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
+ }
+
+ startType = parentDataContextNode.DataContextType;
+
+ XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType);
+ }
+
+ return node;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
new file mode 100644
index 00000000000..70bd15ea122
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
@@ -0,0 +1,24 @@
+using System.Linq;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlCompiledBindingsMetadataRemover : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ while (true)
+ {
+ if (node is NestedScopeMetadataNode nestedScope)
+ node = nestedScope.Value;
+ else if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
+ node = dataContextType.Value;
+ else if (node is AvaloniaXamlIlCompileBindingsNode compileBindings)
+ node = compileBindings.Value;
+ else
+ return node;
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
similarity index 53%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
index 7e8f296b443..35e2624ff9b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
@@ -1,15 +1,17 @@
using System.Linq;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.TypeSystem;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlIlAstTransformer
+ class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstObjectNode on && on.Arguments.Count == 0)
+ if (node is XamlAstObjectNode on && on.Arguments.Count == 0)
{
var ctors = on.Type.GetClrType().Constructors;
if (!ctors.Any(c => c.IsPublic && !c.IsStatic && c.Parameters.Count == 0))
@@ -27,20 +29,20 @@ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlA
return node;
}
- class InjectServiceProviderNode : XamlIlAstNode, IXamlIlAstValueNode,IXamlIlAstNodeNeedsParentStack,
- IXamlIlAstEmitableNode
+ class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
+ IXamlAstEmitableNode
{
- public InjectServiceProviderNode(IXamlIlType type, IXamlIlLineInfo lineInfo) : base(lineInfo)
+ public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
{
- Type = new XamlIlAstClrTypeReference(lineInfo, type, false);
+ Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
- public IXamlIlAstTypeReference Type { get; }
+ public IXamlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
- public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldloc(context.ContextLocal);
- return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
+ return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
similarity index 70%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
index 40386924c32..f95d086bf6c 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
@@ -1,29 +1,29 @@
using System.Linq;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.TypeSystem;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlIlAstTransformer
+ class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (!(node is XamlIlAstObjectNode on
+ if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate"))
return node;
- var tt = on.Children.OfType().FirstOrDefault(ch =>
+ var tt = on.Children.OfType().FirstOrDefault(ch =>
ch.Property.GetClrProperty().Name == "TargetType");
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
// Deja vu. I've just been in this place before
return node;
- IXamlIlAstTypeReference targetType;
+ IXamlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
- if ((tt?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn))
+ if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn))
{
targetType = tn.Value;
}
@@ -33,11 +33,11 @@ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlA
.FirstOrDefault();
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
targetType = parentScope.TargetType;
- else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlIlAstObjectNode directParentNode
+ else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()))
targetType = directParentNode.Type;
else
- targetType = new XamlIlAstClrTypeReference(node,
+ targetType = new XamlAstClrTypeReference(node,
templatableBaseType, false);
}
@@ -48,9 +48,9 @@ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlA
}
}
- class AvaloniaXamlIlTargetTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
+ class AvaloniaXamlIlTargetTypeMetadataNode : XamlValueWithSideEffectNodeBase
{
- public IXamlIlAstTypeReference TargetType { get; set; }
+ public IXamlAstTypeReference TargetType { get; set; }
public ScopeTypes ScopeType { get; }
public enum ScopeTypes
@@ -60,7 +60,7 @@ public enum ScopeTypes
Transitions
}
- public AvaloniaXamlIlTargetTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlAstTypeReference targetType,
+ public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,
ScopeTypes type)
: base(value, value)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
new file mode 100644
index 00000000000..241976241f2
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -0,0 +1,197 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
+ {
+ private const string AvaloniaNs = "https://github.com/avaloniaui";
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlDataContextTypeMetadataNode)
+ {
+ // We've already resolved the data context type for this node.
+ return node;
+ }
+
+ if (node is XamlAstConstructableObjectNode on)
+ {
+ AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
+ AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
+ bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate);
+
+ for (int i = 0; i < on.Children.Count; ++i)
+ {
+ var child = on.Children[i];
+ if (child is XamlAstXmlDirective directive)
+ {
+ if (directive.Namespace == XamlNamespaces.Xaml2006
+ && directive.Name == "DataType"
+ && directive.Values.Count == 1)
+ {
+ on.Children.RemoveAt(i);
+ i--;
+ if (directive.Values[0] is XamlAstTextNode text)
+ {
+ directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on,
+ TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);
+ }
+ else
+ {
+ throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
+ }
+ }
+ }
+ else if (child is XamlPropertyAssignmentNode pa)
+ {
+ if (pa.Property.Name == "DataContext"
+ && pa.Property.DeclaringType.Equals(context.GetAvaloniaTypes().StyledElement)
+ && pa.Values[0] is XamlMarkupExtensionNode ext
+ && ext.Value is XamlAstConstructableObjectNode obj)
+ {
+ inferredDataContextTypeNode = ParseDataContext(context, on, obj);
+ }
+ else if(isDataTemplate
+ && pa.Property.Name == "DataType"
+ && pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
+ {
+ inferredDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, dataTypeNode.Value.GetClrType());
+ }
+ }
+ }
+
+ // If there is no x:DataType directive,
+ // do more specialized inference
+ if (directiveDataContextTypeNode is null)
+ {
+ if (isDataTemplate && inferredDataContextTypeNode is null)
+ {
+ // Infer data type from collection binding on a control that displays items.
+ var parentObject = context.ParentNodes().OfType().FirstOrDefault();
+ if (parentObject != null && context.GetAvaloniaTypes().IItemsPresenterHost.IsDirectlyAssignableFrom(parentObject.Type.GetClrType()))
+ {
+ inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject);
+ }
+ else
+ {
+ inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ }
+ }
+ }
+
+ return directiveDataContextTypeNode ?? inferredDataContextTypeNode ?? node;
+ }
+
+ return node;
+ }
+
+ private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode parentObject)
+ {
+ var parentItemsValue = parentObject
+ .Children.OfType()
+ .FirstOrDefault(pa => pa.Property.Name == "Items")
+ ?.Values[0];
+ if (parentItemsValue is null)
+ {
+ // We can't infer the collection type and the currently calculated type is definitely wrong.
+ // Notify the user that we were unable to infer the data context type if they use a compiled binding.
+ return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ }
+
+ IXamlType itemsCollectionType = null;
+ if (context.GetAvaloniaTypes().IBinding.IsAssignableFrom(parentItemsValue.Type.GetClrType()))
+ {
+ if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)
+ && parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding)
+ {
+ var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType().FirstOrDefault();
+ if (parentItemsDataContext != null)
+ {
+ itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, parentItemsDataContext.DataContextType);
+ }
+ }
+ }
+ else
+ {
+ itemsCollectionType = parentItemsValue.Type.GetClrType();
+ }
+
+ if (itemsCollectionType != null)
+ {
+ foreach (var i in GetAllInterfacesIncludingSelf(itemsCollectionType))
+ {
+ if (i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
+ {
+ return new AvaloniaXamlIlDataContextTypeMetadataNode(on, i.GenericArguments[0]);
+ }
+ }
+ }
+ // We can't infer the collection type and the currently calculated type is definitely wrong.
+ // Notify the user that we were unable to infer the data context type if they use a compiled binding.
+ return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ }
+
+ private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj)
+ {
+ var bindingType = context.GetAvaloniaTypes().IBinding;
+ if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
+ {
+ return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType());
+ }
+ else if (obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+ {
+ IXamlType startType;
+ var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
+ if (parentDataContextNode is null)
+ {
+ throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
+ }
+
+ startType = parentDataContextNode.DataContextType;
+
+ var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startType);
+ return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
+ }
+
+ return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+ }
+
+ private static IEnumerable GetAllInterfacesIncludingSelf(IXamlType type)
+ {
+ if (type.IsInterface)
+ yield return type;
+
+ foreach (var i in type.GetAllInterfaces())
+ yield return i;
+ }
+ }
+
+ [DebuggerDisplay("DataType = {DataContextType}")]
+ class AvaloniaXamlIlDataContextTypeMetadataNode : XamlValueWithSideEffectNodeBase
+ {
+ public virtual IXamlType DataContextType { get; }
+
+ public AvaloniaXamlIlDataContextTypeMetadataNode(IXamlAstValueNode value, IXamlType targetType)
+ : base(value, value)
+ {
+ DataContextType = targetType;
+ }
+ }
+
+ [DebuggerDisplay("DataType = Unknown")]
+ class AvaloniaXamlIlUninferrableDataContextMetadataNode : AvaloniaXamlIlDataContextTypeMetadataNode
+ {
+ public AvaloniaXamlIlUninferrableDataContextMetadataNode(IXamlAstValueNode value)
+ : base(value, null)
+ {
+ }
+
+ public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value);
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
similarity index 68%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
index 9cc4c5cf11f..3096be522fd 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
@@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Linq;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlDesignPropertiesTransformer : IXamlIlAstTransformer
+ class AvaloniaXamlIlDesignPropertiesTransformer : IXamlAstTransformer
{
public bool IsDesignMode { get; set; }
@@ -17,14 +17,14 @@ class AvaloniaXamlIlDesignPropertiesTransformer : IXamlIlAstTransformer
};
private const string AvaloniaNs = "https://github.com/avaloniaui";
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstObjectNode on)
+ if (node is XamlAstObjectNode on)
{
for (var c=0; c()
+ .FirstOrDefault();
+ if(parentScope == null)
+ throw new XamlX.XamlParseException("No target type scope found for property path", text);
+ if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
+ throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
+
+
+ IEnumerable parsed;
+ try
+ {
+ parsed = PropertyPathGrammar.Parse(text.Text);
+ }
+ catch (Exception e)
+ {
+ throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
+ }
+
+ var elements = new List();
+ IXamlType currentType = parentScope.TargetType.GetClrType();
+
+
+ var expectProperty = true;
+ var expectCast = true;
+ var expectTraversal = false;
+ var types = context.GetAvaloniaTypes();
+
+ IXamlType GetType(string ns, string name)
+ {
+ return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
+ text, true).GetClrType();
+ }
+
+ void HandleProperty(string name, string typeNamespace, string typeName)
+ {
+ if(!expectProperty || currentType == null)
+ throw new XamlX.XamlParseException("Unexpected property node", text);
+
+ var propertySearchType =
+ typeName != null ? GetType(typeNamespace, typeName) : currentType;
+
+ IXamlIlPropertyPathElementNode prop = null;
+ var avaloniaPropertyFieldName = name + "Property";
+ var avaloniaPropertyField = propertySearchType.GetAllFields().FirstOrDefault(f =>
+ f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);
+ if (avaloniaPropertyField != null)
+ {
+ prop = new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
+ XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, types, text));
+ }
+ else
+ {
+ var clrProperty = propertySearchType.GetAllProperties().FirstOrDefault(p => p.Name == name);
+ prop = new XamlIClrPropertyPathElementNode(clrProperty);
+ }
+
+ if (prop == null)
+ throw new XamlX.XamlParseException(
+ $"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
+ text);
+
+ currentType = prop.Type;
+ elements.Add(prop);
+ expectProperty = false;
+ expectTraversal = expectCast = true;
+ }
+
+ foreach (var ge in parsed)
+ {
+ if (ge is PropertyPathGrammar.ChildTraversalSyntax)
+ {
+ if (!expectTraversal)
+ throw new XamlX.XamlParseException("Unexpected child traversal .", text);
+ elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
+ expectTraversal = expectCast = false;
+ expectProperty = true;
+ }
+ else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
+ {
+ if(!expectCast)
+ throw new XamlX.XamlParseException("Unexpected cast node", text);
+ currentType = GetType(ets.TypeNamespace, ets.TypeName);
+ elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
+ expectProperty = false;
+ expectCast = expectTraversal = true;
+ }
+ else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
+ {
+ if(!expectCast)
+ throw new XamlX.XamlParseException("Unexpected cast node", text);
+ //TODO: Check if cast can be done
+ currentType = GetType(cts.TypeNamespace, cts.TypeName);
+ elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
+ expectProperty = false;
+ expectCast = expectTraversal = true;
+ }
+ else if (ge is PropertyPathGrammar.PropertySyntax ps)
+ {
+ HandleProperty(ps.Name, null, null);
+ }
+ else if (ge is PropertyPathGrammar.TypeQualifiedPropertySyntax tqps)
+ {
+ HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
+ }
+ else
+ throw new XamlX.XamlParseException("Unexpected node " + ge, text);
+
+ }
+ var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
+ if (propertyPathNode.Type == null)
+ throw new XamlX.XamlParseException("Unexpected end of the property path", text);
+ pv.Values[0] = propertyPathNode;
+ }
+
+ return node;
+ }
+
+ interface IXamlIlPropertyPathElementNode
+ {
+ void Emit(XamlEmitContext context, IXamlILEmitter codeGen);
+ IXamlType Type { get; }
+ }
+
+ class XamlIlChildTraversalPropertyPathElementNode : IXamlIlPropertyPathElementNode
+ {
+ public void Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ => codeGen.EmitCall(
+ context.GetAvaloniaTypes()
+ .PropertyPathBuilder.FindMethod(m => m.Name == "ChildTraversal"));
+
+ public IXamlType Type => null;
+ }
+
+ class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlPropertyPathElementNode
+ {
+ private readonly IXamlField _field;
+
+ public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
+ {
+ _field = field;
+ Type = propertyType;
+ }
+
+ public void Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ => codeGen
+ .Ldsfld(_field)
+ .EmitCall(context.GetAvaloniaTypes()
+ .PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
+
+ public IXamlType Type { get; }
+ }
+
+ class XamlIClrPropertyPathElementNode : IXamlIlPropertyPathElementNode
+ {
+ private readonly IXamlProperty _property;
+
+ public XamlIClrPropertyPathElementNode(IXamlProperty property)
+ {
+ _property = property;
+ }
+
+ public void Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ context.Configuration.GetExtra()
+ .Emit(context, codeGen, _property);
+
+ codeGen.EmitCall(context.GetAvaloniaTypes()
+ .PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
+ }
+
+ public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
+ }
+
+ class XamlIlCastPropertyPathElementNode : IXamlIlPropertyPathElementNode
+ {
+ private readonly IXamlType _type;
+ private readonly bool _ensureType;
+
+ public XamlIlCastPropertyPathElementNode(IXamlType type, bool ensureType)
+ {
+ _type = type;
+ _ensureType = ensureType;
+ }
+
+ public void Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ codeGen
+ .Ldtype(_type)
+ .EmitCall(context.GetAvaloniaTypes()
+ .PropertyPathBuilder.FindMethod(m => m.Name == (_ensureType ? "EnsureType" : "Cast")));
+ }
+
+ public IXamlType Type => _type;
+ }
+
+ class XamlIlPropertyPathNode : XamlAstNode, IXamlIlPropertyPathNode, IXamlAstEmitableNode
+ {
+ private readonly List _elements;
+ private readonly AvaloniaXamlIlWellKnownTypes _types;
+
+ public XamlIlPropertyPathNode(IXamlLineInfo lineInfo,
+ List elements,
+ AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
+ {
+ _elements = elements;
+ _types = types;
+ Type = new XamlAstClrTypeReference(this, types.PropertyPath, false);
+ }
+
+ public IXamlAstTypeReference Type { get; }
+ public IXamlType PropertyType => _elements.LastOrDefault()?.Type;
+ public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ codeGen
+ .Newobj(_types.PropertyPathBuilder.FindConstructor());
+ foreach(var e in _elements)
+ e.Emit(context, codeGen);
+ codeGen.EmitCall(_types.PropertyPathBuilder.FindMethod(m => m.Name == "Build"));
+ return XamlILNodeEmitResult.Type(0, _types.PropertyPath);
+ }
+ }
+ }
+
+ interface IXamlIlPropertyPathNode : IXamlAstValueNode
+ {
+ IXamlType PropertyType { get; }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
new file mode 100644
index 00000000000..c0ac841b7f4
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
@@ -0,0 +1,49 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+#nullable enable
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlResolveByNameMarkupExtensionReplacer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!(node is XamlAstXamlPropertyValueNode propertyValueNode)) return node;
+
+ if (!(propertyValueNode.Property is XamlAstClrProperty clrProperty)) return node;
+
+ IEnumerable attributes = propertyValueNode.Property.GetClrProperty().CustomAttributes;
+
+ if (propertyValueNode.Property is XamlAstClrProperty referenceNode &&
+ referenceNode.Getter != null)
+ {
+ attributes = attributes.Concat(referenceNode.Getter.CustomAttributes);
+ }
+
+ if (attributes.All(attribute => attribute.Type.FullName != "Avalonia.Controls.ResolveByNameAttribute"))
+ return node;
+
+ if (propertyValueNode.Values.Count != 1 || !(propertyValueNode.Values.First() is XamlAstTextNode))
+ return node;
+
+ var newNode = new XamlAstObjectNode(
+ propertyValueNode.Values[0],
+ new XamlAstClrTypeReference(propertyValueNode.Values[0],
+ context.GetAvaloniaTypes().ResolveByNameExtension, true))
+ {
+ Arguments = new List { propertyValueNode.Values[0] }
+ };
+
+ if (XamlTransformHelpers.TryConvertMarkupExtension(context, newNode, out var extensionNode))
+ {
+ propertyValueNode.Values[0] = extensionNode;
+ }
+
+ return node;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
new file mode 100644
index 00000000000..0513c78a7a1
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.IL;
+using XamlX.Emit;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlRootObjectScope : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!context.ParentNodes().Any()
+ && node is XamlValueWithManipulationNode mnode)
+ {
+ mnode.Manipulation = new XamlManipulationGroupNode(mnode,
+ new[]
+ {
+ mnode.Manipulation,
+ new HandleRootObjectScopeNode(mnode, context.GetAvaloniaTypes())
+ });
+ }
+ return node;
+ }
+ class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
+ {
+ private readonly AvaloniaXamlIlWellKnownTypes _types;
+
+ public HandleRootObjectScopeNode(IXamlLineInfo lineInfo,
+ AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
+ {
+ _types = types;
+ }
+ }
+ internal class Emitter : IXamlILAstNodeEmitter
+ {
+ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ if (!(node is HandleRootObjectScopeNode))
+ {
+ return null;
+ }
+ var types = context.GetAvaloniaTypes();
+
+ var next = codeGen.DefineLabel();
+ var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+ f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
+ using (var local = codeGen.LocalsPool.GetLocal(types.StyledElement))
+ {
+ codeGen
+ .Isinst(types.StyledElement)
+ .Dup()
+ .Stloc(local.Local)
+ .Brfalse(next)
+ .Ldloc(local.Local)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(scopeField)
+ .EmitCall(types.NameScopeSetNameScope, true)
+ .MarkLabel(next)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(scopeField)
+ .EmitCall(types.INameScopeComplete, true);
+ }
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
similarity index 69%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
index d5114244cf9..b81d25d6132 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
@@ -3,41 +3,45 @@
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.Transform.Transformers;
-using XamlIl.TypeSystem;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlSelectorTransformer : IXamlIlAstTransformer
+ using XamlParseException = XamlX.XamlParseException;
+ using XamlLoadException = XamlX.XamlLoadException;
+ class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
+ if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
return node;
- var pn = on.Children.OfType()
+ var pn = on.Children.OfType()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (pn == null)
return node;
if (pn.Values.Count != 1)
- throw new XamlIlParseException("Selector property should should have exactly one value", node);
+ throw new XamlParseException("Selector property should should have exactly one value", node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
- if (!(pn.Values[0] is XamlIlAstTextNode tn))
- throw new XamlIlParseException("Selector property should be a text node", node);
+ if (!(pn.Values[0] is XamlAstTextNode tn))
+ throw new XamlParseException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
XamlIlSelectorNode Create(IEnumerable syntax,
- Func typeResolver)
+ Func typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode results = null;
@@ -63,18 +67,18 @@ XamlIlSelectorNode Create(IEnumerable syntax,
var type = result?.TargetType;
if (type == null)
- throw new XamlIlParseException("Property selectors must be applied to a type.", node);
+ throw new XamlParseException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
- throw new XamlIlParseException($"Cannot find '{property.Property}' on '{type}", node);
+ throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
- if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context,
- new XamlIlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
+ if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
+ new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
- throw new XamlIlParseException(
+ throw new XamlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
@@ -100,7 +104,7 @@ XamlIlSelectorNode Create(IEnumerable syntax,
result = initialNode;
break;
default:
- throw new XamlIlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
+ throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
@@ -119,15 +123,15 @@ XamlIlSelectorNode Create(IEnumerable syntax,
}
catch (Exception e)
{
- throw new XamlIlParseException("Unable to parse selector: " + e.Message, node);
+ throw new XamlParseException("Unable to parse selector: " + e.Message, node);
}
var selector = Create(parsed, (p, n)
- => XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
+ => TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
- new XamlIlAstClrTypeReference(selector, selector.TargetType, false),
+ new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
@@ -135,32 +139,32 @@ XamlIlSelectorNode Create(IEnumerable syntax,
- abstract class XamlIlSelectorNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode
+ abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode
{
protected XamlIlSelectorNode Previous { get; }
- public abstract IXamlIlType TargetType { get; }
+ public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
- IXamlIlLineInfo info = null,
- IXamlIlType selectorType = null) : base(info ?? previous)
+ IXamlLineInfo info = null,
+ IXamlType selectorType = null) : base(info ?? previous)
{
Previous = previous;
- Type = selectorType == null ? previous.Type : new XamlIlAstClrTypeReference(this, selectorType, false);
+ Type = selectorType == null ? previous.Type : new XamlAstClrTypeReference(this, selectorType, false);
}
- public IXamlIlAstTypeReference Type { get; }
+ public IXamlAstTypeReference Type { get; }
- public virtual XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public virtual XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
- return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
+ return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
- protected abstract void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
+ protected abstract void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen);
- protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func method)
+ protected void EmitCall(XamlEmitContext context, IXamlILEmitter codeGen, Func method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
@@ -170,27 +174,27 @@ protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<
class XamlIlSelectorInitialNode : XamlIlSelectorNode
{
- public XamlIlSelectorInitialNode(IXamlIlLineInfo info,
- IXamlIlType selectorType) : base(null, info, selectorType)
+ public XamlIlSelectorInitialNode(IXamlLineInfo info,
+ IXamlType selectorType) : base(null, info, selectorType)
{
}
- public override IXamlIlType TargetType => null;
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) => codeGen.Ldnull();
+ public override IXamlType TargetType => null;
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeSelector : XamlIlSelectorNode
{
public bool Concrete { get; }
- public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlIlType type, bool concrete) : base(previous)
+ public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
- public override IXamlIlType TargetType { get; }
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public override IXamlType TargetType { get; }
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
@@ -217,8 +221,8 @@ public XamlIlStringSelector(XamlIlSelectorNode previous, SelectorType type, stri
}
- public override IXamlIlType TargetType => Previous?.TargetType;
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public override IXamlType TargetType => Previous?.TargetType;
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
@@ -242,8 +246,8 @@ public XamlIlCombinatorSelector(XamlIlSelectorNode previous, SelectorType type)
_type = type;
}
- public override IXamlIlType TargetType => null;
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public override IXamlType TargetType => null;
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
@@ -260,8 +264,8 @@ public XamlIlNotSelector(XamlIlSelectorNode previous, XamlIlSelectorNode argumen
Argument = argument;
}
- public override IXamlIlType TargetType => Previous?.TargetType;
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public override IXamlType TargetType => Previous?.TargetType;
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
context.Emit(Argument, codeGen, Type.GetClrType());
EmitCall(context, codeGen,
@@ -272,22 +276,22 @@ protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,
- IXamlIlProperty property,
- IXamlIlAstValueNode value)
+ IXamlProperty property,
+ IXamlAstValueNode value)
: base(previous)
{
Property = property;
Value = value;
}
- public IXamlIlProperty Property { get; set; }
- public IXamlIlAstValueNode Value { get; set; }
+ public IXamlProperty Property { get; set; }
+ public IXamlAstValueNode Value { get; set; }
- public override IXamlIlType TargetType => Previous?.TargetType;
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ public override IXamlType TargetType => Previous?.TargetType;
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
- throw new XamlIlLoadException(
+ throw new XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@@ -302,7 +306,7 @@ protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen
class XamlIlOrSelectorNode : XamlIlSelectorNode
{
List _selectors = new List();
- public XamlIlOrSelectorNode(IXamlIlLineInfo info, IXamlIlType selectorType) : base(null, info, selectorType)
+ public XamlIlOrSelectorNode(IXamlLineInfo info, IXamlType selectorType) : base(null, info, selectorType)
{
}
@@ -311,11 +315,11 @@ public void Add(XamlIlSelectorNode node)
_selectors.Add(node);
}
- public override IXamlIlType TargetType
+ public override IXamlType TargetType
{
get
{
- IXamlIlType result = null;
+ IXamlType result = null;
foreach (var selector in _selectors)
{
@@ -340,10 +344,10 @@ public override IXamlIlType TargetType
}
}
- protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
+ protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
- throw new XamlIlLoadException("Invalid selector count", this);
+ throw new XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
new file mode 100644
index 00000000000..e816265422f
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Data.Core;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ using XamlParseException = XamlX.XamlParseException;
+ using XamlLoadException = XamlX.XamlLoadException;
+ class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!(node is XamlAstObjectNode on
+ && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
+ return node;
+
+ var parent = context.ParentNodes().OfType()
+ .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
+
+ if (parent == null)
+ throw new XamlParseException(
+ "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
+ var selectorProperty = parent.Children.OfType()
+ .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
+ if (selectorProperty == null)
+ throw new XamlParseException(
+ "Can not find parent Style Selector", node);
+ var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
+ if (selector?.TargetType == null)
+ throw new XamlParseException(
+ "Can not resolve parent Style Selector type", node);
+
+ IXamlType propType = null;
+ var property = @on.Children.OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
+ if (property != null)
+ {
+
+ var propertyName = property.Values.OfType().FirstOrDefault()?.Text;
+ if (propertyName == null)
+ throw new XamlParseException("Setter.Property must be a string", node);
+
+
+ var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
+ new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
+ property.Values = new List {avaloniaPropertyNode};
+ propType = avaloniaPropertyNode.AvaloniaPropertyType;
+ }
+ else
+ {
+ var propertyPath = on.Children.OfType()
+ .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
+ if (propertyPath == null)
+ throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
+ if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
+ && ppn.PropertyType != null)
+ propType = ppn.PropertyType;
+ else
+ throw new XamlX.XamlParseException("Unable to get the property path property type", node);
+ }
+
+ var valueProperty = on.Children
+ .OfType().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
+ if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlAstTextNode)
+ {
+ if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
+ propType, out var converted))
+ throw new XamlParseException(
+ $"Unable to convert property value to {propType.GetFqn()}",
+ valueProperty.Values[0]);
+
+ valueProperty.Property = new SetterValueProperty(valueProperty.Property,
+ on.Type.GetClrType(), propType, context.GetAvaloniaTypes());
+ }
+
+ return node;
+ }
+
+ class SetterValueProperty : XamlAstClrProperty
+ {
+ public SetterValueProperty(IXamlLineInfo line, IXamlType setterType, IXamlType targetType,
+ AvaloniaXamlIlWellKnownTypes types)
+ : base(line, "Value", setterType, null)
+ {
+ Getter = setterType.Methods.First(m => m.Name == "get_Value");
+ var method = setterType.Methods.First(m => m.Name == "set_Value");
+ Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding));
+ Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType));
+ Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType));
+ }
+
+ class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter
+ {
+ private readonly IXamlMethod _method;
+ private readonly IXamlType _type;
+ public IXamlType TargetType { get; }
+ public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
+ public IReadOnlyList Parameters { get; }
+ public void Emit(IXamlILEmitter codegen)
+ {
+ if (_type.IsValueType)
+ codegen.Box(_type);
+ codegen.EmitCall(_method, true);
+ }
+
+ public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type)
+ {
+ _method = method;
+ _type = type;
+ Parameters = new[] {type};
+ TargetType = method.ThisOrFirstParameter();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
similarity index 75%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
index 548f0161d66..d78ceeb918c 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
@@ -1,20 +1,22 @@
using System.Collections.Generic;
using System.Linq;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.TypeSystem;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlIlAstTransformer
+ class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstNamePropertyReference prop
- && prop.TargetType is XamlIlAstClrTypeReference targetRef
- && prop.DeclaringType is XamlIlAstClrTypeReference declaringRef)
+ if (node is XamlAstNamePropertyReference prop
+ && prop.TargetType is XamlAstClrTypeReference targetRef
+ && prop.DeclaringType is XamlAstClrTypeReference declaringRef)
{
// Target and declared type aren't assignable but both inherit from AvaloniaObject
var avaloniaObject = context.Configuration.TypeSystem.FindType("Avalonia.AvaloniaObject");
@@ -70,21 +72,21 @@ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlA
return node;
}
- class AvaloniaAttachedInstanceProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
+ class AvaloniaAttachedInstanceProperty : XamlAstClrProperty, IXamlIlAvaloniaProperty
{
- private readonly XamlIlTransformerConfiguration _config;
- private readonly IXamlIlType _declaringType;
- private readonly IXamlIlType _avaloniaPropertyType;
- private readonly IXamlIlType _avaloniaObject;
- private readonly IXamlIlField _field;
-
- public AvaloniaAttachedInstanceProperty(XamlIlAstNamePropertyReference prop,
- XamlIlTransformerConfiguration config,
- IXamlIlType declaringType,
- IXamlIlType type,
- IXamlIlType avaloniaPropertyType,
- IXamlIlType avaloniaObject,
- IXamlIlField field) : base(prop, prop.Name,
+ private readonly TransformerConfiguration _config;
+ private readonly IXamlType _declaringType;
+ private readonly IXamlType _avaloniaPropertyType;
+ private readonly IXamlType _avaloniaObject;
+ private readonly IXamlField _field;
+
+ public AvaloniaAttachedInstanceProperty(XamlAstNamePropertyReference prop,
+ TransformerConfiguration config,
+ IXamlType declaringType,
+ IXamlType type,
+ IXamlType avaloniaPropertyType,
+ IXamlType avaloniaObject,
+ IXamlField field) : base(prop, prop.Name,
declaringType, null)
@@ -104,11 +106,11 @@ public AvaloniaAttachedInstanceProperty(XamlIlAstNamePropertyReference prop,
Getter = new GetterMethod(this);
}
- public IXamlIlType PropertyType { get; }
+ public IXamlType PropertyType { get; }
- public IXamlIlField AvaloniaProperty => _field;
+ public IXamlField AvaloniaProperty => _field;
- class SetterMethod : IXamlIlPropertySetter
+ class SetterMethod : IXamlPropertySetter, IXamlEmitablePropertySetter
{
private readonly AvaloniaAttachedInstanceProperty _parent;
@@ -118,10 +120,10 @@ public SetterMethod(AvaloniaAttachedInstanceProperty parent)
Parameters = new[] {_parent._avaloniaObject, _parent.PropertyType};
}
- public IXamlIlType TargetType => _parent.DeclaringType;
+ public IXamlType TargetType => _parent.DeclaringType;
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
- public IReadOnlyList Parameters { get; }
- public void Emit(IXamlIlEmitter emitter)
+ public IReadOnlyList Parameters { get; }
+ public void Emit(IXamlILEmitter emitter)
{
var so = _parent._config.WellKnownTypes.Object;
var method = _parent._avaloniaObject
@@ -133,7 +135,7 @@ public void Emit(IXamlIlEmitter emitter)
&& m.Parameters[2].IsEnum
);
if (method == null)
- throw new XamlIlTypeSystemException(
+ throw new XamlTypeSystemException(
"Unable to find SetValue(AvaloniaProperty, object, BindingPriority) on AvaloniaObject");
using (var loc = emitter.LocalsPool.GetLocal(_parent.PropertyType))
emitter
@@ -150,7 +152,7 @@ public void Emit(IXamlIlEmitter emitter)
}
}
- class GetterMethod : IXamlIlCustomEmitMethod
+ class GetterMethod : IXamlCustomEmitMethod
{
public GetterMethod(AvaloniaAttachedInstanceProperty parent)
{
@@ -163,16 +165,19 @@ public GetterMethod(AvaloniaAttachedInstanceProperty parent)
public bool IsPublic => true;
public bool IsStatic => true;
public string Name { get; protected set; }
- public IXamlIlType DeclaringType { get; }
- public IXamlIlMethod MakeGenericMethod(IReadOnlyList typeArguments)
+ public IXamlType DeclaringType { get; }
+ public IXamlMethod MakeGenericMethod(IReadOnlyList typeArguments)
=> throw new System.NotSupportedException();
- public bool Equals(IXamlIlMethod other) =>
+ public bool Equals(IXamlMethod other) =>
other is GetterMethod m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
- public IXamlIlType ReturnType => Parent.PropertyType;
- public IReadOnlyList Parameters { get; }
- public void EmitCall(IXamlIlEmitter emitter)
+ public IXamlType ReturnType => Parent.PropertyType;
+ public IReadOnlyList Parameters { get; }
+
+ public IReadOnlyList CustomAttributes => DeclaringType.CustomAttributes;
+
+ public void EmitCall(IXamlILEmitter emitter)
{
var method = Parent._avaloniaObject
.FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "GetValue"
@@ -180,7 +185,7 @@ public void EmitCall(IXamlIlEmitter emitter)
m.Parameters.Count == 1
&& m.Parameters[0].Equals(Parent._avaloniaPropertyType));
if (method == null)
- throw new XamlIlTypeSystemException(
+ throw new XamlTypeSystemException(
"Unable to find T GetValue(AvaloniaProperty) on AvaloniaObject");
emitter
.Ldsfld(Parent._field)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
new file mode 100644
index 00000000000..154c6a235c2
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlTransformSyntheticCompiledBindingMembers : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstNamePropertyReference prop
+ && prop.TargetType is XamlAstClrTypeReference targetRef
+ && targetRef.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+ {
+ if (prop.Name == "ElementName")
+ {
+ return new AvaloniaSyntheticCompiledBindingProperty(node,
+ SyntheticCompiledBindingPropertyName.ElementName);
+ }
+ else if (prop.Name == "RelativeSource")
+ {
+ return new AvaloniaSyntheticCompiledBindingProperty(node,
+ SyntheticCompiledBindingPropertyName.RelativeSource);
+ }
+ else if (prop.Name == "Source")
+ {
+ return new AvaloniaSyntheticCompiledBindingProperty(node,
+ SyntheticCompiledBindingPropertyName.Source);
+ }
+ }
+
+ return node;
+ }
+ }
+
+ enum SyntheticCompiledBindingPropertyName
+ {
+ ElementName,
+ RelativeSource,
+ Source
+ }
+
+ class AvaloniaSyntheticCompiledBindingProperty : XamlAstNode, IXamlAstPropertyReference
+ {
+ public SyntheticCompiledBindingPropertyName Name { get; }
+
+ public AvaloniaSyntheticCompiledBindingProperty(
+ IXamlLineInfo lineInfo,
+ SyntheticCompiledBindingPropertyName name)
+ : base(lineInfo)
+ {
+ Name = name;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
similarity index 74%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
index c4136f48249..4be2fc6f609 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
@@ -1,17 +1,17 @@
-using XamlIl.Ast;
-using XamlIl.Transform;
+using XamlX.Ast;
+using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlIlAstTransformer
+ class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstObjectNode on)
+ if (node is XamlAstObjectNode on)
{
foreach (var ch in on.Children)
{
- if (ch is XamlIlAstXamlPropertyValueNode pn
+ if (ch is XamlAstXamlPropertyValueNode pn
&& pn.Property.GetClrProperty().Getter?.ReturnType.Equals(context.GetAvaloniaTypes().Transitions) == true)
{
for (var c = 0; c < pn.Values.Count; c++)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
new file mode 100644
index 00000000000..58ea11aa8fb
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -0,0 +1,124 @@
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlWellKnownTypes
+ {
+ public IXamlType AvaloniaObject { get; }
+ public IXamlType IAvaloniaObject { get; }
+ public IXamlType BindingPriority { get; }
+ public IXamlType AvaloniaObjectExtensions { get; }
+ public IXamlType AvaloniaProperty { get; }
+ public IXamlType AvaloniaPropertyT { get; }
+ public IXamlType IBinding { get; }
+ public IXamlMethod AvaloniaObjectBindMethod { get; }
+ public IXamlMethod AvaloniaObjectSetValueMethod { get; }
+ public IXamlType IDisposable { get; }
+ public XamlTypeWellKnownTypes XamlIlTypes { get; }
+ public XamlLanguageTypeMappings XamlIlMappings { get; }
+ public IXamlType Transitions { get; }
+ public IXamlType AssignBindingAttribute { get; }
+ public IXamlType UnsetValueType { get; }
+ public IXamlType StyledElement { get; }
+ public IXamlType NameScope { get; }
+ public IXamlMethod NameScopeSetNameScope { get; }
+ public IXamlType INameScope { get; }
+ public IXamlMethod INameScopeRegister { get; }
+ public IXamlMethod INameScopeComplete { get; }
+ public IXamlType IPropertyInfo { get; }
+ public IXamlType ClrPropertyInfo { get; }
+ public IXamlType PropertyPath { get; }
+ public IXamlType PropertyPathBuilder { get; }
+ public IXamlType IPropertyAccessor { get; }
+ public IXamlType PropertyInfoAccessorFactory { get; }
+ public IXamlType CompiledBindingPathBuilder { get; }
+ public IXamlType CompiledBindingPath { get; }
+ public IXamlType CompiledBindingExtension { get; }
+
+ public IXamlType ResolveByNameExtension { get; }
+
+ public IXamlType DataTemplate { get; }
+ public IXamlType IItemsPresenterHost { get; }
+ public IXamlType ReflectionBindingExtension { get; }
+
+ public IXamlType RelativeSource { get; }
+
+ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
+ {
+ XamlIlTypes = cfg.WellKnownTypes;
+ AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject");
+ IAvaloniaObject = cfg.TypeSystem.GetType("Avalonia.IAvaloniaObject");
+ AvaloniaObjectExtensions = cfg.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
+ AvaloniaProperty = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty");
+ AvaloniaPropertyT = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
+ BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority");
+ IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding");
+ IDisposable = cfg.TypeSystem.GetType("System.IDisposable");
+ Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions");
+ AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
+ AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
+ AvaloniaProperty,
+ IBinding, cfg.WellKnownTypes.Object);
+ UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType");
+ StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
+ INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope");
+ INameScopeRegister = INameScope.GetMethod(
+ new FindMethodMethodSignature("Register", XamlIlTypes.Void,
+ XamlIlTypes.String, XamlIlTypes.Object)
+ {
+ IsStatic = false,
+ DeclaringOnly = true,
+ IsExactMatch = true
+ });
+ INameScopeComplete = INameScope.GetMethod(
+ new FindMethodMethodSignature("Complete", XamlIlTypes.Void)
+ {
+ IsStatic = false,
+ DeclaringOnly = true,
+ IsExactMatch = true
+ });
+ NameScope = cfg.TypeSystem.GetType("Avalonia.Controls.NameScope");
+ NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
+ XamlIlTypes.Void, StyledElement, INameScope)
+ { IsStatic = true });
+ AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
+ false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
+ IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo");
+ ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");
+ PropertyPath = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPath");
+ PropertyPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPathBuilder");
+ IPropertyAccessor = cfg.TypeSystem.GetType("Avalonia.Data.Core.Plugins.IPropertyAccessor");
+ PropertyInfoAccessorFactory = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.PropertyInfoAccessorFactory");
+ CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder");
+ CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath");
+ CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
+ ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
+ DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
+ IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
+ ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
+ RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource");
+ }
+ }
+
+ static class AvaloniaXamlIlWellKnownTypesExtensions
+ {
+ public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstTransformationContext ctx)
+ {
+ if (ctx.TryGetItem(out var rv))
+ return rv;
+ ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
+ return rv;
+ }
+
+ public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext ctx)
+ {
+ if (ctx.TryGetItem(out var rv))
+ return rv;
+ ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
+ return rv;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
similarity index 55%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
index a79a4977efe..1f2508715e0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
@@ -1,17 +1,17 @@
using System.Linq;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class IgnoredDirectivesTransformer : IXamlIlAstTransformer
+ class IgnoredDirectivesTransformer : IXamlAstTransformer
{
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstObjectNode no)
+ if (node is XamlAstObjectNode no)
{
- foreach (var d in no.Children.OfType().ToList())
+ foreach (var d in no.Children.OfType().ToList())
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
similarity index 55%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
index dd64ab542af..f4eb7cf5f92 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
@@ -1,30 +1,29 @@
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- class XNameTransformer : IXamlIlAstTransformer
+ class XNameTransformer : IXamlAstTransformer
{
-
///
/// Converts x:Name directives to regular Name assignments
///
///
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is XamlIlAstObjectNode on)
+ if (node is XamlAstObjectNode on)
{
for (var c =0; c< on.Children.Count;c++)
{
var ch = on.Children[c];
- if (ch is XamlIlAstXmlDirective d
+ if (ch is XamlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006
&& d.Name == "Name")
- on.Children[c] = new XamlIlAstXamlPropertyValueNode(d,
- new XamlIlAstNamePropertyReference(d, on.Type, "Name", on.Type),
+ on.Children[c] = new XamlAstXamlPropertyValueNode(d,
+ new XamlAstNamePropertyReference(d, on.Type, "Name", on.Type),
d.Values);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
similarity index 57%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
index 6fc83cb5a5f..7f1b8caf0df 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
@@ -5,17 +5,36 @@
using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Transform;
-using XamlIl.Transform.Transformers;
-using XamlIl.TypeSystem;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+using XamlX.Emit;
+using XamlX.IL;
+
+using XamlIlEmitContext = XamlX.Emit.XamlEmitContext;
+using IXamlIlAstEmitableNode = XamlX.Emit.IXamlAstEmitableNode;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlAvaloniaPropertyHelper
{
- public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, XamlIlAstClrProperty property)
+ public static bool EmitProvideValueTarget(XamlIlEmitContext context, IXamlILEmitter emitter,
+ XamlAstClrProperty property)
+ {
+ if (Emit(context, emitter, property))
+ return true;
+ var foundClr = property.DeclaringType.Properties.FirstOrDefault(p => p.Name == property.Name);
+ if (foundClr == null)
+ return false;
+ context
+ .Configuration.GetExtra()
+ .Emit(context, emitter, foundClr);
+ return true;
+ }
+
+ public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, XamlAstClrProperty property)
{
if (property is IXamlIlAvaloniaProperty ap)
{
@@ -32,7 +51,7 @@ public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, XamlI
return true;
}
- public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property)
+ public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, IXamlProperty property)
{
var type = (property.Getter ?? property.Setter).DeclaringType;
var name = property.Name + "Property";
@@ -44,16 +63,16 @@ public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXaml
return true;
}
- public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
- string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
+ public static IXamlIlAvaloniaPropertyNode CreateNode(AstTransformationContext context,
+ string propertyName, IXamlAstTypeReference selectorTypeReference, IXamlLineInfo lineInfo)
{
- XamlIlAstNamePropertyReference forgedReference;
+ XamlAstNamePropertyReference forgedReference;
var parser = new PropertyParser();
var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan()));
if(parsedPropertyName.owner == null)
- forgedReference = new XamlIlAstNamePropertyReference(lineInfo, selectorTypeReference,
+ forgedReference = new XamlAstNamePropertyReference(lineInfo, selectorTypeReference,
propertyName, selectorTypeReference);
else
{
@@ -62,101 +81,107 @@ public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationCont
xmlOwner += ":";
xmlOwner += parsedPropertyName.owner;
- var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
+ var tref = TypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
var propertyFieldName = parsedPropertyName.name + "Property";
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
- throw new XamlIlParseException(
+ throw new XamlX.XamlParseException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
var clrProperty =
- ((XamlIlAstClrProperty)new XamlIlPropertyReferenceResolver().Transform(context,
+ ((XamlAstClrProperty)new PropertyReferenceResolver().Transform(context,
forgedReference));
return new XamlIlAvaloniaPropertyNode(lineInfo,
context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
clrProperty);
}
+
+ public static IXamlType GetAvaloniaPropertyType(IXamlField field,
+ AvaloniaXamlIlWellKnownTypes types, IXamlLineInfo lineInfo)
+ {
+ var avaloniaPropertyType = field.FieldType;
+ while (avaloniaPropertyType != null)
+ {
+ if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
+ {
+ return avaloniaPropertyType.GenericArguments[0];
+ }
+
+ avaloniaPropertyType = avaloniaPropertyType.BaseType;
+ }
+
+ throw new XamlX.XamlParseException(
+ $"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty