Browse Source

Merge branch 'master' into fixes/4805-scrollcontentpresenter-anchoring

pull/4814/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
eda5955095
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ApiDiff.props
  2. 4
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  3. 4
      samples/RenderDemo/App.xaml.cs
  4. 17
      src/Avalonia.Base/AvaloniaLocator.cs
  5. 44
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  6. 79
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  7. 4
      src/Avalonia.Controls/MenuItem.cs
  8. 18
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  9. 16
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  10. 3
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  11. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  12. 15
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  13. 12
      src/Avalonia.Controls/TreeView.cs
  14. 11
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  15. 3
      src/Avalonia.OpenGL/Egl/EglContext.cs
  16. 22
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  17. 13
      src/Avalonia.OpenGL/GlInterface.cs
  18. 8
      src/Avalonia.Themes.Default/ToggleSwitch.xaml
  19. 2
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  20. 5
      src/Avalonia.Themes.Fluent/Button.xaml
  21. 14
      src/Avalonia.Themes.Fluent/CheckBox.xaml
  22. 12
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  23. 8
      src/Avalonia.Themes.Fluent/DataValidationErrors.xaml
  24. 7
      src/Avalonia.Themes.Fluent/RadioButton.xaml
  25. 2
      src/Avalonia.Themes.Fluent/TabItem.xaml
  26. 4
      src/Avalonia.Themes.Fluent/TabStripItem.xaml
  27. 5
      src/Avalonia.Themes.Fluent/TextBox.xaml
  28. 5
      src/Avalonia.Themes.Fluent/ToggleButton.xaml
  29. 10
      src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
  30. 61
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  31. 29
      src/Avalonia.Visuals/Animation/CrossFade.cs
  32. 44
      src/Avalonia.Visuals/Animation/PageSlide.cs
  33. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  34. 2
      src/Avalonia.Visuals/Media/GlyphRun.cs
  35. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  36. 75
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  37. 44
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  38. 16
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  39. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  40. 2
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  41. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  42. 12
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  43. 64
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  44. 14
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  45. 134
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  46. 32
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  47. 7
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  48. 31
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  49. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  50. 72
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  51. 3
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  52. 2
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  53. 2
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  54. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  55. 11
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  56. 5
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  57. 2
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  58. 2
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  59. 92
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  60. 62
      src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs
  61. 85
      src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
  62. 183
      src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
  63. 85
      src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
  64. 39
      src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
  65. 39
      src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
  66. 18
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  67. 10
      src/Windows/Avalonia.Win32/Win32Platform.cs
  68. 17
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  69. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  70. 18
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  71. 79
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  72. 58
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  73. 124
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  74. 28
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  75. 18
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  76. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  77. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
  78. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
  79. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
  80. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png
  81. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
  82. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
  83. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
  84. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png

2
build/ApiDiff.props

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

4
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -18,8 +18,8 @@
</StackPanel.Styles>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Text="Left aligned text" TextAlignment="Left" />
<TextBlock Text="Center aligned text" TextAlignment="Center" />
<TextBlock Text="Right aligned text" TextAlignment="Right" />

4
samples/RenderDemo/App.xaml.cs

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

17
src/Avalonia.Base/AvaloniaLocator.cs

@ -54,6 +54,23 @@ namespace Avalonia
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()
{
TImpl instance = null;

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Utilities;
@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
/// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
/// <summary>
/// Starts monitoring the value of a property on an object.
@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance);
var p = GetPropertyWithName(instance.GetType(), propertyName);
var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
if (p != null)
{
@ -44,12 +48,40 @@ namespace Avalonia.Data.Core.Plugins
}
}
private static PropertyInfo GetPropertyWithName(Type type, string propertyName)
private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Static | BindingFlags.Instance;
var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo = TryFindAndCacheProperty(type, propertyName);
}
return propertyInfo;
}
private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
{
PropertyInfo found = null;
const BindingFlags bindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var properties = type.GetProperties(bindingFlags);
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.Name == propertyName)
{
found = propertyInfo;
break;
}
}
_propertyLookup.Add((type, propertyName), found);
return type.GetProperty(propertyName, bindingFlags);
return found;
}
private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>

