diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index f74ab07e31..c3b136d41d 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index d627a2bf19..eec21d4423 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -301,14 +301,19 @@ partial class Build : NukeBuild
.Executes(() =>
{
var data = Parameters;
+ var pathToProjectSource = RootDirectory / "samples" / "ControlCatalog.NetCore";
+ var pathToPublish = pathToProjectSource / "bin" / data.Configuration / "publish";
+
+ DotNetPublish(c => c
+ .SetProject(pathToProjectSource / "ControlCatalog.NetCore.csproj")
+ .EnableNoBuild()
+ .SetConfiguration(data.Configuration)
+ .AddProperty("PackageVersion", data.Version)
+ .AddProperty("PublishDir", pathToPublish));
+
Zip(data.ZipCoreArtifacts, data.BinRoot);
Zip(data.ZipNuGetArtifacts, data.NugetRoot);
- Zip(data.ZipTargetControlCatalogDesktopDir,
- GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat(
- GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat(
- GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat(
- GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat(
- GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe")));
+ Zip(data.ZipTargetControlCatalogNetCoreDir, pathToPublish);
});
Target CreateIntermediateNugetPackages => _ => _
diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs
index c76019d9eb..a92c988fbd 100644
--- a/nukebuild/BuildParameters.cs
+++ b/nukebuild/BuildParameters.cs
@@ -58,8 +58,7 @@ public partial class Build
public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; }
- public AbsolutePath ZipSourceControlCatalogDesktopDir { get; }
- public AbsolutePath ZipTargetControlCatalogDesktopDir { get; }
+ public AbsolutePath ZipTargetControlCatalogNetCoreDir { get; }
public BuildParameters(Build b)
@@ -129,9 +128,7 @@ public partial class Build
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
- ZipSourceControlCatalogDesktopDir =
- RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
- ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix);
+ ZipTargetControlCatalogNetCoreDir = ZipRoot / ("ControlCatalog.NetCore-" + FileZipSuffix);
}
string GetVersion()
diff --git a/readme.md b/readme.md
index f73bdffaeb..67b706f428 100644
--- a/readme.md
+++ b/readme.md
@@ -2,8 +2,6 @@
[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) [](https://www.myget.org/gallery/avalonia-ci) 
-
-
## 📖 About AvaloniaUI
Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development.
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index d5aedf7783..3c2d2ee359 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -1,7 +1,7 @@

- Exe
+ WinExe
netcoreapp3.1
true
@@ -15,6 +15,10 @@
+
+
+ en
+
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 1aa926a2a6..53ad213d92 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -27,6 +27,6 @@
-
+
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 93fbe5e412..aa165d13f7 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -57,6 +57,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/PathMeasurementPage.cs b/samples/RenderDemo/Pages/PathMeasurementPage.cs
new file mode 100644
index 0000000000..212377deae
--- /dev/null
+++ b/samples/RenderDemo/Pages/PathMeasurementPage.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Diagnostics;
+using System.Drawing.Drawing2D;
+using System.Security.Cryptography;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Media.Immutable;
+using Avalonia.Threading;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace RenderDemo.Pages
+{
+ public class PathMeasurementPage : Control
+ {
+ static PathMeasurementPage()
+ {
+ AffectsRender(BoundsProperty);
+ }
+
+ private RenderTargetBitmap _bitmap;
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ _bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96));
+ base.OnAttachedToLogicalTree(e);
+ }
+
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ _bitmap.Dispose();
+ _bitmap = null;
+ base.OnDetachedFromLogicalTree(e);
+ }
+
+ readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+ readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+ readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+ readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
+ readonly IPen strokePen4 = new ImmutablePen(Brushes.Red, 1d, null, PenLineCap.Round, PenLineJoin.Round);
+
+ public override void Render(DrawingContext context)
+ {
+ using (var ctxi = _bitmap.CreateDrawingContext(null))
+ using (var bitmapCtx = new DrawingContext(ctxi, false))
+ {
+ ctxi.Clear(default);
+
+ var basePath = new PathGeometry();
+
+ using (var basePathCtx = basePath.Open())
+ {
+ basePathCtx.BeginFigure(new Point(20, 20), false);
+ basePathCtx.LineTo(new Point(400, 50));
+ basePathCtx.LineTo(new Point(80, 100));
+ basePathCtx.LineTo(new Point(300, 150));
+ basePathCtx.EndFigure(false);
+ }
+
+ bitmapCtx.DrawGeometry(null, strokePen, basePath);
+
+
+ var length = basePath.PlatformImpl.ContourLength;
+
+ if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1))
+ bitmapCtx.DrawGeometry(null, strokePen1, dst1);
+
+ if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2))
+ bitmapCtx.DrawGeometry(null, strokePen2, dst2);
+
+ if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3))
+ bitmapCtx.DrawGeometry(null, strokePen3, dst3);
+
+ var pathBounds = basePath.GetRenderBounds(strokePen);
+
+ bitmapCtx.DrawRectangle(null, strokePen4, pathBounds);
+ }
+
+
+ context.DrawImage(_bitmap,
+ new Rect(0, 0, 500, 500),
+ new Rect(0, 0, 500, 500));
+
+ base.Render(context);
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs b/src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs
new file mode 100644
index 0000000000..f207b558a3
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs
@@ -0,0 +1,12 @@
+using System;
+using XamlX.Transform;
+
+namespace Avalonia.Build.Tasks
+{
+ public class DeterministicIdGenerator : IXamlIdentifierGenerator
+ {
+ private int _nextId = 1;
+
+ public string GenerateIdentifierPart() => (_nextId++).ToString();
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 6ef8a98fae..508045dccb 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -22,7 +22,6 @@ using XamlX.IL;
namespace Avalonia.Build.Tasks
{
-
public static partial class XamlCompilerTaskExecutor
{
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
@@ -99,7 +98,8 @@ namespace Avalonia.Build.Tasks
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
- new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
+ new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
+ new DeterministicIdGenerator());
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt
index 0284463f1c..3a6810eed9 100644
--- a/src/Avalonia.Controls/ApiCompatBaseline.txt
+++ b/src/Avalonia.Controls/ApiCompatBaseline.txt
@@ -3,6 +3,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Control
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
+EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index c779e4b0cb..6093cbd581 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -218,6 +218,7 @@ namespace Avalonia.Controls
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
+ CanExecuteChanged(this, EventArgs.Empty);
}
}
diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
index cd60130c5b..be15d3d444 100644
--- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs
+++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
@@ -14,17 +14,21 @@ namespace Avalonia.Controls.Chrome
public class CaptionButtons : TemplatedControl
{
private CompositeDisposable? _disposables;
- private Window? _hostWindow;
- public void Attach(Window hostWindow)
+ ///
+ /// Currently attached window.
+ ///
+ protected Window? HostWindow { get; private set; }
+
+ public virtual void Attach(Window hostWindow)
{
if (_disposables == null)
{
- _hostWindow = hostWindow;
+ HostWindow = hostWindow;
_disposables = new CompositeDisposable
{
- _hostWindow.GetObservable(Window.WindowStateProperty)
+ HostWindow.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
PseudoClasses.Set(":minimized", x == WindowState.Minimized);
@@ -36,14 +40,45 @@ namespace Avalonia.Controls.Chrome
}
}
- public void Detach()
+ public virtual void Detach()
{
if (_disposables != null)
{
_disposables.Dispose();
_disposables = null;
- _hostWindow = null;
+ HostWindow = null;
+ }
+ }
+
+ protected virtual void OnClose()
+ {
+ HostWindow?.Close();
+ }
+
+ protected virtual void OnRestore()
+ {
+ if (HostWindow != null)
+ {
+ HostWindow.WindowState = HostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+ }
+ }
+
+ protected virtual void OnMinimize()
+ {
+ if (HostWindow != null)
+ {
+ HostWindow.WindowState = WindowState.Minimized;
+ }
+ }
+
+ protected virtual void OnToggleFullScreen()
+ {
+ if (HostWindow != null)
+ {
+ HostWindow.WindowState = HostWindow.WindowState == WindowState.FullScreen
+ ? WindowState.Normal
+ : WindowState.FullScreen;
}
}
@@ -56,31 +91,13 @@ namespace Avalonia.Controls.Chrome
var minimiseButton = e.NameScope.Get("PART_MinimiseButton");
var fullScreenButton = e.NameScope.Get("PART_FullScreenButton");
- closeButton.PointerReleased += (sender, e) => _hostWindow?.Close();
+ closeButton.PointerReleased += (sender, e) => OnClose();
- restoreButton.PointerReleased += (sender, e) =>
- {
- if (_hostWindow != null)
- {
- _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
- }
- };
+ restoreButton.PointerReleased += (sender, e) => OnRestore();
- minimiseButton.PointerReleased += (sender, e) =>
- {
- if (_hostWindow != null)
- {
- _hostWindow.WindowState = WindowState.Minimized;
- }
- };
+ minimiseButton.PointerReleased += (sender, e) => OnMinimize();
- fullScreenButton.PointerReleased += (sender, e) =>
- {
- if (_hostWindow != null)
- {
- _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen;
- }
- };
+ fullScreenButton.PointerReleased += (sender, e) => OnToggleFullScreen();
}
}
}
diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs
index 64414b1f47..c2fe1bb691 100644
--- a/src/Avalonia.Controls/NativeControlHost.cs
+++ b/src/Avalonia.Controls/NativeControlHost.cs
@@ -16,30 +16,16 @@ namespace Avalonia.Controls
private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List _propertyChangedSubscriptions = new List();
- private readonly EventHandler _propertyChangedHandler;
- static NativeControlHost()
- {
- IsVisibleProperty.Changed.AddClassHandler(OnVisibleChanged);
- }
-
- public NativeControlHost()
- {
- _propertyChangedHandler = PropertyChangedHandler;
- }
-
- private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
- => host.UpdateHost();
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = e.Root as TopLevel;
var visual = (IVisual)this;
- while (visual != _currentRoot)
+ while (visual != null)
{
-
if (visual is Visual v)
{
- v.PropertyChanged += _propertyChangedHandler;
+ v.PropertyChanged += PropertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
@@ -51,7 +37,7 @@ namespace Avalonia.Controls
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
- if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
+ if (e.IsEffectiveValueChange && (e.Property == BoundsProperty || e.Property == IsVisibleProperty))
EnqueueForMoveResize();
}
@@ -61,7 +47,7 @@ namespace Avalonia.Controls
if (_propertyChangedSubscriptions != null)
{
foreach (var v in _propertyChangedSubscriptions)
- v.PropertyChanged -= _propertyChangedHandler;
+ v.PropertyChanged -= PropertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost();
diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
index de3f58886b..bb3c0288eb 100644
--- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
+++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Platform
///
/// The default for the platform.
///
- Default = SystemChrome,
+ Default = PreferSystemChrome,
///
/// Use SystemChrome
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index e02efc2bd2..6419981fb1 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -341,7 +341,9 @@ namespace Avalonia.Controls
var pointNum = orient ? x.Position.X : x.Position.Y;
var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d);
- var invert = orient ? 0 : 1;
+ var invert = orient ?
+ IsDirectionReversed ? 1 : 0 :
+ IsDirectionReversed ? 0 : 1;
var calcVal = Math.Abs(invert - logicalPos);
var range = Maximum - Minimum;
var finalValue = calcVal * range + Minimum;
diff --git a/src/Avalonia.Diagnostics/DevToolsExtensions.cs b/src/Avalonia.Diagnostics/DevToolsExtensions.cs
index 4bc2ca313f..aa585dca40 100644
--- a/src/Avalonia.Diagnostics/DevToolsExtensions.cs
+++ b/src/Avalonia.Diagnostics/DevToolsExtensions.cs
@@ -15,7 +15,7 @@ namespace Avalonia
/// The window to attach DevTools to.
public static void AttachDevTools(this TopLevel root)
{
- DevTools.Attach(root, new KeyGesture(Key.F12));
+ DevTools.Attach(root, new DevToolsOptions());
}
///
@@ -27,5 +27,15 @@ namespace Avalonia
{
DevTools.Attach(root, gesture);
}
+
+ ///
+ /// Attaches DevTools to a window, to be opened with the specified options.
+ ///
+ /// The window to attach DevTools to.
+ /// Additional settings of DevTools.
+ public static void AttachDevTools(this TopLevel root, DevToolsOptions options)
+ {
+ DevTools.Attach(root, options);
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index 4899be2955..7942d22962 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -6,6 +6,8 @@ using Avalonia.Diagnostics.Views;
using Avalonia.Input;
using Avalonia.Interactivity;
+#nullable enable
+
namespace Avalonia.Diagnostics
{
public static class DevTools
@@ -13,12 +15,20 @@ namespace Avalonia.Diagnostics
private static readonly Dictionary s_open = new Dictionary();
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
+ {
+ return Attach(root, new DevToolsOptions()
+ {
+ Gesture = gesture,
+ });
+ }
+
+ public static IDisposable Attach(TopLevel root, DevToolsOptions options)
{
void PreviewKeyDown(object sender, KeyEventArgs e)
{
- if (gesture.Matches(e))
+ if (options.Gesture.Matches(e))
{
- Open(root);
+ Open(root, options);
}
}
@@ -28,7 +38,9 @@ namespace Avalonia.Diagnostics
RoutingStrategies.Tunnel);
}
- public static IDisposable Open(TopLevel root)
+ public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions());
+
+ public static IDisposable Open(TopLevel root, DevToolsOptions options)
{
if (s_open.TryGetValue(root, out var window))
{
@@ -38,15 +50,15 @@ namespace Avalonia.Diagnostics
{
window = new MainWindow
{
- Width = 1024,
- Height = 512,
Root = root,
+ Width = options.Size.Width,
+ Height = options.Size.Height,
};
window.Closed += DevToolsClosed;
s_open.Add(root, window);
- if (root is Window inspectedWindow)
+ if (options.ShowAsChildWindow && root is Window inspectedWindow)
{
window.Show(inspectedWindow);
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
new file mode 100644
index 0000000000..ee46192207
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -0,0 +1,26 @@
+using Avalonia.Input;
+
+namespace Avalonia.Diagnostics
+{
+ ///
+ /// Describes options used to customize DevTools.
+ ///
+ public class DevToolsOptions
+ {
+ ///
+ /// Gets or sets the key gesture used to open DevTools.
+ ///
+ public KeyGesture Gesture { get; set; } = new KeyGesture(Key.F12);
+
+ ///
+ /// Gets or sets a value indicating whether DevTools should be displayed as a child window
+ /// of the window being inspected. The default value is true.
+ ///
+ public bool ShowAsChildWindow { get; set; } = true;
+
+ ///
+ /// Gets or sets the initial size of the DevTools window. The default value is 1024x512.
+ ///
+ public Size Size { get; set; } = new Size(1024, 512);
+ }
+}
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 6a78f4c6e7..62cac378d7 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -104,6 +104,9 @@ namespace Avalonia.Headless
}
public Rect Bounds { get; set; }
+
+ public double ContourLength { get; } = 0;
+
public virtual bool FillContains(Point point) => Bounds.Contains(point);
public Rect GetRenderBounds(IPen pen)
@@ -126,6 +129,25 @@ namespace Avalonia.Headless
public ITransformedGeometryImpl WithTransform(Matrix transform) =>
new HeadlessTransformedGeometryStub(this, transform);
+
+ public bool TryGetPointAtDistance(double distance, out Point point)
+ {
+ point = new Point();
+ return false;
+ }
+
+ public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+ {
+ point = new Point();
+ tangent = new Point();
+ return false;
+ }
+
+ public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+ {
+ segmentGeometry = null;
+ return false;
+ }
}
class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
@@ -359,6 +381,16 @@ namespace Avalonia.Headless
}
+ public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+ {
+
+ }
+
+ public void PopBitmapBlendMode()
+ {
+
+ }
+
public void Custom(ICustomDrawOperation custom)
{
diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs
index 1be2595ebe..60b6b9e947 100644
--- a/src/Avalonia.Input/Gestures.cs
+++ b/src/Avalonia.Input/Gestures.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Input
public static readonly RoutedEvent ScrollGestureEvent =
RoutedEvent.Register(
"ScrollGesture", RoutingStrategies.Bubble, typeof(Gestures));
-
+
public static readonly RoutedEvent ScrollGestureEndedEvent =
RoutedEvent.Register(
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
@@ -89,7 +89,7 @@ namespace Avalonia.Input
{
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
- e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
+ e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
}
@@ -105,8 +105,14 @@ namespace Avalonia.Input
{
if (e.InitialPressMouseButton == MouseButton.Left || e.InitialPressMouseButton == MouseButton.Right)
{
- var et = e.InitialPressMouseButton != MouseButton.Right ? TappedEvent : RightTappedEvent;
- e.Source.RaiseEvent(new RoutedEventArgs(et));
+ if (e.InitialPressMouseButton == MouseButton.Right)
+ {
+ e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
+ }
+ else
+ {
+ e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e));
+ }
}
}
}
diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs
index 451f80b1df..ba39f7ca8e 100644
--- a/src/Avalonia.Input/PointerEventArgs.cs
+++ b/src/Avalonia.Input/PointerEventArgs.cs
@@ -107,7 +107,9 @@ namespace Avalonia.Input
None,
Left,
Right,
- Middle
+ Middle,
+ XButton1,
+ XButton2
}
public class PointerPressedEventArgs : PointerEventArgs
diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs
index 37d2120d03..9f8285a8e1 100644
--- a/src/Avalonia.Input/PointerPoint.cs
+++ b/src/Avalonia.Input/PointerPoint.cs
@@ -90,6 +90,10 @@ namespace Avalonia.Input
return MouseButton.Middle;
if (kind == PointerUpdateKind.RightButtonPressed || kind == PointerUpdateKind.RightButtonReleased)
return MouseButton.Right;
+ if (kind == PointerUpdateKind.XButton1Pressed || kind == PointerUpdateKind.XButton1Released)
+ return MouseButton.XButton1;
+ if (kind == PointerUpdateKind.XButton2Pressed || kind == PointerUpdateKind.XButton2Released)
+ return MouseButton.XButton2;
return MouseButton.None;
}
}
diff --git a/src/Avalonia.Input/TappedEventArgs.cs b/src/Avalonia.Input/TappedEventArgs.cs
new file mode 100644
index 0000000000..02add509cd
--- /dev/null
+++ b/src/Avalonia.Input/TappedEventArgs.cs
@@ -0,0 +1,18 @@
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Input
+{
+ public class TappedEventArgs : RoutedEventArgs
+ {
+ private readonly PointerEventArgs lastPointerEventArgs;
+
+ public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
+ : base(routedEvent)
+ {
+ this.lastPointerEventArgs = lastPointerEventArgs;
+ }
+
+ public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo);
+ }
+}
diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl
index 9cada1120a..adcbeb2d3a 100644
--- a/src/Avalonia.Native/avn.idl
+++ b/src/Avalonia.Native/avn.idl
@@ -397,7 +397,7 @@ enum AvnExtendClientAreaChromeHints
AvnSystemChrome = 0x01,
AvnPreferSystemChrome = 0x02,
AvnOSXThickTitleBar = 0x08,
- AvnDefaultChrome = AvnSystemChrome,
+ AvnDefaultChrome = AvnPreferSystemChrome,
}
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
index 6771d3e179..359da3d7c2 100644
--- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
+++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
@@ -9,18 +9,22 @@ namespace Avalonia.ReactiveUI
{
///
/// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia
- /// scheduler and Avalonia activation for view fetcher. Always remember to
- /// call this method if you are using ReactiveUI in your application.
+ /// scheduler, an activation for view fetcher, a template binding hook. Remember
+ /// to call this method if you are using ReactiveUI in your application.
///
public static TAppBuilder UseReactiveUI(this TAppBuilder builder)
- where TAppBuilder : AppBuilderBase, new()
- {
- return builder.AfterPlatformServicesSetup(_ =>
+ where TAppBuilder : AppBuilderBase, new() =>
+ builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() =>
{
+ if (Locator.CurrentMutable is null)
+ {
+ return;
+ }
+
+ PlatformRegistrationManager.SetRegistrationNamespaces(RegistrationNamespace.Avalonia);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
- });
- }
+ }));
}
}
diff --git a/src/Avalonia.Styling/ClassBindingManager.cs b/src/Avalonia.Styling/ClassBindingManager.cs
new file mode 100644
index 0000000000..e8b1cc301d
--- /dev/null
+++ b/src/Avalonia.Styling/ClassBindingManager.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Data;
+
+namespace Avalonia
+{
+ internal static class ClassBindingManager
+ {
+ private static readonly Dictionary s_RegisteredProperties =
+ new Dictionary();
+
+ public static IDisposable Bind(IStyledElement target, string className, IBinding source, object anchor)
+ {
+ if (!s_RegisteredProperties.TryGetValue(className, out var prop))
+ s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className);
+ return target.Bind(prop, source, anchor);
+ }
+
+ private static AvaloniaProperty RegisterClassProxyProperty(string className)
+ {
+ var prop = AvaloniaProperty.Register("__AvaloniaReserved::Classes::" + className);
+ prop.Changed.Subscribe(args =>
+ {
+ var classes = ((IStyledElement)args.Sender).Classes;
+ classes.Set(className, args.NewValue.GetValueOrDefault());
+ });
+
+ return prop;
+ }
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs
index 51dca57928..4e2783d4ec 100644
--- a/src/Avalonia.Styling/Controls/Classes.cs
+++ b/src/Avalonia.Styling/Controls/Classes.cs
@@ -265,5 +265,26 @@ namespace Avalonia.Controls
$"The pseudoclass '{name}' may only be {operation} by the control itself.");
}
}
+
+ ///
+ /// Adds a or removes a style class to/from the collection.
+ ///
+ /// The class names.
+ /// If true adds the class, if false, removes it.
+ ///
+ /// Only standard classes may be added or removed via this method. To add pseudoclasses (classes
+ /// beginning with a ':' character) use the protected
+ /// property.
+ ///
+ public void Set(string name, bool value)
+ {
+ if (value)
+ {
+ if (!Contains(name))
+ Add(name);
+ }
+ else
+ Remove(name);
+ }
}
}
diff --git a/src/Avalonia.Styling/StyledElementExtensions.cs b/src/Avalonia.Styling/StyledElementExtensions.cs
new file mode 100644
index 0000000000..0c5a5f7438
--- /dev/null
+++ b/src/Avalonia.Styling/StyledElementExtensions.cs
@@ -0,0 +1,11 @@
+using System;
+using Avalonia.Data;
+
+namespace Avalonia
+{
+ public static class StyledElementExtensions
+ {
+ public static IDisposable BindClass(this IStyledElement target, string className, IBinding source, object anchor) =>
+ ClassBindingManager.Bind(target, className, source, anchor);
+ }
+}
diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt
new file mode 100644
index 0000000000..805d1955ea
--- /dev/null
+++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt
@@ -0,0 +1,9 @@
+Compat issues with assembly Avalonia.Visuals:
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract.
+Total Issues: 7
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index ae4c927ae2..4e3dc8699c 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -121,12 +121,23 @@ namespace Avalonia.Media
/// The stroke pen.
/// The geometry.
public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
+ {
+ DrawGeometry(brush, pen, geometry.PlatformImpl);
+ }
+
+ ///
+ /// Draws a geometry.
+ ///
+ /// The fill brush.
+ /// The stroke pen.
+ /// The geometry.
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
Contract.Requires(geometry != null);
if (brush != null || PenIsVisible(pen))
{
- PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
+ PlatformImpl.DrawGeometry(brush, pen, geometry);
}
}
diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
new file mode 100644
index 0000000000..473b43dab3
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
@@ -0,0 +1,57 @@
+namespace Avalonia.Visuals.Media.Imaging
+{
+ ///
+ /// Controls the way the bitmaps are drawn together.
+ ///
+ public enum BitmapBlendingMode
+ {
+ ///
+ /// Source is placed over the destination.
+ ///
+ SourceOver,
+ ///
+ /// Only the source will be present.
+ ///
+ Source,
+ ///
+ /// Only the destination will be present.
+ ///
+ Destination,
+ ///
+ /// Destination is placed over the source.
+ ///
+ DestinationOver,
+ ///
+ /// The source that overlaps the destination, replaces the destination.
+ ///
+ SourceIn,
+ ///
+ /// Destination which overlaps the source, replaces the source.
+ ///
+ DestinationIn,
+ ///
+ /// Source is placed, where it falls outside of the destination.
+ ///
+ SourceOut,
+ ///
+ /// Destination is placed, where it falls outside of the source.
+ ///
+ DestinationOut,
+ ///
+ /// Source which overlaps the destination, replaces the destination.
+ ///
+ SourceAtop,
+ ///
+ /// Destination which overlaps the source replaces the source.
+ ///
+ DestinationAtop,
+ ///
+ /// The non-overlapping regions of source and destination are combined.
+ ///
+ Xor,
+ ///
+ /// Display the sum of the source image and destination image.
+ ///
+ Plus,
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index d6e88a7507..39d4066e55 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -148,6 +148,17 @@ namespace Avalonia.Platform
/// Pops the latest pushed geometry clip.
///
void PopGeometryClip();
+
+ ///
+ /// Pushes a bitmap blending value.
+ ///
+ /// The bitmap blending mode.
+ void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
+
+ ///
+ /// Pops the latest pushed bitmap blending value.
+ ///
+ void PopBitmapBlendMode();
///
/// Adds a custom draw operation
diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
index 7490ad912a..79e125c5cb 100644
--- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
@@ -11,6 +11,12 @@ namespace Avalonia.Platform
/// Gets the geometry's bounding rectangle.
///
Rect Bounds { get; }
+
+ ///
+ /// Gets the geometry's total length as if all its contours are placed
+ /// in a straight line.
+ ///
+ double ContourLength { get; }
///
/// Gets the geometry's bounding rectangle with the specified pen.
@@ -47,5 +53,38 @@ namespace Avalonia.Platform
/// The transform.
/// The cloned geometry.
ITransformedGeometryImpl WithTransform(Matrix transform);
+
+ ///
+ /// Attempts to get the corresponding point at the
+ /// specified distance
+ ///
+ /// The contour distance to get from.
+ /// The point in the specified distance.
+ /// If there's valid point at the specified distance.
+ bool TryGetPointAtDistance(double distance, out Point point);
+
+ ///
+ /// Attempts to get the corresponding point and
+ /// tangent from the specified distance along the
+ /// contour of the geometry.
+ ///
+ /// The contour distance to get from.
+ /// The point in the specified distance.
+ /// The tangent in the specified distance.
+ /// If there's valid point and tangent at the specified distance.
+ bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent);
+
+ ///
+ /// Attempts to get the corresponding path segment
+ /// given by the two distances specified.
+ /// Imagine it like snipping a part of the current
+ /// geometry.
+ ///
+ /// The contour distance to start snipping from.
+ /// The contour distance to stop snipping to.
+ /// If ture, the resulting snipped path will start with a BeginFigure call.
+ /// The resulting snipped path.
+ /// If the snipping operation is successful.
+ bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
new file mode 100644
index 0000000000..0a5c1f8db6
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
@@ -0,0 +1,68 @@
+using Avalonia.Platform;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ ///
+ /// A node in the scene graph which represents an bitmap blending mode push or pop.
+ ///
+ internal class BitmapBlendModeNode : IDrawOperation
+ {
+ ///
+ /// Initializes a new instance of the class that represents an
+ /// push.
+ ///
+ /// The to push.
+ public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
+ {
+ BlendingMode = bitmapBlend;
+ }
+
+ ///
+ /// Initializes a new instance of the class that represents an
+ /// pop.
+ ///
+ public BitmapBlendModeNode()
+ {
+ }
+
+ ///
+ public Rect Bounds => Rect.Empty;
+
+ ///
+ /// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
+ ///
+ public BitmapBlendingMode? BlendingMode { get; }
+
+ ///
+ public bool HitTest(Point p) => false;
+
+ ///
+ /// Determines if this draw operation equals another.
+ ///
+ /// The opacity 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(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
+
+ ///
+ public void Render(IDrawingContextImpl context)
+ {
+ if (BlendingMode.HasValue)
+ {
+ context.PushBitmapBlendMode(BlendingMode.Value);
+ }
+ else
+ {
+ context.PopBitmapBlendMode();
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 28f426266d..e6092574c5 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ ///
+ public void PopBitmapBlendMode()
+ {
+ var next = NextDrawAs();
+
+ if (next == null || !next.Item.Equals(null))
+ {
+ Add(new BitmapBlendModeNode());
+ }
+ else
+ {
+ ++_drawOperationindex;
+ }
+ }
+
///
public void PopOpacity()
{
@@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ ///
+ public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+ {
+ var next = NextDrawAs();
+
+ if (next == null || !next.Item.Equals(blendingMode))
+ {
+ Add(new BitmapBlendModeNode(blendingMode));
+ }
+ else
+ {
+ ++_drawOperationindex;
+ }
+ }
+
public readonly struct UpdateState : IDisposable
{
public UpdateState(
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
index c9052c6ef2..d3da19d8c9 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
@@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph
/// The scaling mode.
///
public BitmapInterpolationMode BitmapInterpolationMode { get; }
+
+ ///
+ /// The bitmap blending mode.
+ ///
+ ///
+ /// The blending mode.
+ ///
+ public BitmapBlendingMode BitmapBlendingMode { get; }
///
/// Determines if this draw operation equals another.
@@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph
public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
return transform == Transform &&
- Equals(source.Item, Source.Item) &&
- source.Item.Version == SourceVersion &&
- opacity == Opacity &&
- sourceRect == SourceRect &&
- destRect == DestRect &&
- bitmapInterpolationMode == BitmapInterpolationMode;
+ Equals(source.Item, Source.Item) &&
+ source.Item.Version == SourceVersion &&
+ opacity == Opacity &&
+ sourceRect == SourceRect &&
+ destRect == DestRect &&
+ bitmapInterpolationMode == BitmapInterpolationMode;
}
///
diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs
index 1b9f5c67d5..79c4202be4 100644
--- a/src/Avalonia.Visuals/Vector.cs
+++ b/src/Avalonia.Visuals/Vector.cs
@@ -82,6 +82,15 @@ namespace Avalonia
public static Vector operator *(Vector vector, double scale)
=> Multiply(vector, scale);
+ ///
+ /// Scales a vector.
+ ///
+ /// The vector.
+ /// The scaling factor.
+ /// The scaled vector.
+ public static Vector operator *(double scale, Vector vector)
+ => Multiply(vector, scale);
+
///
/// Scales a vector.
///
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index abff763bb1..a191dc59fb 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -41,10 +41,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
InsertBefore(
+ new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
InsertAfter(
- new AvaloniaXamlIlAvaloniaPropertyResolver());
+ new AvaloniaXamlIlAvaloniaPropertyResolver(),
+ new AvaloniaXamlIlReorderClassesPropertiesTransformer()
+ );
InsertBefore(
new AvaloniaXamlIlBindingPathParser(),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
index 0c0dcb1634..f6f47dce0d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
@@ -14,8 +14,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlXmlnsMappings xmlnsMappings,
XamlValueConverter customValueConverter,
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
- XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter)
- : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)
+ XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
+ IXamlIdentifierGenerator identifierGenerator = null)
+ : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
{
ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs
new file mode 100644
index 0000000000..23232dbcf3
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlResolveClassesPropertiesTransformer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstNamePropertyReference prop
+ && prop.TargetType is XamlAstClrTypeReference targetRef
+ && prop.DeclaringType is XamlAstClrTypeReference declaringRef)
+ {
+ var types = context.GetAvaloniaTypes();
+ if (types.StyledElement.IsAssignableFrom(targetRef.Type)
+ && types.Classes.Equals(declaringRef.Type))
+ {
+ return new XamlAstClrProperty(node, "class:" + prop.Name, types.Classes,
+ null)
+ {
+ Setters = { new ClassValueSetter(types, prop.Name), new ClassBindingSetter(types, prop.Name) }
+ };
+ }
+ }
+ return node;
+ }
+
+
+ class ClassValueSetter : IXamlEmitablePropertySetter
+ {
+ private readonly AvaloniaXamlIlWellKnownTypes _types;
+ private readonly string _className;
+
+ public ClassValueSetter(AvaloniaXamlIlWellKnownTypes types, string className)
+ {
+ _types = types;
+ _className = className;
+ Parameters = new[] { types.XamlIlTypes.Boolean };
+ }
+
+ public void Emit(IXamlILEmitter emitter)
+ {
+ using (var value = emitter.LocalsPool.GetLocal(_types.XamlIlTypes.Boolean))
+ {
+ emitter
+ .Stloc(value.Local)
+ .EmitCall(_types.StyledElementClassesProperty.Getter)
+ .Ldstr(_className)
+ .Ldloc(value.Local)
+ .EmitCall(_types.Classes.GetMethod(new FindMethodMethodSignature("Set",
+ _types.XamlIlTypes.Void, _types.XamlIlTypes.String, _types.XamlIlTypes.Boolean)));
+ }
+ }
+
+ public IXamlType TargetType => _types.StyledElement;
+
+ public PropertySetterBinderParameters BinderParameters { get; } =
+ new PropertySetterBinderParameters { AllowXNull = false };
+ public IReadOnlyList Parameters { get; }
+ }
+
+ class ClassBindingSetter : IXamlEmitablePropertySetter
+ {
+ private readonly AvaloniaXamlIlWellKnownTypes _types;
+ private readonly string _className;
+
+ public ClassBindingSetter(AvaloniaXamlIlWellKnownTypes types, string className)
+ {
+ _types = types;
+ _className = className;
+ Parameters = new[] {types.IBinding};
+ }
+
+ public void Emit(IXamlILEmitter emitter)
+ {
+ using (var bloc = emitter.LocalsPool.GetLocal(_types.IBinding))
+ emitter
+ .Stloc(bloc.Local)
+ .Ldstr(_className)
+ .Ldloc(bloc.Local)
+ // TODO: provide anchor?
+ .Ldnull();
+ emitter.EmitCall(_types.ClassesBindMethod, true);
+ }
+
+ public IXamlType TargetType => _types.StyledElement;
+
+ public PropertySetterBinderParameters BinderParameters { get; } =
+ new PropertySetterBinderParameters { AllowXNull = false };
+ public IReadOnlyList Parameters { get; }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlReorderClassesPropertiesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlReorderClassesPropertiesTransformer.cs
new file mode 100644
index 0000000000..ae3515a6d6
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlReorderClassesPropertiesTransformer.cs
@@ -0,0 +1,40 @@
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlReorderClassesPropertiesTransformer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode obj)
+ {
+ IXamlAstNode classesNode = null;
+ IXamlAstNode firstSingleClassNode = null;
+ var types = context.GetAvaloniaTypes();
+ foreach (var child in obj.Children)
+ {
+ if (child is XamlAstXamlPropertyValueNode propValue
+ && propValue.Property is XamlAstClrProperty prop)
+ {
+ if (prop.DeclaringType.Equals(types.Classes))
+ {
+ if (firstSingleClassNode == null)
+ firstSingleClassNode = child;
+ }
+ else if (prop.Name == "Classes" && prop.DeclaringType.Equals(types.StyledElement))
+ classesNode = child;
+ }
+ }
+
+ if (classesNode != null && firstSingleClassNode != null)
+ {
+ obj.Children.Remove(classesNode);
+ obj.Children.Insert(obj.Children.IndexOf(firstSingleClassNode), classesNode);
+ }
+ }
+
+ return node;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 34aae2c5ed..c4995b2de3 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -25,6 +25,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType AssignBindingAttribute { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
+ public IXamlType IStyledElement { get; }
public IXamlType NameScope { get; }
public IXamlMethod NameScopeSetNameScope { get; }
public IXamlType INameScope { get; }
@@ -78,6 +79,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ColumnDefinition { get; }
public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; }
+ public IXamlMethod ClassesBindMethod { get; }
+ public IXamlProperty StyledElementClassesProperty { get; set; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@@ -97,6 +100,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IBinding, cfg.WellKnownTypes.Object);
UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
+ IStyledElement = cfg.TypeSystem.GetType("Avalonia.IStyledElement");
INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope");
INameScopeRegister = INameScope.GetMethod(
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
@@ -168,6 +172,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition");
RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions");
Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes");
+ StyledElementClassesProperty =
+ StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
+ ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
+ .FindMethod( "BindClass", IDisposable, false, IStyledElement,
+ cfg.WellKnownTypes.String,
+ IBinding, cfg.WellKnownTypes.Object);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index 07c5451135..650534b347 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.Templates
public IControl Build(object data, IControl existing)
{
- return existing ?? TemplateContent.Load(Content).Control;
+ return existing ?? TemplateContent.Load(Content)?.Control;
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
index c8843a3176..c096ed7ed7 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
@@ -10,8 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
- public IPanel Build()
- => (IPanel)TemplateContent.Load(Content).Control;
+ public IPanel Build() => (IPanel)TemplateContent.Load(Content)?.Control;
object ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
index 65323ae665..45fae9cb28 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
- public IControl Build() => TemplateContent.Load(Content).Control;
+ public IControl Build() => TemplateContent.Load(Content)?.Control;
object ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
index 96f25668fb..483a1a5d06 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
@@ -1,6 +1,4 @@
using System;
-using Avalonia.Controls;
-using System.Collections.Generic;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates
@@ -14,6 +12,12 @@ namespace Avalonia.Markup.Xaml.Templates
{
return (ControlTemplateResult)direct(null);
}
+
+ if (templateContent is null)
+ {
+ return null;
+ }
+
throw new ArgumentException(nameof(templateContent));
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index d785ac4ac0..7b065c7f47 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -51,8 +51,12 @@ namespace Avalonia.Markup.Xaml.Templates
public IControl Build(object data)
{
- var visualTreeForItem = TemplateContent.Load(Content).Control;
- visualTreeForItem.DataContext = data;
+ var visualTreeForItem = TemplateContent.Load(Content)?.Control;
+ if (visualTreeForItem != null)
+ {
+ visualTreeForItem.DataContext = data;
+ }
+
return visualTreeForItem;
}
}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 2a79a4bb50..b7d5d3ec59 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -23,9 +23,11 @@ namespace Avalonia.Skia
private readonly Vector _dpi;
private readonly Stack _maskStack = new Stack();
private readonly Stack _opacityStack = new Stack();
+ private readonly Stack _blendingModeStack = new Stack();
private readonly Matrix? _postTransform;
private readonly IVisualBrushRenderer _visualBrushRenderer;
private double _currentOpacity = 1.0f;
+ private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform;
private bool _disposed;
@@ -145,6 +147,7 @@ namespace Avalonia.Skia
})
{
paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
+ paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
drawableImage.Draw(this, s, d, paint);
}
@@ -508,6 +511,19 @@ namespace Avalonia.Skia
Canvas.Restore();
}
+ ///
+ public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+ {
+ _blendingModeStack.Push(_currentBlendingMode);
+ _currentBlendingMode = blendingMode;
+ }
+
+ ///
+ public void PopBitmapBlendMode()
+ {
+ _currentBlendingMode = _blendingModeStack.Pop();
+ }
+
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
///
diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs
index 879b18742e..19dbcf9713 100644
--- a/src/Skia/Avalonia.Skia/GeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs
@@ -11,9 +11,36 @@ namespace Avalonia.Skia
internal abstract class GeometryImpl : IGeometryImpl
{
private PathCache _pathCache;
-
+ private SKPathMeasure _pathMeasureCache;
+
+ private SKPathMeasure CachedPathMeasure
+ {
+ get
+ {
+ if (_pathMeasureCache is null)
+ {
+ _pathMeasureCache = new SKPathMeasure(EffectivePath);
+ }
+
+ return _pathMeasureCache;
+ }
+ }
+
///
public abstract Rect Bounds { get; }
+
+ ///
+ public double ContourLength
+ {
+ get
+ {
+ if (EffectivePath is null)
+ return 0;
+
+ return (double)CachedPathMeasure?.Length;
+ }
+ }
+
public abstract SKPath EffectivePath { get; }
///
@@ -30,12 +57,12 @@ namespace Avalonia.Skia
// Usually this function is being called with same stroke width per path, so this saves a lot of Skia traffic.
var strokeWidth = (float)(pen?.Thickness ?? 0);
-
+
if (!_pathCache.HasCacheFor(strokeWidth))
{
UpdatePathCache(strokeWidth);
}
-
+
return PathContainsCore(_pathCache.CachedStrokePath, point);
}
@@ -58,7 +85,7 @@ namespace Avalonia.Skia
{
paint.IsStroke = true;
paint.StrokeWidth = strokeWidth;
-
+
paint.GetFillPath(EffectivePath, strokePath);
_pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect());
@@ -74,13 +101,13 @@ namespace Avalonia.Skia
/// True, if point is contained in a path.
private static bool PathContainsCore(SKPath path, Point point)
{
- return path.Contains((float)point.X, (float)point.Y);
+ return path.Contains((float)point.X, (float)point.Y);
}
///
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
- var result = EffectivePath.Op(((GeometryImpl) geometry).EffectivePath, SKPathOp.Intersect);
+ var result = EffectivePath.Op(((GeometryImpl)geometry).EffectivePath, SKPathOp.Intersect);
return result == null ? null : new StreamGeometryImpl(result);
}
@@ -89,21 +116,74 @@ namespace Avalonia.Skia
public Rect GetRenderBounds(IPen pen)
{
var strokeWidth = (float)(pen?.Thickness ?? 0);
-
+
if (!_pathCache.HasCacheFor(strokeWidth))
{
UpdatePathCache(strokeWidth);
}
-
+
return _pathCache.CachedGeometryRenderBounds;
}
-
+
///
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
return new TransformedGeometryImpl(this, transform);
}
+ ///
+ public bool TryGetPointAtDistance(double distance, out Point point)
+ {
+ if (EffectivePath is null)
+ {
+ point = new Point();
+ return false;
+ }
+
+ var res = CachedPathMeasure.GetPosition((float)distance, out var skPoint);
+ point = new Point(skPoint.X, skPoint.Y);
+ return res;
+ }
+
+ ///
+ public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+ {
+ if (EffectivePath is null)
+ {
+ point = new Point();
+ tangent = new Point();
+ return false;
+ }
+
+ var res = CachedPathMeasure.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent);
+ point = new Point(skPoint.X, skPoint.Y);
+ tangent = new Point(skTangent.X, skTangent.Y);
+ return res;
+ }
+
+ public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
+ out IGeometryImpl segmentGeometry)
+ {
+ if (EffectivePath is null)
+ {
+ segmentGeometry = null;
+ return false;
+ }
+
+ segmentGeometry = null;
+
+ var _skPathSegment = new SKPath();
+
+ var res = CachedPathMeasure.GetSegment((float)startDistance, (float)stopDistance, _skPathSegment, startOnBeginFigure);
+
+ if (res)
+ {
+ segmentGeometry = new StreamGeometryImpl(_skPathSegment);
+ }
+
+ return res;
+ }
+
///
/// Invalidate all caches. Call after chaining path contents.
///
@@ -115,12 +195,12 @@ namespace Avalonia.Skia
private struct PathCache
{
private float _cachedStrokeWidth;
-
+
///
/// Tolerance for two stroke widths to be deemed equal
///
public const float Tolerance = float.Epsilon;
-
+
///
/// Cached contour path.
///
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index bb3dbbfadc..75b4231640 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -25,6 +25,39 @@ namespace Avalonia.Skia
}
}
+ public static SKBlendMode ToSKBlendMode(this BitmapBlendingMode blendingMode)
+ {
+ switch (blendingMode)
+ {
+ case BitmapBlendingMode.SourceOver:
+ return SKBlendMode.SrcOver;
+ case BitmapBlendingMode.Source:
+ return SKBlendMode.Src;
+ case BitmapBlendingMode.SourceIn:
+ return SKBlendMode.SrcIn;
+ case BitmapBlendingMode.SourceOut:
+ return SKBlendMode.SrcOut;
+ case BitmapBlendingMode.SourceAtop:
+ return SKBlendMode.SrcATop;
+ case BitmapBlendingMode.Destination:
+ return SKBlendMode.Dst;
+ case BitmapBlendingMode.DestinationIn:
+ return SKBlendMode.DstIn;
+ case BitmapBlendingMode.DestinationOut:
+ return SKBlendMode.DstOut;
+ case BitmapBlendingMode.DestinationOver:
+ return SKBlendMode.DstOver;
+ case BitmapBlendingMode.DestinationAtop:
+ return SKBlendMode.DstATop;
+ case BitmapBlendingMode.Xor:
+ return SKBlendMode.Xor;
+ case BitmapBlendingMode.Plus:
+ return SKBlendMode.Plus;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null);
+ }
+ }
+
public static SKPoint ToSKPoint(this Point p)
{
return new SKPoint((float)p.X, (float)p.Y);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 47a19aad8c..af35934785 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -5,6 +5,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
+using Avalonia.Visuals.Media.Imaging;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop;
@@ -121,7 +122,9 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{
var interpolationMode = GetInterpolationMode(bitmapInterpolationMode);
-
+
+ // TODO: How to implement CompositeMode here?
+
_deviceContext.DrawBitmap(
d2d.Value,
destRect.ToSharpDX(),
@@ -149,6 +152,35 @@ namespace Avalonia.Direct2D1.Media
}
}
+ public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode)
+ {
+ switch (blendingMode)
+ {
+ case BitmapBlendingMode.SourceIn:
+ return CompositeMode.SourceIn;
+ case BitmapBlendingMode.SourceOut:
+ return CompositeMode.SourceOut;
+ case BitmapBlendingMode.SourceOver:
+ return CompositeMode.SourceOver;
+ case BitmapBlendingMode.SourceAtop:
+ return CompositeMode.SourceAtop;
+ case BitmapBlendingMode.DestinationIn:
+ return CompositeMode.DestinationIn;
+ case BitmapBlendingMode.DestinationOut:
+ return CompositeMode.DestinationOut;
+ case BitmapBlendingMode.DestinationOver:
+ return CompositeMode.DestinationOver;
+ case BitmapBlendingMode.DestinationAtop:
+ return CompositeMode.DestinationAtop;
+ case BitmapBlendingMode.Xor:
+ return CompositeMode.Xor;
+ case BitmapBlendingMode.Plus:
+ return CompositeMode.Plus;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null);
+ }
+ }
+
///
/// Draws a bitmap image.
///
@@ -525,6 +557,16 @@ namespace Avalonia.Direct2D1.Media
PopLayer();
}
+ public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
+ {
+ // TODO: Stubs for now
+ }
+
+ public void PopBitmapBlendMode()
+ {
+ // TODO: Stubs for now
+ }
+
public void PushOpacityMask(IBrush mask, Rect bounds)
{
var parameters = new LayerParameters
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
index d04e2b3110..ec88347a17 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
@@ -1,3 +1,4 @@
+using Avalonia.Logging;
using Avalonia.Platform;
using SharpDX.Direct2D1;
@@ -8,6 +9,8 @@ namespace Avalonia.Direct2D1.Media
///
public abstract class GeometryImpl : IGeometryImpl
{
+ private const float ContourApproximation = 0.0001f;
+
public GeometryImpl(Geometry geometry)
{
Geometry = geometry;
@@ -16,6 +19,9 @@ namespace Avalonia.Direct2D1.Media
///
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
+ ///
+ public double ContourLength => Geometry.ComputeLength(null, ContourApproximation);
+
public Geometry Geometry { get; }
///
@@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media
transform.ToDirect2D()),
this);
}
+
+ ///
+ public bool TryGetPointAtDistance(double distance, out Point point)
+ {
+ Geometry.ComputePointAtLength((float)distance, ContourApproximation, out var tangentVector);
+ point = new Point(tangentVector.X, tangentVector.Y);
+ return true;
+ }
+
+ ///
+ public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
+ {
+ // Direct2D doesnt have this sadly.
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetPointAndTangentAtDistance is not available in Direct2D.");
+ point = new Point();
+ tangent = new Point();
+ return false;
+ }
+
+ public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
+ {
+ // Direct2D doesnt have this too sadly.
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetSegment is not available in Direct2D.");
+
+ segmentGeometry = null;
+ return false;
+ }
protected virtual Geometry GetSourceGeometry() => Geometry;
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index a2b1681c5c..082aca1109 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -85,6 +85,7 @@ namespace Avalonia.Win32
private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
private bool _isCloseRequested;
private bool _shown;
+ private bool _hiddenWindowIsParent;
public WindowImpl()
{
@@ -483,8 +484,8 @@ namespace Avalonia.Win32
IntPtr.Zero,
0,
0,
- requestedClientWidth + (windowRect.Width - clientRect.Width),
- requestedClientHeight + (windowRect.Height - clientRect.Height),
+ requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
+ requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
SetWindowPosFlags.SWP_RESIZE);
}
}
@@ -571,8 +572,7 @@ namespace Avalonia.Win32
public virtual void Show(bool activate)
{
- SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero);
-
+ SetParent(_parent);
ShowWindow(_showWindowState, activate);
}
@@ -581,7 +581,16 @@ namespace Avalonia.Win32
public void SetParent(IWindowImpl parent)
{
_parent = (WindowImpl)parent;
- SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent._hwnd);
+
+ var parentHwnd = _parent?._hwnd ?? IntPtr.Zero;
+
+ if (parentHwnd == IntPtr.Zero && !_windowProperties.ShowInTaskbar)
+ {
+ parentHwnd = OffscreenParentWindow.Handle;
+ _hiddenWindowIsParent = true;
+ }
+
+ SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, parentHwnd);
}
public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable);
@@ -883,20 +892,19 @@ namespace Avalonia.Win32
_isClientAreaExtended = false;
return;
}
-
- GetWindowRect(_hwnd, out var rcClient);
+ GetClientRect(_hwnd, out var rcClient);
+ GetWindowRect(_hwnd, out var rcWindow);
// Inform the application of the frame change.
SetWindowPos(_hwnd,
- IntPtr.Zero,
- rcClient.left, rcClient.top,
- rcClient.Width, rcClient.Height,
- SetWindowPosFlags.SWP_FRAMECHANGED);
+ IntPtr.Zero,
+ rcWindow.left, rcWindow.top,
+ rcClient.Width, rcClient.Height,
+ SetWindowPosFlags.SWP_FRAMECHANGED);
if (_isClientAreaExtended && WindowState != WindowState.FullScreen)
{
var margins = UpdateExtendMargins();
-
DwmExtendFrameIntoClientArea(_hwnd, ref margins);
}
else
@@ -906,6 +914,8 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
+
+ Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling));
}
if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
@@ -1094,16 +1104,38 @@ namespace Avalonia.Win32
if (newProperties.ShowInTaskbar)
{
exStyle |= WindowStyles.WS_EX_APPWINDOW;
+
+ if (_hiddenWindowIsParent)
+ {
+ // Can't enable the taskbar icon by clearing the parent window unless the window
+ // is hidden. Hide the window and show it again with the same activation state
+ // when we've finished. Interestingly it seems to work fine the other way.
+ var shown = IsWindowVisible(_hwnd);
+ var activated = GetActiveWindow() == _hwnd;
+
+ if (shown)
+ Hide();
+
+ _hiddenWindowIsParent = false;
+ SetParent(null);
+
+ if (shown)
+ Show(activated);
+ }
}
else
{
+ // To hide a non-owned window's taskbar icon we need to parent it to a hidden window.
+ if (_parent is null)
+ {
+ SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, OffscreenParentWindow.Handle);
+ _hiddenWindowIsParent = true;
+ }
+
exStyle &= ~WindowStyles.WS_EX_APPWINDOW;
}
SetExtendedStyle(exStyle);
-
- // TODO: To hide non-owned window from taskbar we need to parent it to a hidden window.
- // Otherwise it will still show in the taskbar.
}
WindowStyles style;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
index 4f2b580bce..8af638c5d7 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
@@ -377,5 +377,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
public string Greeting1 { get; set; } = "Hello";
public string Greeting2 { get; set; } = "World";
}
+
+ [Fact]
+ public void Binding_Classes_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ // Note, this test also checks `Classes` reordering, so it should be kept AFTER the last single class
+ var xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var button = window.FindControl