Browse Source

Merge branch 'master' into has-flag-fixes

pull/5773/head
Steven Kirk 5 years ago
committed by GitHub
parent
commit
47bc224ca7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ReactiveUI.props
  2. 17
      nukebuild/Build.cs
  3. 7
      nukebuild/BuildParameters.cs
  4. 2
      readme.md
  5. 6
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  6. 2
      samples/ControlCatalog/ControlCatalog.csproj
  7. 3
      samples/RenderDemo/MainWindow.xaml
  8. 89
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  9. 12
      src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs
  10. 4
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  11. 1
      src/Avalonia.Controls/ApiCompatBaseline.txt
  12. 1
      src/Avalonia.Controls/Button.cs
  13. 73
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  14. 22
      src/Avalonia.Controls/NativeControlHost.cs
  15. 2
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  16. 4
      src/Avalonia.Controls/Slider.cs
  17. 12
      src/Avalonia.Diagnostics/DevToolsExtensions.cs
  18. 24
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  19. 26
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  20. 32
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  21. 14
      src/Avalonia.Input/Gestures.cs
  22. 4
      src/Avalonia.Input/PointerEventArgs.cs
  23. 4
      src/Avalonia.Input/PointerPoint.cs
  24. 18
      src/Avalonia.Input/TappedEventArgs.cs
  25. 2
      src/Avalonia.Native/avn.idl
  26. 18
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  27. 31
      src/Avalonia.Styling/ClassBindingManager.cs
  28. 21
      src/Avalonia.Styling/Controls/Classes.cs
  29. 11
      src/Avalonia.Styling/StyledElementExtensions.cs
  30. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  31. 13
      src/Avalonia.Visuals/Media/DrawingContext.cs
  32. 57
      src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
  33. 11
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  34. 39
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  35. 68
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  36. 30
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  37. 20
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  38. 9
      src/Avalonia.Visuals/Vector.cs
  39. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  40. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  41. 97
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs
  42. 40
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlReorderClassesPropertiesTransformer.cs
  43. 10
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  44. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  45. 3
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  46. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  47. 8
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  48. 8
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  49. 16
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  50. 102
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  51. 33
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  52. 44
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  53. 33
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  54. 62
      src/Windows/Avalonia.Win32/WindowImpl.cs
  55. 32
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  56. 25
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  57. 21
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  58. 10
      tests/Avalonia.Visuals.UnitTests/VectorTests.cs
  59. 17
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="12.1.1" />
<PackageReference Include="ReactiveUI" Version="13.2.10" />
</ItemGroup>
</Project>

17
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 => _ => _

7
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()

2
readme.md

@ -2,8 +2,6 @@
<br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)
<img alt="Avalonia" src="https://user-images.githubusercontent.com/6759207/84897744-cab6d800-b0ae-11ea-8214-e5174d71f5c8.png" width="400"/>
## 📖 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.

6
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
@ -15,6 +15,10 @@
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
</ItemGroup>
<PropertyGroup>
<!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />

2
samples/ControlCatalog/ControlCatalog.csproj

@ -27,6 +27,6 @@
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

3
samples/RenderDemo/MainWindow.xaml

@ -57,6 +57,9 @@
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
</TabControl>
</DockPanel>
</Window>

89
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<PathMeasurementPage>(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);
}
}
}

12
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();
}
}

4
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",

1
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.

1
src/Avalonia.Controls/Button.cs

@ -218,6 +218,7 @@ namespace Avalonia.Controls
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
CanExecuteChanged(this, EventArgs.Empty);
}
}

73
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)
/// <summary>
/// Currently attached window.
/// </summary>
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<Panel>("PART_MinimiseButton");
var fullScreenButton = e.NameScope.Get<Panel>("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();
}
}
}

22
src/Avalonia.Controls/NativeControlHost.cs

@ -16,30 +16,16 @@ namespace Avalonia.Controls
private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost()
{
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(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();

2
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@ -16,7 +16,7 @@ namespace Avalonia.Platform
/// <summary>
/// The default for the platform.
/// </summary>
Default = SystemChrome,
Default = PreferSystemChrome,
/// <summary>
/// Use SystemChrome

4
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;

12
src/Avalonia.Diagnostics/DevToolsExtensions.cs

@ -15,7 +15,7 @@ namespace Avalonia
/// <param name="root">The window to attach DevTools to.</param>
public static void AttachDevTools(this TopLevel root)
{
DevTools.Attach(root, new KeyGesture(Key.F12));
DevTools.Attach(root, new DevToolsOptions());
}
/// <summary>
@ -27,5 +27,15 @@ namespace Avalonia
{
DevTools.Attach(root, gesture);
}
/// <summary>
/// Attaches DevTools to a window, to be opened with the specified options.
/// </summary>
/// <param name="root">The window to attach DevTools to.</param>
/// <param name="options">Additional settings of DevTools.</param>
public static void AttachDevTools(this TopLevel root, DevToolsOptions options)
{
DevTools.Attach(root, options);
}
}
}