79
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -36,11 +36,7 @@ namespace Avalonia.Controls.Primitives
private Button _headerButton;
private Button _nextButton;
private Button _previousButton;
private Grid _monthView;
private Grid _yearView;
private ITemplate<IControl> _dayTitleTemplate;
private CalendarButton _lastCalendarButton;
private CalendarDayButton _lastCalendarDayButton;
private DateTime _currentMonth;
private bool _isMouseLeftButtonDown = false;
@ -160,38 +156,12 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets the Grid that hosts the content when in month mode.
/// </summary>
internal Grid MonthView
{
get { return _monthView; }
private set
{
if (_monthView != null)
_monthView.PointerLeave -= MonthView_MouseLeave;
_monthView = value;
if (_monthView != null)
_monthView.PointerLeave += MonthView_MouseLeave;
}
}
internal Grid MonthView { get; set; }
/// <summary>
/// Gets the Grid that hosts the content when in year or decade mode.
/// </summary>
internal Grid YearView
{
get { return _yearView; }
private set
{
if (_yearView != null)
_yearView.PointerLeave -= YearView_MouseLeave;
_yearView = value;
if (_yearView != null)
_yearView.PointerLeave += YearView_MouseLeave;
}
}
internal Grid YearView { get; set; }
private void PopulateGrids()
{
if (MonthView != null)
@ -226,7 +196,6 @@ namespace Avalonia.Controls.Primitives
cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown;
cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp;
cell.PointerEnter += Cell_MouseEnter;
cell.PointerLeave += Cell_MouseLeave;
cell.Click += Cell_Click;
children.Add(cell);
}
@ -256,7 +225,6 @@ namespace Avalonia.Controls.Primitives
month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown;
month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp;
month.PointerEnter += Month_MouseEnter;
month.PointerLeave += Month_MouseLeave;
children.Add(month);
}
}
@ -937,17 +905,7 @@ namespace Avalonia.Controls.Primitives
}
}
}
internal void Cell_MouseLeave(object sender, PointerEventArgs e)
{
if (_isMouseLeftButtonDown)
{
CalendarDayButton b = (CalendarDayButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Pointer.Captured == b)
e.Pointer.Capture(null);
_lastCalendarDayButton = b;
}
}
internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e)
{
if (Owner != null)
@ -1207,35 +1165,6 @@ namespace Avalonia.Controls.Primitives
}
}
private void Month_MouseLeave(object sender, PointerEventArgs e)
{
if (_isMouseLeftButtonDownYearView)
{
CalendarButton b = (CalendarButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Pointer.Captured == b)
e.Pointer.Capture(null);
//b.ReleaseMouseCapture();
_lastCalendarButton = b;
}
}
private void MonthView_MouseLeave(object sender, PointerEventArgs e)
{
if (_lastCalendarDayButton != null)
{
e.Pointer.Capture(_lastCalendarDayButton);
}
}
private void YearView_MouseLeave(object sender, PointerEventArgs e)
{
if (_lastCalendarButton != null)
{
e.Pointer.Capture(_lastCalendarButton);
}
}
internal void UpdateDisabled(bool isEnabled)
{
PseudoClasses.Set(":calendardisabled", !isEnabled);

4
src/Avalonia.Controls/MenuItem.cs

@ -101,7 +101,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup _popup;
private Popup? _popup;
/// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class.
@ -145,7 +145,7 @@ namespace Avalonia.Controls
{
var parent = x as Control;
return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ??
Observable.Return<DefinitionBase.SharedSizeScope>(null);
Observable.Return<DefinitionBase.SharedSizeScope?>(null);
});
this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope);

