diff --git a/build/ApiDiff.props b/build/ApiDiff.props
index da82fbcc51..3d322f56d5 100644
--- a/build/ApiDiff.props
+++ b/build/ApiDiff.props
@@ -7,6 +7,6 @@
-
+
diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 8fc2a7b77c..3bf6842cd6 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Utilities;
@@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins
///
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
+ private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
+ new Dictionary<(Type, string), PropertyInfo>();
+
///
- public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
+ public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
///
/// Starts monitoring the value of a property on an object.
@@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance);
- var p = GetPropertyWithName(instance.GetType(), propertyName);
+ var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
if (p != null)
{
@@ -44,12 +48,40 @@ namespace Avalonia.Data.Core.Plugins
}
}
- private static PropertyInfo GetPropertyWithName(Type type, string propertyName)
+ private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
- const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
- BindingFlags.Static | BindingFlags.Instance;
+ var key = (type, propertyName);
+
+ if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
+ {
+ propertyInfo = TryFindAndCacheProperty(type, propertyName);
+ }
+
+ return propertyInfo;
+ }
+
+ private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
+ {
+ PropertyInfo found = null;
+
+ const BindingFlags bindingFlags =
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+
+ var properties = type.GetProperties(bindingFlags);
+
+ foreach (PropertyInfo propertyInfo in properties)
+ {
+ if (propertyInfo.Name == propertyName)
+ {
+ found = propertyInfo;
+
+ break;
+ }
+ }
+
+ _propertyLookup.Add((type, propertyName), found);
- return type.GetProperty(propertyName, bindingFlags);
+ return found;
}
private class Accessor : PropertyAccessorBase, IWeakSubscriber
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 1e5e80d144..becb489557 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives
return;
}
- var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType().FirstOrDefault();
+ var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType();
if (placementTarget == null)
{
@@ -586,6 +586,26 @@ namespace Avalonia.Controls.Primitives
}
Closed?.Invoke(this, EventArgs.Empty);
+
+ var focusCheck = FocusManager.Instance?.Current;
+
+ // Focus is set to null as part of popup closing, so we only want to
+ // set focus to PlacementTarget if this is the case
+ if (focusCheck == null)
+ {
+ if (PlacementTarget != null)
+ {
+ FocusManager.Instance?.Focus(PlacementTarget);
+ }
+ else
+ {
+ var anc = this.FindLogicalAncestorOfType();
+ if (anc != null)
+ {
+ FocusManager.Instance?.Focus(anc);
+ }
+ }
+ }
}
private void ListenForNonClientClick(RawInputEventArgs e)
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index b4c30e0149..b2bd5ab2e5 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -117,10 +117,8 @@ namespace Avalonia.Controls
if (value != null)
{
if (selectedItems.Count != 1 || selectedItems[0] != value)
- {
- _syncingSelectedItems = true;
- SelectSingleItem(value);
- _syncingSelectedItems = false;
+ {
+ SelectSingleItem(value);
}
}
else if (SelectedItems.Count > 0)
@@ -219,8 +217,12 @@ namespace Avalonia.Controls
private void SelectSingleItem(object item)
{
- SelectedItems.Clear();
+ _syncingSelectedItems = true;
+ SelectedItems.Clear();
SelectedItems.Add(item);
+ _syncingSelectedItems = false;
+
+ SetAndRaise(SelectedItemProperty, ref _selectedItem, item);
}
///
diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs
index 5365354418..249b4d547f 100644
--- a/src/Avalonia.OpenGL/Egl/EglContext.cs
+++ b/src/Avalonia.OpenGL/Egl/EglContext.cs
@@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl
var old = new RestoreContext(_egl, _disp.Handle, _lock);
var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
- if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context))
+ if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero,
+ surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
success = true;
return old;
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
index fd3de854f5..623364866b 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
@@ -158,15 +158,21 @@ namespace Avalonia.OpenGL.Egl
var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
- var surf = _egl.CreatePBufferSurface(_display, _config, new[]
+
+ var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
+
+ IntPtr surf = IntPtr.Zero;
+ if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
{
- EGL_WIDTH, 1,
- EGL_HEIGHT, 1,
- EGL_NONE
- });
- if (surf == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
- var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf),
+ surf = _egl.CreatePBufferSurface(_display, _config,
+ new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
+ if (surf == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
+ }
+
+ var rv = new EglContext(this, _egl, shareCtx, ctx,
+ context =>
+ surf == IntPtr.Zero ? null : new EglSurface(this, context, surf),
_version, _sampleCount, _stencilSize);
return rv;
}
diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs
index 155339b985..66a8c1dd0c 100644
--- a/src/Avalonia.Visuals/Media/GlyphRun.cs
+++ b/src/Avalonia.Visuals/Media/GlyphRun.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Media
private double _fontRenderingEmSize;
private Size? _size;
private int _biDiLevel;
- private Point _baselineOrigin;
+ private Point? _baselineOrigin;
private ReadOnlySlice _glyphIndices;
private ReadOnlySlice _glyphAdvances;
@@ -97,7 +97,9 @@ namespace Avalonia.Media
{
get
{
- return _baselineOrigin;
+ _baselineOrigin ??= CalculateBaselineOrigin();
+
+ return _baselineOrigin.Value;
}
set => Set(ref _baselineOrigin, value);
}
@@ -538,6 +540,15 @@ namespace Avalonia.Media
return GlyphAdvances[index];
}
+ ///
+ /// Calculates the default baseline origin of the .
+ ///
+ /// The baseline origin.
+ private Point CalculateBaselineOrigin()
+ {
+ return new Point(0, -GlyphTypeface.Ascent * Scale);
+ }
+
///
/// Calculates the size of the .
///
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
index d13b4836ea..fc98e9f6f8 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
- var offsetY = LineMetrics.TextBaseline;
+ var offsetY = LineMetrics.TextBaseline - textRun.GlyphRun.BaselineOrigin.Y;
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
index 72eed9e543..dc44d2d55f 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
@@ -141,17 +141,7 @@ namespace Avalonia.LinuxFramebuffer.Output
_platformGl = new EglPlatformOpenGlInterface(_eglDisplay);
_eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface);
-
- EglContext CreateContext(EglContext share)
- {
- var offSurf = gbm_surface_create(device, 1, 1, GbmColorFormats.GBM_FORMAT_XRGB8888,
- GbmBoFlags.GBM_BO_USE_RENDERING);
- if (offSurf == null)
- throw new InvalidOperationException("Unable to create 1x1 sized GBM surface");
- return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf));
- }
-
- _deferredContext = CreateContext(null);
+ _deferredContext = _platformGl.PrimaryEglContext;
using (_deferredContext.MakeCurrent(_eglSurface))
{
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 9a486b60aa..cbc896813f 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -42,6 +42,11 @@ namespace Avalonia.Skia
///
public struct CreateInfo
{
+ ///
+ /// Canvas to draw to.
+ ///
+ public SKCanvas Canvas;
+
///
/// Surface to draw to.
///
@@ -89,7 +94,7 @@ namespace Avalonia.Skia
if (_grContext != null)
Monitor.Enter(_grContext);
Surface = createInfo.Surface;
- Canvas = createInfo.Surface.Canvas;
+ Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas;
if (Canvas == null)
{
diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
new file mode 100644
index 0000000000..72438609d5
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
@@ -0,0 +1,31 @@
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using SkiaSharp;
+
+namespace Avalonia.Skia.Helpers
+{
+ public class DrawingContextHelper
+ {
+ ///
+ /// Wrap Skia canvas in drawing context so we can use Avalonia api to render to external skia canvas
+ /// this is useful in scenarios where canvas is not controlled by application, but received from another non avalonia api
+ /// like: SKCanvas canvas = SKDocument.BeginPage(...);
+ ///
+ ///
+ ///
+ ///
+ /// DrawingContext
+ public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi, IVisualBrushRenderer visualBrushRenderer = null)
+ {
+ var createInfo = new DrawingContextImpl.CreateInfo
+ {
+ Canvas = canvas,
+ Dpi = dpi,
+ VisualBrushRenderer = visualBrushRenderer,
+ DisableTextLcdRendering = true,
+ };
+
+ return new DrawingContextImpl(createInfo);
+ }
+ }
+}
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
index 784046ac0b..32cdd21e04 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
@@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core
result);
}
+ [Fact]
+ public void Should_Not_Throw_Exception_On_Duplicate_Properties()
+ {
+ // Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733.
+ var source = new MyViewModel();
+ var target = new PropertyAccessorNode("Name", false);
+
+ target.Target = new WeakReference