24
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<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
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);
}

26
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -0,0 +1,26 @@
using Avalonia.Input;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Describes options used to customize DevTools.
/// </summary>
public class DevToolsOptions
{
/// <summary>
/// Gets or sets the key gesture used to open DevTools.
/// </summary>
public KeyGesture Gesture { get; set; } = new KeyGesture(Key.F12);
/// <summary>
/// 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.
/// </summary>
public bool ShowAsChildWindow { get; set; } = true;
/// <summary>
/// Gets or sets the initial size of the DevTools window. The default value is 1024x512.
/// </summary>
public Size Size { get; set; } = new Size(1024, 512);
}
}

32
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)
{

14
src/Avalonia.Input/Gestures.cs

@ -24,7 +24,7 @@ namespace Avalonia.Input
public static readonly RoutedEvent<ScrollGestureEventArgs> ScrollGestureEvent =
RoutedEvent.Register<ScrollGestureEventArgs>(
"ScrollGesture", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<ScrollGestureEventArgs> ScrollGestureEndedEvent =
RoutedEvent.Register<ScrollGestureEventArgs>(
"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));
}
}
}
}

4
src/Avalonia.Input/PointerEventArgs.cs

@ -107,7 +107,9 @@ namespace Avalonia.Input
None,
Left,
Right,
Middle
Middle,
XButton1,
XButton2
}
public class PointerPressedEventArgs : PointerEventArgs

4
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;
}
}

18
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);
}
}

2
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)]

18
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -9,18 +9,22 @@ namespace Avalonia.ReactiveUI
{
/// <summary>
/// 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.
/// </summary>
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
return builder.AfterPlatformServicesSetup(_ =>
where TAppBuilder : AppBuilderBase<TAppBuilder>, 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));
});
}
}));
}
}

31
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<string, AvaloniaProperty> s_RegisteredProperties =
new Dictionary<string, AvaloniaProperty>();
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<StyledElement, bool>("__AvaloniaReserved::Classes::" + className);
prop.Changed.Subscribe(args =>
{
var classes = ((IStyledElement)args.Sender).Classes;
classes.Set(className, args.NewValue.GetValueOrDefault());
});
return prop;
}
}
}

21
src/Avalonia.Styling/Controls/Classes.cs

@ -265,5 +265,26 @@ namespace Avalonia.Controls
$"The pseudoclass '{name}' may only be {operation} by the control itself.");
}
}
/// <summary>
/// Adds a or removes a style class to/from the collection.
/// </summary>
/// <param name="name">The class names.</param>
/// <param name="value">If true adds the class, if false, removes it.</param>
/// <remarks>
/// Only standard classes may be added or removed via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public void Set(string name, bool value)
{
if (value)
{
if (!Contains(name))
Add(name);
}
else
Remove(name);
}
}
}

11
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);
}
}

9
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

13
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -121,12 +121,23 @@ namespace Avalonia.Media
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
{
DrawGeometry(brush, pen, geometry.PlatformImpl);
}
/// <summary>
/// Draws a geometry.
/// </summary>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
Contract.Requires<ArgumentNullException>(geometry != null);
if (brush != null || PenIsVisible(pen))
{
PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
PlatformImpl.DrawGeometry(brush, pen, geometry);
}
}

57
src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs

@ -0,0 +1,57 @@
namespace Avalonia.Visuals.Media.Imaging
{
/// <summary>
/// Controls the way the bitmaps are drawn together.
/// </summary>
public enum BitmapBlendingMode
{
/// <summary>
/// Source is placed over the destination.
/// </summary>
SourceOver,
/// <summary>
/// Only the source will be present.
/// </summary>
Source,
/// <summary>
/// Only the destination will be present.
/// </summary>
Destination,
/// <summary>
/// Destination is placed over the source.
/// </summary>
DestinationOver,
/// <summary>
/// The source that overlaps the destination, replaces the destination.
/// </summary>
SourceIn,
/// <summary>
/// Destination which overlaps the source, replaces the source.
/// </summary>
DestinationIn,
/// <summary>
/// Source is placed, where it falls outside of the destination.
/// </summary>
SourceOut,
/// <summary>
/// Destination is placed, where it falls outside of the source.
/// </summary>
DestinationOut,
/// <summary>
/// Source which overlaps the destination, replaces the destination.
/// </summary>
SourceAtop,
/// <summary>
/// Destination which overlaps the source replaces the source.
/// </summary>
DestinationAtop,
/// <summary>
/// The non-overlapping regions of source and destination are combined.
/// </summary>
Xor,
/// <summary>
/// Display the sum of the source image and destination image.
/// </summary>
Plus,
}
}