18
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -148,6 +148,7 @@ namespace Avalonia.Controls.Platform
{
case Key.Up:
case Key.Down:
{
if (item?.IsTopLevel == true)
{
if (item.HasSubMenu && !item.IsSubMenuOpen)
@ -161,8 +162,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
}
case Key.Left:
{
if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
{
parent.Close();
@ -174,8 +177,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
}
case Key.Right:
{
if (item != null && !item.IsTopLevel && item.HasSubMenu)
{
Open(item, true);
@ -186,8 +191,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
}
case Key.Enter:
{
if (item != null)
{
if (!item.HasSubMenu)
@ -202,12 +209,14 @@ namespace Avalonia.Controls.Platform
e.Handled = true;
}
break;
}
case Key.Escape:
if (item?.Parent != null)
{
if (item?.Parent is IMenuElement parent)
{
item.Parent.Close();
item.Parent.Focus();
parent.Close();
parent.Focus();
}
else
{
@ -216,8 +225,10 @@ namespace Avalonia.Controls.Platform
e.Handled = true;
break;
}
default:
{
var direction = e.Key.ToNavigationDirection();
if (direction.HasValue)
@ -246,6 +257,7 @@ namespace Avalonia.Controls.Platform
}
break;
}
}
if (!e.Handled && item?.Parent is IMenuItem parentItem)

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

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

3
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -77,7 +77,8 @@ namespace Avalonia.Controls.Presenters
static TextPresenter()
{
AffectsRender<TextPresenter>(SelectionBrushProperty);
AffectsRender<TextPresenter>(SelectionBrushProperty, TextBlock.ForegroundProperty,
SelectionForegroundBrushProperty, CaretBrushProperty);
AffectsMeasure<TextPresenter>(TextProperty, PasswordCharProperty, RevealPasswordProperty,
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);

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

@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives
return;
}
var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType<IControl>();
if (placementTarget == null)
{
@ -586,6 +586,26 @@ namespace Avalonia.Controls.Primitives
}
Closed?.Invoke(this, EventArgs.Empty);
var focusCheck = FocusManager.Instance?.Current;
// Focus is set to null as part of popup closing, so we only want to
// set focus to PlacementTarget if this is the case
if (focusCheck == null)
{
if (PlacementTarget != null)
{
FocusManager.Instance?.Focus(PlacementTarget);
}
else
{
var anc = this.FindLogicalAncestorOfType<IControl>();
if (anc != null)
{
FocusManager.Instance?.Focus(anc);
}
}
}
}
private void ListenForNonClientClick(RawInputEventArgs e)

15
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -848,21 +848,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Sets an item container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
/// </summary>
/// <param name="index">The index of the item.</param>
/// <param name="selected">Whether the item should be selected or deselected.</param>
private void MarkItemSelected(int index, bool selected)
{
var container = ItemContainerGenerator?.ContainerFromIndex(index);
if (container != null)
{
MarkContainerSelected(container, selected);
}
}
private void UpdateContainerSelection()
{
if (Presenter?.Panel is IPanel panel)

12
src/Avalonia.Controls/TreeView.cs

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

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; }
@ -267,6 +267,13 @@ namespace Avalonia.Headless
return new HeadlessDrawingContextStub();
}
public void Blit(IDrawingContextImpl context)
{
}
public bool CanBlit => false;
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
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));
}

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

@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl
var old = new RestoreContext(_egl, _disp.Handle, _lock);
var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context))
if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero,
surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
success = true;
return old;

22
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@ -158,15 +158,21 @@ namespace Avalonia.OpenGL.Egl
var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var surf = _egl.CreatePBufferSurface(_display, _config, new[]
var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
IntPtr surf = IntPtr.Zero;
if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
{
EGL_WIDTH, 1,
EGL_HEIGHT, 1,
EGL_NONE
});
if (surf == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf),
surf = _egl.CreatePBufferSurface(_display, _config,
new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
if (surf == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
}
var rv = new EglContext(this, _egl, shareCtx, ctx,
context =>
surf == IntPtr.Zero ? null : new EglSurface(this, context, surf),
_version, _sampleCount, _stencilSize);
return rv;
}

13
src/Avalonia.OpenGL/GlInterface.cs

