Browse Source

Merge branch 'master' into fixes/Direct2D1TextRendering

pull/4825/head^2
Max Katz 6 years ago
committed by GitHub
parent
commit
c807f104f7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ApiDiff.props
  2. 44
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  3. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  4. 12
      src/Avalonia.Controls/TreeView.cs
  5. 3
      src/Avalonia.OpenGL/Egl/EglContext.cs
  6. 22
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  7. 15
      src/Avalonia.Visuals/Media/GlyphRun.cs
  8. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  9. 12
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  10. 7
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  11. 31
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  12. 18
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  13. 79
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  14. 58
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

2
build/ApiDiff.props

@ -7,6 +7,6 @@
<ItemGroup>
<PackageDownload Include="$(NugetPackageName)" Version="[$(ApiContractPackageVersion)]" />
<PackageReference Include="Microsoft.DotNet.ApiCompat" Version="5.0.0-beta.20372.2" PrivateAssets="All" />
<ResolvedMatchingContract Include="$(NuGetPackageRoot)\$(NugetPackageName.ToLower())\$(ApiContractPackageVersion)\lib\$(TargetFramework)\$(AssemblyName).dll" />
<ResolvedMatchingContract Include="$(NuGetPackageRoot)\$(NugetPackageName.ToLowerInvariant())\$(ApiContractPackageVersion)\lib\$(TargetFramework)\$(AssemblyName).dll" />
</ItemGroup>
</Project>

44
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
/// </summary>
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
/// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
/// <summary>
/// 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<PropertyChangedEventArgs>

22
src/Avalonia.Controls/Primitives/Popup.cs

@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives
return;
}
var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType<IControl>();
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<IControl>();
if (anc != null)
{
FocusManager.Instance?.Focus(anc);
}
}
}
}
private void ListenForNonClientClick(RawInputEventArgs e)

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

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

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

15
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<ushort> _glyphIndices;
private ReadOnlySlice<double> _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];
}
/// <summary>
/// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
/// </summary>
/// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin()
{
return new Point(0, -GlyphTypeface.Ascent * Scale);
}
/// <summary>
/// Calculates the size of the <see cref="GlyphRun"/>.
/// </summary>

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

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

7
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -42,6 +42,11 @@ namespace Avalonia.Skia
/// </summary>
public struct CreateInfo
{
/// <summary>
/// Canvas to draw to.
/// </summary>
public SKCanvas Canvas;
/// <summary>
/// Surface to draw to.
/// </summary>
@ -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)
{

31
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
{
/// <summary>
/// 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(...);
/// </summary>
/// <param name="canvas"></param>
/// <param name="dpi"></param>
/// <param name="visualBrushRenderer"></param>
/// <returns>DrawingContext</returns>
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);
}
}
}

18
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<object>(source);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
}
public class MyViewModelBase { public object Name => "Name"; }
public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; }
private interface INext
{
int PropertyChangedSubscriptionCount { get; }

79
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -485,6 +485,85 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
[Fact]
public void Closing_Popup_Sets_Focus_On_PlacementTarget()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
var tb = new TextBox();
var p = new Popup
{
PlacementTarget = window,
Child = tb
};
((ISetLogicalParent)p).SetParent(p.PlacementTarget);
window.Show();
p.Open();
if (p.Host is OverlayPopupHost host)
{
//Need to measure/arrange for visual children to show up
//in OverlayPopupHost
host.Measure(Size.Infinity);
host.Arrange(new Rect(host.DesiredSize));
}
tb.Focus();
p.Close();
var focus = FocusManager.Instance?.Current;
Assert.True(focus == window);
}
}
[Fact]
public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
var windowTB = new TextBox();
window.Content = windowTB;
var popupTB = new TextBox();
var p = new Popup
{
PlacementTarget = window,
IsLightDismissEnabled = false,
Child = popupTB
};
((ISetLogicalParent)p).SetParent(p.PlacementTarget);
window.Show();
p.Open();
if (p.Host is OverlayPopupHost host)
{
//Need to measure/arrange for visual children to show up
//in OverlayPopupHost
host.Measure(Size.Infinity);
host.Arrange(new Rect(host.DesiredSize));
}
popupTB.Focus();
windowTB.Focus();
var focus = FocusManager.Instance?.Current;
Assert.True(focus == windowTB);
p.Close();
Assert.True(focus == windowTB);
}
}
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:

58
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
@ -13,6 +14,7 @@ using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.UnitTests;
using ReactiveUI;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -454,6 +456,41 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection()
{
using (Application())
{
var dataContext = new TestDataContext();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
DataContext = dataContext
};
target.Bind(TreeView.ItemsProperty, new Binding("Items"));
target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem"));
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var selectedValues = new List<object>();
dataContext.WhenAnyValue(x => x.SelectedItem)
.Subscribe(x => selectedValues.Add(x));
_mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left);
_mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left);
Assert.Equal(3, selectedValues.Count);
Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray());
}
}
[Fact]
public void LogicalChildren_Should_Be_Set()
{
@ -1288,5 +1325,26 @@ namespace Avalonia.Controls.UnitTests
private class DerivedTreeView : TreeView
{
}
private class TestDataContext : ReactiveObject
{
private string _selectedItem;
public TestDataContext()
{
Items = new ObservableCollection<string>(Enumerable.Range(0, 5).Select(i => $"Item {i}"));
}
public ObservableCollection<string> Items { get; }
public string SelectedItem
{
get { return _selectedItem; }
set
{
this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
}
}
}
}

Loading…
Cancel
Save