11
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -148,6 +148,17 @@ namespace Avalonia.Platform
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip();
/// <summary>
/// Pushes a bitmap blending value.
/// </summary>
/// <param name="blendingMode">The bitmap blending mode.</param>
void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
/// <summary>
/// Pops the latest pushed bitmap blending value.
/// </summary>
void PopBitmapBlendMode();
/// <summary>
/// Adds a custom draw operation

39
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@ -11,6 +11,12 @@ namespace Avalonia.Platform
/// Gets the geometry's bounding rectangle.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// Gets the geometry's total length as if all its contours are placed
/// in a straight line.
/// </summary>
double ContourLength { get; }
/// <summary>
/// Gets the geometry's bounding rectangle with the specified pen.
@ -47,5 +53,38 @@ namespace Avalonia.Platform
/// <param name="transform">The transform.</param>
/// <returns>The cloned geometry.</returns>
ITransformedGeometryImpl WithTransform(Matrix transform);
/// <summary>
/// Attempts to get the corresponding point at the
/// specified distance
/// </summary>
/// <param name="distance">The contour distance to get from.</param>
/// <param name="point">The point in the specified distance.</param>
/// <returns>If there's valid point at the specified distance.</returns>
bool TryGetPointAtDistance(double distance, out Point point);
/// <summary>
/// Attempts to get the corresponding point and
/// tangent from the specified distance along the
/// contour of the geometry.
/// </summary>
/// <param name="distance">The contour distance to get from.</param>
/// <param name="point">The point in the specified distance.</param>
/// <param name="tangent">The tangent in the specified distance.</param>
/// <returns>If there's valid point and tangent at the specified distance.</returns>
bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent);
/// <summary>
/// Attempts to get the corresponding path segment
/// given by the two distances specified.
/// Imagine it like snipping a part of the current
/// geometry.
/// </summary>
/// <param name="startDistance">The contour distance to start snipping from.</param>
/// <param name="stopDistance">The contour distance to stop snipping to.</param>
/// <param name="startOnBeginFigure">If ture, the resulting snipped path will start with a BeginFigure call.</param>
/// <param name="segmentGeometry">The resulting snipped path.</param>
/// <returns>If the snipping operation is successful.</returns>
bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
}
}

68
src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs

@ -0,0 +1,68 @@
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an bitmap blending mode push or pop.
/// </summary>
internal class BitmapBlendModeNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> push.
/// </summary>
/// <param name="bitmapBlend">The <see cref="BitmapBlendingMode"/> to push.</param>
public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
{
BlendingMode = bitmapBlend;
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> pop.
/// </summary>
public BitmapBlendModeNode()
{
}
/// <inheritdoc/>
public Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
/// </summary>
public BitmapBlendingMode? BlendingMode { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (BlendingMode.HasValue)
{
context.PushBitmapBlendMode(BlendingMode.Value);
}
else
{
context.PopBitmapBlendMode();
}
}
public void Dispose()
{
}
}
}

30
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <inheritdoc/>
public void PopBitmapBlendMode()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacity()
{
@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <inheritdoc/>
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationindex;
}
}
public readonly struct UpdateState : IDisposable
{
public UpdateState(

20
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph
/// The scaling mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
/// <summary>
/// The bitmap blending mode.
/// </summary>
/// <value>
/// The blending mode.
/// </value>
public BitmapBlendingMode BitmapBlendingMode { get; }
/// <summary>
/// Determines if this draw operation equals another.
@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph
public bool Equals(Matrix transform, IRef<IBitmapImpl> 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;
}
/// <inheritdoc/>

9
src/Avalonia.Visuals/Vector.cs

@ -82,6 +82,15 @@ namespace Avalonia
public static Vector operator *(Vector vector, double scale)
=> Multiply(vector, scale);
/// <summary>
/// Scales a vector.
/// </summary>
/// <param name="vector">The vector.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The scaled vector.</returns>
public static Vector operator *(double scale, Vector vector)
=> Multiply(vector, scale);
/// <summary>
/// Scales a vector.
/// </summary>

5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -41,10 +41,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
InsertBefore<PropertyReferenceResolver>(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
InsertAfter<PropertyReferenceResolver>(
new AvaloniaXamlIlAvaloniaPropertyResolver());
new AvaloniaXamlIlAvaloniaPropertyResolver(),
new AvaloniaXamlIlReorderClassesPropertiesTransformer()
);
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlBindingPathParser(),

5
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;

97
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<IXamlILEmitter>
{
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<IXamlType> Parameters { get; }
}
class ClassBindingSetter : IXamlEmitablePropertySetter<IXamlILEmitter>
{
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<IXamlType> Parameters { get; }
}
}
}