@ -117,6 +117,19 @@ namespace Avalonia.OpenGL
public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")]
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);
[GlEntryPoint("glGenRenderbuffers")]

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

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

2
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -439,7 +439,7 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!--Resources for NotificationCard.xaml -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" />
<SolidColorBrush x:Key="NotificationCardProgressBackgroundBrush" Color="#9A9A9A" />
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>

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

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

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

@ -22,16 +22,14 @@
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}" />
BorderThickness="{TemplateBinding BorderThickness}" />
<Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle"
BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}"
UseLayoutRounding="False"
Height="20"
Width="20"
CornerRadius="{DynamicResource ControlCornerRadius}" />
Width="20" />
<Viewbox UseLayoutRounding="False">
<Panel>
@ -52,6 +50,14 @@
</ControlTemplate>
</Setter>
</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 -->
<Style Selector="CheckBox">

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

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

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

@ -31,12 +31,16 @@
</Setter>
<Setter Property="ErrorTemplate">
<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">
<Canvas.Styles>
<Style Selector="ToolTip">
<Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
</Style>
<Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Canvas.Styles>
<ToolTip.Tip>
<ItemsControl Items="{Binding}"/>

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

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

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

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

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

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

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

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

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

@ -29,7 +29,6 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
@ -38,6 +37,10 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ToggleButton /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<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"
TextWrapping="Wrap"/>
<ToggleSwitch
Content="Previewer"
Background="Green"
IsChecked="True" />
</StackPanel>
</StackPanel>
@ -53,7 +53,6 @@
Grid.Row="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{DynamicResource ToggleSwitchTopHeaderMargin}"
VerticalAlignment="Top"/>
<Grid Grid.Row="1"
@ -129,6 +128,13 @@
</Setter>
</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 -->
<Style Selector="ToggleSwitch /template/ Grid#SwitchAreaGrid">
<Setter Property="Background" Value="{DynamicResource ToggleSwitchContainerBackground}"/>

61
src/Avalonia.Visuals/Animation/CompositePageTransition.cs

@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
/// <summary>
/// Defines a composite page transition that can be used to combine multiple transitions.
/// </summary>
/// <remarks>
/// <para>
/// Instantiate the <see cref="CompositePageTransition" /> in XAML and initialize the
/// <see cref="Transitions" /> property in order to have many animations triggered at once.
/// For example, you can combine <see cref="CrossFade"/> and <see cref="PageSlide"/>.
/// <code>
/// <![CDATA[
/// <reactiveUi:RoutedViewHost Router="{Binding Router}">
/// <reactiveUi:RoutedViewHost.PageTransition>
/// <CompositePageTransition>
/// <PageSlide Duration="0.5" />
/// <CrossFade Duration="0.5" />
/// </CompositePageTransition>
/// </reactiveUi:RoutedViewHost.PageTransition>
/// </reactiveUi:RoutedViewHost>
/// ]]>
/// </code>
/// </para>
/// </remarks>
public class CompositePageTransition : IPageTransition
{
/// <summary>
/// Gets or sets the transitions to be executed. Can be defined from XAML.
/// </summary>
[Content]
public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// Defines the direction of the transition.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public Task Start(Visual from, Visual to, bool forward)
{
var transitionTasks = PageTransitions
.Select(transition => transition.Start(from, to, forward))
.ToList();
return Task.WhenAll(transitionTasks);
}
}
}

29
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
using Avalonia.VisualTree;
@ -74,14 +75,26 @@ namespace Avalonia.Animation
/// </summary>
public TimeSpan Duration
{
get
{
return _fadeOutAnimation.Duration;
}
set
{
_fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
}
get => _fadeOutAnimation.Duration;
set => _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
}
/// <summary>
/// Gets or sets element entrance easing.
/// </summary>
public Easing FadeInEasing
{
get => _fadeInAnimation.Easing;
set => _fadeInAnimation.Easing = value;
}
/// <summary>
/// Gets or sets element exit easing.
/// </summary>
public Easing FadeOutEasing
{
get => _fadeOutAnimation.Easing;
set => _fadeOutAnimation.Easing = value;
}
/// <summary>

