Browse Source

Merge remote-tracking branch 'upstream/master' into fixes/Direct2D1TextRendering

pull/4825/head
Benedikt Schroeder 6 years ago
parent
commit
41fe597652
  1. 2
      build/ApiDiff.props
  2. 4
      samples/RenderDemo/App.xaml.cs
  3. 17
      src/Avalonia.Base/AvaloniaLocator.cs
  4. 44
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  5. 6
      src/Avalonia.Controls/IScrollAnchorProvider.cs
  6. 5
      src/Avalonia.Controls/Panel.cs
  7. 16
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  8. 8
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  9. 137
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  10. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  11. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  12. 18
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  13. 2
      src/Avalonia.Controls/Repeater/ViewManager.cs
  14. 14
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  15. 1
      src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
  16. 12
      src/Avalonia.Controls/TreeView.cs
  17. 11
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  18. 2
      src/Avalonia.Layout/StackLayout.cs
  19. 3
      src/Avalonia.OpenGL/Egl/EglContext.cs
  20. 22
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  21. 13
      src/Avalonia.OpenGL/GlInterface.cs
  22. 8
      src/Avalonia.Themes.Default/ToggleSwitch.xaml
  23. 5
      src/Avalonia.Themes.Fluent/Button.xaml
  24. 14
      src/Avalonia.Themes.Fluent/CheckBox.xaml
  25. 12
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  26. 8
      src/Avalonia.Themes.Fluent/DataValidationErrors.xaml
  27. 7
      src/Avalonia.Themes.Fluent/RadioButton.xaml
  28. 2
      src/Avalonia.Themes.Fluent/TabItem.xaml
  29. 4
      src/Avalonia.Themes.Fluent/TabStripItem.xaml
  30. 5
      src/Avalonia.Themes.Fluent/TextBox.xaml
  31. 5
      src/Avalonia.Themes.Fluent/ToggleButton.xaml
  32. 10
      src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
  33. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  34. 19
      src/Avalonia.Visuals/Media/GlyphRun.cs
  35. 18
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  36. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  37. 16
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  38. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  39. 2
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  40. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  41. 12
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  42. 21
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  43. 14
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  44. 134
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  45. 32
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  46. 7
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  47. 31
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  48. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  49. 72
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  50. 3
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  51. 2
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  52. 2
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  53. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  54. 11
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  55. 5
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  56. 2
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  57. 2
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  58. 92
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  59. 62
      src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs
  60. 85
      src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
  61. 183
      src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
  62. 85
      src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
  63. 39
      src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
  64. 39
      src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
  65. 18
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  66. 10
      src/Windows/Avalonia.Win32/Win32Platform.cs
  67. 13
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  68. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  69. 18
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  70. 47
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  71. 79
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  72. 58
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  73. 22
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  74. 2
      tests/Avalonia.UnitTests/TestRoot.cs

2
build/ApiDiff.props

@ -7,6 +7,6 @@
<ItemGroup> <ItemGroup>
<PackageDownload Include="$(NugetPackageName)" Version="[$(ApiContractPackageVersion)]" /> <PackageDownload Include="$(NugetPackageName)" Version="[$(ApiContractPackageVersion)]" />
<PackageReference Include="Microsoft.DotNet.ApiCompat" Version="5.0.0-beta.20372.2" PrivateAssets="All" /> <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> </ItemGroup>
</Project> </Project>

4
samples/RenderDemo/App.xaml.cs

@ -18,6 +18,10 @@ namespace RenderDemo
// App configuration, used by the entry point and previewer // App configuration, used by the entry point and previewer
static AppBuilder BuildAvaloniaApp() static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>() => AppBuilder.Configure<App>()
.With(new Win32PlatformOptions
{
OverlayPopups = true,
})
.UsePlatformDetect() .UsePlatformDetect()
.UseReactiveUI() .UseReactiveUI()
.LogToDebug(); .LogToDebug();

17
src/Avalonia.Base/AvaloniaLocator.cs

@ -54,6 +54,23 @@ namespace Avalonia
return _locator; return _locator;
} }
public AvaloniaLocator ToLazy<TImlp>(Func<TImlp> func) where TImlp : TService
{
var constructed = false;
TImlp instance = default;
_locator._registry[typeof (TService)] = () =>
{
if (!constructed)
{
instance = func();
constructed = true;
}
return instance;
};
return _locator;
}
public AvaloniaLocator ToSingleton<TImpl>() where TImpl : class, TService, new() public AvaloniaLocator ToSingleton<TImpl>() where TImpl : class, TService, new()
{ {
TImpl instance = null; TImpl instance = null;

44
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Reflection; using System.Reflection;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins
/// </summary> /// </summary>
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{ {
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
/// <inheritdoc/> /// <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> /// <summary>
/// Starts monitoring the value of a property on an object. /// Starts monitoring the value of a property on an object.
@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance); reference.TryGetTarget(out object instance);
var p = GetPropertyWithName(instance.GetType(), propertyName); var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
if (p != null) 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 | var key = (type, propertyName);
BindingFlags.Static | BindingFlags.Instance;
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> private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>

6
src/Avalonia.Controls/IScrollAnchorProvider.cs

@ -1,4 +1,6 @@
namespace Avalonia.Controls #nullable enable
namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Specifies a contract for a scrolling control that supports scroll anchoring. /// Specifies a contract for a scrolling control that supports scroll anchoring.
@ -8,7 +10,7 @@
/// <summary> /// <summary>
/// The currently chosen anchor element to use for scroll anchoring. /// The currently chosen anchor element to use for scroll anchoring.
/// </summary> /// </summary>
IControl CurrentAnchor { get; } IControl? CurrentAnchor { get; }
/// <summary> /// <summary>
/// Registers a control as a potential scroll anchor candidate. /// Registers a control as a potential scroll anchor candidate.

5
src/Avalonia.Controls/Panel.cs

@ -137,6 +137,11 @@ namespace Avalonia.Controls
throw new NotSupportedException(); throw new NotSupportedException();
} }
InvalidateMeasureOnChildrenChanged();
}
private protected virtual void InvalidateMeasureOnChildrenChanged()
{
InvalidateMeasure(); InvalidateMeasure();
} }