40
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;
}
}
}

10
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);
}
}

2
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;
}
}
}

3
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();
}

2
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();
}

8
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));
}
}

8
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;
}
}

16
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -23,9 +23,11 @@ namespace Avalonia.Skia
private readonly Vector _dpi;
private readonly Stack<PaintWrapper> _maskStack = new Stack<PaintWrapper>();
private readonly Stack<double> _opacityStack = new Stack<double>();
private readonly Stack<BitmapBlendingMode> _blendingModeStack = new Stack<BitmapBlendingMode>();
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();
}
/// <inheritdoc />
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_blendingModeStack.Push(_currentBlendingMode);
_currentBlendingMode = blendingMode;
}
/// <inheritdoc />
public void PopBitmapBlendMode()
{
_currentBlendingMode = _blendingModeStack.Pop();
}
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
/// <inheritdoc />

102
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;
}
}
/// <inheritdoc />
public abstract Rect Bounds { get; }
/// <inheritdoc />
public double ContourLength
{
get
{
if (EffectivePath is null)
return 0;
return (double)CachedPathMeasure?.Length;
}
}
public abstract SKPath EffectivePath { get; }
/// <inheritdoc />
@ -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
/// <returns>True, if point is contained in a path.</returns>
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);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
return new TransformedGeometryImpl(this, transform);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <summary>
/// Invalidate all caches. Call after chaining path contents.
/// </summary>
@ -115,12 +195,12 @@ namespace Avalonia.Skia
private struct PathCache
{
private float _cachedStrokeWidth;
/// <summary>
/// Tolerance for two stroke widths to be deemed equal
/// </summary>
public const float Tolerance = float.Epsilon;
/// <summary>
/// Cached contour path.
/// </summary>

33
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);

44
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);
}
}
/// <summary>
/// Draws a bitmap image.
/// </summary>
@ -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

33
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
/// </summary>
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
/// <inheritdoc/>
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
/// <inheritdoc />
public double ContourLength => Geometry.ComputeLength(null, ContourApproximation);
public Geometry Geometry { get; }
/// <inheritdoc/>
@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media
transform.ToDirect2D()),
this);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}

62
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;

32
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Button Name='button' Classes.MyClass='{Binding Foo}' Classes.MySecondClass='True' Classes='foo bar'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
button.DataContext = new { Foo = true };
window.ApplyTemplate();
Assert.True(button.Classes.Contains("MyClass"));
Assert.True(button.Classes.Contains("MySecondClass"));
Assert.True(button.Classes.Contains("foo"));
Assert.True(button.Classes.Contains("bar"));
button.DataContext = new { Foo = false };
Assert.False(button.Classes.Contains("MyClass"));
Assert.True(button.Classes.Contains("MySecondClass"));
Assert.True(button.Classes.Contains("foo"));
Assert.True(button.Classes.Contains("bar"));
}
}
}
}

25
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -7,6 +7,31 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class DataTemplateTests : XamlTestBase
{
[Fact]
public void DataTemplate_Can_Be_Empty()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate DataType='{x:Type sys:String}' />
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.Null(target.Presenter.Child);
}
}
[Fact]
public void DataTemplate_Can_Contain_Name()
{

21
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@ -30,6 +30,8 @@ namespace Avalonia.UnitTests
public IGeometryImpl SourceGeometry { get; }
public Rect Bounds => _context.CalculateBounds();
public double ContourLength { get; }
public Matrix Transform { get; }
@ -69,6 +71,25 @@ namespace Avalonia.UnitTests
return new MockStreamGeometryImpl(transform, _context);
}
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 MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();

10
tests/Avalonia.Visuals.UnitTests/VectorTests.cs

@ -105,5 +105,15 @@ namespace Avalonia.Visuals.UnitTests
Assert.Equal(expected, Vector.Multiply(vector, 2));
}
[Fact]
public void Scale_Vector_Should_Be_Commutative()
{
var vector = new Vector(10, 2);
var expected = vector * 2;
Assert.Equal(expected, 2 * vector);
}
}
}

17
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -112,6 +112,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
}
}
public double ContourLength { get; }
public IStreamGeometryImpl Clone()
{
return this;
@ -151,6 +153,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
public bool TryGetPointAtDistance(double distance, out Point point)
{
throw new NotImplementedException();
}
public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
{
throw new NotImplementedException();
}
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
{
throw new NotImplementedException();
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();

Loading…
Cancel
Save