44
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.VisualTree;
@ -48,6 +49,16 @@ namespace Avalonia.Animation
/// Gets the duration of the animation.
/// </summary>
public SlideAxis Orientation { get; set; }
/// <summary>
/// Gets or sets element entrance easing.
/// </summary>
public Easing SlideInEasing { get; set; } = new LinearEasing();
/// <summary>
/// Gets or sets element exit easing.
/// </summary>
public Easing SlideOutEasing { get; set; } = new LinearEasing();
/// <summary>
/// Starts the animation.
@ -75,18 +86,12 @@ namespace Avalonia.Animation
{
var animation = new Animation
{
Children =
Easing = SlideOutEasing,
Children =
{
new KeyFrame
{
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Setters = { new Setter { Property = translateProperty, Value = 0d } },
Cue = new Cue(0d)
},
new KeyFrame
@ -100,10 +105,10 @@ namespace Avalonia.Animation
}
},
Cue = new Cue(1d)
}
}
}
},
Duration = Duration
};
animation.Duration = Duration;
tasks.Add(animation.RunAsync(from));
}
@ -112,9 +117,9 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
Easing = SlideInEasing,
Children =
{
new KeyFrame
{
Setters =
@ -129,19 +134,12 @@ namespace Avalonia.Animation
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Setters = { new Setter { Property = translateProperty, Value = 0d } },
Cue = new Cue(1d)
}
}
},
Duration = Duration
};
animation.Duration = Duration;
tasks.Add(animation.RunAsync(to));
}

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

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

@ -611,8 +611,6 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
_baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale);
var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);

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

@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
return;
}
if (Properties.Typeface == null)
if (Properties.Typeface == default)
{
return;
}

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

@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting
}
/// <summary>
/// Measures the number of characters that fits into available width.
/// Measures the number of characters that fit into available width.
/// </summary>
/// <param name="textCharacters">The text run.</param>
/// <param name="availableWidth">The available width.</param>
/// <returns></returns>
internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
/// <param name="count">The count of fitting characters.</param>
/// <returns>
/// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
/// </returns>
internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count)
{
var glyphRun = textCharacters.GlyphRun;
if (glyphRun.Size.Width < availableWidth)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
var glyphCount = 0;
@ -96,21 +101,42 @@ namespace Avalonia.Media.TextFormatting
}
}
if (glyphCount == 0)
{
count = 0;
return false;
}
if (glyphCount == glyphRun.GlyphIndices.Length)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
if (glyphRun.GlyphClusters.IsEmpty)
{
return glyphCount;
count = glyphCount;
return true;
}
var firstCluster = glyphRun.GlyphClusters[0];
var lastCluster = glyphRun.GlyphClusters[glyphCount];
return lastCluster - firstCluster;
if (glyphRun.IsLeftToRight)
{
count = lastCluster - firstCluster;
}
else
{
count = firstCluster - lastCluster;
}
return count > 0;
}
/// <summary>
@ -350,29 +376,38 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.Size.Width > availableWidth)
{
var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
var breakFound = false;
var currentBreakPosition = 0;
if (measuredLength < currentRun.Text.Length)
if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (measuredLength < currentRun.Text.Length)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
}
var nextBreakPosition = lineBreaker.Current.PositionWrap;
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
currentBreakPosition = nextBreakPosition;
}
}
}
else
{
// Make sure we wrap at least one character.
if (currentLength == 0)
{
measuredLength = 1;
}
}

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