16
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -1,9 +1,9 @@
using System; using System;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
@ -14,6 +14,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Presents a single item of data inside a <see cref="TemplatedControl"/> template. /// Presents a single item of data inside a <see cref="TemplatedControl"/> template.
/// </summary> /// </summary>
[PseudoClasses(":empty")]
public class ContentPresenter : Control, IContentPresenter public class ContentPresenter : Control, IContentPresenter
{ {
/// <summary> /// <summary>
@ -102,6 +103,11 @@ namespace Avalonia.Controls.Presenters
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e)); TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
} }
public ContentPresenter()
{
UpdatePseudoClasses();
}
/// <summary> /// <summary>
/// Gets or sets a brush with which to paint the background. /// Gets or sets a brush with which to paint the background.
/// </summary> /// </summary>
@ -424,9 +430,15 @@ namespace Avalonia.Controls.Presenters
_recyclingDataTemplate = null; _recyclingDataTemplate = null;
} }
UpdatePseudoClasses();
InvalidateMeasure(); InvalidateMeasure();
} }
private void UpdatePseudoClasses()
{
PseudoClasses.Set(":empty", Content is null);
}
private double GetLayoutScale() private double GetLayoutScale()
{ {
var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;

8
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -512,6 +512,14 @@ namespace Avalonia.Controls.Presenters
var generator = Owner.ItemContainerGenerator; var generator = Owner.ItemContainerGenerator;
var newOffset = -1.0; var newOffset = -1.0;
if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue)
{
//before any kind of scrolling we need to make sure panel measure is valid
//or we risk get panel into not valid state
//we make a preemptive quick measure so scrolling is valid
panel.Measure(panel.PreviousMeasure.Value);
}
if (index >= 0 && index < ItemCount) if (index >= 0 && index < ItemCount)
{ {
if (index <= FirstIndex) if (index <= FirstIndex)

137
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -7,6 +7,8 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
/// <summary> /// <summary>
@ -14,6 +16,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider
{ {
private const double EdgeDetectionTolerance = 0.1;
/// <summary> /// <summary>
/// Defines the <see cref="CanHorizontallyScroll"/> property. /// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary> /// </summary>
@ -64,11 +68,13 @@ namespace Avalonia.Controls.Presenters
private bool _arranging; private bool _arranging;
private Size _extent; private Size _extent;
private Vector _offset; private Vector _offset;
private IDisposable _logicalScrollSubscription; private IDisposable? _logicalScrollSubscription;
private Size _viewport; private Size _viewport;
private Dictionary<int, Vector> _activeLogicalGestureScrolls; private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
private List<IControl> _anchorCandidates; private List<IControl>? _anchorCandidates;
private (IControl control, Rect bounds) _anchor; private IControl? _anchorElement;
private Rect _anchorElementBounds;
private bool _isAnchorElementDirty;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class. /// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
@ -90,8 +96,6 @@ namespace Avalonia.Controls.Presenters
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
} }
internal event EventHandler<VectorEventArgs> PreArrange;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally. /// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary> /// </summary>
@ -138,7 +142,14 @@ namespace Avalonia.Controls.Presenters
} }
/// <inheritdoc/> /// <inheritdoc/>
IControl IScrollAnchorProvider.CurrentAnchor => _anchor.control; IControl? IScrollAnchorProvider.CurrentAnchor
{
get
{
EnsureAnchorElementSelection();
return _anchorElement;
}
}
/// <summary> /// <summary>
/// Attempts to bring a portion of the target visual into view by scrolling the content. /// Attempts to bring a portion of the target visual into view by scrolling the content.
@ -215,16 +226,18 @@ namespace Avalonia.Controls.Presenters
_anchorCandidates ??= new List<IControl>(); _anchorCandidates ??= new List<IControl>();
_anchorCandidates.Add(element); _anchorCandidates.Add(element);
_isAnchorElementDirty = true;
} }
/// <inheritdoc/> /// <inheritdoc/>
void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element) void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element)
{ {
_anchorCandidates?.Remove(element); _anchorCandidates?.Remove(element);
_isAnchorElementDirty = true;
if (_anchor.control == element) if (_anchorElement == element)
{ {
_anchor = default; _anchorElement = null;
} }
} }
@ -247,11 +260,6 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
PreArrange?.Invoke(this, new VectorEventArgs
{
Vector = new Vector(finalSize.Width, finalSize.Height),
});
if (_logicalScrollSubscription != null || Child == null) if (_logicalScrollSubscription != null || Child == null)
{ {
return base.ArrangeOverride(finalSize); return base.ArrangeOverride(finalSize);
@ -271,59 +279,69 @@ namespace Avalonia.Controls.Presenters
// If we have an anchor and its position relative to Child has changed during the // If we have an anchor and its position relative to Child has changed during the
// arrange then that change wasn't just due to scrolling (as scrolling doesn't adjust // arrange then that change wasn't just due to scrolling (as scrolling doesn't adjust
// relative positions within Child). // relative positions within Child).
if (_anchor.control != null && if (_anchorElement != null &&
TranslateBounds(_anchor.control, Child, out var updatedBounds) && TranslateBounds(_anchorElement, Child, out var updatedBounds) &&
updatedBounds.Position != _anchor.bounds.Position) updatedBounds.Position != _anchorElementBounds.Position)
{ {
var offset = updatedBounds.Position - _anchor.bounds.Position; var offset = updatedBounds.Position - _anchorElementBounds.Position;
return offset; return offset;
} }
return default; return default;
} }
// Calculate the new anchor element. var isAnchoring = Offset.X >= EdgeDetectionTolerance || Offset.Y >= EdgeDetectionTolerance;
_anchor = CalculateCurrentAnchor();
// Do the arrange. if (isAnchoring)
ArrangeOverrideImpl(size, -Offset); {
// Calculate the new anchor element if necessary.
EnsureAnchorElementSelection();
// If the anchor moved during the arrange, we need to adjust the offset and do another arrange. // Do the arrange.
var anchorShift = TrackAnchor(); ArrangeOverrideImpl(size, -Offset);
if (anchorShift != default) // If the anchor moved during the arrange, we need to adjust the offset and do another arrange.
{ var anchorShift = TrackAnchor();
var newOffset = Offset + anchorShift;
var newExtent = Extent;
var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height);
if (newOffset.X > maxOffset.X) if (anchorShift != default)
{ {
newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width); var newOffset = Offset + anchorShift;
} var newExtent = Extent;
var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height);
if (newOffset.Y > maxOffset.Y) if (newOffset.X > maxOffset.X)
{ {
newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height); newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width);
} }
Extent = newExtent; if (newOffset.Y > maxOffset.Y)
{
newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height);
}
try Extent = newExtent;
{
_arranging = true; try
Offset = newOffset; {
} _arranging = true;
finally Offset = newOffset;
{ }
_arranging = false; finally
{
_arranging = false;
}
ArrangeOverrideImpl(size, -Offset);
} }
}
else
{
ArrangeOverrideImpl(size, -Offset); ArrangeOverrideImpl(size, -Offset);
} }
Viewport = finalSize; Viewport = finalSize;
Extent = Child.Bounds.Size.Inflate(Child.Margin); Extent = Child.Bounds.Size.Inflate(Child.Margin);
_isAnchorElementDirty = true;
return finalSize; return finalSize;
} }
@ -350,7 +368,7 @@ namespace Avalonia.Controls.Presenters
{ {
var logicalUnits = delta.Y / LogicalScrollItemSize; var logicalUnits = delta.Y / LogicalScrollItemSize;
delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize); delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize);
dy = logicalUnits * scrollable.ScrollSize.Height; dy = logicalUnits * scrollable!.ScrollSize.Height;
} }
else else
dy = delta.Y; dy = delta.Y;
@ -368,7 +386,7 @@ namespace Avalonia.Controls.Presenters
{ {
var logicalUnits = delta.X / LogicalScrollItemSize; var logicalUnits = delta.X / LogicalScrollItemSize;
delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize); delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize);
dx = logicalUnits * scrollable.ScrollSize.Width; dx = logicalUnits * scrollable!.ScrollSize.Width;
} }
else else
dx = delta.X; dx = delta.X;
@ -405,7 +423,7 @@ namespace Avalonia.Controls.Presenters
if (Extent.Height > Viewport.Height) if (Extent.Height > Viewport.Height)
{ {
double height = isLogical ? scrollable.ScrollSize.Height : 50; double height = isLogical ? scrollable!.ScrollSize.Height : 50;
y += -e.Delta.Y * height; y += -e.Delta.Y * height;
y = Math.Max(y, 0); y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height); y = Math.Min(y, Extent.Height - Viewport.Height);
@ -413,7 +431,7 @@ namespace Avalonia.Controls.Presenters
if (Extent.Width > Viewport.Width) if (Extent.Width > Viewport.Width)
{ {
double width = isLogical ? scrollable.ScrollSize.Width : 50; double width = isLogical ? scrollable!.ScrollSize.Width : 50;
x += -e.Delta.X * width; x += -e.Delta.X * width;
x = Math.Max(x, 0); x = Math.Max(x, 0);
x = Math.Min(x, Extent.Width - Viewport.Width); x = Math.Min(x, Extent.Width - Viewport.Width);
@ -441,7 +459,7 @@ namespace Avalonia.Controls.Presenters
private void ChildChanged(AvaloniaPropertyChangedEventArgs e) private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
{ {
UpdateScrollableSubscription((IControl)e.NewValue); UpdateScrollableSubscription((IControl?)e.NewValue);
if (e.OldValue != null) if (e.OldValue != null)
{ {
@ -449,7 +467,7 @@ namespace Avalonia.Controls.Presenters
} }
} }
private void UpdateScrollableSubscription(IControl child) private void UpdateScrollableSubscription(IControl? child)
{ {
var scrollable = child as ILogicalScrollable; var scrollable = child as ILogicalScrollable;
@ -498,13 +516,17 @@ namespace Avalonia.Controls.Presenters
} }
} }
private (IControl, Rect) CalculateCurrentAnchor() private void EnsureAnchorElementSelection()
{ {
if (_anchorCandidates == null) if (!_isAnchorElementDirty || _anchorCandidates is null)
{ {
return default; return;
} }
_anchorElement = null;
_anchorElementBounds = default;
_isAnchorElementDirty = false;
var bestCandidate = default(IControl); var bestCandidate = default(IControl);
var bestCandidateDistance = double.MaxValue; var bestCandidateDistance = double.MaxValue;
@ -531,10 +553,9 @@ namespace Avalonia.Controls.Presenters
// bounds aren't relative to the ScrollContentPresenter itself, if they change // bounds aren't relative to the ScrollContentPresenter itself, if they change
// then we know it wasn't just due to scrolling. // then we know it wasn't just due to scrolling.
var unscrolledBounds = TranslateBounds(bestCandidate, Child); var unscrolledBounds = TranslateBounds(bestCandidate, Child);
return (bestCandidate, unscrolledBounds); _anchorElement = bestCandidate;
_anchorElementBounds = unscrolledBounds;
} }
return default;
} }
private bool GetViewportBounds(IControl element, out Rect bounds) private bool GetViewportBounds(IControl element, out Rect bounds)

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

@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives
return; return;
} }
var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault(); var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType<IControl>();
if (placementTarget == null) if (placementTarget == null)
{ {
@ -586,6 +586,26 @@ namespace Avalonia.Controls.Primitives
} }
Closed?.Invoke(this, EventArgs.Empty); 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) private void ListenForNonClientClick(RawInputEventArgs e)

2
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives
set set
{ {
SetAndRaise(IsCheckedProperty, ref _isChecked, value); SetAndRaise(IsCheckedProperty, ref _isChecked, value);
UpdatePseudoClasses(value); UpdatePseudoClasses(IsChecked);
} }
} }

