Browse Source

Merge branch 'master' into removed-dead-code

pull/4871/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
4f232c8a83
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ApiDiff.props
  2. 2
      samples/ControlCatalog/App.xaml.cs
  3. 2
      samples/ControlCatalog/MainView.xaml
  4. 4
      samples/ControlCatalog/MainView.xaml.cs
  5. 4
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  6. 4
      samples/RenderDemo/App.xaml.cs
  7. 17
      src/Avalonia.Base/AvaloniaLocator.cs
  8. 12
      src/Avalonia.Base/AvaloniaObject.cs
  9. 44
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  10. 79
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  11. 6
      src/Avalonia.Controls/IScrollAnchorProvider.cs
  12. 4
      src/Avalonia.Controls/MenuItem.cs
  13. 5
      src/Avalonia.Controls/Panel.cs
  14. 18
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  15. 16
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  16. 8
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  17. 137
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  18. 3
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  19. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  20. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  21. 18
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  22. 2
      src/Avalonia.Controls/Repeater/ViewManager.cs
  23. 14
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  24. 1
      src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
  25. 44
      src/Avalonia.Controls/Shapes/Shape.cs
  26. 12
      src/Avalonia.Controls/TreeView.cs
  27. 11
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  28. 2
      src/Avalonia.Layout/StackLayout.cs
  29. 3
      src/Avalonia.OpenGL/Egl/EglContext.cs
  30. 22
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  31. 13
      src/Avalonia.OpenGL/GlInterface.cs
  32. 8
      src/Avalonia.Themes.Default/ToggleSwitch.xaml
  33. 2
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  34. 2
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  35. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
  36. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
  37. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
  38. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
  39. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
  40. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
  41. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
  42. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf
  43. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf
  44. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf
  45. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf
  46. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf
  47. BIN
      src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf
  48. 5
      src/Avalonia.Themes.Fluent/Button.xaml
  49. 14
      src/Avalonia.Themes.Fluent/CheckBox.xaml
  50. 12
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  51. 8
      src/Avalonia.Themes.Fluent/DataValidationErrors.xaml
  52. 7
      src/Avalonia.Themes.Fluent/RadioButton.xaml
  53. 2
      src/Avalonia.Themes.Fluent/TabItem.xaml
  54. 4
      src/Avalonia.Themes.Fluent/TabStripItem.xaml
  55. 5
      src/Avalonia.Themes.Fluent/TextBox.xaml
  56. 5
      src/Avalonia.Themes.Fluent/ToggleButton.xaml
  57. 10
      src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
  58. 61
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  59. 29
      src/Avalonia.Visuals/Animation/CrossFade.cs
  60. 44
      src/Avalonia.Visuals/Animation/PageSlide.cs
  61. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  62. 6
      src/Avalonia.Visuals/Media/GlyphRun.cs
  63. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  64. 83
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  65. 44
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  66. 16
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  67. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  68. 2
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  69. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  70. 12
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  71. 64
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  72. 14
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  73. 134
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  74. 32
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  75. 7
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  76. 31
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  77. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  78. 72
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  79. 3
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  80. 36
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  81. 2
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  82. 2
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  83. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  84. 11
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  85. 5
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  86. 160
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  87. 2
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  88. 2
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  89. 92
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  90. 62
      src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs
  91. 85
      src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
  92. 183
      src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
  93. 85
      src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
  94. 39
      src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
  95. 39
      src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
  96. 18
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  97. 10
      src/Windows/Avalonia.Win32/Win32Platform.cs
  98. 26
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  99. 65
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  100. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs

2
build/ApiDiff.props

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

2
samples/ControlCatalog/App.xaml.cs

@ -81,7 +81,7 @@ namespace ControlCatalog
public override void Initialize()
{
Styles.Insert(0, FluentDark);
Styles.Insert(0, FluentLight);
AvaloniaXamlLoader.Load(this);
}

2
samples/ControlCatalog/MainView.xaml

@ -77,8 +77,8 @@
<ComboBoxItem>Full Decorations</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="Themes" SelectedIndex="0">
<ComboBoxItem>Fluent - Dark</ComboBoxItem>
<ComboBoxItem>Fluent - Light</ComboBoxItem>
<ComboBoxItem>Fluent - Dark</ComboBoxItem>
<ComboBoxItem>Simple - Light</ComboBoxItem>
<ComboBoxItem>Simple - Dark</ComboBoxItem>
</ComboBox>