@ -39,7 +39,9 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0)))
var offsetY = LineMetrics.TextBaseline - textRun.GlyphRun.BaselineOrigin.Y;
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{
textRun.Draw(drawingContext);
}
@ -75,37 +77,35 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth > availableWidth)
{
var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
var currentBreakPosition = 0;
if (measuredLength < textRange.End)
if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var currentBreakPosition = 0;
if (nextBreakPosition == 0)
{
break;
}
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
currentBreakPosition = nextBreakPosition;
measuredLength = currentBreakPosition;
}
}
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
{
measuredLength = currentBreakPosition;
}
collapsedLength += measuredLength;
var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);

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,
/// but a layer created via this method has no such overhead.
/// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size);
IDrawingContextLayerImpl CreateLayer(Size size);
/// <summary>
/// Pushes a clip rectangle.
@ -155,4 +155,18 @@ namespace Avalonia.Platform
/// <param name="custom">Custom draw operation</param>
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 firstLayer = true;
foreach (var layer in scene.Layers)
{
var bitmap = Layers[layer.LayerRoot].Bitmap;
@ -501,7 +502,10 @@ namespace Avalonia.Rendering
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
{
@ -512,6 +516,8 @@ namespace Avalonia.Rendering
{
context.PopGeometryClip();
}
firstLayer = false;
}
if (_overlay != null)

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

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
IsEmpty = true;
}
public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; }
public IRef<IDrawingContextLayerImpl> Bitmap { get; private set; }
public bool IsEmpty { get; set; }
public double Scaling { 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;
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
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);
_eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface);
EglContext CreateContext(EglContext share)
{
var offSurf = gbm_surface_create(device, 1, 1, GbmColorFormats.GBM_FORMAT_XRGB8888,
GbmBoFlags.GBM_BO_USE_RENDERING);
if (offSurf == null)
throw new InvalidOperationException("Unable to create 1x1 sized GBM surface");
return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf));
}
_deferredContext = CreateContext(null);
_deferredContext = _platformGl.PrimaryEglContext;
using (_deferredContext.MakeCurrent(_eglSurface))
{

64
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -31,6 +31,7 @@ namespace Avalonia.Skia
private bool _disposed;
private GRContext _grContext;
public GRContext GrContext => _grContext;
private ISkiaGpu _gpu;
private readonly SKPaint _strokePaint = new SKPaint();
private readonly SKPaint _fillPaint = new SKPaint();
private readonly SKPaint _boxShadowPaint = new SKPaint();
@ -41,6 +42,11 @@ namespace Avalonia.Skia
/// </summary>
public struct CreateInfo
{
/// <summary>
/// Canvas to draw to.
/// </summary>
public SKCanvas Canvas;
/// <summary>
/// Surface to draw to.
/// </summary>
@ -65,6 +71,11 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional)
/// </summary>
public GRContext GrContext;
/// <summary>
/// Skia GPU provider context (optional)
/// </summary>
public ISkiaGpu Gpu;
}
/// <summary>
@ -79,10 +90,11 @@ namespace Avalonia.Skia
_disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext;
_gpu = createInfo.Gpu;
if (_grContext != null)
Monitor.Enter(_grContext);
Surface = createInfo.Surface;
Canvas = createInfo.Surface.Canvas;
Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas;
if (Canvas == null)
{
@ -415,9 +427,9 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return CreateRenderTarget(size);
return CreateRenderTarget( size);
}
/// <inheritdoc />
@ -583,14 +595,45 @@ namespace Avalonia.Skia
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetSize.Width);
// TODO: There is no SetAlpha in SkiaSharp
//paint.setAlpha(128);
var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
if (origin.Equals(center))
{
paintWrapper.Paint.Shader = shader;
// when the origin is the same as the center the Skia RadialGradient acts the same as D2D
using (var shader =
SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
// when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
// reverse the order of the stops to match D2D
var reversedColors = new SKColor[stopColors.Length];
Array.Copy(stopColors, reversedColors, stopColors.Length);
Array.Reverse(reversedColors);
// and then reverse the reference point of the stops
var reversedStops = new float[stopOffsets.Length];
for (var i = 0; i < stopOffsets.Length; i++)
{
reversedStops[i] = stopOffsets[i];
if (reversedStops[i] > 0 && reversedStops[i] < 1)
{
reversedStops[i] = Math.Abs(1 - stopOffsets[i]);
}
}
// compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
using (var shader = SKShader.CreateCompose(
SKShader.CreateColor(reversedColors[0]),
SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
))
{
paintWrapper.Paint.Shader = shader;
}
}
break;
@ -925,7 +968,8 @@ namespace Avalonia.Skia
Dpi = _dpi,
Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering,
GrContext = _grContext
GrContext = _grContext,
Gpu = _gpu
};
return new SurfaceRenderTarget(createInfo);

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

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
@ -15,6 +16,19 @@ namespace Avalonia.Skia
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
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

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.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces;
@ -10,6 +13,7 @@ namespace Avalonia.Skia
{
private GRContext _grContext;
private IGlContext _glContext;
private bool? _canCreateSurfaces;
public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes)
{
@ -43,6 +47,34 @@ namespace Avalonia.Skia
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);
}
}

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