18
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -267,6 +267,11 @@ namespace Avalonia.Controls
return result; return result;
} }
private protected override void InvalidateMeasureOnChildrenChanged()
{
// Don't invalidate measure when children change.
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -364,6 +369,12 @@ namespace Avalonia.Controls
{ {
var newBounds = element.Bounds; var newBounds = element.Bounds;
virtInfo.ArrangeBounds = newBounds; virtInfo.ArrangeBounds = newBounds;
if (!virtInfo.IsRegisteredAsAnchorCandidate)
{
_viewportManager.RegisterScrollAnchorCandidate(element);
virtInfo.IsRegisteredAsAnchorCandidate = true;
}
} }
} }
@ -515,11 +526,14 @@ namespace Avalonia.Controls
return element; return element;
} }
internal void OnElementPrepared(IControl element, int index) internal void OnElementPrepared(IControl element, VirtualizationInfo virtInfo)
{ {
_viewportManager.OnElementPrepared(element); _viewportManager.OnElementPrepared(element, virtInfo);
if (ElementPrepared != null) if (ElementPrepared != null)
{ {
var index = virtInfo.Index;
if (_elementPreparedArgs == null) if (_elementPreparedArgs == null)
{ {
_elementPreparedArgs = new ItemsRepeaterElementPreparedEventArgs(element, index); _elementPreparedArgs = new ItemsRepeaterElementPreparedEventArgs(element, index);

2
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -661,7 +661,7 @@ namespace Avalonia.Controls
children.Add(element); children.Add(element);
} }
repeater.OnElementPrepared(element, index); repeater.OnElementPrepared(element, virtInfo);
// Update realized indices // Update realized indices
_firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index);

14
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -240,9 +240,14 @@ namespace Avalonia.Controls
} }
} }
public void OnElementPrepared(IControl element) public void OnElementPrepared(IControl element, VirtualizationInfo virtInfo)
{ {
_scroller?.RegisterAnchorCandidate(element); // WinUI registers the element as an anchor candidate here, but I feel that's in error:
// at this point the element has not yet been positioned by the arrange pass so it will
// have its previous position, meaning that when the arrange pass moves it into its new
// position, an incorrect scroll anchoring will occur. Instead signal that it's not yet
// registered as a scroll anchor candidate.
virtInfo.IsRegisteredAsAnchorCandidate = false;
} }
public void OnElementCleared(IControl element) public void OnElementCleared(IControl element)
@ -373,6 +378,11 @@ namespace Avalonia.Controls
} }
} }
public void RegisterScrollAnchorCandidate(IControl element)
{
_scroller?.RegisterAnchorCandidate(element);
}
private IControl GetImmediateChildOfRepeater(IControl descendant) private IControl GetImmediateChildOfRepeater(IControl descendant)
{ {
var targetChild = descendant; var targetChild = descendant;

1
src/Avalonia.Controls/Repeater/VirtualizationInfo.cs

@ -38,6 +38,7 @@ namespace Avalonia.Controls
public bool IsInUniqueIdResetPool => Owner == ElementOwner.UniqueIdResetPool; public bool IsInUniqueIdResetPool => Owner == ElementOwner.UniqueIdResetPool;
public bool MustClearDataContext { get; set; } public bool MustClearDataContext { get; set; }
public bool KeepAlive { get; set; } public bool KeepAlive { get; set; }
public bool IsRegisteredAsAnchorCandidate { get; set; }
public ElementOwner Owner { get; private set; } = ElementOwner.ElementFactory; public ElementOwner Owner { get; private set; } = ElementOwner.ElementFactory;
public string UniqueId { get; private set; } public string UniqueId { get; private set; }

12
src/Avalonia.Controls/TreeView.cs

@ -117,10 +117,8 @@ namespace Avalonia.Controls
if (value != null) if (value != null)
{ {
if (selectedItems.Count != 1 || selectedItems[0] != value) if (selectedItems.Count != 1 || selectedItems[0] != value)
{ {
_syncingSelectedItems = true; SelectSingleItem(value);
SelectSingleItem(value);
_syncingSelectedItems = false;
} }
} }
else if (SelectedItems.Count > 0) else if (SelectedItems.Count > 0)
@ -219,8 +217,12 @@ namespace Avalonia.Controls
private void SelectSingleItem(object item) private void SelectSingleItem(object item)
{ {
SelectedItems.Clear(); _syncingSelectedItems = true;
SelectedItems.Clear();
SelectedItems.Add(item); SelectedItems.Add(item);
_syncingSelectedItems = false;
SetAndRaise(SelectedItemProperty, ref _selectedItem, item);
} }
/// <summary> /// <summary>

11
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -238,7 +238,7 @@ namespace Avalonia.Headless
} }
} }
class HeadlessBitmapStub : IBitmapImpl, IRenderTargetBitmapImpl, IWriteableBitmapImpl class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
{ {
public Size Size { get; } public Size Size { get; }
@ -267,6 +267,13 @@ namespace Avalonia.Headless
return new HeadlessDrawingContextStub(); return new HeadlessDrawingContextStub();
} }
public void Blit(IDrawingContextImpl context)
{
}
public bool CanBlit => false;
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelSize PixelSize { get; } public PixelSize PixelSize { get; }
public int Version { get; set; } public int Version { get; set; }
@ -307,7 +314,7 @@ namespace Avalonia.Headless
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
return new HeadlessBitmapStub(size, new Vector(96, 96)); return new HeadlessBitmapStub(size, new Vector(96, 96));
} }

2
src/Avalonia.Layout/StackLayout.cs

@ -249,8 +249,8 @@ namespace Avalonia.Layout
realizationWindowOffsetInExtent + _orientation.MajorSize(realizationRect) >= 0 && realizationWindowOffsetInExtent <= majorSize) realizationWindowOffsetInExtent + _orientation.MajorSize(realizationRect) >= 0 && realizationWindowOffsetInExtent <= majorSize)
{ {
anchorIndex = (int) (realizationWindowOffsetInExtent / averageElementSize); anchorIndex = (int) (realizationWindowOffsetInExtent / averageElementSize);
offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent);
anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorIndex)); anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorIndex));
offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent);
} }
} }

3
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl
var old = new RestoreContext(_egl, _disp.Handle, _lock); var old = new RestoreContext(_egl, _disp.Handle, _lock);
var surf = surface ?? OffscreenSurface; var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); _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); throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
success = true; success = true;
return old; 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); var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero) if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl); 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, surf = _egl.CreatePBufferSurface(_display, _config,
EGL_HEIGHT, 1, new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
EGL_NONE if (surf == IntPtr.Zero)
}); throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
if (surf == IntPtr.Zero) }
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf), var rv = new EglContext(this, _egl, shareCtx, ctx,
context =>
surf == IntPtr.Zero ? null : new EglSurface(this, context, surf),
_version, _sampleCount, _stencilSize); _version, _sampleCount, _stencilSize);
return rv; return rv;
} }

13
src/Avalonia.OpenGL/GlInterface.cs

@ -117,6 +117,19 @@ namespace Avalonia.OpenGL
public delegate int GlCheckFramebufferStatus(int target); public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")] [GlEntryPoint("glCheckFramebufferStatus")]
public GlCheckFramebufferStatus CheckFramebufferStatus { get; } public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
public delegate void GlBlitFramebuffer(int srcX0,
int srcY0,
int srcX1,
int srcY1,
int dstX0,
int dstY0,
int dstX1,
int dstY1,
int mask,
int filter);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)]
public GlBlitFramebuffer BlitFramebuffer { get; }
public delegate void GlGenRenderbuffers(int count, int[] res); public delegate void GlGenRenderbuffers(int count, int[] res);
[GlEntryPoint("glGenRenderbuffers")] [GlEntryPoint("glGenRenderbuffers")]

8
src/Avalonia.Themes.Default/ToggleSwitch.xaml

@ -87,7 +87,6 @@
Grid.Row="0" Grid.Row="0"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{DynamicResource ToggleSwitchTopHeaderMargin}"
VerticalAlignment="Top"/> VerticalAlignment="Top"/>
<Grid Grid.Row="1" <Grid Grid.Row="1"
@ -163,6 +162,13 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="ToggleSwitch /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="{DynamicResource ToggleSwitchTopHeaderMargin}" />
</Style>
<Style Selector="ToggleSwitch /template/ ContentPresenter#PART_ContentPresenter:empty">
<Setter Property="Margin" Value="0" />
</Style>
<!-- NormalState --> <!-- NormalState -->
<Style Selector="ToggleSwitch /template/ Grid#SwitchAreaGrid"> <Style Selector="ToggleSwitch /template/ Grid#SwitchAreaGrid">
<Setter Property="Background" Value="{DynamicResource ToggleSwitchContainerBackground}"/> <Setter Property="Background" Value="{DynamicResource ToggleSwitchContainerBackground}"/>

5
src/Avalonia.Themes.Fluent/Button.xaml

@ -32,7 +32,6 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
@ -95,4 +94,8 @@
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" /> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style> </Style>
<Style Selector="Button /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
</Styles> </Styles>

14
src/Avalonia.Themes.Fluent/CheckBox.xaml

@ -22,16 +22,14 @@
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}" />
CornerRadius="{DynamicResource ControlCornerRadius}" />
<Grid VerticalAlignment="Top" Height="32"> <Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle" <Border x:Name="NormalRectangle"
BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}" BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}"
UseLayoutRounding="False" UseLayoutRounding="False"
Height="20" Height="20"
Width="20" Width="20" />
CornerRadius="{DynamicResource ControlCornerRadius}" />
<Viewbox UseLayoutRounding="False"> <Viewbox UseLayoutRounding="False">
<Panel> <Panel>
@ -52,6 +50,14 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="CheckBox /template/ Border#PART_Border">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="CheckBox /template/ Border#NormalRectangle">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<!-- Unchecked Normal State --> <!-- Unchecked Normal State -->
<Style Selector="CheckBox"> <Style Selector="CheckBox">

12
src/Avalonia.Themes.Fluent/ComboBox.xaml

@ -61,7 +61,6 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
MinWidth="{DynamicResource ComboBoxThemeMinWidth}" /> MinWidth="{DynamicResource ComboBoxThemeMinWidth}" />
<Border x:Name="HighlightBackground" <Border x:Name="HighlightBackground"
@ -70,8 +69,7 @@
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Background="{DynamicResource ComboBoxBackgroundUnfocused}" Background="{DynamicResource ComboBoxBackgroundUnfocused}"
BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}" BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}" />
CornerRadius="{DynamicResource ControlCornerRadius}" />
<TextBlock x:Name="PlaceholderTextBlock" <TextBlock x:Name="PlaceholderTextBlock"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -226,4 +224,12 @@
<Style Selector="ComboBox:focused:pressed /template/ Path#DropDownGlyph"> <Style Selector="ComboBox:focused:pressed /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" /> <Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" />
</Style> </Style>
<Style Selector="ComboBox /template/ Border#Background">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="ComboBox /template/ Border#HighlightBackground">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
</Styles> </Styles>

8
src/Avalonia.Themes.Fluent/DataValidationErrors.xaml

@ -31,12 +31,16 @@
</Setter> </Setter>
<Setter Property="ErrorTemplate"> <Setter Property="ErrorTemplate">
<DataTemplate> <DataTemplate>
<Canvas Width="14" Height="14" Margin="4 0 1 0" <Canvas Name="PART_ErrorTemplateCanvas"
Width="14" Height="14" Margin="4 0 1 0"
Background="Transparent"> Background="Transparent">
<Canvas.Styles> <Canvas.Styles>
<Style Selector="ToolTip"> <Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
</Style> </Style>
<Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Canvas.Styles> </Canvas.Styles>
<ToolTip.Tip> <ToolTip.Tip>
<ItemsControl Items="{Binding}"/> <ItemsControl Items="{Binding}"/>