4
samples/ControlCatalog/MainView.xaml.cs

@ -38,10 +38,10 @@ namespace ControlCatalog
switch (themes.SelectedIndex)
{
case 0:
Application.Current.Styles[0] = App.FluentDark;
Application.Current.Styles[0] = App.FluentLight;
break;
case 1:
Application.Current.Styles[0] = App.FluentLight;
Application.Current.Styles[0] = App.FluentDark;
break;
case 2:
Application.Current.Styles[0] = App.DefaultLight;

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;

12
src/Avalonia.Base/AvaloniaObject.cs

@ -113,16 +113,8 @@ namespace Avalonia
/// <param name="binding">The binding information.</param>
public IBinding this[IndexerDescriptor binding]
{
get
{
return new IndexerBinding(this, binding.Property, binding.Mode);
}
set
{
var sourceBinding = value as IBinding;
this.Bind(binding.Property, sourceBinding);
}
get { return new IndexerBinding(this, binding.Property, binding.Mode); }
set { this.Bind(binding.Property, value); }
}
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();

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

6
src/Avalonia.Controls/IScrollAnchorProvider.cs

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

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

5
src/Avalonia.Controls/Panel.cs

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

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;

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

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

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

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

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)

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

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

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

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

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

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

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

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

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

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

44
src/Avalonia.Controls/Shapes/Shape.cs

@ -62,7 +62,6 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry? _definingGeometry;
private Geometry? _renderedGeometry;
private bool _calculateTransformOnArrange;
static Shape()
{
@ -248,52 +247,21 @@ namespace Avalonia.Controls.Shapes
protected override Size MeasureOverride(Size availableSize)
{
bool deferCalculateTransform;
switch (Stretch)
if (DefiningGeometry is null)
{
case Stretch.Fill:
case Stretch.UniformToFill:
deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
break;
case Stretch.Uniform:
deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
break;
case Stretch.None:
default:
deferCalculateTransform = false;
break;
return default;
}
if (deferCalculateTransform)
{
_calculateTransformOnArrange = true;
return DefiningGeometry?.Bounds.Size ?? Size.Empty;
}
else
{
_calculateTransformOnArrange = false;
return CalculateShapeSizeAndSetTransform(availableSize);
}
return CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch).size;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (_calculateTransformOnArrange)
{
_calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize);
}
return finalSize;
}
private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
if (DefiningGeometry != null)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
var (_, transform) = CalculateSizeAndTransform(finalSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
@ -301,13 +269,13 @@ namespace Avalonia.Controls.Shapes
_renderedGeometry = null;
}
return size;
return finalSize;
}
return Size.Empty;
}
internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
internal static (Size size, Matrix transform) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
{
Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom);
Matrix translate = Matrix.Identity;

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

2
src/Avalonia.Layout/StackLayout.cs

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

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

@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl
var old = new RestoreContext(_egl, _disp.Handle, _lock);
var 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/Base.xaml

@ -12,7 +12,7 @@
<Color x:Key="SystemColorHotlightColor">#FF0066CC</Color>
<Color x:Key="SystemColorWindowColor">#FFFFFFFF</Color>
<Color x:Key="SystemColorWindowTextColor">#FF000000</Color>
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Themes.Fluent/Assets#Roboto</FontFamily>
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Themes.Fluent/Assets#Inter</FontFamily>
<sys:Double x:Key="ControlContentThemeFontSize">14</sys:Double>
<SolidColorBrush x:Key="SystemControlTransparentBrush" Color="Transparent" />

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"/>

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf

Binary file not shown.

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

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

@ -399,14 +399,14 @@ namespace Avalonia.Media
if (characterIndex > GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
return _glyphClusters.Length - 1;
}
}
else
{
if (characterIndex < GlyphClusters[GlyphClusters.Length - 1])
{
return _glyphClusters.End;
return _glyphClusters.Length - 1;
}
if (characterIndex > GlyphClusters[0])
@ -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;
}

83
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>
@ -313,14 +339,6 @@ namespace Avalonia.Media.TextFormatting
return true;
}
//The line breaker isn't treating \n\r as a pair so we have to fix that here.
if (textRun.Text[lineBreak.PositionMeasure] == '\n'
&& textRun.Text[lineBreak.PositionWrap] == '\r')
{
lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1,
lineBreak.Required);
}
return true;
}
@ -350,29 +368,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;
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
{
break;
}
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
currentBreakPosition = nextBreakPosition;
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);
}
}