@ -8,10 +8,12 @@ namespace Avalonia.Skia
/// </summary>
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
{
private readonly ISkiaGpu _skiaGpu;
private readonly ISkiaGpuRenderTarget _renderTarget;
public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget)
public SkiaGpuRenderTarget(ISkiaGpu skiaGpu, ISkiaGpuRenderTarget renderTarget)
{
_skiaGpu = skiaGpu;
_renderTarget = renderTarget;
}
@ -30,7 +32,8 @@ namespace Avalonia.Skia
Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
DisableTextLcdRendering = true,
Gpu = _skiaGpu
};
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);
if (gpuRenderTarget != null)
{
return new SkiaGpuRenderTarget(gpuRenderTarget);
return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget);
}
foreach (var surface in surfaces)

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

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Reactive.Disposables;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Skia.Helpers;
@ -11,12 +12,31 @@ namespace Avalonia.Skia
/// <summary>
/// Skia render target that writes to a surface.
/// </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 bool _disableLcdRendering;
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>
/// Create new surface render target.
@ -29,9 +49,14 @@ namespace Avalonia.Skia
_disableLcdRendering = createInfo.DisableTextLcdRendering;
_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)
{
@ -70,11 +95,12 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo
{
Surface = _surface,
Surface = _surface.Surface,
Dpi = Dpi,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = _disableLcdRendering,
GrContext = _grContext
GrContext = _grContext,
Gpu = _gpu
};
return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++));
@ -106,19 +132,31 @@ namespace Avalonia.Skia
}
}
/// <inheritdoc />
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
public void Blit(IDrawingContextImpl contextImpl)
{
if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size)
var context = (DrawingContextImpl)contextImpl;
if (_surface.CanBlit)
{
_surface.Canvas.Flush();
_surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint);
_surface.Surface.Canvas.Flush();
_surface.Blit(context.Canvas);
}
else
using (var image = SnapshotImage())
{
context.Canvas.DrawImage(image, sourceRect, destRect, paint);
}
{
var oldMatrix = context.Canvas.TotalMatrix;
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>
@ -127,7 +165,7 @@ namespace Avalonia.Skia
/// <returns>Image snapshot.</returns>
public SKImage SnapshotImage()
{
return _surface.Snapshot();
return _surface.Surface.Snapshot();
}
/// <summary>
@ -178,6 +216,8 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional)
/// </summary>
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<Vector>(glyphOffsets),
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();
return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size);

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

@ -4,6 +4,6 @@ namespace Avalonia.Direct2D1
{
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)
{
@ -346,7 +346,7 @@ namespace Avalonia.Direct2D1.Media
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
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.Rendering;
using Avalonia.Utilities;
@ -8,7 +9,7 @@ using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IDrawingContextLayerImpl, ILayerFactory
{
private readonly BitmapRenderTarget _renderTarget;
@ -34,7 +35,11 @@ namespace Avalonia.Direct2D1.Media.Imaging
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);
}

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