7
src/Avalonia.Themes.Fluent/RadioButton.xaml

@ -25,8 +25,7 @@
<Border Name="RootBorder" <Border Name="RootBorder"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}">
CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid ColumnDefinitions="20,*"> <Grid ColumnDefinitions="20,*">
<Grid VerticalAlignment="Top" <Grid VerticalAlignment="Top"
Height="32"> Height="32">
@ -77,6 +76,10 @@
<Setter Property="Stroke" Value="{DynamicResource RadioButtonCheckGlyphStroke}" /> <Setter Property="Stroke" Value="{DynamicResource RadioButtonCheckGlyphStroke}" />
<Setter Property="Fill" Value="{DynamicResource RadioButtonCheckGlyphFill}" /> <Setter Property="Fill" Value="{DynamicResource RadioButtonCheckGlyphFill}" />
</Style> </Style>
<Style Selector="RadioButton /template/ Border#RootBorder">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<!-- PointerOver State --> <!-- PointerOver State -->

2
src/Avalonia.Themes.Fluent/TabItem.xaml

@ -39,7 +39,6 @@
TextBlock.FontSize="{TemplateBinding FontSize}" TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" /> TextBlock.FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe" <Border Name="PART_SelectedPipe"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" /> Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel> </Panel>
</Border> </Border>
@ -53,6 +52,7 @@
</Style> </Style>
<Style Selector="TabItem /template/ Border#PART_SelectedPipe"> <Style Selector="TabItem /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" /> <Setter Property="IsVisible" Value="False" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style> </Style>
<!-- Selected state --> <!-- Selected state -->

4
src/Avalonia.Themes.Fluent/TabStripItem.xaml

@ -38,7 +38,6 @@
TextBlock.FontSize="{TemplateBinding FontSize}" TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" /> TextBlock.FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe" <Border Name="PART_SelectedPipe"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" /> Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel> </Panel>
</Border> </Border>
@ -46,6 +45,9 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="TabStripItem /template/ Border#PART_SelectedPipe">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<!-- Nornal state --> <!-- Nornal state -->
<Style Selector="TabStripItem /template/ Border#PART_LayoutRoot"> <Style Selector="TabStripItem /template/ Border#PART_LayoutRoot">

5
src/Avalonia.Themes.Fluent/TextBox.xaml

@ -52,7 +52,6 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
MinWidth="{TemplateBinding MinWidth}" MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"> MinHeight="{TemplateBinding MinHeight}">
</Border> </Border>
@ -156,6 +155,10 @@
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
</Style> </Style>
<Style Selector="TextBox /template/ Border#PART_BorderElement">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="TextBox /template/ DockPanel#PART_InnerDockPanel"> <Style Selector="TextBox /template/ DockPanel#PART_InnerDockPanel">
<Setter Property="Cursor" Value="IBeam" /> <Setter Property="Cursor" Value="IBeam" />
</Style> </Style>

5
src/Avalonia.Themes.Fluent/ToggleButton.xaml

@ -29,7 +29,6 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
@ -38,6 +37,10 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ToggleButton /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="ToggleButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPointerOver}" />

10
src/Avalonia.Themes.Fluent/ToggleSwitch.xaml

@ -31,7 +31,7 @@
Text="The previewer Shows a preview off your code, this could slow down your system" Text="The previewer Shows a preview off your code, this could slow down your system"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<ToggleSwitch <ToggleSwitch
Content="Previewer" Background="Green"
IsChecked="True" /> IsChecked="True" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@ -53,7 +53,6 @@
Grid.Row="0" Grid.Row="0"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{DynamicResource ToggleSwitchTopHeaderMargin}"
VerticalAlignment="Top"/> VerticalAlignment="Top"/>
<Grid Grid.Row="1" <Grid Grid.Row="1"
@ -129,6 +128,13 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="ToggleSwitch /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="{DynamicResource ToggleSwitchTopHeaderMargin}" />
</Style>
<Style Selector="ToggleSwitch /template/ ContentPresenter#PART_ContentPresenter:empty">
<Setter Property="Margin" Value="0" />
</Style>
<!-- NormalState --> <!-- NormalState -->
<Style Selector="ToggleSwitch /template/ Grid#SwitchAreaGrid"> <Style Selector="ToggleSwitch /template/ Grid#SwitchAreaGrid">
<Setter Property="Background" Value="{DynamicResource ToggleSwitchContainerBackground}"/> <Setter Property="Background" Value="{DynamicResource ToggleSwitchContainerBackground}"/>

6
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -24,10 +24,14 @@ CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.Tex
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
Total Issues: 31 MembersMustExist : Member 'public Avalonia.Utilities.IRef<Avalonia.Platform.IRenderTargetBitmapImpl> Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract.
Total Issues: 35

19
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -18,7 +18,7 @@ namespace Avalonia.Media
private double _fontRenderingEmSize; private double _fontRenderingEmSize;
private Size? _size; private Size? _size;
private int _biDiLevel; private int _biDiLevel;
private Point _baselineOrigin; private Point? _baselineOrigin;
private ReadOnlySlice<ushort> _glyphIndices; private ReadOnlySlice<ushort> _glyphIndices;
private ReadOnlySlice<double> _glyphAdvances; private ReadOnlySlice<double> _glyphAdvances;
@ -97,7 +97,9 @@ namespace Avalonia.Media
{ {
get get
{ {
return _baselineOrigin; _baselineOrigin ??= CalculateBaselineOrigin();
return _baselineOrigin.Value;
} }
set => Set(ref _baselineOrigin, value); set => Set(ref _baselineOrigin, value);
} }
@ -397,14 +399,14 @@ namespace Avalonia.Media
if (characterIndex > GlyphClusters[GlyphClusters.Length - 1]) if (characterIndex > GlyphClusters[GlyphClusters.Length - 1])
{ {
return _glyphClusters.End; return _glyphClusters.Length - 1;
} }
} }
else else
{ {
if (characterIndex < GlyphClusters[GlyphClusters.Length - 1]) if (characterIndex < GlyphClusters[GlyphClusters.Length - 1])
{ {
return _glyphClusters.End; return _glyphClusters.Length - 1;
} }
if (characterIndex > GlyphClusters[0]) if (characterIndex > GlyphClusters[0])
@ -538,6 +540,15 @@ namespace Avalonia.Media
return GlyphAdvances[index]; 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> /// <summary>
/// Calculates the size of the <see cref="GlyphRun"/>. /// Calculates the size of the <see cref="GlyphRun"/>.
/// </summary> /// </summary>

18
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -126,7 +126,15 @@ namespace Avalonia.Media.TextFormatting
var lastCluster = glyphRun.GlyphClusters[glyphCount]; var lastCluster = glyphRun.GlyphClusters[glyphCount];
count = lastCluster - firstCluster; if (glyphRun.IsLeftToRight)
{
count = lastCluster - firstCluster;
}
else
{
count = firstCluster - lastCluster;
}
return count > 0; return count > 0;
} }
@ -331,14 +339,6 @@ namespace Avalonia.Media.TextFormatting
return true; return true;
} }
//The line breaker isn't treating \n\r as a pair so we have to fix that here.
if (textRun.Text[lineBreak.PositionMeasure] == '\n'
&& textRun.Text[lineBreak.PositionWrap] == '\r')
{
lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1,
lineBreak.Required);
}
return true; return true;
} }

2
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -39,7 +39,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns) foreach (var textRun in _textRuns)
{ {
var offsetY = LineMetrics.TextBaseline; var offsetY = LineMetrics.TextBaseline - textRun.GlyphRun.BaselineOrigin.Y;
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY))) using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{ {

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

@ -98,7 +98,7 @@ namespace Avalonia.Platform
/// has to do a format conversion each time a standard render target bitmap is rendered, /// has to do a format conversion each time a standard render target bitmap is rendered,
/// but a layer created via this method has no such overhead. /// but a layer created via this method has no such overhead.
/// </remarks> /// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size); IDrawingContextLayerImpl CreateLayer(Size size);
/// <summary> /// <summary>
/// Pushes a clip rectangle. /// Pushes a clip rectangle.
@ -155,4 +155,18 @@ namespace Avalonia.Platform
/// <param name="custom">Custom draw operation</param> /// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom); void Custom(ICustomDrawOperation custom);
} }
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl
{
/// <summary>
/// Does optimized blit with Src blend mode
/// </summary>
/// <param name="context"></param>
void Blit(IDrawingContextImpl context);
/// <summary>
/// Returns true if layer supports optimized blit
/// </summary>
bool CanBlit { get; }
}
} }

8
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -489,6 +489,7 @@ namespace Avalonia.Rendering
var clientRect = new Rect(scene.Size); var clientRect = new Rect(scene.Size);
var firstLayer = true;
foreach (var layer in scene.Layers) foreach (var layer in scene.Layers)
{ {
var bitmap = Layers[layer.LayerRoot].Bitmap; var bitmap = Layers[layer.LayerRoot].Bitmap;
@ -501,7 +502,10 @@ namespace Avalonia.Rendering
if (layer.OpacityMask == null) if (layer.OpacityMask == null)
{ {
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect); if (firstLayer && bitmap.Item.CanBlit)
bitmap.Item.Blit(context);
else
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
} }
else else
{ {
@ -512,6 +516,8 @@ namespace Avalonia.Rendering
{ {
context.PopGeometryClip(); context.PopGeometryClip();
} }
firstLayer = false;
} }
if (_overlay != null) if (_overlay != null)

2
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
IsEmpty = true; IsEmpty = true;
} }
public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; } public IRef<IDrawingContextLayerImpl> Bitmap { get; private set; }
public bool IsEmpty { get; set; } public bool IsEmpty { get; set; }
public double Scaling { get; private set; } public double Scaling { get; private set; }
public Size Size { get; private set; } public Size Size { get; private set; }

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