36
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -238,22 +238,46 @@ namespace Avalonia.Direct2D1
width = 0;
for (var i = 0; i < glyphCount; i++)
var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight);
if (glyphRun.GlyphAdvances.IsEmpty)
{
for (var i = 0; i < glyphCount; i++)
{
var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
run.Advances[i] = advance;
width += advance;
}
}
else
{
for (var i = 0; i < glyphCount; i++)
{
var advance = (float)glyphRun.GlyphAdvances[i];
run.Advances[i] = advance;
width += advance;
}
}
if (glyphRun.GlyphOffsets.IsEmpty)
{
run.Advances[i] = (float)glyphRun.GlyphAdvances[i];
width += run.Advances[i];
return new GlyphRunImpl(run);
}
run.Offsets = new GlyphOffset[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
var offset = glyphRun.GlyphOffsets[i];
var (x, y) = glyphRun.GlyphOffsets[i];
run.Offsets[i] = new GlyphOffset
{
AdvanceOffset = (float)offset.X,
AscenderOffset = (float)offset.Y
AdvanceOffset = (float)x,
AscenderOffset = (float)y
};
}

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

160
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -1,6 +1,6 @@
using System.Globalization;
using System;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
@ -15,51 +15,9 @@ namespace Avalonia.Direct2D1.Media
{
using (var buffer = new Buffer())
{
buffer.ContentType = ContentType.Unicode;
FillBuffer(buffer, text);
var breakCharPosition = text.Length - 1;
var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count);
if (codepoint.IsBreakChar)
{
var breakCharCount = 1;
if (text.Length > 1)
{
var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _);
if (codepoint == '\r' && previousCodepoint == '\n'
|| codepoint == '\n' && previousCodepoint == '\r')
{
breakCharCount = 2;
}
}
if (breakCharPosition != text.Start)
{
buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount));
}
var cluster = buffer.GlyphInfos.Length > 0 ?
buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 :
(uint)text.Start;
switch (breakCharCount)
{
case 1:
buffer.Add('\u200C', cluster);
break;
case 2:
buffer.Add('\u200C', cluster);
buffer.Add('\u200D', cluster);
break;
}
}
else
{
buffer.AddUtf16(text.Buffer.Span);
}
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
buffer.GuessSegmentProperties();
@ -67,44 +25,38 @@ namespace Avalonia.Direct2D1.Media
var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font;
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
font.Shape(buffer);
font.GetScale(out var scaleX, out _);
var textScale = fontRenderingEmSize / scaleX;
var len = buffer.Length;
var bufferLength = buffer.Length;
var info = buffer.GetGlyphInfoSpan();
var glyphInfos = buffer.GetGlyphInfoSpan();
var pos = buffer.GetGlyphPositionSpan();
var glyphPositions = buffer.GetGlyphPositionSpan();
var glyphIndices = new ushort[len];
var glyphIndices = new ushort[bufferLength];
var clusters = new ushort[len];
var clusters = new ushort[bufferLength];
var glyphAdvances = new double[len];
double[] glyphAdvances = null;
var glyphOffsets = new Vector[len];
Vector[] glyphOffsets = null;
for (var i = 0; i < len; i++)
for (var i = 0; i < bufferLength; i++)
{
glyphIndices[i] = (ushort)info[i].Codepoint;
clusters[i] = (ushort)(text.Start + info[i].Cluster);
var advanceX = pos[i].XAdvance * textScale;
// Depends on direction of layout
//var advanceY = pos[i].YAdvance * textScale;
glyphIndices[i] = (ushort)glyphInfos[i].Codepoint;
glyphAdvances[i] = advanceX;
clusters[i] = (ushort)glyphInfos[i].Cluster;
var offsetX = pos[i].XOffset * textScale;
var offsetY = pos[i].YOffset * textScale;
if (!glyphTypeface.IsFixedPitch)
{
SetAdvance(glyphPositions, i, textScale, ref glyphAdvances);
}
glyphOffsets[i] = new Vector(offsetX, offsetY);
SetOffset(glyphPositions, i, textScale, ref glyphOffsets);
}
return new GlyphRun(glyphTypeface, fontRenderingEmSize,
@ -115,5 +67,79 @@ namespace Avalonia.Direct2D1.Media
new ReadOnlySlice<ushort>(clusters));
}
}
private static void FillBuffer(Buffer buffer, ReadOnlySlice<char> text)
{
buffer.ContentType = ContentType.Unicode;
var i = 0;
while (i < text.Length)
{
var codepoint = Codepoint.ReadAt(text, i, out var count);
var cluster = (uint)(text.Start + i);
if (codepoint.IsBreakChar)
{
if (i + 1 < text.Length)
{
var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _);
if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r')
{
count++;
buffer.Add('\u200C', cluster);
buffer.Add('\u200D', cluster);
}
else
{
buffer.Add('\u200C', cluster);
}
}
else
{
buffer.Add('\u200C', cluster);
}
}
else
{
buffer.Add(codepoint, cluster);
}
i += count;
}
}
private static void SetOffset(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale,
ref Vector[] offsetBuffer)
{
var position = glyphPositions[index];
if (position.XOffset == 0 && position.YOffset == 0)
{
return;
}
offsetBuffer ??= new Vector[glyphPositions.Length];
var offsetX = position.XOffset * textScale;
var offsetY = position.YOffset * textScale;
offsetBuffer[index] = new Vector(offsetX, offsetY);
}
private static void SetAdvance(ReadOnlySpan<GlyphPosition> glyphPositions, int index, double textScale,
ref double[] advanceBuffer)
{
advanceBuffer ??= new double[glyphPositions.Length];
// Depends on direction of layout
// advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale;
advanceBuffer[index] = glyphPositions[index].XAdvance * textScale;
}
}
}

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;