@ -5,7 +5,7 @@ using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IDrawingContextLayerImpl
{
private readonly WicRenderTarget _renderTarget;
@ -45,5 +45,8 @@ namespace Avalonia.Direct2D1.Media
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);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size 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);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{

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

@ -1011,6 +1011,10 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
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")]
public static extern bool IsWindowEnabled(IntPtr hWnd);
@ -1292,6 +1296,41 @@ namespace Avalonia.Win32.Interop
[DllImport("gdi32.dll")]
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)]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
@ -2121,4 +2160,57 @@ namespace Avalonia.Win32.Interop
public bool fNC;
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.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.Win32.OpenGl;
namespace Avalonia.Win32
{
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;
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());
s_attemptedToInitialize = true;
var wgl = WglPlatformOpenGlInterface.TryCreate();
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.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
@ -39,6 +40,12 @@ namespace Avalonia
public bool AllowEglInitialization { get; set; } = true;
public bool? EnableMultitouch { 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<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider());
if (options.AllowEglInitialization)
Win32GlManager.Initialize();
Win32GlManager.Initialize();
_uiThread = Thread.CurrentThread;

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

@ -200,6 +200,10 @@ namespace Avalonia.Win32
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
// Mouse capture is lost
case WindowsMessage.WM_CANCELMODE:
_mouseDevice.Capture(null);
break;
case WindowsMessage.WM_MOUSEMOVE:
{
@ -338,19 +342,14 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_PAINT:
{
{
using (_rendererLock.Lock())
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
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);
}
Paint?.Invoke(default);
}
ValidateRect(hWnd, IntPtr.Zero);
return IntPtr.Zero;
}

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

@ -13,6 +13,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Win32.OpenGl;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
@ -105,8 +106,12 @@ namespace Avalonia.Win32
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
if (Win32GlManager.EglPlatformInterface != null)
_gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this);
var glPlatform = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
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();

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

@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core
result);
}
[Fact]
public void Should_Not_Throw_Exception_On_Duplicate_Properties()
{
// Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733.
var source = new MyViewModel();
var target = new PropertyAccessorNode("Name", false);
target.Target = new WeakReference<object>(source);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
}
public class MyViewModelBase { public object Name => "Name"; }
public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; }
private interface INext
{
int PropertyChangedSubscriptionCount { get; }

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

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

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

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

124
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -18,7 +18,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush")
{
}
[Fact]
public async Task RadialGradientBrush_RedBlue()
{
@ -43,5 +43,127 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
/// <summary>
/// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
/// </summary>
[Fact]
public async Task RadialGradientBrush_RedBlue_Offset_Inside()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative)
}
}
};
await RenderToFile(target);
CompareImages();
}
/// <summary>
/// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
/// </summary>
[Fact]
public async Task RadialGradientBrush_RedBlue_Offset_Outside()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative)
}
}
};
await RenderToFile(target);
CompareImages();
}
/// <summary>
/// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
/// </summary>
[Fact]
public async Task RadialGradientBrush_RedGreenBlue_Offset_Inside()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Green, Offset = 0.5 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative),
Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
Radius = 0.5
}
}
};
await RenderToFile(target);
CompareImages();
}
/// <summary>
/// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
/// </summary>
[Fact]
public async Task RadialGradientBrush_RedGreenBlue_Offset_Outside()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Green, Offset = 0.25 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative),
Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
Radius = 0.5
}
}
};
await RenderToFile(target);
CompareImages();
}
}
}

28
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -293,6 +293,34 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Wrap_Should_Not_Produce_Empty_Lines()
{
using (Start())
{
const string text = "012345";
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textSourceIndex = 0;
while (textSourceIndex < text.Length)
{
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties);
Assert.NotEqual(0, textLine.TextRange.Length);
textSourceIndex += textLine.TextRange.Length;
}
Assert.Equal(text.Length, textSourceIndex);
}
}
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

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

@ -575,6 +575,24 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Wrap_Min_OneCharacter_EveryLine()
{
using (Start())
{
var layout = new TextLayout(
s_singleLineText,
Typeface.Default,
12,
Brushes.Black,
textWrapping: TextWrapping.Wrap,
maxWidth: 3);
//every character should be new line as there not enough space for even one character
Assert.Equal(s_singleLineText.Length, layout.TextLines.Count);
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -72,7 +72,7 @@ namespace Avalonia.UnitTests
dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() =>
{
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);
return layer.Object;
});

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Loading…
Cancel
Save