@ -218,7 +218,7 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex; ++_drawOperationindex;
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
} }

12
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -141,17 +141,7 @@ namespace Avalonia.LinuxFramebuffer.Output
_platformGl = new EglPlatformOpenGlInterface(_eglDisplay); _platformGl = new EglPlatformOpenGlInterface(_eglDisplay);
_eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface);
_deferredContext = _platformGl.PrimaryEglContext;
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);
using (_deferredContext.MakeCurrent(_eglSurface)) using (_deferredContext.MakeCurrent(_eglSurface))
{ {

21
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -31,6 +31,7 @@ namespace Avalonia.Skia
private bool _disposed; private bool _disposed;
private GRContext _grContext; private GRContext _grContext;
public GRContext GrContext => _grContext; public GRContext GrContext => _grContext;
private ISkiaGpu _gpu;
private readonly SKPaint _strokePaint = new SKPaint(); private readonly SKPaint _strokePaint = new SKPaint();
private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _fillPaint = new SKPaint();
private readonly SKPaint _boxShadowPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint();
@ -41,6 +42,11 @@ namespace Avalonia.Skia
/// </summary> /// </summary>
public struct CreateInfo public struct CreateInfo
{ {
/// <summary>
/// Canvas to draw to.
/// </summary>
public SKCanvas Canvas;
/// <summary> /// <summary>
/// Surface to draw to. /// Surface to draw to.
/// </summary> /// </summary>
@ -65,6 +71,11 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional) /// GPU-accelerated context (optional)
/// </summary> /// </summary>
public GRContext GrContext; public GRContext GrContext;
/// <summary>
/// Skia GPU provider context (optional)
/// </summary>
public ISkiaGpu Gpu;
} }
/// <summary> /// <summary>
@ -79,10 +90,11 @@ namespace Avalonia.Skia
_disposables = disposables; _disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext; _grContext = createInfo.GrContext;
_gpu = createInfo.Gpu;
if (_grContext != null) if (_grContext != null)
Monitor.Enter(_grContext); Monitor.Enter(_grContext);
Surface = createInfo.Surface; Surface = createInfo.Surface;
Canvas = createInfo.Surface.Canvas; Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas;
if (Canvas == null) if (Canvas == null)
{ {
@ -415,9 +427,9 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
return CreateRenderTarget(size); return CreateRenderTarget( size);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -956,7 +968,8 @@ namespace Avalonia.Skia
Dpi = _dpi, Dpi = _dpi,
Format = format, Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering, DisableTextLcdRendering = !_canTextUseLcdRendering,
GrContext = _grContext GrContext = _grContext,
Gpu = _gpu
}; };
return new SurfaceRenderTarget(createInfo); return new SurfaceRenderTarget(createInfo);

14
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.OpenGL.Imaging; using Avalonia.OpenGL.Imaging;
using SkiaSharp; using SkiaSharp;
@ -15,6 +16,19 @@ namespace Avalonia.Skia
/// <param name="surfaces">Surfaces.</param> /// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns> /// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces); ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
/// <summary>
/// Creates an offscreen render target surface
/// </summary>
/// <param name="size">size in pixels</param>
ISkiaSurface TryCreateSurface(PixelSize size);
}
public interface ISkiaSurface : IDisposable
{
SKSurface Surface { get; }
bool CanBlit { get; }
void Blit(SKCanvas canvas);
} }
public interface IOpenGlAwareSkiaGpu : ISkiaGpu public interface IOpenGlAwareSkiaGpu : ISkiaGpu

134
src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs

@ -0,0 +1,134 @@
using System;
using Avalonia.OpenGL;
using SkiaSharp;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
public class FboSkiaSurface : ISkiaSurface
{
private readonly GRContext _grContext;
private readonly IGlContext _glContext;
private readonly PixelSize _pixelSize;
private int _fbo;
private int _depthStencil;
private int _texture;
private static readonly bool[] TrueFalse = new[] { true, false };
public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize)
{
_grContext = grContext;
_glContext = glContext;
_pixelSize = pixelSize;
var InternalFormat = glContext.Version.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8;
var gl = glContext.GlInterface;
// Save old bindings
gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo);
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer);
gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
var arr = new int[2];
// Generate FBO
gl.GenFramebuffers(1, arr);
_fbo = arr[0];
gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
// Create a texture to render into
gl.GenTextures(1, arr);
_texture = arr[0];
gl.BindTexture(GL_TEXTURE_2D, _texture);
gl.TexImage2D(GL_TEXTURE_2D, 0,
InternalFormat, pixelSize.Width, pixelSize.Height,
0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
var success = false;
foreach (var useStencil8 in TrueFalse)
{
gl.GenRenderbuffers(1, arr);
_depthStencil = arr[0];
gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
if (useStencil8)
{
gl.RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, pixelSize.Width, pixelSize.Height);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
}
else
{
gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, pixelSize.Width, pixelSize.Height);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
}
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE)
{
success = true;
break;
}
else
{
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
gl.DeleteRenderbuffers(1, arr);
}
}
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
gl.BindTexture(GL_TEXTURE_2D, oldTexture);
gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo);
if (!success)
{
arr[0] = _fbo;
gl.DeleteFramebuffers(1, arr);
arr[0] = _texture;
gl.DeleteTextures(1, arr);
throw new OpenGlException("Unable to create FBO with stencil");
}
var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 8,
new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat()));
Surface = SKSurface.Create(_grContext, target,
GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888);
CanBlit = gl.BlitFramebuffer != null;
}
public void Dispose()
{
using (_glContext.EnsureCurrent())
{
Surface?.Dispose();
Surface = null;
var gl = _glContext.GlInterface;
if (_fbo != 0)
{
gl.DeleteFramebuffers(1, new[] { _fbo });
gl.DeleteTextures(1, new[] { _texture });
gl.DeleteRenderbuffers(1, new[] { _depthStencil });
_fbo = _texture = _depthStencil = 0;
}
}
}
public SKSurface Surface { get; private set; }
public bool CanBlit { get; }
public void Blit(SKCanvas canvas)
{
// This should set the render target as the current FBO
// which is definitely not the best method, but it works
canvas.Clear();
canvas.Flush();
var gl = _glContext.GlInterface;
gl.GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, out var oldRead);
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
gl.BlitFramebuffer(0, 0, _pixelSize.Width, _pixelSize.Height, 0, 0, _pixelSize.Width, _pixelSize.Height,
GL_COLOR_BUFFER_BIT, GL_LINEAR);
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, oldRead);
}
}
}

32
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging; using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
@ -10,6 +13,7 @@ namespace Avalonia.Skia
{ {
private GRContext _grContext; private GRContext _grContext;
private IGlContext _glContext; private IGlContext _glContext;
private bool? _canCreateSurfaces;
public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes) public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes)
{ {
@ -43,6 +47,34 @@ namespace Avalonia.Skia
return null; return null;
} }
public ISkiaSurface TryCreateSurface(PixelSize size)
{
// Only windows platform needs our FBO trickery
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return null;
// Blit feature requires glBlitFramebuffer
if (_glContext.GlInterface.BlitFramebuffer == null)
return null;
size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1));
if (_canCreateSurfaces == false)
return null;
try
{
var surface = new FboSkiaSurface(_grContext, _glContext, size);
_canCreateSurfaces = true;
return surface;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")
?.Log(this, "Unable to create a Skia-compatible FBO manually");
_canCreateSurfaces ??= false;
return null;
}
}
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
} }
} }

7
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -8,10 +8,12 @@ namespace Avalonia.Skia
/// </summary> /// </summary>
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
{ {
private readonly ISkiaGpu _skiaGpu;
private readonly ISkiaGpuRenderTarget _renderTarget; private readonly ISkiaGpuRenderTarget _renderTarget;
public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget) public SkiaGpuRenderTarget(ISkiaGpu skiaGpu, ISkiaGpuRenderTarget renderTarget)
{ {
_skiaGpu = skiaGpu;
_renderTarget = renderTarget; _renderTarget = renderTarget;
} }
@ -30,7 +32,8 @@ namespace Avalonia.Skia
Surface = session.SkSurface, Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
VisualBrushRenderer = visualBrushRenderer, VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true DisableTextLcdRendering = true,
Gpu = _skiaGpu
}; };
return new DrawingContextImpl(nfo, session); return new DrawingContextImpl(nfo, session);

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

2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -138,7 +138,7 @@ namespace Avalonia.Skia
var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces); var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null) if (gpuRenderTarget != null)
{ {
return new SkiaGpuRenderTarget(gpuRenderTarget); return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget);
} }
foreach (var surface in surfaces) foreach (var surface in surfaces)