26
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,22 +342,22 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_PAINT:
{
using (_rendererLock.Lock())
{
using (_rendererLock.Lock())
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
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);
}
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);
}
return IntPtr.Zero;
}
return IntPtr.Zero;
}
case WindowsMessage.WM_SIZE:
{
using (_rendererLock.Lock())

65
src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -11,70 +10,78 @@ namespace Avalonia.Win32
public partial class WindowImpl
{
// Hit test the frame for resizing and moving.
HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
private HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
{
// Get the point coordinates for the hit test.
// Get the point coordinates for the hit test (screen space).
var ptMouse = PointFromLParam(lParam);
// Get the window rectangle.
// Get the window rectangle.
GetWindowRect(hWnd, out var rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = new RECT();
var rcFrame = new RECT();
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
RECT border_thickness = new RECT();
var borderThickness = new RECT();
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME))
{
AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0);
border_thickness.left *= -1;
border_thickness.top *= -1;
AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0);
borderThickness.left *= -1;
borderThickness.top *= -1;
}
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER))
{
border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
}
if (_extendTitleBarHint >= 0)
{
border_thickness.top = (int)(_extendedMargins.Top * RenderScaling);
borderThickness.top = (int)(_extendedMargins.Top * RenderScaling);
}
// Determine if the hit test is for resizing. Default middle (1,1).
ushort uRow = 1;
ushort uCol = 1;
bool fOnResizeBorder = false;
bool onResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top)
// Determine if the point is at the left or right of the window.
if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left)
{
fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
uRow = 0;
uCol = 0; // left side
}
else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom)
else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right)
{
uRow = 2;
uCol = 2; // right side
}
// Determine if the point is at the left or right of the window.
if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left)
// Determine if the point is at the top or bottom of the window.
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + borderThickness.top)
{
uCol = 0; // left side
onResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
// Two cases where we have a valid row 0 hit test:
// - window resize border (top resize border hit)
// - area below resize border that is actual titlebar (caption hit).
if (onResizeBorder || uCol == 1)
{
uRow = 0;
}
}
else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right)
else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - borderThickness.bottom)
{
uCol = 2; // right side
uRow = 2;
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
HitTestValues[][] hitTests = new[]
ReadOnlySpan<HitTestValues> hitZones = stackalloc HitTestValues[]
{
new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT },
new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT },
new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT },
HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION,
HitTestValues.HTTOPRIGHT, HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT,
HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT
};
return hitTests[uRow][uCol];
var zoneIndex = uRow * 3 + uCol;
return hitZones[zoneIndex];
}
protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp)

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save