diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 9a0da4aa9b..acff8cc117 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,17 +1,31 @@
-This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
+## What does the pull request do?
+
-- What does the pull request do?
-- What is the current behavior?
-- What is the updated/expected behavior with this PR?
-- How was the solution implemented (if it's not obvious)?
-Checklist:
+## What is the current behavior?
+
+
+
+## What is the updated/expected behavior with this PR?
+
+
+
+## How was the solution implemented (if it's not obvious)?
+
+
+
+## Checklist
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
-If the pull request fixes issue(s) list them like this:
+## Breaking changes
+
+
+## Fixed issues
+
diff --git a/Avalonia.sln b/Avalonia.sln
index 2f7560049c..d6472503fe 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -196,9 +196,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -1819,6 +1821,30 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1875,6 +1901,7 @@ Global
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index a43c99e978..cf8e0fd13a 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
+
diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs
index afd1950859..65ba5e9756 100644
--- a/nukebuild/BuildParameters.cs
+++ b/nukebuild/BuildParameters.cs
@@ -109,7 +109,7 @@ public partial class Build
if (!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
- Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta";
+ Version += "-cibuild" + int.Parse(Environment.GetEnvironmentVariable("BUILD_BUILDID")).ToString("0000000") + "-beta";
}
PublishTestResults = true;
diff --git a/readme.md b/readme.md
index 9280125323..12f683bd55 100644
--- a/readme.md
+++ b/readme.md
@@ -2,9 +2,9 @@
# Avalonia
-| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective |
-|---|---|---|
-| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [](#backers) [](#sponsors) |
+| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet |
+|---|---|---|---|---|
+| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [](#backers) [](#sponsors) | [](https://www.nuget.org/packages/Avalonia) | [](https://www.myget.org/gallery/avalonia-ci) |
## About
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index cbdc0120d4..ed078e3d1b 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -33,10 +33,10 @@
+
-
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
index 187250b6ea..e2e9fbd21c 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
@@ -40,10 +40,13 @@ namespace ControlCatalog.Pages
};
this.FindControl("Dialog").Click += delegate
{
- new MainWindow().ShowDialog(GetWindow());
+ var window = new Window();
+ window.Height = 200;
+ window.Width = 200;
+ window.Content = new TextBlock { Text = "Hello world!" };
+ window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ window.ShowDialog(GetWindow());
};
-
-
}
Window GetWindow() => (Window)this.VisualRoot;
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
index fd66185832..b5c6892885 100644
--- a/samples/ControlCatalog/Pages/ScreenPage.cs
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
+using Avalonia.Rendering;
namespace ControlCatalog.Pages
{
@@ -23,6 +24,7 @@ namespace ControlCatalog.Pages
base.Render(context);
Window w = (Window)VisualRoot;
Screen[] screens = w.Screens.All;
+ var scaling = ((IRenderRoot)w).RenderScaling;
Pen p = new Pen(Brushes.Black);
if (screens != null)
@@ -56,7 +58,7 @@ namespace ControlCatalog.Pages
text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
- text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}";
+ text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
}
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index f97a3064e7..625b344b8c 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -26,7 +26,7 @@
PositionChanged { get; set; }
+ public Action PositionChanged { get; set; }
public Action Deactivated { get; set; }
public PopupImpl() : base(ActivityTracker.Current, true)
@@ -36,7 +36,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IScreenImpl Screen { get; }
- public Point Position
+ public PixelPoint Position
{
get { return _position; }
set
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 8e8886269c..f42faeaa63 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -99,14 +99,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
}
- public Point PointToClient(Point point)
+ public Point PointToClient(PixelPoint point)
{
- return point;
+ return point.ToPoint(1);
}
- public Point PointToScreen(Point point)
+ public PixelPoint PointToScreen(Point point)
{
- return point;
+ return PixelPoint.FromPoint(point, 1);
}
public void SetCursor(IPlatformHandle cursor)
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 4b0116a536..953132116c 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -21,7 +21,7 @@ namespace Avalonia
///
public static readonly object UnsetValue = new Unset();
- private static int s_nextId = 1;
+ private static int s_nextId;
private readonly Subject _initialized;
private readonly Subject _changed;
private readonly PropertyMetadata _defaultMetadata;
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index e29e7339ae..5fcdf76c0f 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -13,6 +13,8 @@ namespace Avalonia
///
public class AvaloniaPropertyRegistry
{
+ private readonly Dictionary _properties =
+ new Dictionary();
private readonly Dictionary> _registered =
new Dictionary>();
private readonly Dictionary> _attached =
@@ -28,6 +30,11 @@ namespace Avalonia
public static AvaloniaPropertyRegistry Instance { get; }
= new AvaloniaPropertyRegistry();
+ ///
+ /// Gets a list of all registered properties.
+ ///
+ internal IReadOnlyCollection Properties => _properties.Values;
+
///
/// Gets all non-attached s registered on a type.
///
@@ -148,6 +155,16 @@ namespace Avalonia
return FindRegistered(o.GetType(), name);
}
+ ///
+ /// Finds a registered property by Id.
+ ///
+ /// The property Id.
+ /// The registered property or null if no matching property found.
+ internal AvaloniaProperty FindRegistered(int id)
+ {
+ return id < _properties.Count ? _properties[id] : null;
+ }
+
///
/// Checks whether a is registered on a type.
///
@@ -202,7 +219,12 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
-
+
+ if (!_properties.ContainsKey(property.Id))
+ {
+ _properties.Add(property.Id, property);
+ }
+
_registeredCache.Clear();
}
@@ -237,7 +259,7 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
-
+
_attachedCache.Clear();
}
}
diff --git a/src/Avalonia.Base/Data/Converters/ObjectConverters.cs b/src/Avalonia.Base/Data/Converters/ObjectConverters.cs
new file mode 100644
index 0000000000..3cadfd5b47
--- /dev/null
+++ b/src/Avalonia.Base/Data/Converters/ObjectConverters.cs
@@ -0,0 +1,24 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+
+namespace Avalonia.Data.Converters
+{
+ ///
+ /// Provides a set of useful s for working with objects.
+ ///
+ public static class ObjectConverters
+ {
+ ///
+ /// A value converter that returns true if the input object is a null reference.
+ ///
+ public static readonly IValueConverter IsNull =
+ new FuncValueConverter(x => x is null);
+
+ ///
+ /// A value converter that returns true if the input object is not null.
+ ///
+ public static readonly IValueConverter IsNotNull =
+ new FuncValueConverter(x => !(x is null));
+ }
+}
diff --git a/src/Avalonia.Base/Data/Converters/StringConverters.cs b/src/Avalonia.Base/Data/Converters/StringConverters.cs
index 0101cf5d29..42eb8e1d81 100644
--- a/src/Avalonia.Base/Data/Converters/StringConverters.cs
+++ b/src/Avalonia.Base/Data/Converters/StringConverters.cs
@@ -12,13 +12,13 @@ namespace Avalonia.Data.Converters
///
/// A value converter that returns true if the input string is null or an empty string.
///
- public static readonly IValueConverter NullOrEmpty =
+ public static readonly IValueConverter IsNullOrEmpty =
new FuncValueConverter(string.IsNullOrEmpty);
///
/// A value converter that returns true if the input string is not null or empty.
///
- public static readonly IValueConverter NotNullOrEmpty =
+ public static readonly IValueConverter IsNotNullOrEmpty =
new FuncValueConverter(x => !string.IsNullOrEmpty(x));
}
}
diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs
index c4ffa839e0..f1717bde3b 100644
--- a/src/Avalonia.Base/Data/Core/BindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs
@@ -177,7 +177,7 @@ namespace Avalonia.Data.Core
protected override void Subscribed(IObserver observer, bool first)
{
- if (!first && _value != null && _value.TryGetTarget(out var val) == true)
+ if (!first && _value != null && _value.TryGetTarget(out var val))
{
observer.OnNext(val);
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
index 14ca8ee79e..c41097c274 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
@@ -2,6 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
@@ -10,12 +13,19 @@ namespace Avalonia.Data.Core.Plugins
///
public class ObservableStreamPlugin : IStreamPlugin
{
+ static MethodInfo observableSelect;
+
///
/// Checks whether this plugin handles the specified value.
///
/// A weak reference to the value.
/// True if the plugin can handle the value; otherwise false.
- public virtual bool Match(WeakReference reference) => reference.Target is IObservable;
+ public virtual bool Match(WeakReference reference)
+ {
+ return reference.Target.GetType().GetInterfaces().Any(x =>
+ x.IsGenericType &&
+ x.GetGenericTypeDefinition() == typeof(IObservable<>));
+ }
///
/// Starts producing output based on the specified value.
@@ -26,7 +36,69 @@ namespace Avalonia.Data.Core.Plugins
///
public virtual IObservable Start(WeakReference reference)
{
- return reference.Target as IObservable;
+ var target = reference.Target;
+
+ // If the observable returns a reference type then we can cast it.
+ if (target is IObservable result)
+ {
+ return result;
+ };
+
+ // If the observable returns a value type then we need to call Observable.Select on it.
+ // First get the type of T in `IObservable`.
+ var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
+ x.IsGenericType &&
+ x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
+
+ // Get the Observable.Select method.
+ var select = GetObservableSelect(sourceType);
+
+ // Make a Box<> delegate of the correct type.
+ var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
+ var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)
+ .MakeGenericMethod(sourceType)
+ .CreateDelegate(funcType);
+
+ // Call Observable.Select(target, box);
+ return (IObservable)select.Invoke(
+ null,
+ new object[] { target, box });
+ }
+
+ private static MethodInfo GetObservableSelect(Type source)
+ {
+ return GetObservableSelect().MakeGenericMethod(source, typeof(object));
}
+
+ private static MethodInfo GetObservableSelect()
+ {
+ if (observableSelect == null)
+ {
+ observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
+ {
+ if (x.Name == nameof(Observable.Select) &&
+ x.ContainsGenericParameters &&
+ x.GetGenericArguments().Length == 2)
+ {
+ var parameters = x.GetParameters();
+
+ if (parameters.Length == 2 &&
+ parameters[0].ParameterType.IsConstructedGenericType &&
+ parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
+ parameters[1].ParameterType.IsConstructedGenericType &&
+ parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ });
+ }
+
+ return observableSelect;
+ }
+
+ private static object Box(T value) => (object)value;
}
}
diff --git a/src/Avalonia.Base/ISupportInitialize.cs b/src/Avalonia.Base/ISupportInitialize.cs
deleted file mode 100644
index 04e3d72e6c..0000000000
--- a/src/Avalonia.Base/ISupportInitialize.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-namespace Avalonia
-{
- ///
- /// Specifies that this object supports a simple, transacted notification for batch
- /// initialization.
- ///
- public interface ISupportInitialize
- {
- ///
- /// Signals the object that initialization is starting.
- ///
- void BeginInit();
-
- ///
- /// Signals the object that initialization is complete.
- ///
- void EndInit();
- }
-}
diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
index 22e5c952bf..66024236da 100644
--- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
+++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
@@ -4,6 +4,8 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
+using System.Xml.Linq;
+using System.Linq;
// ReSharper disable AssignNullToNotNullAttribute
@@ -19,10 +21,20 @@ namespace Avalonia.Utilities
{
var ver = new BinaryReader(stream).ReadInt32();
if (ver > LastKnownVersion)
- throw new Exception("Resources index format version is not known");
- var index = (AvaloniaResourcesIndex)
- new DataContractSerializer(typeof(AvaloniaResourcesIndex)).ReadObject(stream);
- return index.Entries;
+ throw new Exception("Resources index format version is not known");
+
+ var assetDoc = XDocument.Load(stream);
+ XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
+ List entries=
+ (from entry in assetDoc.Root.Element(assetNs + "Entries").Elements(assetNs + "AvaloniaResourcesIndexEntry")
+ select new AvaloniaResourcesIndexEntry
+ {
+ Path = entry.Element(assetNs + "Path").Value,
+ Offset = int.Parse(entry.Element(assetNs + "Offset").Value),
+ Size = int.Parse(entry.Element(assetNs + "Size").Value)
+ }).ToList();
+
+ return entries;
}
public static void Write(Stream stream, List entries)
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index adbe89aceb..d520e2b80a 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/src/Avalonia.Base/ValueStore.cs
@@ -7,13 +7,21 @@ namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
+ private struct Entry
+ {
+ internal int PropertyId;
+ internal object Value;
+ }
+
private readonly AvaloniaObject _owner;
- private readonly Dictionary _values =
- new Dictionary();
+ private Entry[] _entries;
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
+
+ // The last item in the list is always int.MaxValue
+ _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } };
}
public IDisposable AddBinding(
@@ -23,7 +31,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
- if (_values.TryGetValue(property, out var v))
+ if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@@ -31,13 +39,13 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
- _values[property] = priorityValue;
+ SetValueInternal(property, priorityValue);
}
}
else
{
priorityValue = CreatePriorityValue(property);
- _values.Add(property, priorityValue);
+ AddValueInternal(property, priorityValue);
}
return priorityValue.Add(source, (int)priority);
@@ -47,7 +55,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
- if (_values.TryGetValue(property, out var v))
+ if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@@ -55,7 +63,7 @@ namespace Avalonia
{
if (priority == (int)BindingPriority.LocalValue)
{
- _values[property] = Validate(property, value);
+ SetValueInternal(property, Validate(property, value));
Changed(property, priority, v, value);
return;
}
@@ -63,7 +71,7 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
- _values[property] = priorityValue;
+ SetValueInternal(property, priorityValue);
}
}
}
@@ -76,14 +84,14 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue)
{
- _values.Add(property, Validate(property, value));
+ AddValueInternal(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
- _values.Add(property, priorityValue);
+ AddValueInternal(property, priorityValue);
}
}
@@ -100,13 +108,22 @@ namespace Avalonia
_owner.PriorityValueChanged(property, priority, oldValue, newValue);
}
- public IDictionary GetSetValues() => _values;
+ public IDictionary GetSetValues()
+ {
+ var dict = new Dictionary(_entries.Length - 1);
+ for (int i = 0; i < _entries.Length - 1; ++i)
+ {
+ dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
+ }
+
+ return dict;
+ }
public object GetValue(AvaloniaProperty property)
{
var result = AvaloniaProperty.UnsetValue;
- if (_values.TryGetValue(property, out var value))
+ if (TryGetValue(property, out var value))
{
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
}
@@ -116,12 +133,12 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property)
{
- return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
+ return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
}
public bool IsSet(AvaloniaProperty property)
{
- if (_values.TryGetValue(property, out var value))
+ if (TryGetValue(property, out var value))
{
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
}
@@ -131,7 +148,7 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property)
{
- if (_values.TryGetValue(property, out var value))
+ if (TryGetValue(property, out var value))
{
(value as PriorityValue)?.Revalidate();
}
@@ -178,5 +195,103 @@ namespace Avalonia
(_deferredSetter = new DeferredSetter());
}
}
+
+ private bool TryGetValue(AvaloniaProperty property, out object value)
+ {
+ (int index, bool found) = TryFindEntry(property.Id);
+ if (!found)
+ {
+ value = null;
+ return false;
+ }
+
+ value = _entries[index].Value;
+ return true;
+ }
+
+ private void AddValueInternal(AvaloniaProperty property, object value)
+ {
+ Entry[] entries = new Entry[_entries.Length + 1];
+
+ for (int i = 0; i < _entries.Length; ++i)
+ {
+ if (_entries[i].PropertyId > property.Id)
+ {
+ if (i > 0)
+ {
+ Array.Copy(_entries, 0, entries, 0, i);
+ }
+
+ entries[i] = new Entry { PropertyId = property.Id, Value = value };
+ Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
+ break;
+ }
+ }
+
+ _entries = entries;
+ }
+
+ private void SetValueInternal(AvaloniaProperty property, object value)
+ {
+ _entries[TryFindEntry(property.Id).Item1].Value = value;
+ }
+
+ private (int, bool) TryFindEntry(int propertyId)
+ {
+ if (_entries.Length <= 12)
+ {
+ // For small lists, we use an optimized linear search. Since the last item in the list
+ // is always int.MaxValue, we can skip a conditional branch in each iteration.
+ // By unrolling the loop, we can skip another unconditional branch in each iteration.
+
+ if (_entries[0].PropertyId >= propertyId) return (0, _entries[0].PropertyId == propertyId);
+ if (_entries[1].PropertyId >= propertyId) return (1, _entries[1].PropertyId == propertyId);
+ if (_entries[2].PropertyId >= propertyId) return (2, _entries[2].PropertyId == propertyId);
+ if (_entries[3].PropertyId >= propertyId) return (3, _entries[3].PropertyId == propertyId);
+ if (_entries[4].PropertyId >= propertyId) return (4, _entries[4].PropertyId == propertyId);
+ if (_entries[5].PropertyId >= propertyId) return (5, _entries[5].PropertyId == propertyId);
+ if (_entries[6].PropertyId >= propertyId) return (6, _entries[6].PropertyId == propertyId);
+ if (_entries[7].PropertyId >= propertyId) return (7, _entries[7].PropertyId == propertyId);
+ if (_entries[8].PropertyId >= propertyId) return (8, _entries[8].PropertyId == propertyId);
+ if (_entries[9].PropertyId >= propertyId) return (9, _entries[9].PropertyId == propertyId);
+ if (_entries[10].PropertyId >= propertyId) return (10, _entries[10].PropertyId == propertyId);
+ }
+ else
+ {
+ int low = 0;
+ int high = _entries.Length;
+ int id;
+
+ while (high - low > 3)
+ {
+ int pivot = (high + low) / 2;
+ id = _entries[pivot].PropertyId;
+
+ if (propertyId == id)
+ return (pivot, true);
+
+ if (propertyId <= id)
+ high = pivot;
+ else
+ low = pivot + 1;
+ }
+
+ do
+ {
+ id = _entries[low].PropertyId;
+
+ if (id == propertyId)
+ return (low, true);
+
+ if (id > propertyId)
+ break;
+
+ ++low;
+ }
+ while (low < high);
+ }
+
+ return (0, false);
+ }
}
}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index 1bc402bc2f..b054804c86 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -1893,7 +1893,7 @@ namespace Avalonia.Controls
{
bool callTextChanged = false;
// Update the Text dependency property
- if ((userInitiated == null || userInitiated == true) && Text != value)
+ if ((userInitiated ?? true) && Text != value)
{
_ignoreTextPropertyChange++;
Text = value;
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index 20ec581108..6da6da54a5 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -45,8 +45,6 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren);
- PseudoClass(ContentProperty, x => x != null, ":valid");
- PseudoClass(ContentProperty, x => x == null, ":invalid");
}
///
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index a00d586233..a7ee027e70 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index 224af979ab..43beb923e5 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
index 8b39cc03b8..c4f83ffd54 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Avalonia.Styling;
namespace Avalonia.Controls.Embedding.Offscreen
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 37bb72e75a..d328e1ee88 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -49,9 +49,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action ScalingChanged { get; set; }
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
- public virtual Point PointToClient(Point point) => point;
+ public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);
- public virtual Point PointToScreen(Point point) => point;
+ public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public virtual void SetCursor(IPlatformHandle cursor)
{
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index 72379e7b53..c696fe7975 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -99,5 +99,22 @@ namespace Avalonia.Controls
return new Size();
}
}
+
+ ///
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ var source = Source;
+
+ if (source != null)
+ {
+ var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
+ var result = Stretch.CalculateSize(finalSize, sourceSize);
+ return result;
+ }
+ else
+ {
+ return new Size();
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 055d49fb0b..99e00ce72e 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -421,7 +421,7 @@ namespace Avalonia.Controls
}
///
- /// Called when the property changes.
+ /// Called when the property changes.
///
/// The property change event.
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index 1f8e991d2e..18cef7d64e 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -287,6 +287,13 @@ namespace Avalonia.Controls
ValueProperty.Changed.Subscribe(OnValueChanged);
}
+ ///
+ protected override void OnLostFocus(RoutedEventArgs e)
+ {
+ CommitInput();
+ base.OnLostFocus(e);
+ }
+
///
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
diff --git a/src/Avalonia.Controls/PixelPointEventArgs.cs b/src/Avalonia.Controls/PixelPointEventArgs.cs
new file mode 100644
index 0000000000..2456d0aea4
--- /dev/null
+++ b/src/Avalonia.Controls/PixelPointEventArgs.cs
@@ -0,0 +1,27 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides data for events.
+ ///
+ public class PixelPointEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The data.
+ public PixelPointEventArgs(PixelPoint point)
+ {
+ Point = point;
+ }
+
+ ///
+ /// Gets the data.
+ ///
+ public PixelPoint Point { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
index 60e25d2be6..8d8ce35c38 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
+++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
@@ -82,14 +82,14 @@ namespace Avalonia.Platform
///
/// The point in screen coordinates.
/// The point in client coordinates.
- Point PointToClient(Point point);
+ Point PointToClient(PixelPoint point);
///
/// Converts a point from client to screen coordinates.
///
/// The point in client coordinates.
/// The point in screen coordinates.
- Point PointToScreen(Point point);
+ PixelPoint PointToScreen(Point point);
///
/// Sets the cursor associated with the toplevel.
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index e788b3c73e..b37521de30 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -27,14 +27,14 @@ namespace Avalonia.Platform
void BeginResizeDrag(WindowEdge edge);
///
- /// Gets position of the window relatively to the screen
+ /// Gets the position of the window in device pixels.
///
- Point Position { get; set; }
+ PixelPoint Position { get; set; }
///
/// Gets or sets a method called when the window's position changes.
///
- Action PositionChanged { get; set; }
+ Action PositionChanged { get; set; }
///
/// Activates the window.
diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs
index 5aa8c2c392..e7c811235c 100644
--- a/src/Avalonia.Controls/Platform/Screen.cs
+++ b/src/Avalonia.Controls/Platform/Screen.cs
@@ -2,17 +2,17 @@
{
public class Screen
{
- public Rect Bounds { get; }
+ public PixelRect Bounds { get; }
- public Rect WorkingArea { get; }
+ public PixelRect WorkingArea { get; }
public bool Primary { get; }
- public Screen(Rect bounds, Rect workingArea, bool primary)
+ public Screen(PixelRect bounds, PixelRect workingArea, bool primary)
{
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/PointEventArgs.cs b/src/Avalonia.Controls/PointEventArgs.cs
deleted file mode 100644
index 4173d367d0..0000000000
--- a/src/Avalonia.Controls/PointEventArgs.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Controls
-{
- ///
- /// Provides data for events.
- ///
- public class PointEventArgs : EventArgs
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The data.
- public PointEventArgs(Point point)
- {
- Point = point;
- }
-
- ///
- /// Gets the data.
- ///
- public Point Point { get; }
- }
-}
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index c05c1672f8..30330ef9ac 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -285,7 +285,7 @@ namespace Avalonia.Controls.Presenters
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
- if (scrollable.IsLogicalScrollEnabled == true)
+ if (scrollable.IsLogicalScrollEnabled)
{
_logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(CanHorizontallyScrollProperty)
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 5e79711f5a..d9070197b6 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives
/// Gets the position for the popup based on the placement properties.
///
/// The popup's position in screen coordinates.
- protected virtual Point GetPosition()
+ protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
@@ -388,35 +388,31 @@ namespace Avalonia.Controls.Primitives
return result;
}
- internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
+ internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
- var zero = default(Point);
- var mode = placement;
-
- if (target?.GetVisualRoot() == null)
- {
- mode = PlacementMode.Pointer;
- }
+ var root = target?.GetVisualRoot();
+ var mode = root != null ? placement : PlacementMode.Pointer;
+ var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
- // Scales the Horizontal and Vertical offset to screen co-ordinates.
- var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling,
- verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling);
- return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
+ var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
+ var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
+ return new PixelPoint(
+ screenOffset.X + mouseOffset.X,
+ screenOffset.Y + mouseOffset.Y);
}
- return default(Point);
+ return default;
case PlacementMode.Bottom:
- return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ??
- zero;
+ return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
- return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero;
+ return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 286f7df3b9..59a8933b4b 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -85,18 +85,18 @@ namespace Avalonia.Controls.Primitives
if (screen != null)
{
var scaling = VisualRoot.RenderScaling;
-
- var screenX = Position.X + (Bounds.Width * scaling) - screen.Bounds.X;
- var screenY = Position.Y + (Bounds.Height * scaling) - screen.Bounds.Y;
+ var bounds = PixelRect.FromRect(Bounds, scaling);
+ var screenX = Position.X + bounds.Width - screen.Bounds.X;
+ var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
- Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
+ Position = Position.WithX(Position.X - screenX - bounds.Width);
}
if (screenY > screen.Bounds.Height)
{
- Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
+ Position = Position.WithY(Position.Y - screenY - bounds.Height);
}
}
}
diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs
index 2bfddc048b..5a90adadbd 100644
--- a/src/Avalonia.Controls/Screens.cs
+++ b/src/Avalonia.Controls/Screens.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Controls
_iScreenImpl = iScreenImpl;
}
- public Screen ScreenFromBounds(Rect bounds){
+ public Screen ScreenFromBounds(PixelRect bounds){
Screen currMaxScreen = null;
double maxAreaSize = 0;
@@ -39,16 +39,16 @@ namespace Avalonia.Controls
return currMaxScreen;
}
- public Screen ScreenFromPoint(Point point)
+ public Screen ScreenFromPoint(PixelPoint point)
{
- return All.FirstOrDefault(x=>x.Bounds.Contains(point));
+ return All.FirstOrDefault(x => x.Bounds.Contains(point));
}
public Screen ScreenFromVisual(IVisual visual)
{
- Point tl = visual.PointToScreen(visual.Bounds.TopLeft);
- Point br = visual.PointToScreen(visual.Bounds.BottomRight);
- return ScreenFromBounds(new Rect(tl,br));
+ var tl = visual.PointToScreen(visual.Bounds.TopLeft);
+ var br = visual.PointToScreen(visual.Bounds.BottomRight);
+ return ScreenFromBounds(new PixelRect(tl, br));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index f77c43acd0..0387328a46 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -20,7 +20,10 @@ namespace Avalonia.Controls.Shapes
AvaloniaProperty.Register(nameof(Stroke));
public static readonly StyledProperty> StrokeDashArrayProperty =
- AvaloniaProperty.Register>("StrokeDashArray");
+ AvaloniaProperty.Register>(nameof(StrokeDashArray));
+
+ public static readonly StyledProperty StrokeDashOffsetProperty =
+ AvaloniaProperty.Register(nameof(StrokeDashOffset));
public static readonly StyledProperty StrokeThicknessProperty =
AvaloniaProperty.Register(nameof(StrokeThickness));
@@ -103,6 +106,12 @@ namespace Avalonia.Controls.Shapes
get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
+
+ public double StrokeDashOffset
+ {
+ get { return GetValue(StrokeDashOffsetProperty); }
+ set { SetValue(StrokeDashOffsetProperty, value); }
+ }
public double StrokeThickness
{
@@ -124,7 +133,7 @@ namespace Avalonia.Controls.Shapes
if (geometry != null)
{
- var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray),
+ var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset),
StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin);
context.DrawGeometry(Fill, pen, geometry);
}
diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs
index 8eaf166f57..d6537ebbca 100644
--- a/src/Avalonia.Controls/TabControl.cs
+++ b/src/Avalonia.Controls/TabControl.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Generators;
+using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@@ -65,6 +66,10 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
AffectsMeasure(TabStripPlacementProperty);
+ ContentControlMixin.Attach(
+ SelectedContentProperty,
+ x => x.LogicalChildren,
+ "PART_SelectedContentHost");
}
///
@@ -142,7 +147,7 @@ namespace Avalonia.Controls
ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter");
- ContentPart = e.NameScope.Get("PART_Content");
+ ContentPart = e.NameScope.Get("PART_SelectedContentHost");
}
///
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 5443baf225..32c40847c5 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -98,6 +98,11 @@ namespace Avalonia.Controls
Renderer = impl.CreateRenderer(this);
+ if (Renderer != null)
+ {
+ Renderer.SceneInvalidated += SceneInvalidated;
+ }
+
impl.SetInputRoot(this);
impl.Closed = HandleClosed;
@@ -131,6 +136,11 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Fired when the window is opened.
+ ///
+ public event EventHandler Opened;
+
///
/// Fired when the window is closed.
///
@@ -231,17 +241,17 @@ namespace Avalonia.Controls
{
PlatformImpl?.Invalidate(rect);
}
-
+
///
- Point IRenderRoot.PointToClient(Point p)
+ Point IRenderRoot.PointToClient(PixelPoint p)
{
- return PlatformImpl?.PointToClient(p) ?? default(Point);
+ return PlatformImpl?.PointToClient(p) ?? default;
}
///
- Point IRenderRoot.PointToScreen(Point p)
+ PixelPoint IRenderRoot.PointToScreen(Point p)
{
- return PlatformImpl?.PointToScreen(p) ?? default(Point);
+ return PlatformImpl?.PointToScreen(p) ?? default;
}
///
@@ -306,6 +316,12 @@ namespace Avalonia.Controls
$"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
}
+ ///
+ /// Raises the event.
+ ///
+ /// The event args.
+ protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e);
+
///
/// Tries to get a service from an , logging a
/// warning if not found.
@@ -349,5 +365,10 @@ namespace Avalonia.Controls
{
_inputManager.ProcessInput(e);
}
+
+ private void SceneInvalidated(object sender, SceneInvalidatedEventArgs e)
+ {
+ (this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect);
+ }
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 46916efc1d..f5af6774b5 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -388,45 +388,49 @@ namespace Avalonia.Controls
PlatformImpl?.Show();
Renderer?.Start();
}
- SetWindowStartupLocation();
+ SetWindowStartupLocation(Owner?.PlatformImpl);
+ OnOpened(EventArgs.Empty);
}
///
/// Shows the window as a dialog.
///
+ /// The dialog's owner window.
///
/// A task that can be used to track the lifetime of the dialog.
///
- public Task ShowDialog(Window parent)
+ public Task ShowDialog(Window owner)
{
- return ShowDialog(parent);
+ return ShowDialog(owner);
}
-
///
/// Shows the window as a dialog.
///
///
/// The type of the result produced by the dialog.
///
+ /// The dialog's owner window.
/// .
/// A task that can be used to retrieve the result of the dialog when it closes.
///
- public Task ShowDialog(Window parent) => ShowDialog(parent.PlatformImpl);
-
+ public Task ShowDialog(Window owner) => ShowDialog(owner.PlatformImpl);
+
///
/// Shows the window as a dialog.
///
///
/// The type of the result produced by the dialog.
///
+ /// The dialog's owner window.
/// .
/// A task that can be used to retrieve the result of the dialog when it closes.
///
- public Task ShowDialog(IWindowImpl parent)
+ public Task ShowDialog(IWindowImpl owner)
{
- if(parent == null)
- throw new ArgumentNullException(nameof(parent));
+ if(owner == null)
+ throw new ArgumentNullException(nameof(owner));
+
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
@@ -435,15 +439,15 @@ namespace Avalonia.Controls
AddWindow(this);
EnsureInitialized();
- SetWindowStartupLocation();
IsVisible = true;
LayoutManager.ExecuteInitialLayoutPass(this);
+ var result = new TaskCompletionSource();
+
using (BeginAutoSizing())
{
- PlatformImpl?.ShowDialog(parent);
- var result = new TaskCompletionSource();
+ PlatformImpl?.ShowDialog(owner);
Renderer?.Start();
Observable.FromEventPattern(
@@ -452,29 +456,43 @@ namespace Avalonia.Controls
.Take(1)
.Subscribe(_ =>
{
- parent.Activate();
+ owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
-
- return result.Task;
+ OnOpened(EventArgs.Empty);
}
+
+ SetWindowStartupLocation(owner);
+ return result.Task;
}
- void SetWindowStartupLocation()
+ private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
{
+ var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1;
+
+ // TODO: We really need non-client size here.
+ var rect = new PixelRect(
+ PixelPoint.Origin,
+ PixelSize.FromSize(ClientSize, scaling));
+
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
- var screen = Screens.ScreenFromPoint(Bounds.Position);
+ var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
- Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
+ {
+ Position = screen.WorkingArea.CenterRect(rect).Position;
+ }
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
{
- if (Owner != null)
+ if (owner != null)
{
- var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
- Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
+ // TODO: We really need non-client size here.
+ var ownerRect = new PixelRect(
+ owner.Position,
+ PixelSize.FromSize(owner.ClientSize, scaling));
+ Position = ownerRect.CenterRect(rect).Position;
}
}
}
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index f609432545..363af05a0b 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -81,7 +82,7 @@ namespace Avalonia.Controls
///
/// Fired when the window position is changed.
///
- public event EventHandler PositionChanged;
+ public event EventHandler PositionChanged;
[CanBeNull]
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
@@ -98,9 +99,9 @@ namespace Avalonia.Controls
///
/// Gets or sets the window position in screen coordinates.
///
- public Point Position
+ public PixelPoint Position
{
- get { return PlatformImpl?.Position ?? default(Point); }
+ get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
set
{
if (PlatformImpl is IWindowBaseImpl impl)
@@ -163,7 +164,7 @@ namespace Avalonia.Controls
}
///
- /// Shows the popup.
+ /// Shows the window.
///
public virtual void Show()
{
@@ -181,6 +182,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show();
Renderer?.Start();
+ OnOpened(EventArgs.Empty);
}
finally
{
@@ -267,9 +269,9 @@ namespace Avalonia.Controls
/// .
///
/// The window position.
- private void HandlePositionChanged(Point pos)
+ private void HandlePositionChanged(PixelPoint pos)
{
- PositionChanged?.Invoke(this, new PointEventArgs(pos));
+ PositionChanged?.Invoke(this, new PixelPointEventArgs(pos));
}
///
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index cff59f2f8b..dc01bcb07e 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -35,8 +35,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
- public Point Position { get; set; }
- public Action PositionChanged { get; set; }
+ public PixelPoint Position { get; set; }
+ public Action PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Func Closing { get; set; }
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index 8f5b97af41..d849bfc021 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -29,8 +29,8 @@ namespace Avalonia.DesignerSupport.Remote
public Func Closing { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
- public Point Position { get; set; }
- public Action PositionChanged { get; set; }
+ public PixelPoint Position { get; set; }
+ public Action PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action WindowStateChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
@@ -45,9 +45,9 @@ namespace Avalonia.DesignerSupport.Remote
{
}
- public Point PointToClient(Point point) => point;
+ public Point PointToClient(PixelPoint p) => p.ToPoint(1);
- public Point PointToScreen(Point point) => point;
+ public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
{
@@ -157,6 +157,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1;
public Screen[] AllScreens { get; } =
- {new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)};
+ {new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true)};
}
}
diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs
index 58e2222295..a1d1bb3eb8 100644
--- a/src/Avalonia.Input/IMouseDevice.cs
+++ b/src/Avalonia.Input/IMouseDevice.cs
@@ -11,6 +11,6 @@ namespace Avalonia.Input
///
/// Gets the mouse position, in screen coordinates.
///
- Point Position { get; }
+ PixelPoint Position { get; }
}
}
diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs
index 8613717f60..932cbc989f 100644
--- a/src/Avalonia.Input/IPointerDevice.cs
+++ b/src/Avalonia.Input/IPointerDevice.cs
@@ -12,5 +12,7 @@ namespace Avalonia.Input
void Capture(IInputElement control);
Point GetPosition(IVisual relativeTo);
+
+ void SceneInvalidated(IInputRoot root, Rect rect);
}
}
diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs
index e01dedeede..7945ee8ee4 100644
--- a/src/Avalonia.Input/MouseDevice.cs
+++ b/src/Avalonia.Input/MouseDevice.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Input
///
/// Gets the mouse position, in screen coordinates.
///
- public Point Position
+ public PixelPoint Position
{
get;
protected set;
@@ -104,6 +104,23 @@ namespace Avalonia.Input
ProcessRawEvent(margs);
}
+ public void SceneInvalidated(IInputRoot root, Rect rect)
+ {
+ var clientPoint = root.PointToClient(Position);
+
+ if (rect.Contains(clientPoint))
+ {
+ if (Captured == null)
+ {
+ SetPointerOver(this, root, clientPoint);
+ }
+ else
+ {
+ SetPointerOver(this, root, Captured);
+ }
+ }
+ }
+
private void ProcessRawEvent(RawMouseEventArgs e)
{
Contract.Requires(e != null);
@@ -305,16 +322,41 @@ namespace Avalonia.Input
Device = device,
};
+ if (element!=null && !element.IsAttachedToVisualTree)
+ {
+ // element has been removed from visual tree so do top down cleanup
+ if (root.IsPointerOver)
+ ClearChildrenPointerOver(e, root,true);
+ }
while (element != null)
{
e.Source = element;
+ e.Handled = false;
element.RaiseEvent(e);
element = (IInputElement)element.VisualParent;
}
-
+
root.PointerOverElement = null;
}
+ private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot)
+ {
+ foreach (IInputElement el in element.VisualChildren)
+ {
+ if (el.IsPointerOver)
+ {
+ ClearChildrenPointerOver(e, el, true);
+ break;
+ }
+ }
+ if(clearRoot)
+ {
+ e.Source = element;
+ e.Handled = false;
+ element.RaiseEvent(e);
+ }
+ }
+
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p)
{
Contract.Requires(device != null);
@@ -361,13 +403,18 @@ namespace Avalonia.Input
el = root.PointerOverElement;
e.RoutedEvent = InputElement.PointerLeaveEvent;
+ if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
+ {
+ ClearChildrenPointerOver(e,branch,false);
+ }
+
while (el != null && el != branch)
{
e.Source = el;
e.Handled = false;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
- }
+ }
el = root.PointerOverElement = element;
e.RoutedEvent = InputElement.PointerEnterEvent;
diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs
index c9d1256e91..a77e7d4ff8 100644
--- a/src/Avalonia.Native/Helpers.cs
+++ b/src/Avalonia.Native/Helpers.cs
@@ -12,11 +12,21 @@ namespace Avalonia.Native
return new Point(pt.X, pt.Y);
}
+ public static PixelPoint ToAvaloniaPixelPoint(this AvnPoint pt)
+ {
+ return new PixelPoint((int)pt.X, (int)pt.Y);
+ }
+
public static AvnPoint ToAvnPoint (this Point pt)
{
return new AvnPoint { X = pt.X, Y = pt.Y };
}
+ public static AvnPoint ToAvnPoint(this PixelPoint pt)
+ {
+ return new AvnPoint { X = pt.X, Y = pt.Y };
+ }
+
public static AvnSize ToAvnSize (this Size size)
{
return new AvnSize { Height = size.Height, Width = size.Width };
@@ -31,5 +41,10 @@ namespace Avalonia.Native
{
return new Rect(rect.X, rect.Y, rect.Width, rect.Height);
}
+
+ public static PixelRect ToAvaloniaPixelRect(this AvnRect rect)
+ {
+ return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
+ }
}
}
diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs
index 079062c254..c1edd6c846 100644
--- a/src/Avalonia.Native/ScreenImpl.cs
+++ b/src/Avalonia.Native/ScreenImpl.cs
@@ -29,7 +29,10 @@ namespace Avalonia.Native
{
var screen = _native.GetScreen(i);
- result[i] = new Screen(screen.Bounds.ToAvaloniaRect(), screen.WorkingArea.ToAvaloniaRect(), screen.Primary);
+ result[i] = new Screen(
+ screen.Bounds.ToAvaloniaPixelRect(),
+ screen.WorkingArea.ToAvaloniaPixelRect(),
+ screen.Primary);
}
return result;
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 629c91a2e8..3c8d8b597f 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -159,7 +159,7 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
{
- _parent.PositionChanged?.Invoke(position.ToAvaloniaPoint());
+ _parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint());
}
void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
@@ -275,20 +275,20 @@ namespace Avalonia.Native
}
- public Point Position
+ public PixelPoint Position
{
- get => _native.GetPosition().ToAvaloniaPoint();
+ get => _native.GetPosition().ToAvaloniaPixelPoint();
set => _native.SetPosition(value.ToAvnPoint());
}
- public Point PointToClient(Point point)
+ public Point PointToClient(PixelPoint point)
{
return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
}
- public Point PointToScreen(Point point)
+ public PixelPoint PointToScreen(Point point)
{
- return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPoint();
+ return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
}
public void Hide()
@@ -320,7 +320,7 @@ namespace Avalonia.Native
_native.Cursor = newCursor.Cursor;
}
- public Action PositionChanged { get; set; }
+ public Action PositionChanged { get; set; }
public Action Input { get; set; }
diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs
index 2a75e9458e..b14932acfe 100644
--- a/src/Avalonia.OpenGL/EglDisplay.cs
+++ b/src/Avalonia.OpenGL/EglDisplay.cs
@@ -86,18 +86,8 @@ namespace Avalonia.OpenGL
if (_contextAttributes == null)
throw new OpenGlException("No suitable EGL config was found");
-
- GlInterface = new GlInterface((proc, optional) =>
- {
- using (var u = new Utf8Buffer(proc))
- {
- var rv = _egl.GetProcAddress(u);
- if (rv == IntPtr.Zero && !optional)
- throw new OpenGlException("Missing function " + proc);
- return rv;
- }
- });
+ GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b));
}
public EglDisplay() : this(new EglInterface())
diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs
index c41a01340c..00fcd97af0 100644
--- a/src/Avalonia.OpenGL/EglInterface.cs
+++ b/src/Avalonia.OpenGL/EglInterface.cs
@@ -34,61 +34,61 @@ namespace Avalonia.OpenGL
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int EglGetError();
- [EntryPoint("eglGetError")]
+ [GlEntryPoint("eglGetError")]
public EglGetError GetError { get; }
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
- [EntryPoint("eglGetDisplay")]
+ [GlEntryPoint("eglGetDisplay")]
public EglGetDisplay GetDisplay { get; }
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
- [EntryPoint("eglGetPlatformDisplayEXT", true)]
+ [GlEntryPoint("eglGetPlatformDisplayEXT", true)]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
- [EntryPoint("eglInitialize")]
+ [GlEntryPoint("eglInitialize")]
public EglInitialize Initialize { get; }
public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
- [EntryPoint("eglGetProcAddress")]
+ [GlEntryPoint("eglGetProcAddress")]
public EglGetProcAddress GetProcAddress { get; }
public delegate bool EglBindApi(int api);
- [EntryPoint("eglBindAPI")]
+ [GlEntryPoint("eglBindAPI")]
public EglBindApi BindApi { get; }
public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
- [EntryPoint("eglChooseConfig")]
+ [GlEntryPoint("eglChooseConfig")]
public EglChooseConfig ChooseConfig { get; }
public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs);
- [EntryPoint("eglCreateContext")]
+ [GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
- [EntryPoint("eglCreatePbufferSurface")]
+ [GlEntryPoint("eglCreatePbufferSurface")]
public EglCreatePBufferSurface CreatePBufferSurface { get; }
public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
- [EntryPoint("eglMakeCurrent")]
+ [GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
- [EntryPoint("eglDestroySurface")]
+ [GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
- [EntryPoint("eglSwapBuffers")]
+ [GlEntryPoint("eglSwapBuffers")]
public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
public delegate IntPtr
EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
- [EntryPoint("eglCreateWindowSurface")]
+ [GlEntryPoint("eglCreateWindowSurface")]
public EglCreateWindowSurface CreateWindowSurface { get; }
public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
- [EntryPoint("eglGetConfigAttrib")]
+ [GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
diff --git a/src/Avalonia.OpenGL/EntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs
similarity index 56%
rename from src/Avalonia.OpenGL/EntryPointAttribute.cs
rename to src/Avalonia.OpenGL/GlEntryPointAttribute.cs
index 241f517df9..016c3d0af9 100644
--- a/src/Avalonia.OpenGL/EntryPointAttribute.cs
+++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs
@@ -2,12 +2,13 @@ using System;
namespace Avalonia.OpenGL
{
- class EntryPointAttribute : Attribute
+ [AttributeUsage(AttributeTargets.Property)]
+ public class GlEntryPointAttribute : Attribute
{
public string EntryPoint { get; }
public bool Optional { get; }
- public EntryPointAttribute(string entryPoint, bool optional = false)
+ public GlEntryPointAttribute(string entryPoint, bool optional = false)
{
EntryPoint = entryPoint;
Optional = optional;
diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs
index f23e1c5829..718afc4a94 100644
--- a/src/Avalonia.OpenGL/GlInterface.cs
+++ b/src/Avalonia.OpenGL/GlInterface.cs
@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
+using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
@@ -7,43 +8,56 @@ namespace Avalonia.OpenGL
public class GlInterface : GlInterfaceBase
{
- private readonly Func _getProcAddress;
+ public string Version { get; }
public GlInterface(Func getProcAddress) : base(getProcAddress)
{
- _getProcAddress = getProcAddress;
+ var versionPtr = GetString(GlConsts.GL_VERSION);
+ if (versionPtr != IntPtr.Zero)
+ Version = Marshal.PtrToStringAnsi(versionPtr);
}
- public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
+ public GlInterface(Func n) : this(ConvertNative(n))
+ {
+
+ }
+
+ public static GlInterface FromNativeUtf8GetProcAddress(Func getProcAddress) =>
+ new GlInterface(getProcAddress);
+
public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int GlGetError();
- [EntryPoint("glGetError")]
+ [GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate void GlClearStencil(int s);
- [EntryPoint("glClearStencil")]
+ [GlEntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; }
public delegate void GlClearColor(int r, int g, int b, int a);
- [EntryPoint("glClearColor")]
+ [GlEntryPoint("glClearColor")]
public GlClearColor ClearColor { get; }
public delegate void GlClear(int bits);
- [EntryPoint("glClear")]
+ [GlEntryPoint("glClear")]
public GlClear Clear { get; }
public delegate void GlViewport(int x, int y, int width, int height);
- [EntryPoint("glViewport")]
+ [GlEntryPoint("glViewport")]
public GlViewport Viewport { get; }
- [EntryPoint("glFlush")]
+ [GlEntryPoint("glFlush")]
public Action Flush { get; }
+ public delegate IntPtr GlGetString(int v);
+ [GlEntryPoint("glGetString")]
+ public GlGetString GetString { get; }
+
public delegate void GlGetIntegerv(int name, out int rv);
- [EntryPoint("glGetIntegerv")]
+ [GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs
index 33191a6567..89ec0e77d4 100644
--- a/src/Avalonia.OpenGL/GlInterfaceBase.cs
+++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs
@@ -1,16 +1,20 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
+using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase
{
+
+ private readonly Func _getProcAddress;
public GlInterfaceBase(Func getProcAddress)
{
+ _getProcAddress = getProcAddress;
foreach (var prop in this.GetType().GetProperties())
{
- var a = prop.GetCustomAttribute();
+ var a = prop.GetCustomAttribute();
if (a != null)
{
var fieldName = $"<{prop.Name}>k__BackingField";
@@ -24,5 +28,26 @@ namespace Avalonia.OpenGL
}
}
}
+
+ protected static Func ConvertNative(Func func) =>
+ (proc, optional) =>
+ {
+ using (var u = new Utf8Buffer(proc))
+ {
+ var rv = func(u);
+ if (rv == IntPtr.Zero && !optional)
+ throw new OpenGlException("Missing function " + proc);
+ return rv;
+ }
+ };
+
+ public GlInterfaceBase(Func nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress))
+ {
+
+ }
+
+ public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
+ public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional);
+
}
}
diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
index cf386a235e..e1db604e95 100644
--- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
+++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
@@ -29,8 +29,8 @@ namespace Avalonia
{
var windowLoaded = Observable
.FromEventPattern(
- x => window.Initialized += x,
- x => window.Initialized -= x)
+ x => window.Opened += x,
+ x => window.Opened -= x)
.Select(args => true);
var windowUnloaded = Observable
.FromEventPattern(
@@ -59,4 +59,4 @@ namespace Avalonia
.DistinctUntilChanged();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
index 04c6f100a6..43e2ef93b6 100644
--- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
+++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
@@ -16,7 +16,7 @@ namespace Avalonia
public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class
{
public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty
- .Register, TViewModel>(nameof(ViewModel));
+ .Register, TViewModel>(nameof(ViewModel));
///
/// Initializes a new instance of the class.
@@ -41,4 +41,4 @@ namespace Avalonia
set => ViewModel = (TViewModel)value;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs
index f6bb73129f..925fe10681 100644
--- a/src/Avalonia.Remote.Protocol/MetsysBson.cs
+++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs
@@ -1190,10 +1190,6 @@ namespace Metsys.Bson
object container = null;
var property = typeHelper.FindProperty(name);
var propertyType = property != null ? property.Type : _typeMap.ContainsKey(storageType) ? _typeMap[storageType] : typeof(object);
- if (property == null && typeHelper.Expando == null)
- {
- throw new BsonException(string.Format("Deserialization failed: type {0} does not have a property named {1}", type.FullName, name));
- }
if (property != null && property.Setter == null)
{
container = property.Getter(instance);
@@ -1201,7 +1197,8 @@ namespace Metsys.Bson
var value = isNull ? null : DeserializeValue(propertyType, storageType, container, options);
if (property == null)
{
- ((IDictionary)typeHelper.Expando.Getter(instance))[name] = value;
+ if (typeHelper.Expando != null)
+ ((IDictionary)typeHelper.Expando.Getter(instance))[name] = value;
}
else if (container == null && value != null && !property.Ignored)
{
diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs
index 562dbdf8f9..d01265c9f4 100644
--- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs
+++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs
@@ -46,7 +46,7 @@ namespace Avalonia.Remote.Protocol
{
try
{
- var cl = await server.AcceptTcpClientAsync();
+ var cl = await server.AcceptTcpClientAsync().ConfigureAwait(false);
AcceptNew();
await Task.Run(async () =>
{
@@ -54,7 +54,7 @@ namespace Avalonia.Remote.Protocol
var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0));
cb(t);
await tcs.Task;
- });
+ }).ConfigureAwait(false);
}
catch
{
@@ -69,7 +69,7 @@ namespace Avalonia.Remote.Protocol
public async Task Connect(IPAddress address, int port)
{
var c = new TcpClient();
- await c.ConnectAsync(address, port);
+ await c.ConnectAsync(address, port).ConfigureAwait(false);
return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose);
}
}
diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
index 1e821b7c24..d7919af9d9 100644
--- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
+++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Remote.Protocol
public Task Send(object data)
{
- var tcs = new TaskCompletionSource();
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
lock (_lock)
{
if (!_workerIsAlive)
@@ -79,8 +79,9 @@ namespace Avalonia.Remote.Protocol
});
if (_signal != null)
{
- _signal.SetResult(0);
+ var signal = _signal;
_signal = null;
+ signal.SetResult(0);
}
}
return tcs.Task;
@@ -98,4 +99,4 @@ namespace Avalonia.Remote.Protocol
remove => _onException.Remove(value);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Styling/Styling/NotSelector.cs b/src/Avalonia.Styling/Styling/NotSelector.cs
new file mode 100644
index 0000000000..bcf76620be
--- /dev/null
+++ b/src/Avalonia.Styling/Styling/NotSelector.cs
@@ -0,0 +1,72 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Styling
+{
+ ///
+ /// The `:not()` style selector.
+ ///
+ internal class NotSelector : Selector
+ {
+ private readonly Selector _previous;
+ private readonly Selector _argument;
+ private string _selectorString;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The previous selector.
+ /// The selector to be not-ed.
+ public NotSelector(Selector previous, Selector argument)
+ {
+ _previous = previous;
+ _argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
+ }
+
+ ///
+ public override bool InTemplate => _argument.InTemplate;
+
+ ///
+ public override bool IsCombinator => false;
+
+ ///
+ public override Type TargetType => _previous?.TargetType;
+
+ ///
+ public override string ToString()
+ {
+ if (_selectorString == null)
+ {
+ _selectorString = ":not(" + _argument.ToString() + ")";
+ }
+
+ return _selectorString;
+ }
+
+ protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
+ {
+ var innerResult = _argument.Match(control, subscribe);
+
+ switch (innerResult.Result)
+ {
+ case SelectorMatchResult.AlwaysThisInstance:
+ return SelectorMatch.NeverThisInstance;
+ case SelectorMatchResult.AlwaysThisType:
+ return SelectorMatch.NeverThisType;
+ case SelectorMatchResult.NeverThisInstance:
+ return SelectorMatch.AlwaysThisInstance;
+ case SelectorMatchResult.NeverThisType:
+ return SelectorMatch.AlwaysThisType;
+ case SelectorMatchResult.Sometimes:
+ return new SelectorMatch(innerResult.Activator.Select(x => !x));
+ default:
+ throw new InvalidOperationException("Invalid SelectorMatchResult.");
+ }
+ }
+
+ protected override Selector MovePrevious() => _previous;
+ }
+}
diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs
index c91cc7af04..4284c7e798 100644
--- a/src/Avalonia.Styling/Styling/Selectors.cs
+++ b/src/Avalonia.Styling/Styling/Selectors.cs
@@ -94,6 +94,17 @@ namespace Avalonia.Styling
}
}
+ ///
+ /// Returns a selector which inverts the results of selector argument.
+ ///
+ /// The previous selector.
+ /// The selector to be not-ed.
+ /// The selector.
+ public static Selector Not(this Selector previous, Func argument)
+ {
+ return new NotSelector(previous, argument(null));
+ }
+
///
/// Returns a selector which matches a type.
///
diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml
index e2301b9b09..f87ae2674f 100644
--- a/src/Avalonia.Themes.Default/CheckBox.xaml
+++ b/src/Avalonia.Themes.Default/CheckBox.xaml
@@ -1,9 +1,10 @@
-
+
-
diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml
index a492698feb..60c70ebfda 100644
--- a/src/Avalonia.Themes.Default/TabControl.xaml
+++ b/src/Avalonia.Themes.Default/TabControl.xaml
@@ -18,7 +18,7 @@
MemberSelector="{TemplateBinding MemberSelector}" >
+ Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/>
@@ -36,7 +36,7 @@
+ IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
PlatformImpl.Item.Dpi;
///
- public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi);
+ public Size Size => PlatformImpl.Item.PixelSize.ToSizeWithDpi(Dpi);
///
public PixelSize PixelSize => PlatformImpl.Item.PixelSize;
diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs
new file mode 100644
index 0000000000..995781ee9f
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/PixelPoint.cs
@@ -0,0 +1,201 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using Avalonia.Utilities;
+
+namespace Avalonia
+{
+ ///
+ /// Represents a point in device pixels.
+ ///
+ public readonly struct PixelPoint
+ {
+ ///
+ /// A point representing 0,0.
+ ///
+ public static readonly PixelPoint Origin = new PixelPoint(0, 0);
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The X co-ordinate.
+ /// The Y co-ordinate.
+ public PixelPoint(int x, int y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ ///
+ /// Gets the X co-ordinate.
+ ///
+ public int X { get; }
+
+ ///
+ /// Gets the Y co-ordinate.
+ ///
+ public int Y { get; }
+
+ ///
+ /// Checks for equality between two s.
+ ///
+ /// The first point.
+ /// The second point.
+ /// True if the points are equal; otherwise false.
+ public static bool operator ==(PixelPoint left, PixelPoint right)
+ {
+ return left.X == right.X && left.Y == right.Y;
+ }
+
+ ///
+ /// Checks for inequality between two s.
+ ///
+ /// The first point.
+ /// The second point.
+ /// True if the points are unequal; otherwise false.
+ public static bool operator !=(PixelPoint left, PixelPoint right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The .
+ public static PixelPoint Parse(string s)
+ {
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
+ {
+ return new PixelPoint(
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32());
+ }
+ }
+
+ ///
+ /// Checks for equality between a point and an object.
+ ///
+ /// The object.
+ ///
+ /// True if is a point that equals the current point.
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is PixelPoint other)
+ {
+ return this == other;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns a hash code for a .
+ ///
+ /// The hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = (hash * 23) + X.GetHashCode();
+ hash = (hash * 23) + Y.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Returns a new with the same Y co-ordinate and the specified X co-ordinate.
+ ///
+ /// The X co-ordinate.
+ /// The new .
+ public PixelPoint WithX(int x) => new PixelPoint(x, Y);
+
+ ///
+ /// Returns a new with the same X co-ordinate and the specified Y co-ordinate.
+ ///
+ /// The Y co-ordinate.
+ /// The new .
+ public PixelPoint WithY(int y) => new PixelPoint(X, y);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent point.
+ public Point ToPoint(double scale) => new Point(X / scale, Y / scale);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent point.
+ public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The point.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint(
+ (int)(point.X * scale),
+ (int)(point.Y * scale));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The point.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint(
+ (int)(point.X * scale.X),
+ (int)(point.Y * scale.Y));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The point.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96);
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The point.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96));
+
+ ///
+ /// Returns the string representation of the point.
+ ///
+ /// The string representation of the point.
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs
new file mode 100644
index 0000000000..9c8e5ad1c4
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/PixelRect.cs
@@ -0,0 +1,436 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using Avalonia.Utilities;
+
+namespace Avalonia
+{
+ ///
+ /// Represents a rectangle in device pixels.
+ ///
+ public readonly struct PixelRect
+ {
+ ///
+ /// An empty rectangle.
+ ///
+ public static readonly PixelRect Empty = default;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The X position.
+ /// The Y position.
+ /// The width.
+ /// The height.
+ public PixelRect(int x, int y, int width, int height)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The size of the rectangle.
+ public PixelRect(PixelSize size)
+ {
+ X = 0;
+ Y = 0;
+ Width = size.Width;
+ Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The position of the rectangle.
+ /// The size of the rectangle.
+ public PixelRect(PixelPoint position, PixelSize size)
+ {
+ X = position.X;
+ Y = position.Y;
+ Width = size.Width;
+ Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The top left position of the rectangle.
+ /// The bottom right position of the rectangle.
+ public PixelRect(PixelPoint topLeft, PixelPoint bottomRight)
+ {
+ X = topLeft.X;
+ Y = topLeft.Y;
+ Width = bottomRight.X - topLeft.X;
+ Height = bottomRight.Y - topLeft.Y;
+ }
+
+ ///
+ /// Gets the X position.
+ ///
+ public int X { get; }
+
+ ///
+ /// Gets the Y position.
+ ///
+ public int Y { get; }
+
+ ///
+ /// Gets the width.
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height.
+ ///
+ public int Height { get; }
+
+ ///
+ /// Gets the position of the rectangle.
+ ///
+ public PixelPoint Position => new PixelPoint(X, Y);
+
+ ///
+ /// Gets the size of the rectangle.
+ ///
+ public PixelSize Size => new PixelSize(Width, Height);
+
+ ///
+ /// Gets the right position of the rectangle.
+ ///
+ public int Right => X + Width;
+
+ ///
+ /// Gets the bottom position of the rectangle.
+ ///
+ public int Bottom => Y + Height;
+
+ ///
+ /// Gets the top left point of the rectangle.
+ ///
+ public PixelPoint TopLeft => new PixelPoint(X, Y);
+
+ ///
+ /// Gets the top right point of the rectangle.
+ ///
+ public PixelPoint TopRight => new PixelPoint(Right, Y);
+
+ ///
+ /// Gets the bottom left point of the rectangle.
+ ///
+ public PixelPoint BottomLeft => new PixelPoint(X, Bottom);
+
+ ///
+ /// Gets the bottom right point of the rectangle.
+ ///
+ public PixelPoint BottomRight => new PixelPoint(Right, Bottom);
+
+ ///
+ /// Gets the center point of the rectangle.
+ ///
+ public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
+
+ ///
+ /// Gets a value that indicates whether the rectangle is empty.
+ ///
+ public bool IsEmpty => Width == 0 && Height == 0;
+
+ ///
+ /// Checks for equality between two s.
+ ///
+ /// The first rect.
+ /// The second rect.
+ /// True if the rects are equal; otherwise false.
+ public static bool operator ==(PixelRect left, PixelRect right)
+ {
+ return left.Position == right.Position && left.Size == right.Size;
+ }
+
+ ///
+ /// Checks for inequality between two s.
+ ///
+ /// The first rect.
+ /// The second rect.
+ /// True if the rects are unequal; otherwise false.
+ public static bool operator !=(PixelRect left, PixelRect right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Determines whether a point in in the bounds of the rectangle.
+ ///
+ /// The point.
+ /// true if the point is in the bounds of the rectangle; otherwise false.
+ public bool Contains(PixelPoint p)
+ {
+ return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
+ }
+
+ ///
+ /// Determines whether the rectangle fully contains another rectangle.
+ ///
+ /// The rectangle.
+ /// true if the rectangle is fully contained; otherwise false.
+ public bool Contains(PixelRect r)
+ {
+ return Contains(r.TopLeft) && Contains(r.BottomRight);
+ }
+
+ ///
+ /// Centers another rectangle in this rectangle.
+ ///
+ /// The rectangle to center.
+ /// The centered rectangle.
+ public PixelRect CenterRect(PixelRect rect)
+ {
+ return new PixelRect(
+ X + ((Width - rect.Width) / 2),
+ Y + ((Height - rect.Height) / 2),
+ rect.Width,
+ rect.Height);
+ }
+
+ ///
+ /// Returns a boolean indicating whether the given object is equal to this rectangle.
+ ///
+ /// The object to compare against.
+ /// True if the object is equal to this rectangle; false otherwise.
+ public override bool Equals(object obj)
+ {
+ if (obj is PixelRect other)
+ {
+ return this == other;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ /// The hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = (hash * 23) + X.GetHashCode();
+ hash = (hash * 23) + Y.GetHashCode();
+ hash = (hash * 23) + Width.GetHashCode();
+ hash = (hash * 23) + Height.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Gets the intersection of two rectangles.
+ ///
+ /// The other rectangle.
+ /// The intersection.
+ public PixelRect Intersect(PixelRect rect)
+ {
+ var newLeft = (rect.X > X) ? rect.X : X;
+ var newTop = (rect.Y > Y) ? rect.Y : Y;
+ var newRight = (rect.Right < Right) ? rect.Right : Right;
+ var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom;
+
+ if ((newRight > newLeft) && (newBottom > newTop))
+ {
+ return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop);
+ }
+ else
+ {
+ return Empty;
+ }
+ }
+
+ ///
+ /// Determines whether a rectangle intersects with this rectangle.
+ ///
+ /// The other rectangle.
+ ///
+ /// True if the specified rectangle intersects with this one; otherwise false.
+ ///
+ public bool Intersects(PixelRect rect)
+ {
+ return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
+ }
+
+ ///
+ /// Gets the union of two rectangles.
+ ///
+ /// The other rectangle.
+ /// The union.
+ public PixelRect Union(PixelRect rect)
+ {
+ if (IsEmpty)
+ {
+ return rect;
+ }
+ else if (rect.IsEmpty)
+ {
+ return this;
+ }
+ else
+ {
+ var x1 = Math.Min(X, rect.X);
+ var x2 = Math.Max(Right, rect.Right);
+ var y1 = Math.Min(Y, rect.Y);
+ var y2 = Math.Max(Bottom, rect.Bottom);
+
+ return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2));
+ }
+ }
+
+ ///
+ /// Returns a new with the specified X position.
+ ///
+ /// The x position.
+ /// The new .
+ public PixelRect WithX(int x)
+ {
+ return new PixelRect(x, Y, Width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified Y position.
+ ///
+ /// The y position.
+ /// The new .
+ public PixelRect WithY(int y)
+ {
+ return new PixelRect(X, y, Width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified width.
+ ///
+ /// The width.
+ /// The new .
+ public PixelRect WithWidth(int width)
+ {
+ return new PixelRect(X, Y, width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified height.
+ ///
+ /// The height.
+ /// The new .
+ public PixelRect WithHeight(int height)
+ {
+ return new PixelRect(X, Y, Width, Height);
+ }
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent rect.
+ public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent rect.
+ public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent rect.
+ public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent rect.
+ public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The rect.
+ /// The scaling factor.
+ /// The device-independent rect.
+ public static PixelRect FromRect(Rect rect, double scale) => new PixelRect(
+ PixelPoint.FromPoint(rect.Position, scale),
+ PixelSize.FromSize(rect.Size, scale));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The rect.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect(
+ PixelPoint.FromPoint(rect.Position, scale),
+ PixelSize.FromSize(rect.Size, scale));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The rect.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect(
+ PixelPoint.FromPointWithDpi(rect.Position, dpi),
+ PixelSize.FromSizeWithDpi(rect.Size, dpi));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The rect.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect(
+ PixelPoint.FromPointWithDpi(rect.Position, dpi),
+ PixelSize.FromSizeWithDpi(rect.Size, dpi));
+
+ ///
+ /// Returns the string representation of the rectangle.
+ ///
+ /// The string representation of the rectangle.
+ public override string ToString()
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}, {1}, {2}, {3}",
+ X,
+ Y,
+ Width,
+ Height);
+ }
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The parsed .
+ public static PixelRect Parse(string s)
+ {
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect"))
+ {
+ return new PixelRect(
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32()
+ );
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs
index 6785b51716..b903b804f9 100644
--- a/src/Avalonia.Visuals/Media/PixelSize.cs
+++ b/src/Avalonia.Visuals/Media/PixelSize.cs
@@ -72,7 +72,7 @@ namespace Avalonia
/// The .
public static PixelSize Parse(string s)
{
- using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize"))
{
return new PixelSize(
tokenizer.ReadInt32(),
@@ -126,13 +126,29 @@ namespace Avalonia
/// The new .
public PixelSize WithHeight(int height) => new PixelSize(Width, height);
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent size.
+ public Size ToSize(double scale) => new Size(Width / scale, Height / scale);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent size.
+ public Size ToSize(Vector scale) => new Size(Width / scale.X, Height / scale.Y);
+
///
/// Converts the to a device-independent using the
/// specified dots per inch (DPI).
///
/// The dots per inch.
/// The device-independent size.
- public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96));
+ public Size ToSizeWithDpi(double dpi) => ToSize(dpi / 96);
///
/// Converts the to a device-independent using the
@@ -140,7 +156,27 @@ namespace Avalonia
///
/// The dots per inch.
/// The device-independent size.
- public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96));
+ public Size ToSizeWithDpi(Vector dpi) => ToSize(new Vector(dpi.X / 96, dpi.Y / 96));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The size.
+ /// The scaling factor.
+ /// The device-independent size.
+ public static PixelSize FromSize(Size size, double scale) => new PixelSize(
+ (int)Math.Ceiling(size.Width * scale),
+ (int)Math.Ceiling(size.Height * scale));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The size.
+ /// The scaling factor.
+ /// The device-independent size.
+ public static PixelSize FromSize(Size size, Vector scale) => new PixelSize(
+ (int)Math.Ceiling(size.Width * scale.X),
+ (int)Math.Ceiling(size.Height * scale.Y));
///
/// Converts a to device pixels using the specified dots per inch (DPI).
@@ -148,9 +184,7 @@ namespace Avalonia
/// The size.
/// The dots per inch.
/// The device-independent size.
- public static PixelSize FromSize(Size size, double dpi) => new PixelSize(
- (int)(size.Width * (dpi / 96)),
- (int)(size.Height * (dpi / 96)));
+ public static PixelSize FromSizeWithDpi(Size size, double dpi) => FromSize(size, dpi / 96);
///
/// Converts a to device pixels using the specified dots per inch (DPI).
@@ -158,9 +192,7 @@ namespace Avalonia
/// The size.
/// The dots per inch.
/// The device-independent size.
- public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize(
- (int)Math.Ceiling(size.Width * (dpi.X / 96)),
- (int)Math.Ceiling(size.Height * (dpi.Y / 96)));
+ public static PixelSize FromSizeWithDpi(Size size, Vector dpi) => FromSize(size, new Vector(dpi.X / 96, dpi.Y / 96));
///
/// Returns the string representation of the size.
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index 9893091e7a..3bc5e92fb4 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -29,6 +29,7 @@ namespace Avalonia.Rendering
private readonly ISceneBuilder _sceneBuilder;
private bool _running;
+ private bool _disposed;
private volatile IRef _scene;
private DirtyVisuals _dirty;
private IRef _overlay;
@@ -99,6 +100,9 @@ namespace Avalonia.Rendering
///
public string DebugFramesPath { get; set; }
+ ///
+ public event EventHandler SceneInvalidated;
+
///
/// Gets the render layers.
///
@@ -122,6 +126,9 @@ namespace Avalonia.Rendering
{
lock (_sceneLock)
{
+ if (_disposed)
+ return;
+ _disposed = true;
var scene = _scene;
_scene = null;
scene?.Dispose();
@@ -168,7 +175,7 @@ namespace Avalonia.Rendering
var t = (IRenderLoopTask)this;
if(t.NeedsUpdate)
UpdateScene();
- if(_scene.Item != null)
+ if(_scene?.Item != null)
Render(true);
}
@@ -248,15 +255,19 @@ namespace Avalonia.Rendering
}
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
+
using (scene)
{
- var overlay = DrawDirtyRects || DrawFps;
- if (DrawDirtyRects)
- _dirtyRectsDisplay.Tick();
- if (overlay)
- RenderOverlay(scene.Item, GetContext());
- if (updated || forceComposite || overlay)
- RenderComposite(scene.Item, GetContext());
+ if (scene?.Item != null)
+ {
+ var overlay = DrawDirtyRects || DrawFps;
+ if (DrawDirtyRects)
+ _dirtyRectsDisplay.Tick();
+ if (overlay)
+ RenderOverlay(scene.Item, GetContext());
+ if (updated || forceComposite || overlay)
+ RenderComposite(scene.Item, GetContext());
+ }
}
}
finally
@@ -478,6 +489,8 @@ namespace Avalonia.Rendering
Dispatcher.UIThread.VerifyAccess();
lock (_sceneLock)
{
+ if (_disposed)
+ return;
if (_scene?.Item.Generation > _lastSceneId)
return;
}
@@ -506,6 +519,21 @@ namespace Avalonia.Rendering
oldScene?.Dispose();
}
+ if (SceneInvalidated != null)
+ {
+ var rect = new Rect();
+
+ foreach (var layer in scene.Layers)
+ {
+ foreach (var dirty in layer.Dirty)
+ {
+ rect = rect.Union(dirty);
+ }
+ }
+
+ SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
+ }
+
_dirty.Clear();
}
else
diff --git a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs
index 2b364e5d22..044911ca95 100644
--- a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs
+++ b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs
@@ -41,15 +41,15 @@ namespace Avalonia.Rendering
///
/// Converts a point from screen to client coordinates.
///
- /// The point in screen coordinates.
+ /// The point in screen device coordinates.
/// The point in client coordinates.
- Point PointToClient(Point point);
+ Point PointToClient(PixelPoint point);
///
/// Converts a point from client to screen coordinates.
///
/// The point in client coordinates.
- /// The point in screen coordinates.
- Point PointToScreen(Point point);
+ /// The point in screen device coordinates.
+ PixelPoint PointToScreen(Point point);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs
index 9085e63aa9..36a1f7d220 100644
--- a/src/Avalonia.Visuals/Rendering/IRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs
@@ -18,11 +18,20 @@ namespace Avalonia.Rendering
bool DrawFps { get; set; }
///
- /// Gets or sets a value indicating whether the renderer should a visual representation
+ /// Gets or sets a value indicating whether the renderer should draw a visual representation
/// of its dirty rectangles.
///
bool DrawDirtyRects { get; set; }
+ ///
+ /// Raised when a portion of the scene has been invalidated.
+ ///
+ ///
+ /// Indicates that the underlying low-level scene information has been updated. Used to
+ /// signal that an update to the current pointer-over state may be required.
+ ///
+ event EventHandler SceneInvalidated;
+
///
/// Mark a visual as dirty and needing re-rendering.
///
@@ -63,4 +72,4 @@ namespace Avalonia.Rendering
///
void Stop();
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 03ac004fd5..21129e38af 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -42,6 +42,9 @@ namespace Avalonia.Rendering
///
public bool DrawDirtyRects { get; set; }
+ ///
+ public event EventHandler SceneInvalidated;
+
///
public void Paint(Rect rect)
{
@@ -81,6 +84,8 @@ namespace Avalonia.Rendering
_renderTarget.Dispose();
_renderTarget = null;
}
+
+ SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
index ffa0b0bcc5..ad4c475d89 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph
private IEnumerable HitTest(IVisualNode node, Point p, Rect? clip, Func filter)
{
- if (filter?.Invoke(node.Visual) != false)
+ if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
{
var clipped = false;
@@ -186,7 +186,7 @@ namespace Avalonia.Rendering.SceneGraph
}
}
- if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree)
+ if (node.HitTest(p))
{
yield return node.Visual;
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 159c3cd0fa..2fb8e84a2e 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -236,7 +236,7 @@ namespace Avalonia.Rendering.SceneGraph
{
foreach (var operation in DrawOperations)
{
- if (operation.Item.HitTest(p) == true)
+ if (operation.Item.HitTest(p))
{
return true;
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs
new file mode 100644
index 0000000000..40cb9f3356
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs
@@ -0,0 +1,36 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Rendering
+{
+ ///
+ /// Provides data for the event.
+ ///
+ public class SceneInvalidatedEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The render root that has been updated.
+ /// The updated area.
+ public SceneInvalidatedEventArgs(
+ IRenderRoot root,
+ Rect dirtyRect)
+ {
+ RenderRoot = root;
+ DirtyRect = dirtyRect;
+ }
+
+ ///
+ /// Gets the invalidated area.
+ ///
+ public Rect DirtyRect { get; }
+
+ ///
+ /// Gets the render root that has been invalidated.
+ ///
+ public IRenderRoot RenderRoot { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs
index 7c5d011ce8..650921a985 100644
--- a/src/Avalonia.Visuals/VisualExtensions.cs
+++ b/src/Avalonia.Visuals/VisualExtensions.cs
@@ -18,10 +18,12 @@ namespace Avalonia
/// The visual.
/// The point in screen coordinates.
/// The point in client coordinates.
- public static Point PointToClient(this IVisual visual, Point point)
+ public static Point PointToClient(this IVisual visual, PixelPoint point)
{
- var p = GetRootAndPosition(visual);
- return p.Item1.PointToClient(point - p.Item2);
+ var (root, offset) = GetRootAndPosition(visual);
+ var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling);
+ var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y);
+ return root.PointToClient(screenPoint);
}
///
@@ -30,7 +32,7 @@ namespace Avalonia
/// The visual.
/// The point in client coordinates.
/// The point in screen coordinates.
- public static Point PointToScreen(this IVisual visual, Point point)
+ public static PixelPoint PointToScreen(this IVisual visual, Point point)
{
var p = GetRootAndPosition(visual);
return p.Item1.PointToScreen(point + p.Item2);
diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj
index 087ba017ae..1629890568 100644
--- a/src/Avalonia.X11/Avalonia.X11.csproj
+++ b/src/Avalonia.X11/Avalonia.X11.csproj
@@ -7,7 +7,6 @@
-
diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs
new file mode 100644
index 0000000000..c3a2fd2050
--- /dev/null
+++ b/src/Avalonia.X11/Glx/Glx.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Avalonia.OpenGL;
+using Avalonia.Platform.Interop;
+// ReSharper disable UnassignedGetOnlyAutoProperty
+
+namespace Avalonia.X11.Glx
+{
+ unsafe class GlxInterface : GlInterfaceBase
+ {
+ private const string libGL = "libGL.so.1";
+ [GlEntryPointAttribute("glXMakeContextCurrent")]
+ public GlxMakeContextCurrent MakeContextCurrent { get; }
+ public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
+
+ [GlEntryPoint("glXCreatePbuffer")]
+ public GlxCreatePbuffer CreatePbuffer { get; }
+
+ public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
+
+ [GlEntryPointAttribute("glXChooseVisual")]
+ public GlxChooseVisual ChooseVisual { get; }
+ public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
+
+
+ [GlEntryPointAttribute("glXCreateContext")]
+ public GlxCreateContext CreateContext { get; }
+ public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
+
+
+ [GlEntryPointAttribute("glXCreateContextAttribsARB")]
+ public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
+ public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
+ bool direct, int[] attribs);
+
+
+ [DllImport(libGL, EntryPoint = "glXGetProcAddress")]
+ public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
+
+
+ [GlEntryPointAttribute("glXDestroyContext")]
+ public GlxDestroyContext DestroyContext { get; }
+ public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
+
+
+ [GlEntryPointAttribute("glXChooseFBConfig")]
+ public GlxChooseFBConfig ChooseFBConfig { get; }
+ public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
+
+
+ public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable attribs, out int nelements)
+ {
+ var arr = attribs.Concat(new[]{0}).ToArray();
+ return ChooseFBConfig(dpy, screen, arr, out nelements);
+ }
+
+ [GlEntryPointAttribute("glXGetVisualFromFBConfig")]
+ public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
+ public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
+
+
+ [GlEntryPointAttribute("glXGetFBConfigAttrib")]
+ public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
+ public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
+
+
+ [GlEntryPointAttribute("glXSwapBuffers")]
+ public GlxSwapBuffers SwapBuffers { get; }
+ public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
+
+
+ [GlEntryPointAttribute("glXWaitX")]
+ public GlxWaitX WaitX { get; }
+ public delegate void GlxWaitX();
+
+
+ [GlEntryPointAttribute("glXWaitGL")]
+ public GlxWaitGL WaitGL { get; }
+ public delegate void GlxWaitGL();
+
+ public delegate int GlGetError();
+ [GlEntryPoint("glGetError")]
+ public GlGetError GetError { get; }
+
+ public GlxInterface() : base(GlxGetProcAddress)
+ {
+ }
+ }
+}
diff --git a/src/Avalonia.X11/Glx/GlxConsts.cs b/src/Avalonia.X11/Glx/GlxConsts.cs
new file mode 100644
index 0000000000..65f253696a
--- /dev/null
+++ b/src/Avalonia.X11/Glx/GlxConsts.cs
@@ -0,0 +1,106 @@
+// ReSharper disable InconsistentNaming
+// ReSharper disable IdentifierTypo
+// ReSharper disable UnusedMember.Global
+#pragma warning disable 414
+namespace Avalonia.X11.Glx
+{
+ class GlxConsts
+ {
+ public const int GLX_USE_GL = 1;
+ public const int GLX_BUFFER_SIZE = 2;
+ public const int GLX_LEVEL = 3;
+ public const int GLX_RGBA = 4;
+ public const int GLX_DOUBLEBUFFER = 5;
+ public const int GLX_STEREO = 6;
+ public const int GLX_AUX_BUFFERS = 7;
+ public const int GLX_RED_SIZE = 8;
+ public const int GLX_GREEN_SIZE = 9;
+ public const int GLX_BLUE_SIZE = 10;
+ public const int GLX_ALPHA_SIZE = 11;
+ public const int GLX_DEPTH_SIZE = 12;
+ public const int GLX_STENCIL_SIZE = 13;
+ public const int GLX_ACCUM_RED_SIZE = 14;
+ public const int GLX_ACCUM_GREEN_SIZE = 15;
+ public const int GLX_ACCUM_BLUE_SIZE = 16;
+ public const int GLX_ACCUM_ALPHA_SIZE = 17;
+ public const int GLX_BAD_SCREEN = 1;
+ public const int GLX_BAD_ATTRIBUTE = 2;
+ public const int GLX_NO_EXTENSION = 3;
+ public const int GLX_BAD_VISUAL = 4;
+ public const int GLX_BAD_CONTEXT = 5;
+ public const int GLX_BAD_VALUE = 6;
+ public const int GLX_BAD_ENUM = 7;
+ public const int GLX_VENDOR = 1;
+ public const int GLX_VERSION = 2;
+ public const int GLX_EXTENSIONS= 3;
+ public const int GLX_CONFIG_CAVEAT = 0x20;
+ public const int GLX_DONT_CARE = unchecked((int)0xFFFFFFFF);
+ public const int GLX_X_VISUAL_TYPE = 0x22;
+ public const int GLX_TRANSPARENT_TYPE = 0x23;
+ public const int GLX_TRANSPARENT_INDEX_VALUE = 0x24;
+ public const int GLX_TRANSPARENT_RED_VALUE = 0x25;
+ public const int GLX_TRANSPARENT_GREEN_VALUE = 0x26;
+ public const int GLX_TRANSPARENT_BLUE_VALUE = 0x27;
+ public const int GLX_TRANSPARENT_ALPHA_VALUE = 0x28;
+ public const int GLX_WINDOW_BIT = 0x00000001;
+ public const int GLX_PIXMAP_BIT = 0x00000002;
+ public const int GLX_PBUFFER_BIT = 0x00000004;
+ public const int GLX_AUX_BUFFERS_BIT = 0x00000010;
+ public const int GLX_FRONT_LEFT_BUFFER_BIT = 0x00000001;
+ public const int GLX_FRONT_RIGHT_BUFFER_BIT = 0x00000002;
+ public const int GLX_BACK_LEFT_BUFFER_BIT = 0x00000004;
+ public const int GLX_BACK_RIGHT_BUFFER_BIT = 0x00000008;
+ public const int GLX_DEPTH_BUFFER_BIT = 0x00000020;
+ public const int GLX_STENCIL_BUFFER_BIT = 0x00000040;
+ public const int GLX_ACCUM_BUFFER_BIT = 0x00000080;
+ public const int GLX_NONE = 0x8000;
+ public const int GLX_SLOW_CONFIG = 0x8001;
+ public const int GLX_TRUE_COLOR = 0x8002;
+ public const int GLX_DIRECT_COLOR = 0x8003;
+ public const int GLX_PSEUDO_COLOR = 0x8004;
+ public const int GLX_STATIC_COLOR = 0x8005;
+ public const int GLX_GRAY_SCALE = 0x8006;
+ public const int GLX_STATIC_GRAY = 0x8007;
+ public const int GLX_TRANSPARENT_RGB = 0x8008;
+ public const int GLX_TRANSPARENT_INDEX = 0x8009;
+ public const int GLX_VISUAL_ID = 0x800B;
+ public const int GLX_SCREEN = 0x800C;
+ public const int GLX_NON_CONFORMANT_CONFIG = 0x800D;
+ public const int GLX_DRAWABLE_TYPE = 0x8010;
+ public const int GLX_RENDER_TYPE = 0x8011;
+ public const int GLX_X_RENDERABLE = 0x8012;
+ public const int GLX_FBCONFIG_ID = 0x8013;
+ public const int GLX_RGBA_TYPE = 0x8014;
+ public const int GLX_COLOR_INDEX_TYPE = 0x8015;
+ public const int GLX_MAX_PBUFFER_WIDTH = 0x8016;
+ public const int GLX_MAX_PBUFFER_HEIGHT = 0x8017;
+ public const int GLX_MAX_PBUFFER_PIXELS = 0x8018;
+ public const int GLX_PRESERVED_CONTENTS = 0x801B;
+ public const int GLX_LARGEST_PBUFFER = 0x801C;
+ public const int GLX_WIDTH = 0x801D;
+ public const int GLX_HEIGHT = 0x801E;
+ public const int GLX_EVENT_MASK = 0x801F;
+ public const int GLX_DAMAGED = 0x8020;
+ public const int GLX_SAVED = 0x8021;
+ public const int GLX_WINDOW = 0x8022;
+ public const int GLX_PBUFFER = 0x8023;
+ public const int GLX_PBUFFER_HEIGHT = 0x8040;
+ public const int GLX_PBUFFER_WIDTH = 0x8041;
+ public const int GLX_RGBA_BIT = 0x00000001;
+ public const int GLX_COLOR_INDEX_BIT = 0x00000002;
+ public const int GLX_PBUFFER_CLOBBER_MASK = 0x08000000;
+ public const int GLX_SAMPLE_BUFFERS = 0x186a0 /*100000*/;
+ public const int GLX_SAMPLES = 0x186a1 /*100001*/;
+ public const int GLX_PbufferClobber = 0;
+ public const int GLX_BufferSwapComplete = 1;
+ public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001;
+ public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002;
+ public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
+ public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
+ public const int GLX_CONTEXT_FLAGS_ARB = 0x2094;
+ public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
+ public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
+ public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
+
+ }
+}
diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs
new file mode 100644
index 0000000000..dd95841d57
--- /dev/null
+++ b/src/Avalonia.X11/Glx/GlxContext.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+using Avalonia.OpenGL;
+namespace Avalonia.X11.Glx
+{
+ class GlxContext : IGlContext
+ {
+ public IntPtr Handle { get; }
+ public GlxInterface Glx { get; }
+ private readonly X11Info _x11;
+ private readonly IntPtr _defaultXid;
+ private readonly object _lock = new object();
+
+ public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
+ {
+ Handle = handle;
+ Glx = glx;
+ _x11 = x11;
+ _defaultXid = defaultXid;
+ Display = display;
+ }
+
+ public GlxDisplay Display { get; }
+ IGlDisplay IGlContext.Display => Display;
+
+ public IDisposable Lock()
+ {
+ Monitor.Enter(_lock);
+ return Disposable.Create(() => Monitor.Exit(_lock));
+ }
+
+ public void MakeCurrent() => MakeCurrent(_defaultXid);
+
+ public void MakeCurrent(IntPtr xid)
+ {
+ if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
+ throw new OpenGlException("glXMakeContextCurrent failed ");
+ }
+ }
+}
diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs
new file mode 100644
index 0000000000..5602b33280
--- /dev/null
+++ b/src/Avalonia.X11/Glx/GlxDisplay.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Linq;
+using Avalonia.OpenGL;
+using static Avalonia.X11.Glx.GlxConsts;
+
+namespace Avalonia.X11.Glx
+{
+ unsafe class GlxDisplay : IGlDisplay
+ {
+ private readonly X11Info _x11;
+ private readonly IntPtr _fbconfig;
+ private readonly XVisualInfo* _visual;
+ public GlDisplayType Type => GlDisplayType.OpenGL2;
+ public GlInterface GlInterface { get; }
+
+ public XVisualInfo* VisualInfo => _visual;
+ public int SampleCount { get; }
+ public int StencilSize { get; }
+
+ public GlxContext ImmediateContext { get; }
+ public GlxContext DeferredContext { get; }
+ public GlxInterface Glx { get; } = new GlxInterface();
+ public GlxDisplay(X11Info x11)
+ {
+ _x11 = x11;
+
+ var baseAttribs = new[]
+ {
+ GLX_X_RENDERABLE, 1,
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT,
+ GLX_DOUBLEBUFFER, 1,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 8,
+ GLX_DEPTH_SIZE, 1,
+ GLX_STENCIL_SIZE, 8,
+
+ };
+
+ foreach (var attribs in new[]
+ {
+ //baseAttribs.Concat(multiattribs),
+ baseAttribs,
+ })
+ {
+ var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen,
+ attribs, out var count);
+ for (var c = 0 ; c < count; c++)
+ {
+
+ var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]);
+ // We prefer 32 bit visuals
+ if (_fbconfig == IntPtr.Zero || visual->depth == 32)
+ {
+ _fbconfig = ptr[c];
+ _visual = visual;
+ if(visual->depth == 32)
+ break;
+ }
+ }
+
+ if (_fbconfig != IntPtr.Zero)
+ break;
+ }
+
+ if (_fbconfig == IntPtr.Zero)
+ throw new OpenGlException("Unable to choose FBConfig");
+
+ if (_visual == null)
+ throw new OpenGlException("Unable to get visual info from FBConfig");
+ if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
+ SampleCount = samples;
+ if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
+ StencilSize = stencil;
+
+ var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[]
+ {
+ GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0
+ })).ToList();
+
+ XLib.XFlush(_x11.Display);
+
+ ImmediateContext = CreateContext(pbuffers[0],null);
+ DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
+ ImmediateContext.MakeCurrent();
+ var err = Glx.GetError();
+
+ GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
+ if (GlInterface.Version == null)
+ throw new OpenGlException("GL version string is null, aborting");
+ }
+
+ public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
+ IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
+
+ public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
+ public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
+ {
+ var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
+ IntPtr handle = default;
+ foreach (var ver in new[]
+ {
+ new Version(4, 0), new Version(3, 2),
+ new Version(3, 0), new Version(2, 0)
+ })
+ {
+
+ var attrs = new[]
+ {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major,
+ GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor,
+ GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
+ 0
+ };
+ try
+ {
+ handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs);
+ if (handle != IntPtr.Zero)
+ break;
+ }
+ catch
+ {
+ break;
+ }
+ }
+
+ if (handle == IntPtr.Zero)
+ throw new OpenGlException("Unable to create direct GLX context");
+ return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
+ }
+
+ public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);
+ }
+}
diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
new file mode 100644
index 0000000000..45a46bd6f5
--- /dev/null
+++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
@@ -0,0 +1,86 @@
+using System;
+using Avalonia.OpenGL;
+
+namespace Avalonia.X11.Glx
+{
+ class GlxGlPlatformSurface: IGlPlatformSurface
+ {
+
+ private readonly GlxDisplay _display;
+ private readonly GlxContext _context;
+ private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
+
+ public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
+ {
+ _display = display;
+ _context = context;
+ _info = info;
+ }
+
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ {
+ return new RenderTarget(_context, _info);
+ }
+
+ class RenderTarget : IGlPlatformSurfaceRenderTarget
+ {
+ private readonly GlxContext _context;
+ private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
+
+ public RenderTarget(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
+ {
+ _context = context;
+ _info = info;
+ }
+
+ public void Dispose()
+ {
+ // No-op
+ }
+
+ public IGlPlatformSurfaceRenderingSession BeginDraw()
+ {
+ var l = _context.Lock();
+ try
+ {
+ _context.MakeCurrent(_info.Handle);
+ return new Session(_context, _info, l);
+ }
+ catch
+ {
+ l.Dispose();
+ throw;
+ }
+ }
+
+ class Session : IGlPlatformSurfaceRenderingSession
+ {
+ private readonly GlxContext _context;
+ private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
+ private IDisposable _lock;
+
+ public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
+ IDisposable @lock)
+ {
+ _context = context;
+ _info = info;
+ _lock = @lock;
+ }
+
+ public void Dispose()
+ {
+ _context.Display.GlInterface.Flush();
+ _context.Glx.WaitGL();
+ _context.Display.SwapBuffers(_info.Handle);
+ _context.Glx.WaitX();
+ _context.Display.ClearContext();
+ _lock.Dispose();
+ }
+
+ public IGlDisplay Display => _context.Display;
+ public PixelSize Size => _info.Size;
+ public double Scaling => _info.Scaling;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs
new file mode 100644
index 0000000000..d15b5fe4b8
--- /dev/null
+++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs
@@ -0,0 +1,44 @@
+using System;
+using Avalonia.Logging;
+using Avalonia.OpenGL;
+
+namespace Avalonia.X11.Glx
+{
+ class GlxGlPlatformFeature : IWindowingPlatformGlFeature
+ {
+ public GlxDisplay Display { get; private set; }
+ public IGlContext ImmediateContext { get; private set; }
+ public GlxContext DeferredContext { get; private set; }
+
+ public static bool TryInitialize(X11Info x11)
+ {
+ var feature = TryCreate(x11);
+ if (feature != null)
+ {
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static GlxGlPlatformFeature TryCreate(X11Info x11)
+ {
+ try
+ {
+ var disp = new GlxDisplay(x11);
+ return new GlxGlPlatformFeature
+ {
+ Display = disp,
+ ImmediateContext = disp.ImmediateContext,
+ DeferredContext = disp.DeferredContext
+ };
+ }
+ catch(Exception e)
+ {
+ Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e);
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs
new file mode 100644
index 0000000000..100996984b
--- /dev/null
+++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Platform.Interop;
+// ReSharper disable IdentifierTypo
+namespace Avalonia.X11.NativeDialogs
+{
+
+ static unsafe class Glib
+ {
+ private const string GlibName = "libglib-2.0.so.0";
+ private const string GObjectName = "libgobject-2.0.so.0";
+
+ [DllImport(GlibName)]
+ public static extern void g_slist_free(GSList* data);
+
+ [DllImport(GObjectName)]
+ private static extern void g_object_ref(IntPtr instance);
+
+ [DllImport(GObjectName)]
+ private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal,
+ IntPtr handler, IntPtr userData, int flags);
+
+ [DllImport(GObjectName)]
+ private static extern void g_object_unref(IntPtr instance);
+
+ [DllImport(GObjectName)]
+ private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId);
+
+ private delegate bool timeout_callback(IntPtr data);
+
+ [DllImport(GlibName)]
+ private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data,
+ IntPtr destroy);
+
+
+ class ConnectedSignal : IDisposable
+ {
+ private readonly IntPtr _instance;
+ private GCHandle _handle;
+ private readonly ulong _id;
+
+ public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id)
+ {
+ _instance = instance;
+ g_object_ref(instance);
+ _handle = handle;
+ _id = id;
+ }
+
+ public void Dispose()
+ {
+ if (_handle.IsAllocated)
+ {
+ g_signal_handler_disconnect(_instance, _id);
+ g_object_unref(_instance);
+ _handle.Free();
+ }
+ }
+ }
+
+ public static IDisposable ConnectSignal(IntPtr obj, string name, T handler)
+ {
+ var handle = GCHandle.Alloc(handler);
+ var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
+ using (var utf = new Utf8Buffer(name))
+ {
+ var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0);
+ if (id == 0)
+ throw new ArgumentException("Unable to connect to signal " + name);
+ return new ConnectedSignal(obj, handle, id);
+ }
+ }
+
+
+ static bool TimeoutHandler(IntPtr data)
+ {
+ var handle = GCHandle.FromIntPtr(data);
+ var cb = (Func)handle.Target;
+ if (!cb())
+ {
+ handle.Free();
+ return false;
+ }
+
+ return true;
+ }
+
+ private static readonly timeout_callback s_pinnedHandler;
+
+ static Glib()
+ {
+ s_pinnedHandler = TimeoutHandler;
+ }
+
+ static void AddTimeout(int priority, uint interval, Func callback)
+ {
+ var handle = GCHandle.Alloc(callback);
+ g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
+ }
+
+ public static Task RunOnGlibThread(Func action)
+ {
+ var tcs = new TaskCompletionSource();
+ AddTimeout(0, 0, () =>
+ {
+
+ try
+ {
+ tcs.SetResult(action());
+ }
+ catch (Exception e)
+ {
+ tcs.TrySetException(e);
+ }
+
+ return false;
+ });
+ return tcs.Task;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct GSList
+ {
+ public readonly IntPtr Data;
+ public readonly GSList* Next;
+ }
+
+ enum GtkFileChooserAction
+ {
+ Open,
+ Save,
+ SelectFolder,
+ }
+
+ // ReSharper disable UnusedMember.Global
+ enum GtkResponseType
+ {
+ Help = -11,
+ Apply = -10,
+ No = -9,
+ Yes = -8,
+ Close = -7,
+ Cancel = -6,
+ Ok = -5,
+ DeleteEvent = -4,
+ Accept = -3,
+ Reject = -2,
+ None = -1,
+ }
+ // ReSharper restore UnusedMember.Global
+
+ static unsafe class Gtk
+ {
+ private static IntPtr s_display;
+ private const string GdkName = "libgdk-3.so.0";
+ private const string GtkName = "libgtk-3.so.0";
+
+ [DllImport(GtkName)]
+ static extern void gtk_main_iteration();
+
+
+ [DllImport(GtkName)]
+ public static extern void gtk_window_set_modal(IntPtr window, bool modal);
+
+ [DllImport(GtkName)]
+ public static extern void gtk_window_present(IntPtr gtkWindow);
+
+
+ public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData);
+
+ public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData);
+
+ [DllImport(GtkName)]
+ public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent,
+ GtkFileChooserAction action, IntPtr ignore);
+
+ [DllImport(GtkName)]
+ public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow);
+
+ [DllImport(GtkName)]
+ public static extern void
+ gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id);
+
+ [DllImport(GtkName)]
+ public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser);
+
+ [DllImport(GtkName)]
+ public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
+
+ [DllImport(GtkName)]
+ public static extern void gtk_widget_realize(IntPtr gtkWidget);
+
+ [DllImport(GtkName)]
+ public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
+
+ [DllImport(GtkName)]
+ public static extern void gtk_widget_hide(IntPtr gtkWidget);
+
+ [DllImport(GtkName)]
+ static extern bool gtk_init_check(int argc, IntPtr argv);
+
+ [DllImport(GdkName)]
+ static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
+
+ [DllImport(GdkName)]
+ static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
+
+ [DllImport(GdkName)]
+ static extern IntPtr gdk_display_get_default();
+
+ [DllImport(GtkName)]
+ static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
+
+ [DllImport(GdkName)]
+ public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
+
+ public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid);
+
+ public static Task StartGtk()
+ {
+ var tcs = new TaskCompletionSource();
+ new Thread(() =>
+ {
+ try
+ {
+ using (var backends = new Utf8Buffer("x11"))
+ gdk_set_allowed_backends(backends);
+ }
+ catch
+ {
+ //Ignore
+ }
+
+ Environment.SetEnvironmentVariable("WAYLAND_DISPLAY",
+ "/proc/fake-display-to-prevent-wayland-initialization-by-gtk3");
+
+ if (!gtk_init_check(0, IntPtr.Zero))
+ {
+ tcs.SetResult(false);
+ return;
+ }
+
+ IntPtr app;
+ using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid():N}"))
+ app = gtk_application_new(utf, 0);
+ if (app == IntPtr.Zero)
+ {
+ tcs.SetResult(false);
+ return;
+ }
+
+ s_display = gdk_display_get_default();
+ tcs.SetResult(true);
+ while (true)
+ gtk_main_iteration();
+ }) {Name = "GTK3THREAD", IsBackground = true}.Start();
+ return tcs.Task;
+ }
+ }
+}
diff --git a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
new file mode 100644
index 0000000000..61047ef2a9
--- /dev/null
+++ b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Platform.Interop;
+using static Avalonia.X11.NativeDialogs.Glib;
+using static Avalonia.X11.NativeDialogs.Gtk;
+// ReSharper disable AccessToModifiedClosure
+namespace Avalonia.X11.NativeDialogs
+{
+ class GtkSystemDialog : ISystemDialogImpl
+ {
+ private Task _initialized;
+ private unsafe Task ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
+ bool multiSelect, string initialFileName)
+ {
+ IntPtr dlg;
+ using (var name = new Utf8Buffer(title))
+ dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero);
+ UpdateParent(dlg, parent);
+ if (multiSelect)
+ gtk_file_chooser_set_select_multiple(dlg, true);
+
+ gtk_window_set_modal(dlg, true);
+ var tcs = new TaskCompletionSource();
+ List disposables = null;
+
+ void Dispose()
+ {
+ // ReSharper disable once PossibleNullReferenceException
+ foreach (var d in disposables) d.Dispose();
+ disposables.Clear();
+ }
+
+ disposables = new List
+ {
+ ConnectSignal(dlg, "close", delegate
+ {
+ tcs.TrySetResult(null);
+ Dispose();
+ return false;
+ }),
+ ConnectSignal(dlg, "response", (_, resp, __) =>
+ {
+ string[] result = null;
+ if (resp == GtkResponseType.Accept)
+ {
+ var resultList = new List();
+ var gs = gtk_file_chooser_get_filenames(dlg);
+ var cgs = gs;
+ while (cgs != null)
+ {
+ if (cgs->Data != IntPtr.Zero)
+ resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data));
+ cgs = cgs->Next;
+ }
+ g_slist_free(gs);
+ result = resultList.ToArray();
+ }
+
+ gtk_widget_hide(dlg);
+ Dispose();
+ tcs.TrySetResult(result);
+ return false;
+ })
+ };
+ using (var open = new Utf8Buffer("Open"))
+ gtk_dialog_add_button(dlg, open, GtkResponseType.Accept);
+ using (var open = new Utf8Buffer("Cancel"))
+ gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
+ if (initialFileName != null)
+ using (var fn = new Utf8Buffer(initialFileName))
+ gtk_file_chooser_set_filename(dlg, fn);
+ gtk_window_present(dlg);
+ return tcs.Task;
+ }
+
+ public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
+ {
+ await EnsureInitialized();
+ return await await RunOnGlibThread(
+ () => ShowDialog(dialog.Title, parent,
+ dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
+ (dialog as OpenFileDialog)?.AllowMultiple ?? false,
+ Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
+ string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName)));
+ }
+
+ public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
+ {
+ await EnsureInitialized();
+ return await await RunOnGlibThread(async () =>
+ {
+ var res = await ShowDialog(dialog.Title, parent,
+ GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
+ return res?.FirstOrDefault();
+ });
+ }
+
+ async Task EnsureInitialized()
+ {
+ if (_initialized == null) _initialized = StartGtk();
+
+ if (!(await _initialized))
+ throw new Exception("Unable to initialize GTK on separate thread");
+ }
+
+ void UpdateParent(IntPtr chooser, IWindowImpl parentWindow)
+ {
+ var xid = parentWindow.Handle.Handle;
+ gtk_widget_realize(chooser);
+ var window = gtk_widget_get_window(chooser);
+ var parent = GetForeignWindow(xid);
+ if (window != IntPtr.Zero && parent != IntPtr.Zero)
+ gdk_window_set_transient_for(window, parent);
+ }
+ }
+}
diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs
index 70220cebcd..07c88b51a8 100644
--- a/src/Avalonia.X11/X11FramebufferSurface.cs
+++ b/src/Avalonia.X11/X11FramebufferSurface.cs
@@ -8,12 +8,14 @@ namespace Avalonia.X11
{
private readonly IntPtr _display;
private readonly IntPtr _xid;
+ private readonly int _depth;
private readonly Func _scaling;
- public X11FramebufferSurface(IntPtr display, IntPtr xid, Func scaling)
+ public X11FramebufferSurface(IntPtr display, IntPtr xid, int depth, Func scaling)
{
_display = display;
_xid = xid;
+ _depth = depth;
_scaling = scaling;
}
@@ -23,7 +25,7 @@ namespace Avalonia.X11
XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height,
out var bw, out var d);
XUnlockDisplay(_display);
- return new X11Framebuffer(_display, _xid, 24,width, height, _scaling());
+ return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling());
}
}
}
diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs
index 6e4b31fb5c..8cfcbb9836 100644
--- a/src/Avalonia.X11/X11Info.cs
+++ b/src/Avalonia.X11/X11Info.cs
@@ -7,7 +7,7 @@ using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Avalonia.X11
{
- class X11Info
+ unsafe class X11Info
{
public IntPtr Display { get; }
public IntPtr DeferredDisplay { get; }
@@ -31,6 +31,7 @@ namespace Avalonia.X11
public Version XInputVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
+ public XVisualInfo? TransparentVisualInfo { get; set; }
public unsafe X11Info(IntPtr display, IntPtr deferredDisplay)
{
@@ -45,7 +46,10 @@ namespace Avalonia.X11
//TODO: Open an actual XIM once we get support for preedit in our textbox
XSetLocaleModifiers("@im=none");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
-
+ XMatchVisualInfo(Display, DefaultScreen, 32, 4, out var visual);
+ if (visual.depth == 32)
+ TransparentVisualInfo = visual;
+
try
{
if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0)
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 3b50dff5c8..5a19187a22 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -2,13 +2,14 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
-using Avalonia.Gtk3;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.X11;
+using Avalonia.X11.Glx;
+using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
@@ -23,7 +24,7 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
- public void Initialize()
+ public void Initialize(X11PlatformOptions options)
{
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
@@ -44,7 +45,7 @@ namespace Avalonia.X11
.Bind().ToConstant(new X11Clipboard(this))
.Bind().ToConstant(new PlatformSettingsStub())
.Bind().ToConstant(new X11IconLoader(Info))
- .Bind().ToConstant(new Gtk3ForeignX11SystemDialog());
+ .Bind().ToConstant(new GtkSystemDialog());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);
@@ -54,8 +55,14 @@ namespace Avalonia.X11
if (xi2.Init(this))
XI2 = xi2;
}
- EglGlPlatformFeature.TryInitialize();
+ if (options.UseGpu)
+ {
+ if (options.UseEGL)
+ EglGlPlatformFeature.TryInitialize();
+ else
+ GlxGlPlatformFeature.TryInitialize(Info);
+ }
}
public IntPtr DeferredDisplay { get; set; }
@@ -79,15 +86,22 @@ namespace Avalonia.X11
namespace Avalonia
{
+
+ public class X11PlatformOptions
+ {
+ public bool UseEGL { get; set; }
+ public bool UseGpu { get; set; } = true;
+ }
public static class AvaloniaX11PlatformExtensions
{
- public static T UseX11(this T builder) where T : AppBuilderBase, new()
+ public static T UseX11(this T builder, X11PlatformOptions options = null) where T : AppBuilderBase, new()
{
- builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize());
+ builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions()));
return builder;
}
- public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize();
+ public static void InitializeX11Platform(X11PlatformOptions options = null) =>
+ new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions());
}
}
diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs
index f66afc4a83..0215e43d98 100644
--- a/src/Avalonia.X11/X11PlatformThreading.cs
+++ b/src/Avalonia.X11/X11PlatformThreading.cs
@@ -164,39 +164,33 @@ namespace Avalonia.X11
void HandleX11(CancellationToken cancellationToken)
{
- while (true)
+ while (XPending(_display) != 0)
{
- var pending = XPending(_display);
- if (pending == 0)
- break;
- while (pending > 0)
+ if (cancellationToken.IsCancellationRequested)
+ return;
+ XNextEvent(_display, out var xev);
+ if (xev.type == XEventName.GenericEvent)
+ XGetEventData(_display, &xev.GenericEventCookie);
+ try
{
- if (cancellationToken.IsCancellationRequested)
- return;
- XNextEvent(_display, out var xev);
if (xev.type == XEventName.GenericEvent)
- XGetEventData(_display, &xev.GenericEventCookie);
- pending--;
- try
{
- if (xev.type == XEventName.GenericEvent)
+ if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
+ xev.GenericEventCookie.extension)
{
- if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
- xev.GenericEventCookie.extension)
- {
- _platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
- }
+ _platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
- else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
- handler(xev);
- }
- finally
- {
- if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
- XFreeEventData(_display, &xev.GenericEventCookie);
}
+ else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
+ handler(xev);
+ }
+ finally
+ {
+ if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
+ XFreeEventData(_display, &xev.GenericEventCookie);
}
}
+
Dispatcher.UIThread.RunJobs();
}
diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs
index d775261de0..6bfc8779da 100644
--- a/src/Avalonia.X11/X11Screens.cs
+++ b/src/Avalonia.X11/X11Screens.cs
@@ -20,7 +20,7 @@ namespace Avalonia.X11
static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens)
{
- var rect = default(Rect);
+ var rect = default(PixelRect);
foreach (var s in screens)
{
rect = rect.Union(s.Bounds);
@@ -46,7 +46,7 @@ namespace Avalonia.X11
return screens;
var pwa = (IntPtr*)prop;
- var wa = new Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
+ var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
foreach (var s in screens)
@@ -105,7 +105,7 @@ namespace Avalonia.X11
density *= _settings.GlobalScaleFactor;
- var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height);
+ var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
screens[c] = new X11Screen(bounds,
mon.Primary != 0,
name,
@@ -130,12 +130,12 @@ namespace Avalonia.X11
Screens = UpdateWorkArea(info,
new[]
{
- new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null,
+ new X11Screen(new PixelRect(0, 0, geo.width, geo.height), true, "Default", null,
settings.GlobalScaleFactor)
});
}
- Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
+ Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
}
public X11Screen[] Screens { get; }
@@ -218,14 +218,15 @@ namespace Avalonia.X11
class X11Screen
{
+ private const int FullHDWidth = 1920;
public bool Primary { get; }
public string Name { get; set; }
- public Rect Bounds { get; set; }
+ public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
public double PixelDensity { get; set; }
- public Rect WorkingArea { get; set; }
+ public PixelRect WorkingArea { get; set; }
- public X11Screen(Rect bounds, bool primary,
+ public X11Screen(PixelRect bounds, bool primary,
string name, Size? physicalSize, double? pixelDensity)
{
Primary = primary;
@@ -247,6 +248,6 @@ namespace Avalonia.X11
}
public static double GuessPixelDensity(double pixelWidth, double mmWidth)
- => Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
+ => pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
}
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 710951fcab..cbff4e38cf 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -12,6 +12,7 @@ using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
+using Avalonia.X11.Glx;
using static Avalonia.X11.XLib;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
@@ -24,12 +25,12 @@ namespace Avalonia.X11
private readonly X11Info _x11;
private bool _invalidated;
private XConfigureEvent? _configure;
- private Point? _configurePoint;
+ private PixelPoint? _configurePoint;
private bool _triggeredExpose;
private IInputRoot _inputRoot;
private readonly IMouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
- private Point? _position;
+ private PixelPoint? _position;
private PixelSize _realSize;
private IntPtr _handle;
private IntPtr _xic;
@@ -45,7 +46,7 @@ namespace Avalonia.X11
}
private readonly Queue _inputQueue = new Queue();
private InputEventContainer _lastEvent;
-
+ private bool _useRenderWindow = false;
public X11Window(AvaloniaX11Platform platform, bool popup)
{
_platform = platform;
@@ -53,9 +54,8 @@ namespace Avalonia.X11
_x11 = platform.Info;
_mouse = platform.MouseDevice;
_keyboard = platform.KeyboardDevice;
- _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
- XNames.XNClientWindow, _handle, IntPtr.Zero);
-
+
+ var glfeature = AvaloniaLocator.Current.GetService();
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
@@ -71,16 +71,44 @@ namespace Avalonia.X11
attr.override_redirect = true;
valueMask |= SetWindowValuemask.OverrideRedirect;
}
+
+ XVisualInfo? visualInfo = null;
+
+ // OpenGL seems to be do weird things to it's current window which breaks resize sometimes
+ _useRenderWindow = glfeature != null;
+
+ var glx = glfeature as GlxGlPlatformFeature;
+ if (glx != null)
+ visualInfo = *glx.Display.VisualInfo;
+ else if (glfeature == null)
+ visualInfo = _x11.TransparentVisualInfo;
+
+ var egl = glfeature as EglGlPlatformFeature;
+ var visual = IntPtr.Zero;
+ var depth = 24;
+ if (visualInfo != null)
+ {
+ visual = visualInfo.Value.visual;
+ depth = (int)visualInfo.Value.depth;
+ attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, visualInfo.Value.visual, 0);
+ valueMask |= SetWindowValuemask.ColorMap;
+ }
+
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0,
- 24,
- (int)CreateWindowArgs.InputOutput, IntPtr.Zero,
+ depth,
+ (int)CreateWindowArgs.InputOutput,
+ visual,
new UIntPtr((uint)valueMask), ref attr);
- _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24,
- (int)CreateWindowArgs.InputOutput,
- IntPtr.Zero,
- new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
- SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
+
+ if (_useRenderWindow)
+ _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth,
+ (int)CreateWindowArgs.InputOutput,
+ visual,
+ new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
+ SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
+ else
+ _renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(300, 200);
@@ -99,18 +127,26 @@ namespace Avalonia.X11
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
-
- var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService();
+
+
var surfaces = new List
{
- new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling)
+ new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
+ depth, () => Scaling)
};
- if (feature != null)
+
+ if (egl != null)
surfaces.Insert(0,
- new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext,
+ new EglGlPlatformSurface((EglDisplay)egl.Display, egl.DeferredContext,
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
+ if (glx != null)
+ surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
+ new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
+
Surfaces = surfaces.ToArray();
UpdateMotifHints();
+ _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
+ XNames.XNClientWindow, _handle, IntPtr.Zero);
XFlush(_x11.Display);
}
@@ -136,8 +172,6 @@ namespace Avalonia.X11
XLockDisplay(_display);
XGetGeometry(_display, _parent, out var geo);
XResizeWindow(_display, Handle, geo.width, geo.height);
- XFlush(_display);
- XSync(_display, true);
XUnlockDisplay(_display);
return new PixelSize(geo.width, geo.height);
}
@@ -235,7 +269,7 @@ namespace Avalonia.X11
public Func Closing { get; set; }
public Action WindowStateChanged { get; set; }
public Action Closed { get; set; }
- public Action PositionChanged { get; set; }
+ public Action PositionChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
new DeferredRenderer(root, AvaloniaLocator.Current.GetService());
@@ -252,7 +286,8 @@ namespace Avalonia.X11
if (ev.type == XEventName.MapNotify)
{
_mapped = true;
- XMapWindow(_x11.Display, _renderHandle);
+ if (_useRenderWindow)
+ XMapWindow(_x11.Display, _renderHandle);
}
else if (ev.type == XEventName.UnmapNotify)
_mapped = false;
@@ -321,13 +356,13 @@ namespace Avalonia.X11
var needEnqueue = (_configure == null);
_configure = ev.ConfigureEvent;
if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event)
- _configurePoint = new Point(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
+ _configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
else
{
XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow,
0, 0,
out var tx, out var ty, out _);
- _configurePoint = new Point(tx, ty);
+ _configurePoint = new PixelPoint(tx, ty);
}
if (needEnqueue)
Dispatcher.UIThread.Post(() =>
@@ -356,7 +391,9 @@ namespace Avalonia.X11
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}, DispatcherPriority.Layout);
- XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height);
+ if (_useRenderWindow)
+ XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width,
+ ev.ConfigureEvent.height);
}
else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle)
{
@@ -619,7 +656,7 @@ namespace Avalonia.X11
Closed?.Invoke();
}
- if (_renderHandle != IntPtr.Zero)
+ if (_useRenderWindow && _renderHandle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _renderHandle);
_renderHandle = IntPtr.Zero;
@@ -660,9 +697,11 @@ namespace Avalonia.X11
public void Hide() => XUnmapWindow(_x11.Display, _handle);
- public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
+ public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
- public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y);
+ public PixelPoint PointToScreen(Point point) => new PixelPoint(
+ (int)(point.X * Scaling + Position.X),
+ (int)(point.Y * Scaling + Position.Y));
public void SetSystemDecorations(bool enabled)
{
@@ -685,7 +724,8 @@ namespace Avalonia.X11
var pixelSize = ToPixelSize(clientSize);
UpdateSizeHints(pixelSize);
XConfigureResizeWindow(_x11.Display, _handle, pixelSize);
- XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
+ if (_useRenderWindow)
+ XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
XFlush(_x11.Display);
if (force || (_popup && needImmediatePopupResize))
@@ -716,7 +756,7 @@ namespace Avalonia.X11
public IPlatformHandle Handle { get; }
- public Point Position
+ public PixelPoint Position
{
get => _position ?? default;
set
@@ -752,7 +792,7 @@ namespace Avalonia.X11
public IScreenImpl Screen => _platform.Screens;
- public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity)
+ public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
diff --git a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs
index 7c28dd47b9..a2b4604130 100644
--- a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs
+++ b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Gtk3
{
private readonly int _screenId;
- public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
+ public GtkScreen(PixelRect bounds, PixelRect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
{
this._screenId = screenId;
}
@@ -21,4 +21,4 @@ namespace Avalonia.Gtk3
return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
index 97b5029219..9a19ed00e0 100644
--- a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
@@ -27,8 +27,8 @@ namespace Avalonia.Gtk3
GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle();
Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry);
Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea);
- Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
- Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
+ PixelRect workAreaRect = new PixelRect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
+ PixelRect geometryRect = new PixelRect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i);
screens[i] = s;
}
diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
index 6d4544bfbd..c4697462d2 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Gtk3
private readonly EglGlPlatformSurface _egl;
protected readonly List Disposables = new List();
private Size _lastSize;
- private Point _lastPosition;
+ private PixelPoint _lastPosition;
private double _lastScaling;
private uint _lastKbdEvent;
private uint _lastSmoothScrollEvent;
@@ -383,7 +383,7 @@ namespace Avalonia.Gtk3
public Action Paint { get; set; }
public Action Resized { get; set; }
public Action ScalingChanged { get; set; } //TODO
- public Action PositionChanged { get; set; }
+ public Action PositionChanged { get; set; }
public void Activate() => Native.GtkWidgetActivate(GtkWidget);
@@ -402,7 +402,7 @@ namespace Avalonia.Gtk3
Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input);
}
- public Point PointToClient(Point point)
+ public Point PointToClient(PixelPoint point)
{
int x, y;
Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y);
@@ -410,11 +410,11 @@ namespace Avalonia.Gtk3
return new Point(point.X - x, point.Y - y);
}
- public Point PointToScreen(Point point)
+ public PixelPoint PointToScreen(Point point)
{
int x, y;
Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y);
- return new Point(point.X + x, point.Y + y);
+ return new PixelPoint((int)(point.X + x), (int)(point.Y + y));
}
public void SetCursor(IPlatformHandle cursor)
@@ -490,13 +490,13 @@ namespace Avalonia.Gtk3
get;
} = new ScreenImpl();
- public Point Position
+ public PixelPoint Position
{
get
{
int x, y;
Native.GtkWindowGetPosition(GtkWidget, out x, out y);
- return new Point(x, y);
+ return new PixelPoint(x, y);
}
set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); }
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index 968873abc0..6a63014e1a 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -51,9 +51,9 @@ namespace Avalonia.LinuxFramebuffer
InputRoot = inputRoot;
}
- public Point PointToClient(Point point) => point;
+ public Point PointToClient(PixelPoint p) => p.ToPoint(1);
- public Point PointToScreen(Point point) => point;
+ public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
index a825deeae3..b99864b050 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
@@ -8,11 +8,14 @@ using Avalonia.Platform;
using Portable.Xaml;
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
+using System.Xml.Linq;
+using System.Linq;
namespace Avalonia.Markup.Xaml
{
@@ -240,15 +243,21 @@ namespace Avalonia.Markup.Xaml
{
using (var xamlInfoStream = assetLocator.Open(xamlInfoUri))
{
- var xamlInfo = (AvaloniaResourceXamlInfo)s_xamlInfoSerializer.ReadObject(xamlInfoStream);
- if (xamlInfo.ClassToResourcePathIndex.TryGetValue(typeName, out var rv) == true)
+ var assetDoc = XDocument.Load(xamlInfoStream);
+ XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
+ XNamespace arrayNs = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
+ Dictionary xamlInfo =
+ assetDoc.Root.Element(assetNs + "ClassToResourcePathIndex").Elements(arrayNs + "KeyValueOfstringstring")
+ .ToDictionary(entry =>entry.Element(arrayNs + "Key").Value,
+ entry => entry.Element(arrayNs + "Value").Value);
+
+ if (xamlInfo.TryGetValue(typeName, out var rv))
{
yield return new Uri($"avares://{asm}{rv}");
yield break;
}
}
- }
-
+ }
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
index 5d1a98f6f8..9fa6c26c35 100644
--- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
@@ -77,40 +77,15 @@ namespace Avalonia.Markup.Xaml.PortableXaml
_delayedValuesHelper.ApplyAll();
}
- protected internal override void OnAfterBeginInit(object value)
- {
- //not called for avalonia objects
- //as it's called inly for
- //Portable.Xaml.ComponentModel.ISupportInitialize
- base.OnAfterBeginInit(value);
- }
-
- protected internal override void OnAfterEndInit(object value)
- {
- //not called for avalonia objects
- //as it's called inly for
- //Portable.Xaml.ComponentModel.ISupportInitialize
- base.OnAfterEndInit(value);
- }
-
protected internal override void OnAfterProperties(object value)
{
_delayedValuesHelper.EndInit(value);
base.OnAfterProperties(value);
-
- //AfterEndInit is not called as it supports only
- //Portable.Xaml.ComponentModel.ISupportInitialize
- //and we have Avalonia.ISupportInitialize so we need some hacks
- HandleEndEdit(value);
}
protected internal override void OnBeforeProperties(object value)
{
- //OnAfterBeginInit is not called as it supports only
- //Portable.Xaml.ComponentModel.ISupportInitialize
- //and we have Avalonia.ISupportInitialize so we need some hacks
- HandleBeginInit(value);
if (value != null)
_delayedValuesHelper.BeginInit(value);
@@ -127,16 +102,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
return base.OnSetValue(target, member, value);
}
- private void HandleBeginInit(object value)
- {
- (value as Avalonia.ISupportInitialize)?.BeginInit();
- }
-
- private void HandleEndEdit(object value)
- {
- (value as Avalonia.ISupportInitialize)?.EndInit();
- }
-
public override void WriteStartMember(XamlMember property)
{
foreach(var d in DesignDirectives)
diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
index 2d6e046f51..9a493a85c0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
private IRuntimeTypeProvider _avaloniaTypeProvider;
- protected internal override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
+ protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
{
XamlType type = null;
try
diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
index 8abbe09592..ab55261737 160000
--- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
+++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
@@ -1 +1 @@
-Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78
+Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
index f66d3e51fc..55c3aab81f 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@@ -32,6 +33,11 @@ namespace Avalonia.Markup.Parsers
public static IEnumerable Parse(string s)
{
var r = new CharacterReader(s.AsSpan());
+ return Parse(ref r, null);
+ }
+
+ private static IEnumerable Parse(ref CharacterReader r, char? end)
+ {
var state = State.Start;
var selector = new List();
while (!r.End && state != State.End)
@@ -43,7 +49,7 @@ namespace Avalonia.Markup.Parsers
state = ParseStart(ref r);
break;
case State.Middle:
- state = ParseMiddle(ref r);
+ state = ParseMiddle(ref r, end);
break;
case State.CanHaveType:
state = ParseCanHaveType(ref r);
@@ -107,7 +113,7 @@ namespace Avalonia.Markup.Parsers
return State.TypeName;
}
- private static State ParseMiddle(ref CharacterReader r)
+ private static State ParseMiddle(ref CharacterReader r, char? end)
{
if (r.TakeIf(':'))
{
@@ -129,6 +135,10 @@ namespace Avalonia.Markup.Parsers
{
return State.Name;
}
+ else if (end.HasValue && !r.End && r.Peek == end.Value)
+ {
+ return State.End;
+ }
return State.TypeName;
}
@@ -151,16 +161,23 @@ namespace Avalonia.Markup.Parsers
}
const string IsKeyword = "is";
+ const string NotKeyword = "not";
+
if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('('))
{
var syntax = ParseType(ref r, new IsSyntax());
- if (r.End || !r.TakeIf(')'))
- {
- throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}");
- }
+ Expect(ref r, ')');
return (State.CanHaveType, syntax);
}
+ if (identifier.SequenceEqual(NotKeyword.AsSpan()) && r.TakeIf('('))
+ {
+ var argument = Parse(ref r, ')');
+ Expect(ref r, ')');
+
+ var syntax = new NotSyntax { Argument = argument };
+ return (State.Middle, syntax);
+ }
else
{
return (
@@ -282,6 +299,18 @@ namespace Avalonia.Markup.Parsers
return syntax;
}
+ private static void Expect(ref CharacterReader r, char c)
+ {
+ if (r.End)
+ {
+ throw new ExpressionParseException(r.Position, $"Expected '{c}', got end of selector.");
+ }
+ else if (!r.TakeIf(')'))
+ {
+ throw new ExpressionParseException(r.Position, $"Expected '{c}', got '{r.Peek}'.");
+ }
+ }
+
public interface ISyntax
{
}
@@ -376,5 +405,15 @@ namespace Avalonia.Markup.Parsers
return obj is TemplateSyntax;
}
}
+
+ public class NotSyntax : ISyntax
+ {
+ public IEnumerable Argument { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument);
+ }
+ }
}
}
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
index bf5b396bec..8d1216e1dc 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Globalization;
using Avalonia.Styling;
using Avalonia.Utilities;
@@ -25,7 +26,7 @@ namespace Avalonia.Markup.Parsers
///
public SelectorParser(Func typeResolver)
{
- this._typeResolver = typeResolver;
+ _typeResolver = typeResolver;
}
///
@@ -36,6 +37,11 @@ namespace Avalonia.Markup.Parsers
public Selector Parse(string s)
{
var syntax = SelectorGrammar.Parse(s);
+ return Create(syntax);
+ }
+
+ private Selector Create(IEnumerable syntax)
+ {
var result = default(Selector);
foreach (var i in syntax)
@@ -97,6 +103,11 @@ namespace Avalonia.Markup.Parsers
case SelectorGrammar.TemplateSyntax template:
result = result.Template();
break;
+ case SelectorGrammar.NotSyntax not:
+ result = result.Not(x => Create(not.Argument));
+ break;
+ default:
+ throw new NotSupportedException($"Unsupported selector grammar '{i.GetType()}'.");
}
}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index fabfe2b225..353354da5e 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -401,15 +401,15 @@ namespace Avalonia.Skia
/// Tile brush image.
private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
{
- var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSize(_dpi), targetSize);
+ var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetSize);
var intermediate = CreateRenderTarget(calc.IntermediateSize);
paintWrapper.AddDisposable(intermediate);
using (var context = intermediate.CreateDrawingContext(null))
{
- var sourceRect = new Rect(tileBrushImage.PixelSize.ToSize(96));
- var targetRect = new Rect(tileBrushImage.PixelSize.ToSize(_dpi));
+ var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96));
+ var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi));
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
@@ -634,7 +634,7 @@ namespace Avalonia.Skia
///
private SurfaceRenderTarget CreateRenderTarget(Size size, PixelFormat? format = null)
{
- var pixelSize = PixelSize.FromSize(size, _dpi);
+ var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi);
var createInfo = new SurfaceRenderTarget.CreateInfo
{
Width = pixelSize.Width,
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 255e84355a..c6e68b1c8b 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -27,11 +27,12 @@ namespace Avalonia.Skia
if (gl != null)
{
var display = gl.ImmediateContext.Display;
+ gl.ImmediateContext.MakeCurrent();
using (var iface = display.Type == GlDisplayType.OpenGL2
? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
- gl.ImmediateContext.MakeCurrent();
+
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index aaca1a3b00..5e150ff647 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -330,7 +330,7 @@ namespace Avalonia.Direct2D1.Media
{
var platform = AvaloniaLocator.Current.GetService();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
- var pixelSize = PixelSize.FromSize(size, dpi);
+ var pixelSize = PixelSize.FromSizeWithDpi(size, dpi);
return platform.CreateRenderTargetBitmap(pixelSize, dpi);
}
}
@@ -438,12 +438,12 @@ namespace Avalonia.Direct2D1.Media
// D2D alters the DPI of the render target, which messes stuff up. PixelSize.FromSize
// will do the rounding for us.
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
- var pixelSize = PixelSize.FromSize(intermediateSize, dpi);
+ var pixelSize = PixelSize.FromSizeWithDpi(intermediateSize, dpi);
using (var intermediate = new BitmapRenderTarget(
_deviceContext,
CompatibleRenderTargetOptions.None,
- pixelSize.ToSize(dpi).ToSharpDX()))
+ pixelSize.ToSizeWithDpi(dpi).ToSharpDX()))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer))
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
index 19d320b783..fbc6d21cb7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Direct2D1.Media
Size targetSize)
{
var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height);
- var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(dpi), targetSize);
+ var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSizeWithDpi(dpi), targetSize);
if (!calc.NeedsIntermediate)
{
@@ -101,7 +101,7 @@ namespace Avalonia.Direct2D1.Media
using (var context = new RenderTarget(result).CreateDrawingContext(null))
{
var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height);
- var rect = new Rect(bitmap.PixelSize.ToSize(dpi));
+ var rect = new Rect(bitmap.PixelSize.ToSizeWithDpi(dpi));
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
index fbe7f840d7..8ec368c999 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
@@ -61,8 +61,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
dc.DrawImage(
RefCountable.CreateUnownedNotClonable(this),
1,
- new Rect(PixelSize.ToSize(Dpi.X)),
- new Rect(PixelSize.ToSize(Dpi.X)));
+ new Rect(PixelSize.ToSizeWithDpi(Dpi.X)),
+ new Rect(PixelSize.ToSizeWithDpi(Dpi.X)));
}
wic.Save(stream);
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
index 44c4c84cd6..cbfc259eda 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
@@ -3,7 +3,9 @@
static class WpfInteropExtensions
{
public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y);
+ public static System.Windows.Point ToWpfPoint(this PixelPoint pt) => new System.Windows.Point(pt.X, pt.Y);
public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y);
+ public static PixelPoint ToAvaloniaPixelPoint(this System.Windows.Point pt) => new PixelPoint((int)pt.X, (int)pt.Y);
public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height);
public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height);
}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index b5fb158168..7005459487 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -135,9 +135,9 @@ namespace Avalonia.Win32.Interop.Wpf
void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
- Point ITopLevelImpl.PointToClient(Point point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+ Point ITopLevelImpl.PointToClient(PixelPoint point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint();
- Point ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+ PixelPoint ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPixelPoint();
protected override void OnLostFocus(RoutedEventArgs e) => LostFocus?.Invoke();
diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs
index 7013931be9..28065566d0 100644
--- a/src/Windows/Avalonia.Win32/OleDropTarget.cs
+++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs
@@ -177,7 +177,7 @@ namespace Avalonia.Win32
int x = (int)dragPoint;
int y = (int)(dragPoint >> 32);
- Point screenPt = new Point(x, y);
+ var screenPt = new PixelPoint(x, y);
return _target.PointToClient(screenPt);
}
}
diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs
index 5cfceb68b7..aa3b23eb50 100644
--- a/src/Windows/Avalonia.Win32/ScreenImpl.cs
+++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs
@@ -31,10 +31,10 @@ namespace Avalonia.Win32
{
RECT bounds = monitorInfo.rcMonitor;
RECT workingArea = monitorInfo.rcWork;
- Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left,
+ PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left,
bounds.bottom - bounds.top);
- Rect avaloniaWorkArea =
- new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
+ PixelRect avaloniaWorkArea =
+ new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
workingArea.bottom - workingArea.top);
screens[index] =
new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs
index 2f8dcb0f3e..e849800e62 100644
--- a/src/Windows/Avalonia.Win32/WinScreen.cs
+++ b/src/Windows/Avalonia.Win32/WinScreen.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Win32
{
private readonly IntPtr _hMonitor;
- public WinScreen(Rect bounds, Rect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary)
+ public WinScreen(PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary)
{
this._hMonitor = hMonitor;
}
@@ -22,4 +22,4 @@ namespace Avalonia.Win32
return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index f4eb0e2ea8..081a713e95 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -87,7 +87,7 @@ namespace Avalonia.Win32
public Action ScalingChanged { get; set; }
- public Action PositionChanged { get; set; }
+ public Action PositionChanged { get; set; }
public Action WindowStateChanged { get; set; }
@@ -297,19 +297,19 @@ namespace Avalonia.Win32
UnmanagedMethods.InvalidateRect(_hwnd, ref r, false);
}
- public Point PointToClient(Point point)
+ public Point PointToClient(PixelPoint point)
{
var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y };
UnmanagedMethods.ScreenToClient(_hwnd, ref p);
return new Point(p.X, p.Y) / Scaling;
}
- public Point PointToScreen(Point point)
+ public PixelPoint PointToScreen(Point point)
{
point *= Scaling;
var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y };
UnmanagedMethods.ClientToScreen(_hwnd, ref p);
- return new Point(p.X, p.Y);
+ return new PixelPoint(p.X, p.Y);
}
public void SetInputRoot(IInputRoot inputRoot)
@@ -357,21 +357,21 @@ namespace Avalonia.Win32
#endif
}
- public Point Position
+ public PixelPoint Position
{
get
{
UnmanagedMethods.RECT rc;
UnmanagedMethods.GetWindowRect(_hwnd, out rc);
- return new Point(rc.left, rc.top);
+ return new PixelPoint(rc.left, rc.top);
}
set
{
UnmanagedMethods.SetWindowPos(
Handle.Handle,
IntPtr.Zero,
- (int)value.X,
- (int)value.Y,
+ value.X,
+ value.Y,
0,
0,
UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
@@ -661,7 +661,7 @@ namespace Avalonia.Win32
return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_MOVE:
- PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
+ PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO:
@@ -785,9 +785,9 @@ namespace Avalonia.Win32
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;
}
- private Point PointFromLParam(IntPtr lParam)
+ private PixelPoint PointFromLParam(IntPtr lParam)
{
- return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16));
+ return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16));
}
private Point ScreenToClient(Point point)
diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
index 1101f53222..83d10b8b44 100644
--- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs
+++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
@@ -68,9 +68,9 @@ namespace Avalonia.iOS
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
- public Point PointToClient(Point point) => point;
+ public Point PointToClient(PixelPoint point) => point.ToPoint(1);
- public Point PointToScreen(Point point) => point;
+ public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public void SetCursor(IPlatformHandle cursor)
{
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
index c34e26ac5c..d11319114f 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
@@ -19,6 +19,19 @@ namespace Avalonia.Base.UnitTests
p = AttachedOwner.AttachedProperty;
}
+ [Fact]
+ public void Registered_Properties_Count_Reflects_Newly_Added_Attached_Property()
+ {
+ var registry = new AvaloniaPropertyRegistry();
+ var metadata = new StyledPropertyMetadata();
+ var property = new AttachedProperty("test", typeof(object), metadata, true);
+ registry.Register(typeof(object), property);
+ registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property);
+ property.AddOwner();
+
+ Assert.Equal(1, registry.Properties.Count);
+ }
+
[Fact]
public void GetRegistered_Returns_Registered_Properties()
{
@@ -138,5 +151,9 @@ namespace Avalonia.Base.UnitTests
private class AttachedOwner2 : AttachedOwner
{
}
+
+ private class Class4 : AvaloniaObject
+ {
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
index 701fdbce9c..4585181ab7 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
@@ -150,6 +150,26 @@ namespace Avalonia.Base.UnitTests.Data.Core
}
}
+ [Fact]
+ public void Should_Work_With_Value_Type()
+ {
+ using (var sync = UnitTestSynchronizationContext.Begin())
+ {
+ var source = new BehaviorSubject(1);
+ var data = new { Foo = source };
+ var target = ExpressionObserver.Create(data, o => o.Foo.StreamBinding());
+ var result = new List();
+
+ var sub = target.Subscribe(x => result.Add((int)x));
+ source.OnNext(42);
+ sync.ExecutePostedCallbacks();
+
+ Assert.Equal(new[] { 1, 42 }, result);
+
+ GC.KeepAlive(data);
+ }
+ }
+
private class Class1 : NotifyingBase
{
public Subject Next { get; } = new Subject();
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 76f2898700..91b37596b7 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -238,9 +238,9 @@ namespace Avalonia.Controls.UnitTests
public void Invalidate(Rect rect) => throw new NotImplementedException();
- public Point PointToClient(Point point) => throw new NotImplementedException();
+ public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
- public Point PointToScreen(Point point) => throw new NotImplementedException();
+ public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 66f0cc3a40..834c49ba6b 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests
private IDisposable Application()
{
- var screen = new Rect(new Point(), new Size(100, 100));
+ var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
var screenImpl = new Mock();
screenImpl.Setup(x => x.ScreenCount).Returns(1);
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) });
diff --git a/tests/Avalonia.Controls.UnitTests/ImageTests.cs b/tests/Avalonia.Controls.UnitTests/ImageTests.cs
index e92fc572b4..71d0d1e328 100644
--- a/tests/Avalonia.Controls.UnitTests/ImageTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ImageTests.cs
@@ -61,5 +61,61 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(50, 50), target.DesiredSize);
}
+
+ [Fact]
+ public void Arrange_Should_Return_Correct_Size_For_No_Stretch()
+ {
+ var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100));
+ var target = new Image();
+ target.Stretch = Stretch.None;
+ target.Source = bitmap;
+
+ target.Measure(new Size(50, 50));
+ target.Arrange(new Rect(0, 0, 100, 400));
+
+ Assert.Equal(new Size(50, 100), target.Bounds.Size);
+ }
+
+ [Fact]
+ public void Arrange_Should_Return_Correct_Size_For_Fill_Stretch()
+ {
+ var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100));
+ var target = new Image();
+ target.Stretch = Stretch.Fill;
+ target.Source = bitmap;
+
+ target.Measure(new Size(50, 50));
+ target.Arrange(new Rect(0, 0, 25, 100));
+
+ Assert.Equal(new Size(25, 100), target.Bounds.Size);
+ }
+
+ [Fact]
+ public void Arrange_Should_Return_Correct_Size_For_Uniform_Stretch()
+ {
+ var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100));
+ var target = new Image();
+ target.Stretch = Stretch.Uniform;
+ target.Source = bitmap;
+
+ target.Measure(new Size(50, 50));
+ target.Arrange(new Rect(0, 0, 25, 100));
+
+ Assert.Equal(new Size(25, 50), target.Bounds.Size);
+ }
+
+ [Fact]
+ public void Arrange_Should_Return_Correct_Size_For_UniformToFill_Stretch()
+ {
+ var bitmap = Mock.Of(x => x.PixelSize == new PixelSize(50, 100));
+ var target = new Image();
+ target.Stretch = Stretch.UniformToFill;
+ target.Source = bitmap;
+
+ target.Measure(new Size(50, 50));
+ target.Arrange(new Rect(0, 0, 25, 100));
+
+ Assert.Equal(new Size(25, 100), target.Bounds.Size);
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index ba4ccb0527..3ea32ed719 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
@@ -336,25 +336,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; } = new LayoutManager();
- public IRenderTarget CreateRenderTarget()
- {
- throw new NotImplementedException();
- }
-
- public void Invalidate(Rect rect)
- {
- throw new NotImplementedException();
- }
- public Point PointToClient(Point point)
- {
- throw new NotImplementedException();
- }
-
- public Point PointToScreen(Point point)
- {
- throw new NotImplementedException();
- }
+ public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
+ public void Invalidate(Rect rect) => throw new NotImplementedException();
+ public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
+ public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private class TestItemsPresenter : ItemsPresenter
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index 7187ea16da..9921a8de6c 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
@@ -1077,30 +1077,10 @@ namespace Avalonia.Controls.UnitTests.Presenters
public ILayoutManager LayoutManager { get; } = new LayoutManager();
- public IRenderTarget CreateRenderTarget()
- {
- throw new NotImplementedException();
- }
-
- public void Invalidate(Rect rect)
- {
- throw new NotImplementedException();
- }
-
- public Point PointToClient(Point point)
- {
- throw new NotImplementedException();
- }
-
- public Point PointToScreen(Point point)
- {
- throw new NotImplementedException();
- }
-
- protected override Size MeasureOverride(Size availableSize)
- {
- return base.MeasureOverride(availableSize);
- }
+ public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
+ public void Invalidate(Rect rect) => throw new NotImplementedException();
+ public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
+ public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private class TestItemsPresenter : ItemsPresenter
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index bbe1d85acb..2df925301f 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index a5c3881d37..e00084eba4 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -267,26 +267,46 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(page.Content);
}
+ [Fact]
+ public void DataTemplate_Created_Content_Should_Be_Logical_Child_After_ApplyTemplate()
+ {
+ TabControl target = new TabControl
+ {
+ Template = TabControlTemplate(),
+ ContentTemplate = new FuncDataTemplate(x =>
+ new TextBlock { Tag = "bar", Text = x }),
+ Items = new[] { "Foo" },
+ };
+
+ ApplyTemplate(target);
+ target.ContentPart.UpdateChild();
+
+ var content = Assert.IsType(target.ContentPart.Child);
+ Assert.Equal("bar", content.Tag);
+ Assert.Same(target, content.GetLogicalParent());
+ Assert.Single(target.GetLogicalChildren(), content);
+ }
+
private IControlTemplate TabControlTemplate()
{
return new FuncControlTemplate(parent =>
-
new StackPanel
{
- Children = {
- new ItemsPresenter
- {
- Name = "PART_ItemsPresenter",
- [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
- [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
- },
- new ContentPresenter
- {
- Name = "PART_Content",
- [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
- [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
- }
- }
+ Children =
+ {
+ new ItemsPresenter
+ {
+ Name = "PART_ItemsPresenter",
+ [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
+ [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
+ },
+ new ContentPresenter
+ {
+ Name = "PART_SelectedContentHost",
+ [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
+ [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
+ }
+ }
});
}
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index 51a4d21392..6d00409ae0 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
@@ -199,6 +199,22 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Showing_Should_Raise_Opened()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new TestWindowBase();
+ var raised = false;
+
+ target.Opened += (s, e) => raised = true;
+
+ target.Show();
+
+ Assert.True(raised);
+ }
+ }
+
[Fact]
public void Hiding_Should_Stop_Renderer()
{
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index adf9bd9cec..8221dadc86 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -228,18 +228,35 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void ShowDialog_Should_Start_Renderer()
{
-
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
+ var parent = Mock.Of();
var renderer = new Mock();
var target = new Window(CreateImpl(renderer));
- target.Show();
+ target.ShowDialog(parent);
renderer.Verify(x => x.Start(), Times.Once);
}
}
+ [Fact]
+ public void ShowDialog_Should_Raise_Opened()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var parent = Mock.Of();
+ var target = new Window();
+ var raised = false;
+
+ target.Opened += (s, e) => raised = true;
+
+ target.ShowDialog(parent);
+
+ Assert.True(raised);
+ }
+ }
+
[Fact]
public void Hiding_Should_Stop_Renderer()
{
@@ -278,8 +295,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{
- var screen1 = new Mock(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true);
- var screen2 = new Mock(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false);
+ var screen1 = new Mock(new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true);
+ var screen2 = new Mock(new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false);
var screens = new Mock();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object });
@@ -294,13 +311,13 @@ namespace Avalonia.Controls.UnitTests
{
var window = new Window(windowImpl.Object);
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
- window.Position = new Point(60, 40);
+ window.Position = new PixelPoint(60, 40);
window.Show();
- var expectedPosition = new Point(
- screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2,
- screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2);
+ var expectedPosition = new PixelPoint(
+ (int)(screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2),
+ (int)(screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2));
Assert.Equal(window.Position, expectedPosition);
}
@@ -330,7 +347,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(parentWindowServices))
{
var parentWindow = new Window();
- parentWindow.Position = new Point(60, 40);
+ parentWindow.Position = new PixelPoint(60, 40);
parentWindow.Show();
@@ -338,14 +355,14 @@ namespace Avalonia.Controls.UnitTests
{
var window = new Window();
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
- window.Position = new Point(60, 40);
+ window.Position = new PixelPoint(60, 40);
window.Owner = parentWindow;
window.Show();
- var expectedPosition = new Point(
- parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2,
- parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2);
+ var expectedPosition = new PixelPoint(
+ (int)(parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2),
+ (int)(parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2));
Assert.Equal(window.Position, expectedPosition);
}
diff --git a/tests/Avalonia.DesignerSupport.Tests/Helpers.cs b/tests/Avalonia.DesignerSupport.Tests/Helpers.cs
new file mode 100644
index 0000000000..223a86a9af
--- /dev/null
+++ b/tests/Avalonia.DesignerSupport.Tests/Helpers.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.DesignerSupport.Tests
+{
+ static class Helpers
+ {
+ public static void StructDiff(object parsed, object expected) => StructDiff(parsed, expected, "{root}");
+
+ static void StructDiff(object parsed, object expected, string path)
+ {
+ if (parsed == null && expected == null)
+ return;
+ if ((parsed == null && expected != null) || (parsed != null && expected == null))
+ throw new Exception(
+ $"{path}: Null mismatch: {(parsed == null ? "null" : "not-null")} {(expected == null ? "null" : "not-null")}");
+
+ if (parsed.GetType() != expected.GetType())
+ throw new Exception($"{path}: Type mismatch: {parsed.GetType()} {expected.GetType()}");
+
+ if (parsed is string || parsed.GetType().IsPrimitive)
+ {
+ if (!parsed.Equals(expected))
+ throw new Exception($"{path}: Not equal {parsed} {expected}");
+ }
+ else if (parsed is IDictionary dic)
+ {
+ var dic2 = (IDictionary) expected;
+ if (dic.Count != dic2.Count)
+ throw new Exception($"{path}: Dictionary count mismatch: {dic.Count} {dic2.Count}");
+
+ foreach (var k in dic.Keys.Cast().OrderBy(o => o.ToString()))
+ {
+ var v1 = dic[k];
+ var v2 = dic2[k];
+ StructDiff(v1, v2, path + "['" + k + "']");
+ }
+ }
+ else if (parsed is IList col)
+ {
+ var col2 = (IList) expected;
+ if (col.Count != col2.Count)
+ throw new Exception($"{path}: Collection count mismatch: {col.Count} {col2.Count}");
+ for (var c = 0; c < col.Count; c++)
+ StructDiff(col[c], col2[c], path + "[" + c + "]");
+ }
+ else
+ {
+ foreach (var prop in parsed.GetType().GetProperties()
+ .Where(p => p.GetMethod != null && p.GetMethod.IsPublic))
+ {
+ StructDiff(prop.GetValue(parsed), prop.GetValue(expected), path + "." + prop.Name);
+ }
+ }
+
+
+
+ }
+ }
+}
diff --git a/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs
new file mode 100644
index 0000000000..e5a477cc32
--- /dev/null
+++ b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Remote.Protocol;
+using Avalonia.Remote.Protocol.Viewport;
+using Xunit;
+
+namespace Avalonia.DesignerSupport.Tests
+{
+ public class RemoteProtocolTests : IDisposable
+ {
+ private readonly List _disposables = new List();
+ private IAvaloniaRemoteTransportConnection _server;
+ private IAvaloniaRemoteTransportConnection _client;
+ private BlockingCollection _serverMessages = new BlockingCollection();
+ private BlockingCollection _clientMessages = new BlockingCollection();
+ private SynchronizationContext _originalContext;
+
+
+ class DisabledSyncContext : SynchronizationContext
+ {
+ public override void Post(SendOrPostCallback d, object state)
+ {
+ throw new InvalidCastException("Not allowed");
+ }
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ throw new InvalidCastException("Not allowed");
+ }
+ }
+
+ void Init(IMessageTypeResolver clientResolver = null, IMessageTypeResolver serverResolver = null)
+ {
+ _originalContext = SynchronizationContext.Current;
+ SynchronizationContext.SetSynchronizationContext(new DisabledSyncContext());
+ var clientTransport = new BsonTcpTransport(clientResolver ?? new DefaultMessageTypeResolver());
+ var serverTransport = new BsonTcpTransport(serverResolver ?? new DefaultMessageTypeResolver());
+
+ var tcpListener = new TcpListener(IPAddress.Loopback, 0);
+ tcpListener.Start();
+ var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
+ tcpListener.Stop();
+
+ var tcs = new TaskCompletionSource();
+ serverTransport.Listen(IPAddress.Loopback, port, connected =>
+ {
+ _server = connected;
+ tcs.SetResult(0);
+ });
+ _client = clientTransport.Connect(IPAddress.Loopback, port).Result;
+ _disposables.Add(_client);
+ _client.OnMessage += (_, m) => _clientMessages.Add(m);
+ tcs.Task.Wait();
+ _disposables.Add(_server);
+ _server.OnMessage += (_, m) => _serverMessages.Add(m);
+
+ }
+
+ object TakeServer()
+ {
+ var src = new CancellationTokenSource(200);
+ try
+ {
+ return _serverMessages.Take(src.Token);
+ }
+ finally
+ {
+ src.Dispose();
+ }
+
+ }
+
+ [Fact]
+ void EntitiesAreProperlySerializedAndDeserialized()
+ {
+ Init();
+ var rnd = new Random();
+ _server.OnMessage += (_, message) => { };
+
+
+ object GetRandomValue(Type t, string pathInfo)
+ {
+ if (t.IsArray)
+ {
+ var arr = Array.CreateInstance(t.GetElementType(), 1);
+ ((IList)arr)[0] = GetRandomValue(t.GetElementType(), pathInfo);
+ return arr;
+ }
+
+ if (t == typeof(bool))
+ return true;
+ if (t == typeof(int) || t == typeof(long))
+ return rnd.Next();
+ if (t == typeof(byte))
+ return (byte)rnd.Next(255);
+ if (t == typeof(double))
+ return rnd.NextDouble();
+ if (t.IsEnum)
+ return ((IList)Enum.GetValues(t)).Cast().Last();
+ if (t == typeof(string))
+ return Guid.NewGuid().ToString();
+ if (t == typeof(Guid))
+ return Guid.NewGuid();
+ throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}");
+ }
+
+ foreach (var t in typeof(MeasureViewportMessage).Assembly.GetTypes().Where(t =>
+ t.GetCustomAttribute(typeof(AvaloniaRemoteMessageGuidAttribute)) != null))
+ {
+ var o = Activator.CreateInstance(t);
+ foreach (var p in t.GetProperties())
+ p.SetValue(o, GetRandomValue(p.PropertyType, $"{t.FullName}.{p.Name}"));
+
+ _client.Send(o).Wait(200);
+ var received = TakeServer();
+ Helpers.StructDiff(received, o);
+
+ }
+
+
+ }
+
+ [Fact]
+ void RemoteProtocolShouldBeBackwardsCompatible()
+ {
+ Init(new DefaultMessageTypeResolver(typeof(ExtendedMeasureViewportMessage).Assembly));
+ _client.Send(new ExtendedMeasureViewportMessage()
+ {
+ Width = 100, Height = 200, SomeNewProperty = 300,
+ SomeArrayProperty = new[]{1,2,3},
+ SubObjectProperty = new ExtendedMeasureViewportMessage.SubObject()
+ {
+ Foo = 543
+ }
+ });
+ var received = (MeasureViewportMessage)TakeServer();
+ Assert.Equal(100, received.Width);
+ Assert.Equal(200, received.Height);
+
+ }
+
+ public void Dispose()
+ {
+ _disposables.ForEach(d => d.Dispose());
+ SynchronizationContext.SetSynchronizationContext(_originalContext);
+ }
+ }
+
+ [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")]
+ public class ExtendedMeasureViewportMessage
+ {
+ public double Width { get; set; }
+
+ public int SomeNewProperty { get; set; }
+ public int[] SomeArrayProperty { get; set; }
+ public class SubObject
+ {
+ public int Foo { get; set; }
+ }
+ public SubObject SubObjectProperty { get; set; }
+ public double Height { get; set; }
+ }
+}
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 4dcd98cb93..b7aa452a18 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -348,6 +348,7 @@ namespace Avalonia.LeakTests
{
public bool DrawFps { get; set; }
public bool DrawDirtyRects { get; set; }
+ public event EventHandler SceneInvalidated;
public void AddDirty(IVisual visual)
{
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
index 123aadfda5..1ede4ee0b0 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
@@ -22,14 +22,14 @@ namespace Avalonia.Markup.UnitTests.Data
var target = new Binding(nameof(Class1.Foo))
{
- Converter = StringConverters.NullOrEmpty,
+ Converter = StringConverters.IsNullOrEmpty,
};
var expressionObserver = (BindingExpression)target.Initiate(
textBlock,
TextBlock.TextProperty).Observable;
- Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter);
+ Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter);
}
public class When_Binding_To_String
@@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Data
var target = new Binding(nameof(Class1.Foo))
{
- Converter = StringConverters.NotNullOrEmpty,
+ Converter = StringConverters.IsNotNullOrEmpty,
StringFormat = "Hello {0}",
};
diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
index 88fe5a2a12..e3ce4b0968 100644
--- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
@@ -200,6 +200,67 @@ namespace Avalonia.Markup.UnitTests.Parsers
result);
}
+ [Fact]
+ public void Not_OfType()
+ {
+ var result = SelectorGrammar.Parse(":not(Button)");
+
+ Assert.Equal(
+ new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.NotSyntax
+ {
+ Argument = new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+ },
+ }
+ },
+ result);
+ }
+
+ [Fact]
+ public void OfType_Not_Class()
+ {
+ var result = SelectorGrammar.Parse("Button:not(.foo)");
+
+ Assert.Equal(
+ new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+ new SelectorGrammar.NotSyntax
+ {
+ Argument = new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.ClassSyntax { Class = "foo" },
+ },
+ }
+ },
+ result);
+ }
+
+ [Fact]
+ public void Is_Descendent_Not_OfType_Class()
+ {
+ var result = SelectorGrammar.Parse(":is(Control) :not(Button.foo)");
+
+ Assert.Equal(
+ new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.IsSyntax { TypeName = "Control" },
+ new SelectorGrammar.DescendantSyntax { },
+ new SelectorGrammar.NotSyntax
+ {
+ Argument = new SelectorGrammar.ISyntax[]
+ {
+ new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+ new SelectorGrammar.ClassSyntax { Class = "foo" },
+ },
+ }
+ },
+ result);
+ }
+
[Fact]
public void Namespace_Alone_Fails()
{
@@ -223,5 +284,17 @@ namespace Avalonia.Markup.UnitTests.Parsers
{
Assert.Throws(() => SelectorGrammar.Parse(".%foo"));
}
+
+ [Fact]
+ public void Not_Without_Argument_Fails()
+ {
+ Assert.Throws(() => SelectorGrammar.Parse(":not()"));
+ }
+
+ [Fact]
+ public void Not_Without_Closing_Parenthesis_Fails()
+ {
+ Assert.Throws(() => SelectorGrammar.Parse(":not(Button"));
+ }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs
index 3ecb2d9f37..104f46cbac 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs
@@ -4,6 +4,7 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using System.Collections.Generic;
+using System.ComponentModel;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
@@ -39,4 +40,4 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Order.Add($"EndInit {InitState}");
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
index beaf7477d0..a84ce74a88 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
@@ -198,5 +198,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
ex.InnerException.Message);
}
}
+
+ [Fact]
+ public void Style_Can_Use_Not_Selector()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var foo = window.FindControl("foo");
+ var notFoo = window.FindControl("notFoo");
+
+ Assert.Null(foo.Background);
+ Assert.Equal(Colors.Red, ((ISolidColorBrush)notFoo.Background).Color);
+ }
+ }
}
}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
index b782311729..70a5504a7d 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
@@ -10,6 +10,7 @@ using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
+using Avalonia.Markup.Xaml;
namespace Avalonia
{
@@ -70,12 +71,40 @@ namespace Avalonia
public class ActivatableWindow : ReactiveWindow
{
- public ActivatableWindow() => this.WhenActivated(disposables => { });
+ public ActivatableWindow()
+ {
+ InitializeComponent();
+ Assert.IsType(Content);
+ this.WhenActivated(disposables => { });
+ }
+
+ private void InitializeComponent()
+ {
+ var loader = new AvaloniaXamlLoader();
+ loader.Load(@"
+
+
+ ", null, this);
+ }
}
public class ActivatableUserControl : ReactiveUserControl
{
- public ActivatableUserControl() => this.WhenActivated(disposables => { });
+ public ActivatableUserControl()
+ {
+ InitializeComponent();
+ Assert.IsType(Content);
+ this.WhenActivated(disposables => { });
+ }
+
+ private void InitializeComponent()
+ {
+ var loader = new AvaloniaXamlLoader();
+ loader.Load(@"
+
+
+ ", null, this);
+ }
}
public AvaloniaActivationForViewFetcherTest()
@@ -183,4 +212,4 @@ namespace Avalonia
Assert.False(viewModel.IsActivated);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs
new file mode 100644
index 0000000000..2f3e2b8f34
--- /dev/null
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs
@@ -0,0 +1,114 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Xunit;
+
+namespace Avalonia.Styling.UnitTests
+{
+ public class SelectorTests_Not
+ {
+ [Fact]
+ public void Not_Selector_Should_Have_Correct_String_Representation()
+ {
+ var target = default(Selector).Not(x => x.Class("foo"));
+
+ Assert.Equal(":not(.foo)", target.ToString());
+ }
+
+ [Fact]
+ public void Not_OfType_Matches_Control_Of_Incorrect_Type()
+ {
+ var control = new Control1();
+ var target = default(Selector).Not(x => x.OfType());
+
+ Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
+ }
+
+ [Fact]
+ public void Not_OfType_Doesnt_Match_Control_Of_Correct_Type()
+ {
+ var control = new Control2();
+ var target = default(Selector).Not(x => x.OfType());
+
+ Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
+ }
+
+ [Fact]
+ public async Task Not_Class_Doesnt_Match_Control_With_Class()
+ {
+ var control = new Control1
+ {
+ Classes = new Classes { "foo" },
+ };
+
+ var target = default(Selector).Not(x => x.Class("foo"));
+ var match = target.Match(control);
+
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.False(await match.Activator.Take(1));
+ }
+
+ [Fact]
+ public async Task Not_Class_Matches_Control_Without_Class()
+ {
+ var control = new Control1
+ {
+ Classes = new Classes { "bar" },
+ };
+
+ var target = default(Selector).Not(x => x.Class("foo"));
+ var match = target.Match(control);
+
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.True(await match.Activator.Take(1));
+ }
+
+ [Fact]
+ public async Task OfType_Not_Class_Matches_Control_Without_Class()
+ {
+ var control = new Control1
+ {
+ Classes = new Classes { "bar" },
+ };
+
+ var target = default(Selector).OfType().Not(x => x.Class("foo"));
+ var match = target.Match(control);
+
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.True(await match.Activator.Take(1));
+ }
+
+ [Fact]
+ public void OfType_Not_Class_Doesnt_Match_Control_Of_Wrong_Type()
+ {
+ var control = new Control2
+ {
+ Classes = new Classes { "foo" },
+ };
+
+ var target = default(Selector).OfType().Not(x => x.Class("foo"));
+ var match = target.Match(control);
+
+ Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
+ }
+
+ [Fact]
+ public void Returns_Correct_TargetType()
+ {
+ var target = default(Selector).OfType().Not(x => x.Class("foo"));
+
+ Assert.Equal(typeof(Control1), target.TargetType);
+ }
+
+ public class Control1 : TestControlBase
+ {
+ }
+
+ public class Control2 : TestControlBase
+ {
+ }
+ }
+}
diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
index 4096dcf380..4970addd81 100644
--- a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
+++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
@@ -10,6 +10,7 @@ using Avalonia.UnitTests;
using Xunit;
using Avalonia.LogicalTree;
using Avalonia.Controls;
+using System.ComponentModel;
namespace Avalonia.Styling.UnitTests
{
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index 972b1d78c0..370ddba410 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -90,9 +90,9 @@ namespace Avalonia.UnitTests
{
}
- public Point PointToClient(Point p) => p;
+ public Point PointToClient(PixelPoint p) => p.ToPoint(1);
- public Point PointToScreen(Point p) => p;
+ public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
void INameScope.Register(string name, object element)
{
diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs
index 2d896435a3..5d42699d3f 100644
--- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs
+++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs
@@ -57,9 +57,9 @@ namespace Avalonia.UnitTests
throw new NotImplementedException();
}
- public Point PointToClient(Point p) => p;
+ public Point PointToClient(PixelPoint p) => p.ToPoint(1);
- public Point PointToScreen(Point p) => p;
+ public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
void INameScope.Register(string name, object element)
{