72
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Skia.Helpers; using Avalonia.Skia.Helpers;
@ -11,12 +12,31 @@ namespace Avalonia.Skia
/// <summary> /// <summary>
/// Skia render target that writes to a surface. /// Skia render target that writes to a surface.
/// </summary> /// </summary>
internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl internal class SurfaceRenderTarget : IDrawingContextLayerImpl, IDrawableBitmapImpl
{ {
private readonly SKSurface _surface; private readonly ISkiaSurface _surface;
private readonly SKCanvas _canvas; private readonly SKCanvas _canvas;
private readonly bool _disableLcdRendering; private readonly bool _disableLcdRendering;
private readonly GRContext _grContext; private readonly GRContext _grContext;
private readonly ISkiaGpu _gpu;
class SkiaSurfaceWrapper : ISkiaSurface
{
public SKSurface Surface { get; private set; }
public bool CanBlit => false;
public void Blit(SKCanvas canvas) => throw new NotSupportedException();
public SkiaSurfaceWrapper(SKSurface surface)
{
Surface = surface;
}
public void Dispose()
{
Surface?.Dispose();
Surface = null;
}
}
/// <summary> /// <summary>
/// Create new surface render target. /// Create new surface render target.
@ -29,9 +49,14 @@ namespace Avalonia.Skia
_disableLcdRendering = createInfo.DisableTextLcdRendering; _disableLcdRendering = createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext; _grContext = createInfo.GrContext;
_surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format); _gpu = createInfo.Gpu;
_canvas = _surface?.Canvas; _surface = _gpu?.TryCreateSurface(PixelSize);
if (_surface == null)
_surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height,
createInfo.Format));
_canvas = _surface?.Surface.Canvas;
if (_surface == null || _canvas == null) if (_surface == null || _canvas == null)
{ {
@ -70,11 +95,12 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo var createInfo = new DrawingContextImpl.CreateInfo
{ {
Surface = _surface, Surface = _surface.Surface,
Dpi = Dpi, Dpi = Dpi,
VisualBrushRenderer = visualBrushRenderer, VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = _disableLcdRendering, DisableTextLcdRendering = _disableLcdRendering,
GrContext = _grContext GrContext = _grContext,
Gpu = _gpu
}; };
return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++)); return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++));
@ -106,19 +132,31 @@ namespace Avalonia.Skia
} }
} }
/// <inheritdoc /> public void Blit(IDrawingContextImpl contextImpl)
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{ {
if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) var context = (DrawingContextImpl)contextImpl;
if (_surface.CanBlit)
{ {
_surface.Canvas.Flush(); _surface.Surface.Canvas.Flush();
_surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); _surface.Blit(context.Canvas);
} }
else else
using (var image = SnapshotImage()) {
{ var oldMatrix = context.Canvas.TotalMatrix;
context.Canvas.DrawImage(image, sourceRect, destRect, paint); context.Canvas.ResetMatrix();
} _surface.Surface.Draw(context.Canvas, 0, 0, null);
context.Canvas.SetMatrix(oldMatrix);
}
}
public bool CanBlit => true;
/// <inheritdoc />
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{
using var image = SnapshotImage();
context.Canvas.DrawImage(image, sourceRect, destRect, paint);
} }
/// <summary> /// <summary>
@ -127,7 +165,7 @@ namespace Avalonia.Skia
/// <returns>Image snapshot.</returns> /// <returns>Image snapshot.</returns>
public SKImage SnapshotImage() public SKImage SnapshotImage()
{ {
return _surface.Snapshot(); return _surface.Surface.Snapshot();
} }
/// <summary> /// <summary>
@ -178,6 +216,8 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional) /// GPU-accelerated context (optional)
/// </summary> /// </summary>
public GRContext GrContext; public GRContext GrContext;
public ISkiaGpu Gpu;
} }
} }
} }

3
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -64,7 +64,8 @@ namespace Avalonia.Skia
new ReadOnlySlice<double>(glyphAdvances), new ReadOnlySlice<double>(glyphAdvances),
new ReadOnlySlice<Vector>(glyphOffsets), new ReadOnlySlice<Vector>(glyphOffsets),
text, text,
new ReadOnlySlice<ushort>(clusters)); new ReadOnlySlice<ushort>(clusters),
buffer.Direction == Direction.LeftToRight ? 0 : 1);
} }
} }

2
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -38,7 +38,7 @@ namespace Avalonia.Direct2D1
}); });
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size); return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size);

2
src/Windows/Avalonia.Direct2D1/ILayerFactory.cs

@ -4,6 +4,6 @@ namespace Avalonia.Direct2D1
{ {
public interface ILayerFactory public interface ILayerFactory
{ {
IRenderTargetBitmapImpl CreateLayer(Size size); IDrawingContextLayerImpl CreateLayer(Size size);
} }
} }

4
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -335,7 +335,7 @@ namespace Avalonia.Direct2D1.Media
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
if (_layerFactory != null) if (_layerFactory != null)
{ {
@ -346,7 +346,7 @@ namespace Avalonia.Direct2D1.Media
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); var pixelSize = PixelSize.FromSizeWithDpi(size, dpi);
return platform.CreateRenderTargetBitmap(pixelSize, dpi); return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi);
} }
} }

11
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -1,4 +1,5 @@
using System.IO; using System;
using System.IO;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -8,7 +9,7 @@ using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media.Imaging namespace Avalonia.Direct2D1.Media.Imaging
{ {
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IDrawingContextLayerImpl, ILayerFactory
{ {
private readonly BitmapRenderTarget _renderTarget; private readonly BitmapRenderTarget _renderTarget;
@ -34,7 +35,11 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++); return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++);
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;
public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
return CreateCompatible(_renderTarget, size); return CreateCompatible(_renderTarget, size);
} }

5
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -5,7 +5,7 @@ using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl public class WicRenderTargetBitmapImpl : WicBitmapImpl, IDrawingContextLayerImpl
{ {
private readonly WicRenderTarget _renderTarget; private readonly WicRenderTarget _renderTarget;
@ -45,5 +45,8 @@ namespace Avalonia.Direct2D1.Media
finishedCallback?.Invoke(); finishedCallback?.Invoke();
}); });
} }
public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;
} }
} }

2
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size);
} }

2
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -35,7 +35,7 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain);
} }
public IRenderTargetBitmapImpl CreateLayer(Size size) public IDrawingContextLayerImpl CreateLayer(Size size)
{ {
if (_deviceContext == null) if (_deviceContext == null)
{ {

92
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1011,6 +1011,10 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase); public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool IsWindowEnabled(IntPtr hWnd); public static extern bool IsWindowEnabled(IntPtr hWnd);
@ -1292,6 +1296,41 @@ namespace Avalonia.Win32.Interop
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int ChoosePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int DescribePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern bool SwapBuffers(IntPtr hdc);
[DllImport("opengl32.dll")]
public static extern IntPtr wglCreateContext(IntPtr hdc);
[DllImport("opengl32.dll")]
public static extern bool wglDeleteContext(IntPtr context);
[DllImport("opengl32.dll")]
public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context);
[DllImport("opengl32.dll")]
public static extern IntPtr wglGetCurrentContext();
[DllImport("opengl32.dll")]
public static extern IntPtr wglGetCurrentDC();
[DllImport("opengl32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr wglGetProcAddress(string name);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(IntPtr hFile, public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes, IntPtr lpFileMappingAttributes,
@ -2121,4 +2160,57 @@ namespace Avalonia.Win32.Interop
public bool fNC; public bool fNC;
public bool fWide; public bool fWide;
} }
[Flags]
internal enum PixelFormatDescriptorFlags : uint
{
PFD_DOUBLEBUFFER = 0x00000001,
PFD_STEREO = 0x00000002,
PFD_DRAW_TO_WINDOW = 0x00000004,
PFD_DRAW_TO_BITMAP = 0x00000008,
PFD_SUPPORT_GDI = 0x00000010,
PFD_SUPPORT_OPENGL = 0x00000020,
PFD_GENERIC_FORMAT = 0x00000040,
PFD_NEED_PALETTE = 0x00000080,
PFD_NEED_SYSTEM_PALETTE = 0x00000100,
PFD_SWAP_EXCHANGE = 0x00000200,
PFD_SWAP_COPY = 0x00000400,
PFD_SWAP_LAYER_BUFFERS = 0x00000800,
PFD_GENERIC_ACCELERATED = 0x00001000,
PFD_SUPPORT_DIRECTDRAW = 0x00002000,
PFD_DEPTH_DONTCARE = 0x20000000,
PFD_DOUBLEBUFFER_DONTCARE = 0x40000000,
PFD_STEREO_DONTCARE = 0x80000000,
}
[StructLayout(LayoutKind.Sequential)]
internal struct PixelFormatDescriptor
{
public ushort Size;
public ushort Version;
public PixelFormatDescriptorFlags Flags;
public byte PixelType;
public byte ColorBits;
public byte RedBits;
public byte RedShift;
public byte GreenBits;
public byte GreenShift;
public byte BlueBits;
public byte BlueShift;
public byte AlphaBits;
public byte AlphaShift;
public byte AccumBits;
public byte AccumRedBits;
public byte AccumGreenBits;
public byte AccumBlueBits;
public byte AccumAlphaBits;
public byte DepthBits;
public byte StencilBits;
public byte AuxBuffers;
public byte LayerType;
private byte Reserved;
public uint LayerMask;
public uint VisibleMask;
public uint DamageMask;
}
} }

62
src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs

@ -0,0 +1,62 @@
namespace Avalonia.Win32.OpenGl
{
internal class WglConsts
{
public const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
public const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
public const int WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
public const int WGL_CONTEXT_FLAGS_ARB = 0x2094;
public const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
public const int WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000;
public const int WGL_DRAW_TO_WINDOW_ARB = 0x2001;
public const int WGL_DRAW_TO_BITMAP_ARB = 0x2002;
public const int WGL_ACCELERATION_ARB = 0x2003;
public const int WGL_NEED_PALETTE_ARB = 0x2004;
public const int WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005;
public const int WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006;
public const int WGL_SWAP_METHOD_ARB = 0x2007;
public const int WGL_NUMBER_OVERLAYS_ARB = 0x2008;
public const int WGL_NUMBER_UNDERLAYS_ARB = 0x2009;
public const int WGL_TRANSPARENT_ARB = 0x200A;
public const int WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037;
public const int WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038;
public const int WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039;
public const int WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A;
public const int WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B;
public const int WGL_SHARE_DEPTH_ARB = 0x200C;
public const int WGL_SHARE_STENCIL_ARB = 0x200D;
public const int WGL_SHARE_ACCUM_ARB = 0x200E;
public const int WGL_SUPPORT_GDI_ARB = 0x200F;
public const int WGL_SUPPORT_OPENGL_ARB = 0x2010;
public const int WGL_DOUBLE_BUFFER_ARB = 0x2011;
public const int WGL_STEREO_ARB = 0x2012;
public const int WGL_PIXEL_TYPE_ARB = 0x2013;
public const int WGL_COLOR_BITS_ARB = 0x2014;
public const int WGL_RED_BITS_ARB = 0x2015;
public const int WGL_RED_SHIFT_ARB = 0x2016;
public const int WGL_GREEN_BITS_ARB = 0x2017;
public const int WGL_GREEN_SHIFT_ARB = 0x2018;
public const int WGL_BLUE_BITS_ARB = 0x2019;
public const int WGL_BLUE_SHIFT_ARB = 0x201A;
public const int WGL_ALPHA_BITS_ARB = 0x201B;
public const int WGL_ALPHA_SHIFT_ARB = 0x201C;
public const int WGL_ACCUM_BITS_ARB = 0x201D;
public const int WGL_ACCUM_RED_BITS_ARB = 0x201E;
public const int WGL_ACCUM_GREEN_BITS_ARB = 0x201F;
public const int WGL_ACCUM_BLUE_BITS_ARB = 0x2020;
public const int WGL_ACCUM_ALPHA_BITS_ARB = 0x2021;
public const int WGL_DEPTH_BITS_ARB = 0x2022;
public const int WGL_STENCIL_BITS_ARB = 0x2023;
public const int WGL_AUX_BUFFERS_ARB = 0x2024;
public const int WGL_NO_ACCELERATION_ARB = 0x2025;
public const int WGL_GENERIC_ACCELERATION_ARB = 0x2026;
public const int WGL_FULL_ACCELERATION_ARB = 0x2027;
public const int WGL_SWAP_EXCHANGE_ARB = 0x2028;
public const int WGL_SWAP_COPY_ARB = 0x2029;
public const int WGL_SWAP_UNDEFINED_ARB = 0x202A;
public const int WGL_TYPE_RGBA_ARB = 0x202B;
public const int WGL_TYPE_COLORINDEX_ARB = 0x202C;
}
}

85
src/Windows/Avalonia.Win32/OpenGl/WglContext.cs

@ -0,0 +1,85 @@
using System;
using System.Reactive.Disposables;
using Avalonia.OpenGL;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
namespace Avalonia.Win32.OpenGl
{
class WglContext : IGlContext
{
private object _lock = new object();
private readonly WglContext _sharedWith;
private readonly IntPtr _context;
private readonly IntPtr _hWnd;
private readonly IntPtr _dc;
private readonly int _pixelFormat;
private readonly PixelFormatDescriptor _formatDescriptor;
public IntPtr Handle => _context;
public WglContext(WglContext sharedWith, GlVersion version, IntPtr context, IntPtr hWnd, IntPtr dc, int pixelFormat,
PixelFormatDescriptor formatDescriptor)
{
Version = version;
_sharedWith = sharedWith;
_context = context;
_hWnd = hWnd;
_dc = dc;
_pixelFormat = pixelFormat;
_formatDescriptor = formatDescriptor;
StencilSize = formatDescriptor.StencilBits;
using (MakeCurrent())
GlInterface = new GlInterface(version, proc =>
{
var ext = wglGetProcAddress(proc);
if (ext != IntPtr.Zero)
return ext;
return GetProcAddress(WglDisplay.OpenGl32Handle, proc);
});
}
public void Dispose()
{
wglDeleteContext(_context);
ReleaseDC(_hWnd, _dc);
DestroyWindow(_hWnd);
}
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc;
public IDisposable MakeCurrent()
{
if(IsCurrent)
return Disposable.Empty;
return new WglRestoreContext(_dc, _context, _lock);
}
public IDisposable EnsureCurrent() => MakeCurrent();
public IntPtr CreateConfiguredDeviceContext(IntPtr hWnd)
{
var dc = GetDC(hWnd);
var fmt = _formatDescriptor;
SetPixelFormat(dc, _pixelFormat, ref fmt);
return dc;
}
public IDisposable MakeCurrent(IntPtr hdc) => new WglRestoreContext(hdc, _context, _lock);
public bool IsSharedWith(IGlContext context)
{
var c = (WglContext)context;
return c == this
|| c._sharedWith == this
|| _sharedWith == context
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
}
}

183
src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs

@ -0,0 +1,183 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
namespace Avalonia.Win32.OpenGl
{
internal class WglDisplay
{
private static bool? _initialized;
private static ushort _windowClass;
private static readonly WndProc _wndProcDelegate = WndProc;
private static readonly DebugCallbackDelegate _debugCallback = DebugCallback;
private static IntPtr _bootstrapContext;
private static IntPtr _bootstrapWindow;
private static IntPtr _bootstrapDc;
private static PixelFormatDescriptor _defaultPfd;
private static int _defaultPixelFormat;
public static IntPtr OpenGl32Handle = LoadLibrary("opengl32");
private delegate bool WglChoosePixelFormatARBDelegate(IntPtr hdc, int[] piAttribIList, float[] pfAttribFList,
int nMaxFormats, int[] piFormats, out int nNumFormats);
private static WglChoosePixelFormatARBDelegate WglChoosePixelFormatArb;
private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList);
private static WglCreateContextAttribsARBDelegate WglCreateContextAttribsArb;
private delegate void GlDebugMessageCallbackDelegate(IntPtr callback, IntPtr userParam);
private static GlDebugMessageCallbackDelegate GlDebugMessageCallback;
private delegate void DebugCallbackDelegate(int source, int type, int id, int severity, int len, IntPtr message,
IntPtr userParam);
static bool Initialize()
{
if (!_initialized.HasValue)
_initialized = InitializeCore();
return _initialized.Value;
}
static bool InitializeCore()
{
var wndClassEx = new WNDCLASSEX
{
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
hInstance = GetModuleHandle(null),
lpfnWndProc = _wndProcDelegate,
lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(),
style = (int)ClassStyles.CS_OWNDC
};
_windowClass = RegisterClassEx(ref wndClassEx);
_bootstrapWindow = CreateOffscreenWindow();
_bootstrapDc = GetDC(_bootstrapWindow);
_defaultPfd = new PixelFormatDescriptor
{
Size = (ushort)Marshal.SizeOf<PixelFormatDescriptor>(),
Version = 1,
Flags = PixelFormatDescriptorFlags.PFD_DRAW_TO_WINDOW |
PixelFormatDescriptorFlags.PFD_SUPPORT_OPENGL | PixelFormatDescriptorFlags.PFD_DOUBLEBUFFER,
DepthBits = 24,
StencilBits = 8,
ColorBits = 32
};
_defaultPixelFormat = ChoosePixelFormat(_bootstrapDc, ref _defaultPfd);
SetPixelFormat(_bootstrapDc, _defaultPixelFormat, ref _defaultPfd);
_bootstrapContext = wglCreateContext(_bootstrapDc);
if (_bootstrapContext == IntPtr.Zero)
return false;
wglMakeCurrent(_bootstrapDc, _bootstrapContext);
WglCreateContextAttribsArb = Marshal.GetDelegateForFunctionPointer<WglCreateContextAttribsARBDelegate>(
wglGetProcAddress("wglCreateContextAttribsARB"));
WglChoosePixelFormatArb =
Marshal.GetDelegateForFunctionPointer<WglChoosePixelFormatARBDelegate>(
wglGetProcAddress("wglChoosePixelFormatARB"));
GlDebugMessageCallback =
Marshal.GetDelegateForFunctionPointer<GlDebugMessageCallbackDelegate>(
wglGetProcAddress("glDebugMessageCallback"));
var formats = new int[1];
WglChoosePixelFormatArb(_bootstrapDc, new int[]
{
WGL_DRAW_TO_WINDOW_ARB, 1,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_SUPPORT_OPENGL_ARB, 1,
WGL_DOUBLE_BUFFER_ARB, 1,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 0,
WGL_STENCIL_BITS_ARB, 0,
0, // End
}, null, 1, formats, out int numFormats);
if (numFormats != 0)
{
DescribePixelFormat(_bootstrapDc, formats[0], Marshal.SizeOf<PixelFormatDescriptor>(), ref _defaultPfd);
_defaultPixelFormat = formats[0];
}
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
return true;
}
static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private static void DebugCallback(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userparam)
{
var err = Marshal.PtrToStringAnsi(message, len);
Console.Error.WriteLine(err);
}
public static WglContext CreateContext(GlVersion[] versions, IGlContext share)
{
if (!Initialize())
return null;
var shareContext = (WglContext)share;
using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null))
{
var window = CreateOffscreenWindow();
var dc = GetDC(window);
SetPixelFormat(dc, _defaultPixelFormat, ref _defaultPfd);
foreach (var version in versions)
{
if(version.Type != GlProfileType.OpenGL)
continue;
var context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero,
new[]
{
// major
WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major,
// minor
WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor,
// core profile
WGL_CONTEXT_PROFILE_MASK_ARB, 1,
// debug
// WGL_CONTEXT_FLAGS_ARB, 1,
// end
0, 0
});
using(new WglRestoreContext(dc, context, null))
GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero);
if (context != IntPtr.Zero)
return new WglContext(shareContext, version, context, window, dc,
_defaultPixelFormat, _defaultPfd);
}
ReleaseDC(window, dc);
DestroyWindow(window);
return null;
}
}
static IntPtr CreateOffscreenWindow() =>
CreateWindowEx(
0,
_windowClass,
null,
(int)WindowStyles.WS_OVERLAPPEDWINDOW,
0,
0,
640,
480,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
}

85
src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs

@ -0,0 +1,85 @@
using System;
using System.Diagnostics;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Win32.Interop;
using static Avalonia.OpenGL.GlConsts;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.OpenGl
{
class WglGlPlatformSurface: IGlPlatformSurface
{
private readonly WglContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public WglGlPlatformSurface(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
return new RenderTarget(_context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly WglContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IntPtr _hdc;
public RenderTarget(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
_hdc = context.CreateConfiguredDeviceContext(info.Handle);
}
public void Dispose()
{
UnmanagedMethods.ReleaseDC(_hdc, _info.Handle);
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var oldContext = _context.MakeCurrent(_hdc);
// Reset to default FBO first
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, 0);
return new Session(_context, _hdc, _info, oldContext);
}
class Session : IGlPlatformSurfaceRenderingSession
{
private readonly WglContext _context;
private readonly IntPtr _hdc;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private readonly IDisposable _clearContext;
public IGlContext Context => _context;
public Session(WglContext context, IntPtr hdc, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable clearContext)
{
_context = context;
_hdc = hdc;
_info = info;
_clearContext = clearContext;
}
public void Dispose()
{
_context.GlInterface.Flush();
UnmanagedMethods.SwapBuffers(_hdc);
_clearContext.Dispose();
}
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }
}
}
}
}

39
src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs

@ -0,0 +1,39 @@
using System;
using System.Linq;
using Avalonia.Logging;
using Avalonia.OpenGL;
namespace Avalonia.Win32.OpenGl
{
class WglPlatformOpenGlInterface : IPlatformOpenGlInterface
{
public WglContext PrimaryContext { get; }
IGlContext IPlatformOpenGlInterface.PrimaryContext => PrimaryContext;
public IGlContext CreateSharedContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, PrimaryContext);
public bool CanShareContexts => true;
public bool CanCreateContexts => true;
public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null);
private WglPlatformOpenGlInterface(WglContext primary)
{
PrimaryContext = primary;
}
public static WglPlatformOpenGlInterface TryCreate()
{
try
{
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
var primary = WglDisplay.CreateContext(opts.WglProfiles.ToArray(), null);
return new WglPlatformOpenGlInterface(primary);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("WGL", "Unable to initialize WGL: " + e);
}
return null;
}
}
}

39
src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs

@ -0,0 +1,39 @@
using System;
using System.Threading;
using Avalonia.OpenGL;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.OpenGl
{
internal class WglRestoreContext : IDisposable
{
private readonly object _monitor;
private readonly IntPtr _oldDc;
private readonly IntPtr _oldContext;
public WglRestoreContext(IntPtr gc, IntPtr context, object monitor, bool takeMonitor = true)
{
_monitor = monitor;
_oldDc = wglGetCurrentDC();
_oldContext = wglGetCurrentContext();
if (monitor != null && takeMonitor)
Monitor.Enter(monitor);
if (!wglMakeCurrent(gc, context))
{
if(monitor != null && takeMonitor)
Monitor.Exit(monitor);
throw new OpenGlException("Unable to make the context current");
}
}
public void Dispose()
{
if (!wglMakeCurrent(_oldDc, _oldContext))
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
if (_monitor != null)
Monitor.Exit(_monitor);
}
}
}

18
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -1,27 +1,29 @@
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
using Avalonia.Win32.OpenGl;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
static class Win32GlManager static class Win32GlManager
{ {
/// <summary>This property is initialized if drawing platform requests OpenGL support</summary>
public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; }
private static bool s_attemptedToInitialize; private static bool s_attemptedToInitialize;
public static void Initialize() public static void Initialize()
{ {
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToFunc(() => AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToLazy<IPlatformOpenGlInterface>(() =>
{ {
if (!s_attemptedToInitialize) var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>();
if (opts?.UseWgl == true)
{ {
EglPlatformInterface = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); var wgl = WglPlatformOpenGlInterface.TryCreate();
s_attemptedToInitialize = true; return wgl;
} }
if (opts?.AllowEglInitialization == true)
return EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
return EglPlatformInterface; return null;
}); });
} }
} }

10
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -11,6 +11,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
@ -39,6 +40,12 @@ namespace Avalonia
public bool AllowEglInitialization { get; set; } = true; public bool AllowEglInitialization { get; set; } = true;
public bool? EnableMultitouch { get; set; } public bool? EnableMultitouch { get; set; }
public bool OverlayPopups { get; set; } public bool OverlayPopups { get; set; }
public bool UseWgl { get; set; }
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
new GlVersion(GlProfileType.OpenGL, 3, 2),
};
} }
} }
@ -96,8 +103,7 @@ namespace Avalonia.Win32
.Bind<AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider>().ToConstant(new NonPumpingWaitProvider()) .Bind<AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider>().ToConstant(new NonPumpingWaitProvider())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider()); .Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider());
if (options.AllowEglInitialization) Win32GlManager.Initialize();
Win32GlManager.Initialize();
_uiThread = Thread.CurrentThread; _uiThread = Thread.CurrentThread;

13
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -342,19 +342,14 @@ namespace Avalonia.Win32
} }
case WindowsMessage.WM_PAINT: case WindowsMessage.WM_PAINT:
{ {
using (_rendererLock.Lock()) using (_rendererLock.Lock())
{ {
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) Paint?.Invoke(default);
{
var f = RenderScaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
EndPaint(_hwnd, ref ps);
}
} }
ValidateRect(hWnd, IntPtr.Zero);
return IntPtr.Zero; return IntPtr.Zero;
} }

9
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -13,6 +13,7 @@ using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using Avalonia.Win32.OpenGl;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32 namespace Avalonia.Win32
@ -105,8 +106,12 @@ namespace Avalonia.Win32
CreateWindow(); CreateWindow();
_framebuffer = new FramebufferManager(_hwnd); _framebuffer = new FramebufferManager(_hwnd);
if (Win32GlManager.EglPlatformInterface != null) var glPlatform = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
_gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this);
if(glPlatform is EglPlatformOpenGlInterface egl)
_gl = new EglGlPlatformSurface(egl, this);
else if (glPlatform is WglPlatformOpenGlInterface wgl)
_gl = new WglGlPlatformSurface(wgl.PrimaryContext, this);
Screen = new ScreenImpl(); Screen = new ScreenImpl();

18
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core
result); 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 private interface INext
{ {
int PropertyChangedSubscriptionCount { get; } int PropertyChangedSubscriptionCount { get; }

47
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -407,6 +407,53 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, raised); Assert.Equal(1, raised);
} }
[Fact]
public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = new AvaloniaList<string>();
var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
var target = new ListBox()
{
VerticalAlignment = Layout.VerticalAlignment.Top,
AutoScrollToSelectedItem = true,
Width = 50,
VirtualizationMode = ItemVirtualizationMode.Simple,
ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
Items = items,
};
wnd.Content = target;
var lm = wnd.LayoutManager;
lm.ExecuteInitialLayoutPass();
var panel = target.Presenter.Panel;
items.Add("Item 1");
target.Selection.Select(0);
lm.ExecuteLayoutPass();
Assert.Equal(1, panel.Children.Count);
items.Add("Item 2");
target.Selection.Select(1);
lm.ExecuteLayoutPass();
Assert.Equal(2, panel.Children.Count);
//make sure we have enough space to show all items
Assert.True(panel.Bounds.Height >= panel.Children.Sum(c => c.Bounds.Height));
//make sure we show items and they completelly visible, not only partially
Assert.True(panel.Children[0].Bounds.Top >= 0 && panel.Children[0].Bounds.Bottom <= panel.Bounds.Height, "first item is not completelly visible!");
Assert.True(panel.Children[1].Bounds.Top >= 0 && panel.Children[1].Bounds.Bottom <= panel.Bounds.Height, "second item is not completelly visible!");
}
}
private FuncControlTemplate ListBoxTemplate() private FuncControlTemplate ListBoxTemplate()
{ {
return new FuncControlTemplate<ListBox>((parent, scope) => return new FuncControlTemplate<ListBox>((parent, scope) =>

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() private IDisposable CreateServices()
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:

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

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
@ -13,6 +14,7 @@ using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using ReactiveUI;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests 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] [Fact]
public void LogicalChildren_Should_Be_Set() public void LogicalChildren_Should_Be_Set()
{ {
@ -1288,5 +1325,26 @@ namespace Avalonia.Controls.UnitTests
private class DerivedTreeView : TreeView 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);
}
}
}
} }
} }

22
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -417,7 +417,6 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
[Theory] [Theory]
[InlineData("abcde\r\n", 7)] // Carriage Return + Line Feed [InlineData("abcde\r\n", 7)] // Carriage Return + Line Feed
[InlineData("abcde\n\r", 7)] // This isn't valid but we somehow have to support it.
[InlineData("abcde\u000A", 6)] // Line Feed [InlineData("abcde\u000A", 6)] // Line Feed
[InlineData("abcde\u000B", 6)] // Vertical Tab [InlineData("abcde\u000B", 6)] // Vertical Tab
[InlineData("abcde\u000C", 6)] // Form Feed [InlineData("abcde\u000C", 6)] // Form Feed
@ -575,6 +574,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
[Fact]
public void Should_Process_Multiple_NewLines_Properly()
{
using (Start())
{
var text = "123\r\n\r\n456\r\n\r\n";
var layout = new TextLayout(
text,
Typeface.Default,
12.0f,
Brushes.Black);
Assert.Equal(5, layout.TextLines.Count);
Assert.Equal("123\r\n", layout.TextLines[0].TextRuns[0].Text);
Assert.Equal("\r\n", layout.TextLines[1].TextRuns[0].Text);
Assert.Equal("456\r\n", layout.TextLines[2].TextRuns[0].Text);
Assert.Equal("\r\n", layout.TextLines[3].TextRuns[0].Text);
}
}
[Fact] [Fact]
public void Should_Wrap_Min_OneCharacter_EveryLine() public void Should_Wrap_Min_OneCharacter_EveryLine()
{ {

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -72,7 +72,7 @@ namespace Avalonia.UnitTests
dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() => dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() =>
{ {
var layerDc = new Mock<IDrawingContextImpl>(); var layerDc = new Mock<IDrawingContextImpl>();
var layer = new Mock<IRenderTargetBitmapImpl>(); var layer = new Mock<IDrawingContextLayerImpl>();
layer.Setup(x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(layerDc.Object); layer.Setup(x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(layerDc.Object);
return layer.Object; return layer.Object;
}); });

Loading…
Cancel
Save