Browse Source

Merge branch 'master' into master

pull/4589/head
GMIKE 6 years ago
committed by GitHub
parent
commit
fd699ed9b3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      Avalonia.sln
  2. 7
      azure-pipelines.yml
  3. 2
      build/NetFX.props
  4. 4
      src/Avalonia.Controls/ApiCompatBaseline.txt
  5. 14
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  6. 7
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  7. 10
      src/Avalonia.Controls/ColumnDefinitions.cs
  8. 4
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  9. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  10. 24
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  11. 2
      src/Avalonia.Controls/TextBlock.cs
  12. 6
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs
  13. 51
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  14. 4
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  15. 22
      src/Avalonia.Input/AccessKeyHandler.cs
  16. 2
      src/Avalonia.Input/Avalonia.Input.csproj
  17. 6
      src/Avalonia.Input/DataObject.cs
  18. 6
      src/Avalonia.Input/DragDropDevice.cs
  19. 47
      src/Avalonia.Input/FocusManager.cs
  20. 16
      src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs
  21. 15
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  22. 9
      src/Avalonia.Input/Gestures.cs
  23. 2
      src/Avalonia.Input/IAccessKeyHandler.cs
  24. 6
      src/Avalonia.Input/IDataObject.cs
  25. 6
      src/Avalonia.Input/IFocusManager.cs
  26. 2
      src/Avalonia.Input/IInputElement.cs
  27. 4
      src/Avalonia.Input/IInputRoot.cs
  28. 4
      src/Avalonia.Input/IKeyboardDevice.cs
  29. 4
      src/Avalonia.Input/IPointer.cs
  30. 4
      src/Avalonia.Input/IPointerDevice.cs
  31. 8
      src/Avalonia.Input/InputElement.cs
  32. 2
      src/Avalonia.Input/KeyEventArgs.cs
  33. 8
      src/Avalonia.Input/KeyboardDevice.cs
  34. 13
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  35. 73
      src/Avalonia.Input/MouseDevice.cs
  36. 42
      src/Avalonia.Input/Navigation/TabNavigation.cs
  37. 9
      src/Avalonia.Input/Pointer.cs
  38. 14
      src/Avalonia.Input/PointerEventArgs.cs
  39. 2
      src/Avalonia.Input/Raw/RawDragEventType.cs
  40. 2
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  41. 4
      src/Avalonia.Input/TextInputEventArgs.cs
  42. 4
      src/Avalonia.Layout/Layoutable.cs
  43. 36
      src/Avalonia.OpenGL/Angle/AngleEglInterface.cs
  44. 88
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  45. 5
      src/Avalonia.OpenGL/AngleOptions.cs
  46. 13
      src/Avalonia.OpenGL/EglConsts.cs
  47. 76
      src/Avalonia.OpenGL/EglDisplay.cs
  48. 7
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  49. 84
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  50. 103
      src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs
  51. 29
      src/Avalonia.OpenGL/EglInterface.cs
  52. 4
      src/Avalonia.Themes.Fluent/CheckBox.xaml
  53. 4
      src/Avalonia.Themes.Fluent/RadioButton.xaml
  54. 12
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  55. 67
      src/Avalonia.Visuals/Media/FontManager.cs
  56. 40
      src/Avalonia.Visuals/Media/Fonts/FontKey.cs
  57. 7
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  58. 31
      src/Avalonia.Visuals/Media/Typeface.cs
  59. 5
      src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
  60. 3
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  61. 2
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  62. 7
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  63. 19
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  64. 2
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  65. 6
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  66. 7
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  67. 3
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  68. 13
      src/Windows/Avalonia.Win32/WindowImpl.cs
  69. 31
      tests/Avalonia.LeakTests/ControlTests.cs
  70. 2
      tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs
  71. 8
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  72. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  73. 5
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  74. 7
      tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

28
Avalonia.sln

@ -222,9 +222,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "sr
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}"
EndProject EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -2040,6 +2040,30 @@ Global
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.Build.0 = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

7
azure-pipelines.yml

@ -56,13 +56,6 @@ jobs:
xcodeVersion: '10' # Options: 8, 9, default, specifyPath xcodeVersion: '10' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2
displayName: 'Install CastXML'
inputs:
script: |
brew update
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Install Nuke'
inputs: inputs:

2
build/NetFX.props

@ -1,7 +1,7 @@
<Project> <Project>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -12,7 +12,9 @@ MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.Tree
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Controls.SelectionChangedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Controls.SelectionChangedEventArgs> Avalonia.Controls.TreeView.SelectionChangedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Controls.SelectionChangedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Controls.SelectionChangedEventArgs> Avalonia.Controls.TreeView.SelectionChangedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Controls.ISelectionModel Avalonia.Controls.TreeView.Selection.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Controls.ISelectionModel Avalonia.Controls.TreeView.Selection.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TreeView.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.TreeView.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args.get()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.Primitives.SelectingItemsControl, Avalonia.Controls.ISelectionModel> Avalonia.DirectProperty<Avalonia.Controls.Primitives.SelectingItemsControl, Avalonia.Controls.ISelectionModel> Avalonia.Controls.Primitives.SelectingItemsControl.SelectionProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.Primitives.SelectingItemsControl, Avalonia.Controls.ISelectionModel> Avalonia.DirectProperty<Avalonia.Controls.Primitives.SelectingItemsControl, Avalonia.Controls.ISelectionModel> Avalonia.Controls.Primitives.SelectingItemsControl.SelectionProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected Avalonia.Controls.ISelectionModel Avalonia.Controls.Primitives.SelectingItemsControl.Selection.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected Avalonia.Controls.ISelectionModel Avalonia.Controls.Primitives.SelectingItemsControl.Selection.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.SelectingItemsControl.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.SelectingItemsControl.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract.
Total Issues: 16 Total Issues: 18

14
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -47,6 +47,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
/// <summary>
/// Gets the arguments passed to the AppBuilder Start method.
/// </summary>
public string[] Args { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public ShutdownMode ShutdownMode { get; set; } public ShutdownMode ShutdownMode { get; set; }
@ -68,9 +73,6 @@ namespace Avalonia.Controls.ApplicationLifetimes
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow)
Shutdown(); Shutdown();
} }
public void Shutdown(int exitCode = 0) public void Shutdown(int exitCode = 0)
{ {
@ -123,7 +125,11 @@ namespace Avalonia
this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
where T : AppBuilderBase<T>, new() where T : AppBuilderBase<T>, new()
{ {
var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode}; var lifetime = new ClassicDesktopStyleApplicationLifetime()
{
Args = args,
ShutdownMode = shutdownMode
};
builder.SetupWithLifetime(lifetime); builder.SetupWithLifetime(lifetime);
return lifetime.Start(args); return lifetime.Start(args);
} }

7
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -8,6 +8,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// </summary> /// </summary>
public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime
{ {
/// <summary>
/// Gets the arguments passed to the
/// <see cref="ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime{T}(T, string[], ShutdownMode)"/>
/// method.
/// </summary>
string[] Args { get; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly. /// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly.
/// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called. /// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.

10
src/Avalonia.Controls/ColumnDefinitions.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text;
using Avalonia.Collections; using Avalonia.Collections;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -13,7 +14,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinitions"/> class. /// Initializes a new instance of the <see cref="ColumnDefinitions"/> class.
/// </summary> /// </summary>
public ColumnDefinitions() : base () public ColumnDefinitions()
{ {
} }
@ -27,6 +28,11 @@ namespace Avalonia.Controls
AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x))); AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
} }
public override string ToString()
{
return string.Join(",", this.Select(x => x.Width));
}
/// <summary> /// <summary>
/// Parses a string representation of column definitions collection. /// Parses a string representation of column definitions collection.
/// </summary> /// </summary>
@ -34,4 +40,4 @@ namespace Avalonia.Controls
/// <returns>The <see cref="ColumnDefinitions"/>.</returns> /// <returns>The <see cref="ColumnDefinitions"/>.</returns>
public static ColumnDefinitions Parse(string s) => new ColumnDefinitions(s); public static ColumnDefinitions Parse(string s) => new ColumnDefinitions(s);
} }
} }

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

@ -282,7 +282,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText return new FormattedText
{ {
Constraint = constraint, Constraint = constraint,
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize, FontSize = FontSize,
Text = text ?? string.Empty, Text = text ?? string.Empty,
TextAlignment = TextAlignment, TextAlignment = TextAlignment,
@ -499,7 +499,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText return new FormattedText
{ {
Text = "X", Text = "X",
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize, FontSize = FontSize,
TextAlignment = TextAlignment, TextAlignment = TextAlignment,
Constraint = availableSize, Constraint = availableSize,

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

@ -141,7 +141,7 @@ namespace Avalonia.Controls.Primitives
_ => throw new InvalidOperationException("Invalid value for ScrollBar.Visibility.") _ => throw new InvalidOperationException("Invalid value for ScrollBar.Visibility.")
}; };
SetValue(IsVisibleProperty, isVisible, BindingPriority.Style); SetValue(IsVisibleProperty, isVisible);
} }
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)

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

@ -7,10 +7,10 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -681,8 +681,15 @@ namespace Avalonia.Controls
if (oldValue != null) if (oldValue != null)
{ {
oldValue.UninitializeForContext(LayoutContext); oldValue.UninitializeForContext(LayoutContext);
oldValue.MeasureInvalidated -= InvalidateMeasureForLayout;
oldValue.ArrangeInvalidated -= InvalidateArrangeForLayout; WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
oldValue,
nameof(AttachedLayout.MeasureInvalidated),
InvalidateMeasureForLayout);
WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
oldValue,
nameof(AttachedLayout.ArrangeInvalidated),
InvalidateArrangeForLayout);
// Walk through all the elements and make sure they are cleared // Walk through all the elements and make sure they are cleared
foreach (var element in Children) foreach (var element in Children)
@ -699,8 +706,15 @@ namespace Avalonia.Controls
if (newValue != null) if (newValue != null)
{ {
newValue.InitializeForContext(LayoutContext); newValue.InitializeForContext(LayoutContext);
newValue.MeasureInvalidated += InvalidateMeasureForLayout;
newValue.ArrangeInvalidated += InvalidateArrangeForLayout; WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
newValue,
nameof(AttachedLayout.MeasureInvalidated),
InvalidateMeasureForLayout);
WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
newValue,
nameof(AttachedLayout.ArrangeInvalidated),
InvalidateArrangeForLayout);
} }
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;

2
src/Avalonia.Controls/TextBlock.cs

@ -452,7 +452,7 @@ namespace Avalonia.Controls
return new TextLayout( return new TextLayout(
text ?? string.Empty, text ?? string.Empty,
FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), new Typeface(FontFamily, FontStyle, FontWeight),
FontSize, FontSize,
Foreground, Foreground,
TextAlignment, TextAlignment,

6
src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs

@ -38,11 +38,13 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
public HtmlWebSocketTransport(IAvaloniaRemoteTransportConnection signalTransport, Uri listenUri) public HtmlWebSocketTransport(IAvaloniaRemoteTransportConnection signalTransport, Uri listenUri)
{ {
if (listenUri.Scheme != "http") if (listenUri.Scheme != "http")
throw new ArgumentException("listenUri"); throw new ArgumentException("URI scheme is not HTTP.", nameof(listenUri));
var resourcePrefix = "Avalonia.DesignerSupport.Remote.HtmlTransport.webapp.build."; var resourcePrefix = "Avalonia.DesignerSupport.Remote.HtmlTransport.webapp.build.";
_resources = typeof(HtmlWebSocketTransport).Assembly.GetManifestResourceNames() _resources = typeof(HtmlWebSocketTransport).Assembly.GetManifestResourceNames()
.Where(r => r.StartsWith(resourcePrefix) && r.EndsWith(".gz")).ToDictionary( .Where(r => r.StartsWith(resourcePrefix, StringComparison.OrdinalIgnoreCase)
&& r.EndsWith(".gz", StringComparison.OrdinalIgnoreCase))
.ToDictionary(
r => r.Substring(resourcePrefix.Length).Substring(0,r.Length-resourcePrefix.Length-3), r => r.Substring(resourcePrefix.Length).Substring(0,r.Length-resourcePrefix.Length-3),
r => r =>
{ {

51
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -8,8 +8,8 @@ namespace Avalonia.Diagnostics.ViewModels
internal abstract class PropertyViewModel : ViewModelBase internal abstract class PropertyViewModel : ViewModelBase
{ {
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
private static readonly Type[] StringParameter = new[] { typeof(string) }; private static readonly Type[] StringParameter = { typeof(string) };
private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) }; private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) };
public abstract object Key { get; } public abstract object Key { get; }
public abstract string Name { get; } public abstract string Name { get; }
@ -26,35 +26,46 @@ namespace Avalonia.Diagnostics.ViewModels
} }
var converter = TypeDescriptor.GetConverter(value); var converter = TypeDescriptor.GetConverter(value);
return converter?.ConvertToString(value) ?? value.ToString();
//CollectionConverter does not deliver any important information. It just displays "(Collection)".
if (!converter.CanConvertTo(typeof(string)) ||
converter.GetType() == typeof(CollectionConverter))
{
return value.ToString();
}
return converter.ConvertToString(value);
} }
protected static object ConvertFromString(string s, Type targetType) private static object InvokeParse(string s, Type targetType)
{ {
var converter = TypeDescriptor.GetConverter(targetType); var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
if (converter != null && converter.CanConvertFrom(typeof(string))) if (method != null)
{ {
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture });
} }
else
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null);
if (method != null)
{ {
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); return method.Invoke(null, new object[] { s });
}
if (method != null) throw new InvalidCastException("Unable to convert value.");
{ }
return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture });
}
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); protected static object ConvertFromString(string s, Type targetType)
{
var converter = TypeDescriptor.GetConverter(targetType);
if (method != null) if (converter.CanConvertFrom(typeof(string)))
{ {
return method.Invoke(null, new object[] { s }); return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
}
} }
throw new InvalidCastException("Unable to convert value."); return InvokeParse(s, targetType);
} }
} }
} }

4
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -155,9 +155,9 @@ namespace Avalonia.Headless
return new List<string> { "Arial" }; return new List<string> { "Arial" };
} }
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{ {
fontKey = new FontKey("Arial", fontStyle, fontWeight); typeface = new Typeface("Arial", fontStyle, fontWeight);
return true; return true;
} }
} }

22
src/Avalonia.Input/AccessKeyHandler.cs

@ -28,7 +28,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot _owner; private IInputRoot? _owner;
/// <summary> /// <summary>
/// Whether access keys are currently being shown; /// Whether access keys are currently being shown;
@ -48,17 +48,17 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Element to restore following AltKey taking focus. /// Element to restore following AltKey taking focus.
/// </summary> /// </summary>
private IInputElement _restoreFocusElement; private IInputElement? _restoreFocusElement;
/// <summary> /// <summary>
/// The window's main menu. /// The window's main menu.
/// </summary> /// </summary>
private IMainMenu _mainMenu; private IMainMenu? _mainMenu;
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
public IMainMenu MainMenu public IMainMenu? MainMenu
{ {
get => _mainMenu; get => _mainMenu;
set set
@ -86,14 +86,12 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetOwner(IInputRoot owner) public void SetOwner(IInputRoot owner)
{ {
Contract.Requires<ArgumentNullException>(owner != null);
if (_owner != null) if (_owner != null)
{ {
throw new InvalidOperationException("AccessKeyHandler owner has already been set."); throw new InvalidOperationException("AccessKeyHandler owner has already been set.");
} }
_owner = owner; _owner = owner ?? throw new ArgumentNullException(nameof(owner));
_owner.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel); _owner.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
_owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Bubble); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Bubble);
@ -149,7 +147,7 @@ namespace Avalonia.Input
// When Alt is pressed without a main menu, or with a closed main menu, show // When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File"). // access key markers in the window (i.e. "_File").
_owner.ShowAccessKeys = _showingAccessKeys = true; _owner!.ShowAccessKeys = _showingAccessKeys = true;
} }
else else
{ {
@ -241,7 +239,7 @@ namespace Avalonia.Input
{ {
if (_showingAccessKeys) if (_showingAccessKeys)
{ {
_owner.ShowAccessKeys = false; _owner!.ShowAccessKeys = false;
} }
} }
@ -250,13 +248,13 @@ namespace Avalonia.Input
/// </summary> /// </summary>
private void CloseMenu() private void CloseMenu()
{ {
MainMenu.Close(); MainMenu!.Close();
_owner.ShowAccessKeys = _showingAccessKeys = false; _owner!.ShowAccessKeys = _showingAccessKeys = false;
} }
private void MainMenuClosed(object sender, EventArgs e) private void MainMenuClosed(object sender, EventArgs e)
{ {
_owner.ShowAccessKeys = false; _owner!.ShowAccessKeys = false;
} }
} }
} }

2
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

6
src/Avalonia.Input/DataObject.cs

@ -11,7 +11,7 @@ namespace Avalonia.Input
return _items.ContainsKey(dataFormat); return _items.ContainsKey(dataFormat);
} }
public object Get(string dataFormat) public object? Get(string dataFormat)
{ {
if (_items.ContainsKey(dataFormat)) if (_items.ContainsKey(dataFormat))
return _items[dataFormat]; return _items[dataFormat];
@ -23,12 +23,12 @@ namespace Avalonia.Input
return _items.Keys; return _items.Keys;
} }
public IEnumerable<string> GetFileNames() public IEnumerable<string>? GetFileNames()
{ {
return Get(DataFormats.FileNames) as IEnumerable<string>; return Get(DataFormats.FileNames) as IEnumerable<string>;
} }
public string GetText() public string? GetText()
{ {
return Get(DataFormats.Text) as string; return Get(DataFormats.Text) as string;
} }

6
src/Avalonia.Input/DragDropDevice.cs

@ -9,9 +9,9 @@ namespace Avalonia.Input
{ {
public static readonly DragDropDevice Instance = new DragDropDevice(); public static readonly DragDropDevice Instance = new DragDropDevice();
private Interactive _lastTarget = null; private Interactive? _lastTarget = null;
private Interactive GetTarget(IInputRoot root, Point local) private Interactive? GetTarget(IInputRoot root, Point local)
{ {
var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault(); var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target)) if (target != null && DragDrop.GetAllowDrop(target))
@ -19,7 +19,7 @@ namespace Avalonia.Input
return null; return null;
} }
private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) private DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers)
{ {
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;

47
src/Avalonia.Input/FocusManager.cs

@ -15,8 +15,8 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The focus scopes in which the focus is currently defined. /// The focus scopes in which the focus is currently defined.
/// </summary> /// </summary>
private readonly ConditionalWeakTable<IFocusScope, IInputElement> _focusScopes = private readonly ConditionalWeakTable<IFocusScope, IInputElement?> _focusScopes =
new ConditionalWeakTable<IFocusScope, IInputElement>(); new ConditionalWeakTable<IFocusScope, IInputElement?>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class. /// Initializes a new instance of the <see cref="FocusManager"/> class.
@ -37,12 +37,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
public IInputElement Current => KeyboardDevice.Instance?.FocusedElement; public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
/// </summary> /// </summary>
public IFocusScope Scope public IFocusScope? Scope
{ {
get; get;
private set; private set;
@ -55,7 +55,7 @@ namespace Avalonia.Input
/// <param name="method">The method by which focus was changed.</param> /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Focus( public void Focus(
IInputElement control, IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
@ -75,17 +75,18 @@ namespace Avalonia.Input
// If control is null, set focus to the topmost focus scope. // If control is null, set focus to the topmost focus scope.
foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList()) foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList())
{ {
IInputElement element; if (_focusScopes.TryGetValue(scope, out var element) && element != null)
if (_focusScopes.TryGetValue(scope, out element) && element != null)
{ {
Focus(element, method); Focus(element, method);
return; return;
} }
} }
// Couldn't find a focus scope, clear focus. if (Scope is object)
SetFocusedElement(Scope, null); {
// Couldn't find a focus scope, clear focus.
SetFocusedElement(Scope, null);
}
} }
} }
@ -102,13 +103,13 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetFocusedElement( public void SetFocusedElement(
IFocusScope scope, IFocusScope scope,
IInputElement element, IInputElement? element,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
Contract.Requires<ArgumentNullException>(scope != null); scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (_focusScopes.TryGetValue(scope, out IInputElement existingElement)) if (_focusScopes.TryGetValue(scope, out var existingElement))
{ {
if (element != existingElement) if (element != existingElement)
{ {
@ -133,11 +134,9 @@ namespace Avalonia.Input
/// <param name="scope">The new focus scope.</param> /// <param name="scope">The new focus scope.</param>
public void SetFocusScope(IFocusScope scope) public void SetFocusScope(IFocusScope scope)
{ {
Contract.Requires<ArgumentNullException>(scope != null); scope = scope ?? throw new ArgumentNullException(nameof(scope));
IInputElement e; if (!_focusScopes.TryGetValue(scope, out var e))
if (!_focusScopes.TryGetValue(scope, out e))
{ {
// TODO: Make this do something useful, i.e. select the first focusable // TODO: Make this do something useful, i.e. select the first focusable
// control, select a control that the user has specified to have default // control, select a control that the user has specified to have default
@ -164,17 +163,19 @@ namespace Avalonia.Input
/// <returns>The focus scopes.</returns> /// <returns>The focus scopes.</returns>
private static IEnumerable<IFocusScope> GetFocusScopeAncestors(IInputElement control) private static IEnumerable<IFocusScope> GetFocusScopeAncestors(IInputElement control)
{ {
while (control != null) IInputElement? c = control;
while (c != null)
{ {
var scope = control as IFocusScope; var scope = c as IFocusScope;
if (scope != null && control.VisualRoot?.IsVisible == true) if (scope != null && c.VisualRoot?.IsVisible == true)
{ {
yield return scope; yield return scope;
} }
control = control.GetVisualParent<IInputElement>() ?? c = c.GetVisualParent<IInputElement>() ??
((control as IHostedVisualTreeRoot)?.Host as IInputElement); ((c as IHostedVisualTreeRoot)?.Host as IInputElement);
} }
} }
@ -190,7 +191,7 @@ namespace Avalonia.Input
if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement; IVisual? element = ev.Pointer?.Captured ?? e.Source as IInputElement;
while (element != null) while (element != null)
{ {

16
src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -1,8 +1,6 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
@ -11,8 +9,8 @@ namespace Avalonia.Input.GestureRecognizers
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher
{ {
private readonly IInputElement _inputElement; private readonly IInputElement _inputElement;
private List<IGestureRecognizer> _recognizers; private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer> _pointerGrabs; private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
public GestureRecognizerCollection(IInputElement inputElement) public GestureRecognizerCollection(IInputElement inputElement)
@ -72,7 +70,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{ {
capture.PointerReleased(e); capture.PointerReleased(e);
} }
@ -90,7 +88,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{ {
capture.PointerMoved(e); capture.PointerMoved(e);
} }
@ -108,7 +106,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return; return;
_pointerGrabs.Remove(e.Pointer); _pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
r.PointerCaptureLost(e.Pointer); r.PointerCaptureLost(e.Pointer);
@ -118,8 +116,8 @@ namespace Avalonia.Input.GestureRecognizers
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
{ {
pointer.Capture(_inputElement); pointer.Capture(_inputElement);
_pointerGrabs[pointer] = recognizer; _pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers) foreach (var r in _recognizers!)
{ {
if (r != recognizer) if (r != recognizer)
r.PointerCaptureLost(pointer); r.PointerCaptureLost(pointer);

15
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers namespace Avalonia.Input.GestureRecognizers
@ -11,9 +10,9 @@ namespace Avalonia.Input.GestureRecognizers
{ {
private bool _scrolling; private bool _scrolling;
private Point _trackedRootPoint; private Point _trackedRootPoint;
private IPointer _tracking; private IPointer? _tracking;
private IInputElement _target; private IInputElement? _target;
private IGestureRecognizerActionsDispatcher _actions; private IGestureRecognizerActionsDispatcher? _actions;
private bool _canHorizontallyScroll; private bool _canHorizontallyScroll;
private bool _canVerticallyScroll; private bool _canVerticallyScroll;
private int _gestureId; private int _gestureId;
@ -95,7 +94,7 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true; _scrolling = true;
if (_scrolling) if (_scrolling)
{ {
_actions.Capture(e.Pointer, this); _actions!.Capture(e.Pointer, this);
} }
} }
@ -110,7 +109,7 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint = rootPoint; _trackedRootPoint = rootPoint;
if (elapsed.TotalSeconds > 0) if (elapsed.TotalSeconds > 0)
_inertia = vector / elapsed.TotalSeconds; _inertia = vector / elapsed.TotalSeconds;
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true; e.Handled = true;
} }
} }
@ -128,7 +127,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
_inertia = default; _inertia = default;
_scrolling = false; _scrolling = false;
_target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0; _gestureId = 0;
_lastMoveTimestamp = null; _lastMoveTimestamp = null;
} }
@ -165,7 +164,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds; var distance = speed * elapsedSinceLastTick.TotalSeconds;
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));

9
src/Avalonia.Input/Gestures.cs

@ -29,7 +29,9 @@ namespace Avalonia.Input
RoutedEvent.Register<ScrollGestureEventArgs>( RoutedEvent.Register<ScrollGestureEventArgs>(
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null); private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
static Gestures() static Gestures()
{ {
@ -69,6 +71,11 @@ namespace Avalonia.Input
private static void PointerPressed(RoutedEventArgs ev) private static void PointerPressed(RoutedEventArgs ev)
{ {
if (ev.Source is null)
{
return;
}
if (ev.Route == RoutingStrategies.Bubble) if (ev.Route == RoutingStrategies.Bubble)
{ {
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
@ -76,7 +83,7 @@ namespace Avalonia.Input
if (e.ClickCount <= 1) if (e.ClickCount <= 1)
{ {
s_lastPress = new WeakReference<IInteractive>(e.Source); s_lastPress = new WeakReference<IInteractive>(ev.Source);
} }
else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {

2
src/Avalonia.Input/IAccessKeyHandler.cs

@ -8,7 +8,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
IMainMenu MainMenu { get; set; } IMainMenu? MainMenu { get; set; }
/// <summary> /// <summary>
/// Sets the owner of the access key handler. /// Sets the owner of the access key handler.

6
src/Avalonia.Input/IDataObject.cs

@ -23,17 +23,17 @@ namespace Avalonia.Input
/// Returns the dragged text if the DataObject contains any text. /// Returns the dragged text if the DataObject contains any text.
/// <seealso cref="DataFormats.Text"/> /// <seealso cref="DataFormats.Text"/>
/// </summary> /// </summary>
string GetText(); string? GetText();
/// <summary> /// <summary>
/// Returns a list of filenames if the DataObject contains filenames. /// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/> /// <seealso cref="DataFormats.FileNames"/>
/// </summary> /// </summary>
IEnumerable<string> GetFileNames(); IEnumerable<string>? GetFileNames();
/// <summary> /// <summary>
/// Tries to get the data of the given DataFormat. /// Tries to get the data of the given DataFormat.
/// </summary> /// </summary>
object Get(string dataFormat); object? Get(string dataFormat);
} }
} }

6
src/Avalonia.Input/IFocusManager.cs

@ -8,12 +8,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
IInputElement Current { get; } IInputElement? Current { get; }
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
/// </summary> /// </summary>
IFocusScope Scope { get; } IFocusScope? Scope { get; }
/// <summary> /// <summary>
/// Focuses a control. /// Focuses a control.
@ -22,7 +22,7 @@ namespace Avalonia.Input
/// <param name="method">The method by which focus was changed.</param> /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Focus( void Focus(
IInputElement control, IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None); KeyModifiers keyModifiers = KeyModifiers.None);

2
src/Avalonia.Input/IInputElement.cs

@ -78,7 +78,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the associated mouse cursor. /// Gets or sets the associated mouse cursor.
/// </summary> /// </summary>
Cursor Cursor { get; } Cursor? Cursor { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this control and all its parents are enabled. /// Gets a value indicating whether this control and all its parents are enabled.

4
src/Avalonia.Input/IInputRoot.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the input element that the pointer is currently over. /// Gets or sets the input element that the pointer is currently over.
/// </summary> /// </summary>
IInputElement PointerOverElement { get; set; } IInputElement? PointerOverElement { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
@ -31,6 +31,6 @@ namespace Avalonia.Input
/// Gets associated mouse device /// Gets associated mouse device
/// </summary> /// </summary>
[CanBeNull] [CanBeNull]
IMouseDevice MouseDevice { get; } IMouseDevice? MouseDevice { get; }
} }
} }

4
src/Avalonia.Input/IKeyboardDevice.cs

@ -58,10 +58,10 @@ namespace Avalonia.Input
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{ {
IInputElement FocusedElement { get; } IInputElement? FocusedElement { get; }
void SetFocusedElement( void SetFocusedElement(
IInputElement element, IInputElement? element,
NavigationMethod method, NavigationMethod method,
KeyModifiers modifiers); KeyModifiers modifiers);
} }

4
src/Avalonia.Input/IPointer.cs

@ -3,8 +3,8 @@ namespace Avalonia.Input
public interface IPointer public interface IPointer
{ {
int Id { get; } int Id { get; }
void Capture(IInputElement control); void Capture(IInputElement? control);
IInputElement Captured { get; } IInputElement? Captured { get; }
PointerType Type { get; } PointerType Type { get; }
bool IsPrimary { get; } bool IsPrimary { get; }

4
src/Avalonia.Input/IPointerDevice.cs

@ -6,10 +6,10 @@ namespace Avalonia.Input
public interface IPointerDevice : IInputDevice public interface IPointerDevice : IInputDevice
{ {
[Obsolete("Use IPointer")] [Obsolete("Use IPointer")]
IInputElement Captured { get; } IInputElement? Captured { get; }
[Obsolete("Use IPointer")] [Obsolete("Use IPointer")]
void Capture(IInputElement control); void Capture(IInputElement? control);
[Obsolete("Use PointerEventArgs.GetPosition")] [Obsolete("Use PointerEventArgs.GetPosition")]
Point GetPosition(IVisual relativeTo); Point GetPosition(IVisual relativeTo);

8
src/Avalonia.Input/InputElement.cs

@ -37,8 +37,8 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
/// </summary> /// </summary>
public static readonly StyledProperty<Cursor> CursorProperty = public static readonly StyledProperty<Cursor?> CursorProperty =
AvaloniaProperty.Register<InputElement, Cursor>(nameof(Cursor), null, true); AvaloniaProperty.Register<InputElement, Cursor?>(nameof(Cursor), null, true);
/// <summary> /// <summary>
/// Defines the <see cref="IsFocused"/> property. /// Defines the <see cref="IsFocused"/> property.
@ -160,7 +160,7 @@ namespace Avalonia.Input
private bool _isFocused; private bool _isFocused;
private bool _isFocusVisible; private bool _isFocusVisible;
private bool _isPointerOver; private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers; private GestureRecognizerCollection? _gestureRecognizers;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="InputElement"/> class. /// Initializes static members of the <see cref="InputElement"/> class.
@ -336,7 +336,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
/// </summary> /// </summary>
public Cursor Cursor public Cursor? Cursor
{ {
get { return GetValue(CursorProperty); } get { return GetValue(CursorProperty); }
set { SetValue(CursorProperty, value); } set { SetValue(CursorProperty, value); }

2
src/Avalonia.Input/KeyEventArgs.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
{ {
public class KeyEventArgs : RoutedEventArgs public class KeyEventArgs : RoutedEventArgs
{ {
public IKeyboardDevice Device { get; set; } public IKeyboardDevice? Device { get; set; }
public Key Key { get; set; } public Key Key { get; set; }

8
src/Avalonia.Input/KeyboardDevice.cs

@ -8,9 +8,9 @@ namespace Avalonia.Input
{ {
public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged
{ {
private IInputElement _focusedElement; private IInputElement? _focusedElement;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>(); public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -18,7 +18,7 @@ namespace Avalonia.Input
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>(); public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IInputElement FocusedElement public IInputElement? FocusedElement
{ {
get get
{ {
@ -33,7 +33,7 @@ namespace Avalonia.Input
} }
public void SetFocusedElement( public void SetFocusedElement(
IInputElement element, IInputElement? element,
NavigationMethod method, NavigationMethod method,
KeyModifiers keyModifiers) KeyModifiers keyModifiers)
{ {

13
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot _owner; private IInputRoot? _owner;
/// <summary> /// <summary>
/// Sets the owner of the keyboard navigation handler. /// Sets the owner of the keyboard navigation handler.
@ -24,15 +24,12 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetOwner(IInputRoot owner) public void SetOwner(IInputRoot owner)
{ {
Contract.Requires<ArgumentNullException>(owner != null);
if (_owner != null) if (_owner != null)
{ {
throw new InvalidOperationException("AccessKeyHandler owner has already been set."); throw new InvalidOperationException("AccessKeyHandler owner has already been set.");
} }
_owner = owner; _owner = owner ?? throw new ArgumentNullException(nameof(owner));
_owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown);
} }
@ -45,11 +42,11 @@ namespace Avalonia.Input
/// The next element in the specified direction, or null if <paramref name="element"/> /// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction. /// was the last in the requested direction.
/// </returns> /// </returns>
public static IInputElement GetNext( public static IInputElement? GetNext(
IInputElement element, IInputElement element,
NavigationDirection direction) NavigationDirection direction)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
var customHandler = element.GetSelfAndVisualAncestors() var customHandler = element.GetSelfAndVisualAncestors()
.OfType<ICustomKeyboardNavigation>() .OfType<ICustomKeyboardNavigation>()
@ -97,7 +94,7 @@ namespace Avalonia.Input
NavigationDirection direction, NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
var next = GetNext(element, direction); var next = GetNext(element, direction);

73
src/Avalonia.Input/MouseDevice.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input
private readonly Pointer _pointer; private readonly Pointer _pointer;
private bool _disposed; private bool _disposed;
public MouseDevice(Pointer pointer = null) public MouseDevice(Pointer? pointer = null)
{ {
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
} }
@ -34,7 +34,7 @@ namespace Avalonia.Input
/// <see cref="Capture"/> method. /// <see cref="Capture"/> method.
/// </remarks> /// </remarks>
[Obsolete("Use IPointer instead")] [Obsolete("Use IPointer instead")]
public IInputElement Captured => _pointer.Captured; public IInputElement? Captured => _pointer.Captured;
/// <summary> /// <summary>
/// Gets the mouse position, in screen coordinates. /// Gets the mouse position, in screen coordinates.
@ -54,7 +54,7 @@ namespace Avalonia.Input
/// within the control's bounds or not. The current mouse capture control is exposed /// within the control's bounds or not. The current mouse capture control is exposed
/// by the <see cref="Captured"/> property. /// by the <see cref="Captured"/> property.
/// </remarks> /// </remarks>
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
_pointer.Capture(control); _pointer.Capture(control);
} }
@ -66,7 +66,7 @@ namespace Avalonia.Input
/// <returns>The mouse position in the control's coordinates.</returns> /// <returns>The mouse position in the control's coordinates.</returns>
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual relativeTo)
{ {
Contract.Requires<ArgumentNullException>(relativeTo != null); relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
if (relativeTo.VisualRoot == null) if (relativeTo.VisualRoot == null)
{ {
@ -75,7 +75,7 @@ namespace Avalonia.Input
var rootPoint = relativeTo.VisualRoot.PointToClient(Position); var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo); var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform.Value; return rootPoint * transform!.Value;
} }
public void ProcessRawEvent(RawInputEventArgs e) public void ProcessRawEvent(RawInputEventArgs e)
@ -126,7 +126,7 @@ namespace Avalonia.Input
private void ProcessRawEvent(RawPointerEventArgs e) private void ProcessRawEvent(RawPointerEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
var mouse = (MouseDevice)e.Device; var mouse = (MouseDevice)e.Device;
if(mouse._disposed) if(mouse._disposed)
@ -173,8 +173,8 @@ namespace Avalonia.Input
private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties, private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
ClearPointerOver(this, timestamp, root, properties, inputModifiers); ClearPointerOver(this, timestamp, root, properties, inputModifiers);
} }
@ -214,8 +214,8 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -250,10 +250,10 @@ namespace Avalonia.Input
private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
IInputElement source; IInputElement? source;
if (_pointer.Captured == null) if (_pointer.Captured == null)
{ {
@ -265,18 +265,23 @@ namespace Avalonia.Input
source = _pointer.Captured; source = _pointer.Captured;
} }
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, if (source is object)
p, timestamp, properties, inputModifiers); {
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
p, timestamp, properties, inputModifiers);
source?.RaiseEvent(e); source.RaiseEvent(e);
return e.Handled; return e.Handled;
}
return false;
} }
private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props, private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -298,8 +303,8 @@ namespace Avalonia.Input
PointerPointProperties props, PointerPointProperties props,
Vector delta, KeyModifiers inputModifiers) Vector delta, KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -317,21 +322,21 @@ namespace Avalonia.Input
private IInteractive GetSource(IVisual hit) private IInteractive GetSource(IVisual hit)
{ {
Contract.Requires<ArgumentNullException>(hit != null); hit = hit ?? throw new ArgumentNullException(nameof(hit));
return _pointer.Captured ?? return _pointer.Captured ??
(hit as IInteractive) ?? (hit as IInteractive) ??
hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault(); hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault();
} }
private IInputElement HitTest(IInputElement root, Point p) private IInputElement? HitTest(IInputElement root, Point p)
{ {
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
return _pointer.Captured ?? root.InputHitTest(p); return _pointer.Captured ?? root.InputHitTest(p);
} }
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive source, PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
@ -343,8 +348,8 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.PointerOverElement; var element = root.PointerOverElement;
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers); var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers);
@ -384,12 +389,12 @@ namespace Avalonia.Input
} }
} }
private IInputElement SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p, private IInputElement? SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.InputHitTest(p); var element = root.InputHitTest(p);
@ -412,11 +417,11 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
IInputElement branch = null; IInputElement? branch = null;
var el = element; var el = element;

42
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -22,15 +22,17 @@ namespace Avalonia.Input.Navigation
/// The next element in the specified direction, or null if <paramref name="element"/> /// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction. /// was the last in the requested direction.
/// </returns> /// </returns>
public static IInputElement GetNextInTabOrder( public static IInputElement? GetNextInTabOrder(
IInputElement element, IInputElement element,
NavigationDirection direction, NavigationDirection direction,
bool outsideElement = false) bool outsideElement = false)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
Contract.Requires<ArgumentException>(
direction == NavigationDirection.Next || if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous)
direction == NavigationDirection.Previous); {
throw new ArgumentException("Invalid direction: must be Next or Previous.");
}
var container = element.GetVisualParent<IInputElement>(); var container = element.GetVisualParent<IInputElement>();
@ -110,7 +112,7 @@ namespace Avalonia.Input.Navigation
if (customNext.handled) if (customNext.handled)
{ {
yield return customNext.next; yield return customNext.next!;
} }
else else
{ {
@ -143,12 +145,14 @@ namespace Avalonia.Input.Navigation
/// If true will not descend into <paramref name="element"/> to find next control. /// If true will not descend into <paramref name="element"/> to find next control.
/// </param> /// </param>
/// <returns>The next element, or null if the element is the last.</returns> /// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement GetNextInContainer( private static IInputElement? GetNextInContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction, NavigationDirection direction,
bool outsideElement) bool outsideElement)
{ {
IInputElement? e = element;
if (direction == NavigationDirection.Next && !outsideElement) if (direction == NavigationDirection.Next && !outsideElement)
{ {
var descendant = GetFocusableDescendants(element, direction).FirstOrDefault(); var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
@ -167,13 +171,13 @@ namespace Avalonia.Input.Navigation
// INavigableContainer. // INavigableContainer.
if (navigable != null) if (navigable != null)
{ {
while (element != null) while (e != null)
{ {
element = navigable.GetControl(direction, element, false); e = navigable.GetControl(direction, e, false);
if (element != null && if (e != null &&
element.CanFocus() && e.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) element)) KeyboardNavigation.GetIsTabStop((InputElement)e))
{ {
break; break;
} }
@ -183,12 +187,12 @@ namespace Avalonia.Input.Navigation
{ {
// TODO: Do a spatial search here if the container doesn't implement // TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer. // INavigableContainer.
element = null; e = null;
} }
if (element != null && direction == NavigationDirection.Previous) if (e != null && direction == NavigationDirection.Previous)
{ {
var descendant = GetFocusableDescendants(element, direction).LastOrDefault(); var descendant = GetFocusableDescendants(e, direction).LastOrDefault();
if (descendant != null) if (descendant != null)
{ {
@ -196,7 +200,7 @@ namespace Avalonia.Input.Navigation
} }
} }
return element; return e;
} }
return null; return null;
@ -209,13 +213,13 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The container.</param> /// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param> /// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement? GetFirstInNextContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction) NavigationDirection direction)
{ {
var parent = container.GetVisualParent<IInputElement>(); var parent = container.GetVisualParent<IInputElement>();
IInputElement next = null; IInputElement? next = null;
if (parent != null) if (parent != null)
{ {
@ -268,7 +272,7 @@ namespace Avalonia.Input.Navigation
return next; return next;
} }
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element,
NavigationDirection direction) NavigationDirection direction)
{ {
if (element is ICustomKeyboardNavigation custom) if (element is ICustomKeyboardNavigation custom)

9
src/Avalonia.Input/Pointer.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Interactivity;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
@ -20,7 +19,7 @@ namespace Avalonia.Input
public int Id { get; } public int Id { get; }
IInputElement FindCommonParent(IInputElement control1, IInputElement control2) IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2)
{ {
if (control1 == null || control2 == null) if (control1 == null || control2 == null)
return null; return null;
@ -28,12 +27,12 @@ namespace Avalonia.Input
return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains); return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains);
} }
protected virtual void PlatformCapture(IInputElement element) protected virtual void PlatformCapture(IInputElement? element)
{ {
} }
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
if (Captured != null) if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached; Captured.DetachedFromVisualTree -= OnCaptureDetached;
@ -66,7 +65,7 @@ namespace Avalonia.Input
} }
public IInputElement Captured { get; private set; } public IInputElement? Captured { get; private set; }
public PointerType Type { get; } public PointerType Type { get; }
public bool IsPrimary { get; } public bool IsPrimary { get; }

14
src/Avalonia.Input/PointerEventArgs.cs

@ -7,14 +7,14 @@ namespace Avalonia.Input
{ {
public class PointerEventArgs : RoutedEventArgs public class PointerEventArgs : RoutedEventArgs
{ {
private readonly IVisual _rootVisual; private readonly IVisual? _rootVisual;
private readonly Point _rootVisualPosition; private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties; private readonly PointerPointProperties _properties;
public PointerEventArgs(RoutedEvent routedEvent, public PointerEventArgs(RoutedEvent routedEvent,
IInteractive source, IInteractive? source,
IPointer pointer, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, IVisual? rootVisual, Point rootVisualPosition,
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers) KeyModifiers modifiers)
@ -40,8 +40,8 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException(); public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement Captured => _ev.Pointer.Captured; public IInputElement? Captured => _ev.Pointer.Captured;
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
_ev.Pointer.Capture(control); _ev.Pointer.Capture(control);
} }
@ -52,7 +52,7 @@ namespace Avalonia.Input
public IPointer Pointer { get; } public IPointer Pointer { get; }
public ulong Timestamp { get; } public ulong Timestamp { get; }
private IPointerDevice _device; private IPointerDevice? _device;
[Obsolete("Use Pointer to get pointer-specific information")] [Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this)); public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
@ -76,7 +76,7 @@ namespace Avalonia.Input
public KeyModifiers KeyModifiers { get; } public KeyModifiers KeyModifiers { get; }
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual? relativeTo)
{ {
if (_rootVisual == null) if (_rootVisual == null)
return default; return default;

2
src/Avalonia.Input/Raw/RawDragEventType.cs

@ -7,4 +7,4 @@
DragLeave, DragLeave,
Drop Drop
} }
} }

2
src/Avalonia.Input/Raw/RawInputEventArgs.cs

@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw
/// <param name="root">The root from which the event originates.</param> /// <param name="root">The root from which the event originates.</param>
public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Device = device; Device = device;
Timestamp = timestamp; Timestamp = timestamp;

4
src/Avalonia.Input/TextInputEventArgs.cs

@ -4,8 +4,8 @@ namespace Avalonia.Input
{ {
public class TextInputEventArgs : RoutedEventArgs public class TextInputEventArgs : RoutedEventArgs
{ {
public IKeyboardDevice Device { get; set; } public IKeyboardDevice? Device { get; set; }
public string Text { get; set; } public string? Text { get; set; }
} }
} }

4
src/Avalonia.Layout/Layoutable.cs

@ -758,8 +758,6 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTreeCore(e);
if (e.Root is ILayoutRoot r) if (e.Root is ILayoutRoot r)
{ {
if (_layoutUpdated is object) if (_layoutUpdated is object)
@ -772,6 +770,8 @@ namespace Avalonia.Layout
r.LayoutManager.UnregisterEffectiveViewportListener(this); r.LayoutManager.UnregisterEffectiveViewportListener(this);
} }
} }
base.OnDetachedFromVisualTreeCore(e);
} }
/// <summary> /// <summary>

36
src/Avalonia.OpenGL/Angle/AngleEglInterface.cs

@ -0,0 +1,36 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL.Angle
{
public class AngleEglInterface : EglInterface
{
[DllImport("libegl.dll", CharSet = CharSet.Ansi)]
static extern IntPtr eglGetProcAddress(string proc);
public AngleEglInterface() : base(LoadAngle())
{
}
static Func<string, IntPtr> LoadAngle()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var disp = eglGetProcAddress("eglGetPlatformDisplayEXT");
if (disp == IntPtr.Zero)
{
throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point");
}
return eglGetProcAddress;
}
throw new PlatformNotSupportedException();
}
}
}

88
src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL.Angle
{
public class AngleWin32EglDisplay : EglDisplay
{
struct AngleInfo
{
public IntPtr Display { get; set; }
public AngleOptions.PlatformApi PlatformApi { get; set; }
}
static AngleInfo CreateAngleDisplay(EglInterface _egl)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException();
var display = IntPtr.Zero;
AngleOptions.PlatformApi angleApi = default;
{
if (_egl.GetPlatformDisplayEXT == null)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
?? new [] { AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9 };
foreach (var platformApi in allowedApis)
{
int dapi;
if (platformApi == AngleOptions.PlatformApi.DirectX9)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
else if (platformApi == AngleOptions.PlatformApi.DirectX11)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
else
continue;
display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
if (display != IntPtr.Zero)
{
angleApi = platformApi;
break;
}
}
if (display == IntPtr.Zero)
throw new OpenGlException("Unable to create ANGLE display");
return new AngleInfo { Display = display, PlatformApi = angleApi };
}
}
private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, info.Display)
{
PlatformApi = info.PlatformApi;
}
public AngleWin32EglDisplay(EglInterface egl) : this(egl, CreateAngleDisplay(egl))
{
}
public AngleWin32EglDisplay() : this(new AngleEglInterface())
{
}
public AngleOptions.PlatformApi PlatformApi { get; }
public IntPtr GetDirect3DDevice()
{
if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice))
throw new OpenGlException("Unable to get EGL_DEVICE_EXT");
if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle))
throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE");
return d3dDeviceHandle;
}
public EglSurface WrapDirect3D11Texture(IntPtr handle)
{
if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
throw new InvalidOperationException("Current platform API is " + PlatformApi);
return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE });
}
}
}

5
src/Avalonia.OpenGL/AngleOptions.cs

@ -10,9 +10,6 @@ namespace Avalonia.OpenGL
DirectX11 DirectX11
} }
public List<PlatformApi> AllowedPlatformApis = new List<PlatformApi> public IList<PlatformApi> AllowedPlatformApis { get; set; } = null;
{
PlatformApi.DirectX9
};
} }
} }

13
src/Avalonia.OpenGL/EglConsts.cs

@ -186,11 +186,24 @@ namespace Avalonia.OpenGL
public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206; public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E;
//EGL_ANGLE_platform_angle_d3d //EGL_ANGLE_platform_angle_d3d
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209;
public const int EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F; public const int EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C;
//EXT_device_query
public const int EGL_DEVICE_EXT = 0x322C;
//ANGLE_device_d3d
public const int EGL_D3D9_DEVICE_ANGLE = 0x33A0;
public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1;
public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3;
} }
} }

76
src/Avalonia.OpenGL/EglDisplay.cs

@ -15,7 +15,6 @@ namespace Avalonia.OpenGL
private readonly int _surfaceType; private readonly int _surfaceType;
public IntPtr Handle => _display; public IntPtr Handle => _display;
private AngleOptions.PlatformApi? _angleApi;
private int _sampleCount; private int _sampleCount;
private int _stencilSize; private int _stencilSize;
private GlVersion _version; private GlVersion _version;
@ -24,56 +23,41 @@ namespace Avalonia.OpenGL
{ {
} }
public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
{
_egl = egl;
static IntPtr CreateDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
{
var display = IntPtr.Zero;
if (platformType == -1 && platformDisplay == IntPtr.Zero) if (platformType == -1 && platformDisplay == IntPtr.Zero)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (display == IntPtr.Zero)
{ display = egl.GetDisplay(IntPtr.Zero);
if (_egl.GetPlatformDisplayEXT == null)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
?? new List<AngleOptions.PlatformApi> {AngleOptions.PlatformApi.DirectX9};
foreach (var platformApi in allowedApis)
{
int dapi;
if (platformApi == AngleOptions.PlatformApi.DirectX9)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
else if (platformApi == AngleOptions.PlatformApi.DirectX11)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
else
continue;
_display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
new[] {EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE});
if (_display != IntPtr.Zero)
{
_angleApi = platformApi;
break;
}
}
if (_display == IntPtr.Zero)
throw new OpenGlException("Unable to create ANGLE display");
}
if (_display == IntPtr.Zero)
_display = _egl.GetDisplay(IntPtr.Zero);
} }
else else
{ {
if (_egl.GetPlatformDisplayEXT == null) if (egl.GetPlatformDisplayEXT == null)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
_display = _egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs); display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs);
} }
if (display == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglGetDisplay", egl);
return display;
}
if (_display == IntPtr.Zero) public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
throw OpenGlException.GetFormattedException("eglGetDisplay", _egl); : this(egl, CreateDisplay(egl, platformType, platformDisplay, attrs))
{
}
public EglDisplay(EglInterface egl, IntPtr display)
{
_egl = egl;
_display = display;
if(_display == IntPtr.Zero)
throw new ArgumentException();
if (!_egl.Initialize(_display, out var major, out var minor)) if (!_egl.Initialize(_display, out var major, out var minor))
throw OpenGlException.GetFormattedException("eglInitialize", _egl); throw OpenGlException.GetFormattedException("eglInitialize", _egl);
@ -172,5 +156,15 @@ namespace Avalonia.OpenGL
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl); throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl);
return new EglSurface(this, _egl, s); return new EglSurface(this, _egl, s);
} }
public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs)
{
var s = _egl.CreatePbufferFromClientBuffer(_display, bufferType, handle,
_config, attribs);
if (s == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", _egl);
return new EglSurface(this, _egl, s);
}
} }
} }

7
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@ -20,12 +20,13 @@ namespace Avalonia.OpenGL
if (feature != null) if (feature != null)
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature); AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature);
} }
public static EglGlPlatformFeature TryCreate() public static EglGlPlatformFeature TryCreate() => TryCreate(() => new EglDisplay());
public static EglGlPlatformFeature TryCreate(Func<EglDisplay> displayFactory)
{ {
try try
{ {
var disp = new EglDisplay(); var disp = displayFactory();
return new EglGlPlatformFeature return new EglGlPlatformFeature
{ {
_display = disp, _display = disp,

84
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -3,33 +3,26 @@ using System.Threading;
namespace Avalonia.OpenGL namespace Avalonia.OpenGL
{ {
public class EglGlPlatformSurface : IGlPlatformSurface public class EglGlPlatformSurface : EglGlPlatformSurfaceBase
{ {
public interface IEglWindowGlPlatformSurfaceInfo
{
IntPtr Handle { get; }
PixelSize Size { get; }
double Scaling { get; }
}
private readonly EglDisplay _display; private readonly EglDisplay _display;
private readonly EglContext _context; private readonly EglContext _context;
private readonly IEglWindowGlPlatformSurfaceInfo _info; private readonly IEglWindowGlPlatformSurfaceInfo _info;
public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base()
{ {
_display = context.Display; _display = context.Display;
_context = context; _context = context;
_info = info; _info = info;
} }
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{ {
var glSurface = _display.CreateWindowSurface(_info.Handle); var glSurface = _display.CreateWindowSurface(_info.Handle);
return new RenderTarget(_display, _context, glSurface, _info); return new RenderTarget(_display, _context, glSurface, _info);
} }
class RenderTarget : IGlPlatformSurfaceRenderTargetWithCorruptionInfo class RenderTarget : EglPlatformSurfaceRenderTargetBase
{ {
private readonly EglDisplay _display; private readonly EglDisplay _display;
private readonly EglContext _context; private readonly EglContext _context;
@ -38,7 +31,7 @@ namespace Avalonia.OpenGL
private PixelSize _initialSize; private PixelSize _initialSize;
public RenderTarget(EglDisplay display, EglContext context, public RenderTarget(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context)
{ {
_display = display; _display = display;
_context = context; _context = context;
@ -47,70 +40,11 @@ namespace Avalonia.OpenGL
_initialSize = info.Size; _initialSize = info.Size;
} }
public void Dispose() => _glSurface.Dispose(); public override void Dispose() => _glSurface.Dispose();
public bool IsCorrupted => _initialSize != _info.Size; public override bool IsCorrupted => _initialSize != _info.Size;
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var l = _context.Lock();
try
{
if (IsCorrupted)
throw new RenderTargetCorruptedException();
var restoreContext = _context.MakeCurrent(_glSurface);
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
return new Session(_display, _context, _glSurface, _info, l, restoreContext);
}
catch
{
l.Dispose();
throw;
}
}
class Session : IGlPlatformSurfaceRenderingSession public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info);
{
private readonly EglContext _context;
private readonly EglSurface _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private IDisposable _lock;
private readonly IDisposable _restoreContext;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock, IDisposable restoreContext)
{
_context = context;
_display = display;
_glSurface = glSurface;
_info = info;
_lock = @lock;
_restoreContext = restoreContext;
}
public void Dispose()
{
_context.GlInterface.Flush();
_display.EglInterface.WaitGL();
_glSurface.SwapBuffers();
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
_restoreContext.Dispose();
_lock.Dispose();
}
public IGlContext Context => _context;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }
}
} }
} }
} }

103
src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs

@ -0,0 +1,103 @@
using System;
namespace Avalonia.OpenGL
{
public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface
{
public interface IEglWindowGlPlatformSurfaceInfo
{
IntPtr Handle { get; }
PixelSize Size { get; }
double Scaling { get; }
}
public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget();
}
public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
private readonly EglDisplay _display;
private readonly EglContext _context;
protected EglPlatformSurfaceRenderTargetBase(EglDisplay display, EglContext context)
{
_display = display;
_context = context;
}
public abstract bool IsCorrupted { get; }
public virtual void Dispose()
{
}
public abstract IGlPlatformSurfaceRenderingSession BeginDraw();
protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false)
{
var l = _context.Lock();
try
{
if (IsCorrupted)
throw new RenderTargetCorruptedException();
var restoreContext = _context.MakeCurrent(surface);
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
return new Session(_display, _context, surface, info, l, restoreContext, onFinish, isYFlipped);
}
catch
{
l.Dispose();
throw;
}
}
class Session : IGlPlatformSurfaceRenderingSession
{
private readonly EglContext _context;
private readonly EglSurface _glSurface;
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private readonly IDisposable _lock;
private readonly IDisposable _restoreContext;
private readonly Action _onFinish;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock, IDisposable restoreContext, Action onFinish, bool isYFlipped)
{
IsYFlipped = isYFlipped;
_context = context;
_display = display;
_glSurface = glSurface;
_info = info;
_lock = @lock;
_restoreContext = restoreContext;
_onFinish = onFinish;
}
public void Dispose()
{
_context.GlInterface.Flush();
_display.EglInterface.WaitGL();
_glSurface.SwapBuffers();
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
_restoreContext.Dispose();
_lock.Dispose();
_onFinish?.Invoke();
}
public IGlContext Context => _context;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }
}
}
}

29
src/Avalonia.OpenGL/EglInterface.cs

@ -17,25 +17,21 @@ namespace Avalonia.OpenGL
} }
public EglInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress)
{
}
public EglInterface(string library) : base(Load(library)) public EglInterface(string library) : base(Load(library))
{ {
} }
[DllImport("libegl.dll", CharSet = CharSet.Ansi)]
static extern IntPtr eglGetProcAddress(string proc);
static Func<string, IntPtr> Load() static Func<string, IntPtr> Load()
{ {
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem; var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android) if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
return Load("libEGL.so.1"); return Load("libEGL.so.1");
if (os == OperatingSystemType.WinNT)
{
var disp = eglGetProcAddress("eglGetPlatformDisplayEXT");
if (disp == IntPtr.Zero)
throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point");
return eglGetProcAddress;
}
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
} }
@ -147,6 +143,21 @@ namespace Avalonia.OpenGL
return null; return null;
return Marshal.PtrToStringAnsi(rv); return Marshal.PtrToStringAnsi(rv);
} }
public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
[GlEntryPoint("eglCreatePbufferFromClientBuffer")]
public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; }
public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; }
public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty // ReSharper restore UnassignedGetOnlyAutoProperty
} }

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

@ -8,11 +8,11 @@
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
<Setter Property="Padding" Value="8,5,0,0" /> <Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="MinWidth" Value="120" /> <Setter Property="MinWidth" Value="120" />
<Setter Property="MinHeight" Value="32" /> <Setter Property="MinHeight" Value="32" />

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

@ -13,11 +13,11 @@
<Setter Property="Background" Value="{DynamicResource RadioButtonBackground}" /> <Setter Property="Background" Value="{DynamicResource RadioButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForeground}" /> <Setter Property="Foreground" Value="{DynamicResource RadioButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource RadioButtonBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource RadioButtonBorderBrush}" />
<Setter Property="Padding" Value="8,6,0,0" /> <Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="MinWidth" Value="120" /> <Setter Property="MinWidth" Value="120" />
<Setter Property="Template"> <Setter Property="Template">

12
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,5 +1,15 @@
Compat issues with assembly Avalonia.Visuals: Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract.
TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' 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' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
Total Issues: 3 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: 13

67
src/Avalonia.Visuals/Media/FontManager.cs

@ -13,8 +13,8 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public sealed class FontManager public sealed class FontManager
{ {
private readonly ConcurrentDictionary<FontKey, Typeface> _typefaceCache = private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<FontKey, Typeface>(); new ConcurrentDictionary<Typeface, GlyphTypeface>();
private readonly FontFamily _defaultFontFamily; private readonly FontFamily _defaultFontFamily;
public FontManager(IFontManagerImpl platformImpl) public FontManager(IFontManagerImpl platformImpl)
@ -76,79 +76,52 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <summary> /// <summary>
/// Returns a new typeface, or an existing one if a matching typeface exists. /// Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists.
/// </summary> /// </summary>
/// <param name="fontFamily">The font family.</param> /// <param name="typeface">The typeface.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <returns> /// <returns>
/// The typeface. /// The <see cref="GlyphTypeface"/>.
/// </returns> /// </returns>
public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal, public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
FontWeight fontWeight = FontWeight.Normal)
{ {
while (true) while (true)
{ {
if (fontFamily.IsDefault) if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface))
{ {
fontFamily = _defaultFontFamily; return glyphTypeface;
} }
var key = new FontKey(fontFamily.Name, fontStyle, fontWeight); glyphTypeface = new GlyphTypeface(typeface);
if (_typefaceCache.TryGetValue(key, out var typeface)) if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{ {
return typeface; return glyphTypeface;
} }
typeface = new Typeface(fontFamily, fontStyle, fontWeight); if (typeface.FontFamily == _defaultFontFamily)
if (_typefaceCache.TryAdd(key, typeface))
{
return typeface;
}
if (fontFamily == _defaultFontFamily)
{ {
return null; return null;
} }
fontFamily = _defaultFontFamily; typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
} }
} }
/// <summary> /// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties. /// Tries to match a specified character to a <see cref="Typeface"/> that supports specified font properties.
/// Returns <c>null</c> if no fallback was found.
/// </summary> /// </summary>
/// <param name="codepoint">The codepoint to match against.</param> /// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</param> /// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param> /// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param> /// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param> /// <param name="culture">The culture.</param>
/// <param name="typeface">The matching <see cref="Typeface"/>.</param>
/// <returns> /// <returns>
/// The matched typeface. /// <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns> /// </returns>
public Typeface MatchCharacter(int codepoint, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontStyle fontStyle = FontStyle.Normal, FontWeight fontWeight,
FontWeight fontWeight = FontWeight.Normal, FontFamily fontFamily, CultureInfo culture, out Typeface typeface) =>
FontFamily fontFamily = null, CultureInfo culture = null) PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
{
foreach (var cachedTypeface in _typefaceCache.Values)
{
// First try to find a cached typeface by style and weight to avoid redundant glyph index lookup.
if (cachedTypeface.Style == fontStyle && cachedTypeface.Weight == fontWeight
&& cachedTypeface.GlyphTypeface.GetGlyph((uint)codepoint) != 0)
{
return cachedTypeface;
}
}
var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ?
_typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) :
null;
return matchedTypeface;
}
} }
} }

40
src/Avalonia.Visuals/Media/Fonts/FontKey.cs

@ -1,40 +0,0 @@
using System;
namespace Avalonia.Media.Fonts
{
public readonly struct FontKey : IEquatable<FontKey>
{
public FontKey(string familyName, FontStyle style, FontWeight weight)
{
FamilyName = familyName;
Style = style;
Weight = weight;
}
public string FamilyName { get; }
public FontStyle Style { get; }
public FontWeight Weight { get; }
public override int GetHashCode()
{
var hash = FamilyName.GetHashCode();
hash = hash * 31 + (int)Style;
hash = hash * 31 + (int)Weight;
return hash;
}
public override bool Equals(object other)
{
return other is FontKey key && Equals(key);
}
public bool Equals(FontKey other)
{
return FamilyName == other.FamilyName &&
Style == other.Style &&
Weight == other.Weight;
}
}
}

7
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -70,10 +70,11 @@ namespace Avalonia.Media.TextFormatting
var codepoint = Codepoint.ReadAt(text, count, out _); var codepoint = Codepoint.ReadAt(text, count, out _);
//ToDo: Fix FontFamily fallback //ToDo: Fix FontFamily fallback
currentTypeface = var matchFound =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily); FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface);
if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) if (matchFound && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count))
{ {
//Fallback found //Fallback found
return new ShapeableTextCharacters(text.Take(count), return new ShapeableTextCharacters(text.Take(count),

31
src/Avalonia.Visuals/Media/Typeface.cs

@ -8,17 +8,15 @@ namespace Avalonia.Media
/// Represents a typeface. /// Represents a typeface.
/// </summary> /// </summary>
[DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")] [DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")]
public class Typeface : IEquatable<Typeface> public readonly struct Typeface : IEquatable<Typeface>
{ {
private GlyphTypeface _glyphTypeface;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Typeface"/> class. /// Initializes a new instance of the <see cref="Typeface"/> class.
/// </summary> /// </summary>
/// <param name="fontFamily">The font family.</param> /// <param name="fontFamily">The font family.</param>
/// <param name="style">The font style.</param> /// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param> /// <param name="weight">The font weight.</param>
public Typeface([NotNull]FontFamily fontFamily, public Typeface([NotNull] FontFamily fontFamily,
FontStyle style = FontStyle.Normal, FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal) FontWeight weight = FontWeight.Normal)
{ {
@ -45,7 +43,7 @@ namespace Avalonia.Media
{ {
} }
public static Typeface Default => FontManager.Current?.GetOrAddTypeface(FontFamily.Default); public static Typeface Default { get; } = new Typeface(FontFamily.Default);
/// <summary> /// <summary>
/// Gets the font family. /// Gets the font family.
@ -68,7 +66,7 @@ namespace Avalonia.Media
/// <value> /// <value>
/// The glyph typeface. /// The glyph typeface.
/// </value> /// </value>
public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this)); public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b) public static bool operator !=(Typeface a, Typeface b)
{ {
@ -77,32 +75,17 @@ namespace Avalonia.Media
public static bool operator ==(Typeface a, Typeface b) public static bool operator ==(Typeface a, Typeface b)
{ {
if (ReferenceEquals(a, b)) return a.Equals(b);
{
return true;
}
return !(a is null) && a.Equals(b);
} }
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (obj is Typeface typeface) return obj is Typeface typeface && Equals(typeface);
{
return Equals(typeface);
}
return false;
} }
public bool Equals(Typeface other) public bool Equals(Typeface other)
{ {
if (other is null) return FontFamily == other.FontFamily && Style == other.Style && Weight == other.Weight;
{
return false;
}
return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight;
} }
public override int GetHashCode() public override int GetHashCode()

5
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -26,13 +25,13 @@ namespace Avalonia.Platform
/// <param name="fontWeight">The font weight.</param> /// <param name="fontWeight">The font weight.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param> /// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param> /// <param name="culture">The culture.</param>
/// <param name="fontKey">The matching font key.</param> /// <param name="typeface">The matching typeface.</param>
/// <returns> /// <returns>
/// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise. /// <c>True</c>, if the <see cref="IFontManagerImpl"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns> /// </returns>
bool TryMatchCharacter(int codepoint, FontStyle fontStyle, bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey); FontFamily fontFamily, CultureInfo culture, out Typeface typeface);
/// <summary> /// <summary>
/// Creates a glyph typeface. /// Creates a glyph typeface.

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

@ -30,12 +30,13 @@ namespace Avalonia.Rendering
{ {
if (Size != size || Scaling != scaling) if (Size != size || Scaling != scaling)
{ {
Bitmap.Dispose();
var resized = RefCountable.Create(drawingContext.CreateLayer(size)); var resized = RefCountable.Create(drawingContext.CreateLayer(size));
using (var context = resized.Item.CreateDrawingContext(null)) using (var context = resized.Item.CreateDrawingContext(null))
{ {
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
Bitmap.Dispose();
Bitmap = resized; Bitmap = resized;
Scaling = scaling; Scaling = scaling;
Size = size; Size = size;

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

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
_useManualFpsCounting = useManualFpsCounting; _useManualFpsCounting = useManualFpsCounting;
_fpsText = new FormattedText _fpsText = new FormattedText
{ {
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default), Typeface = new Typeface(FontFamily.Default),
FontSize = s_fontSize FontSize = s_fontSize
}; };
} }

7
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
@ -31,7 +30,7 @@ namespace Avalonia.Skia
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) FontFamily fontFamily, CultureInfo culture, out Typeface fontKey)
{ {
SKFontStyle skFontStyle; SKFontStyle skFontStyle;
@ -81,7 +80,7 @@ namespace Avalonia.Skia
continue; continue;
} }
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight); fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
return true; return true;
} }
@ -92,7 +91,7 @@ namespace Avalonia.Skia
if (skTypeface != null) if (skTypeface != null)
{ {
fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight); fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight);
return true; return true;
} }

19
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -2,31 +2,28 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
internal class SKTypefaceCollection internal class SKTypefaceCollection
{ {
private readonly ConcurrentDictionary<FontKey, SKTypeface> _typefaces = private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces =
new ConcurrentDictionary<FontKey, SKTypeface>(); new ConcurrentDictionary<Typeface, SKTypeface>();
public void AddTypeface(FontKey key, SKTypeface typeface) public void AddTypeface(Typeface key, SKTypeface typeface)
{ {
_typefaces.TryAdd(key, typeface); _typefaces.TryAdd(key, typeface);
} }
public SKTypeface Get(Typeface typeface) public SKTypeface Get(Typeface typeface)
{ {
var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight); return GetNearestMatch(_typefaces, typeface);
return GetNearestMatch(_typefaces, key);
} }
private static SKTypeface GetNearestMatch(IDictionary<FontKey, SKTypeface> typefaces, FontKey key) private static SKTypeface GetNearestMatch(IDictionary<Typeface, SKTypeface> typefaces, Typeface key)
{ {
if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface)) if (typefaces.TryGetValue(key, out var typeface))
{ {
return typeface; return typeface;
} }
@ -42,7 +39,7 @@ namespace Avalonia.Skia
{ {
if (weight - j >= 100) if (weight - j >= 100)
{ {
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface)) if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface))
{ {
return typeface; return typeface;
} }
@ -53,7 +50,7 @@ namespace Avalonia.Skia
continue; continue;
} }
if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface)) if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface))
{ {
return typeface; return typeface;
} }

2
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -56,7 +56,7 @@ namespace Avalonia.Skia
continue; continue;
} }
var key = new FontKey(fontFamily.Name, typeface.FontSlant.ToAvalonia(), var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(),
(FontWeight)typeface.FontWeight); (FontWeight)typeface.FontWeight);
typeFaceCollection.AddTypeface(key, typeface); typeFaceCollection.AddTypeface(key, typeface);

6
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -16,6 +16,10 @@ namespace Avalonia
/// <summary> /// <summary>
/// The maximum number of bytes for video memory to store textures and resources. /// The maximum number of bytes for video memory to store textures and resources.
/// </summary> /// </summary>
public long? MaxGpuResourceSizeBytes { get; set; } /// <remarks>
/// This is set by default to the recommended value for Avalonia.
/// Setting this to null will give you the default Skia value.
/// </remarks>
public long? MaxGpuResourceSizeBytes { get; set; } = 1024 * 600 * 4 * 12; // ~28mb 12x 1024 x 600 textures.
} }
} }

7
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily; using FontFamily = Avalonia.Media.FontFamily;
@ -34,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight, FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{ {
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@ -51,12 +50,12 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0); var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight); typeface = new Typeface(fontFamilyName, fontStyle, fontWeight);
return true; return true;
} }
fontKey = default; typeface = default;
return false; return false;
} }

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

@ -1,4 +1,5 @@
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -15,7 +16,7 @@ namespace Avalonia.Win32
{ {
if (!s_attemptedToInitialize) if (!s_attemptedToInitialize)
{ {
EglFeature = EglGlPlatformFeature.TryCreate(); EglFeature = EglGlPlatformFeature.TryCreate(() => new AngleWin32EglDisplay());
s_attemptedToInitialize = true; s_attemptedToInitialize = true;
} }

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

@ -853,7 +853,7 @@ namespace Avalonia.Win32
private void ShowWindow(WindowState state) private void ShowWindow(WindowState state)
{ {
ShowWindowCommand command; ShowWindowCommand? command;
var newWindowProperties = _windowProperties; var newWindowProperties = _windowProperties;
@ -875,8 +875,8 @@ namespace Avalonia.Win32
case WindowState.FullScreen: case WindowState.FullScreen:
newWindowProperties.IsFullScreen = true; newWindowProperties.IsFullScreen = true;
UpdateWindowProperties(newWindowProperties); command = IsWindowVisible(_hwnd) ? (ShowWindowCommand?)null : ShowWindowCommand.Restore;
return; break;
default: default:
throw new ArgumentException("Invalid WindowState."); throw new ArgumentException("Invalid WindowState.");
@ -884,7 +884,10 @@ namespace Avalonia.Win32
UpdateWindowProperties(newWindowProperties); UpdateWindowProperties(newWindowProperties);
UnmanagedMethods.ShowWindow(_hwnd, command); if (command.HasValue)
{
UnmanagedMethods.ShowWindow(_hwnd, command.Value);
}
if (state == WindowState.Maximized) if (state == WindowState.Maximized)
{ {
@ -1007,10 +1010,12 @@ namespace Avalonia.Win32
if (newProperties.IsResizable) if (newProperties.IsResizable)
{ {
style |= WindowStyles.WS_SIZEFRAME; style |= WindowStyles.WS_SIZEFRAME;
style |= WindowStyles.WS_MAXIMIZEBOX;
} }
else else
{ {
style &= ~WindowStyles.WS_SIZEFRAME; style &= ~WindowStyles.WS_SIZEFRAME;
style &= ~WindowStyles.WS_MAXIMIZEBOX;
} }
SetStyle(style); SetStyle(style);

31
tests/Avalonia.LeakTests/ControlTests.cs

@ -552,6 +552,37 @@ namespace Avalonia.LeakTests
} }
} }
[Fact]
public void ItemsRepeater_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window
{
Content = new ItemsRepeater(),
};
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<ItemsRepeater>(window.Presenter.Child);
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ItemsRepeater>()).ObjectsCount));
}
}
private IDisposable Start() private IDisposable Start()
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With( return UnitTestApplication.Start(TestServices.StyledWindow.With(

2
tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs

@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{ {
var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var r = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return r.CreateFormattedText(text, return r.CreateFormattedText(text,
FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight), new Typeface(fontFamily, fontStyle, fontWeight),
fontSize, fontSize,
textAlignment, textAlignment,
wrapping, wrapping,

8
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -39,7 +39,7 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName }; private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey) CultureInfo culture, out Typeface typeface)
{ {
foreach (var customTypeface in _customTypefaces) foreach (var customTypeface in _customTypefaces)
{ {
@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue; continue;
} }
fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight); typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
return true; return true;
} }
@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight, var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint); SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight); typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
return true; return true;
} }
@ -73,13 +73,13 @@ namespace Avalonia.Skia.UnitTests.Media
skTypeface = typefaceCollection.Get(typeface); skTypeface = typefaceCollection.Get(typeface);
break; break;
} }
case "Noto Sans": case "Noto Sans":
{ {
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily); var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily);
skTypeface = typefaceCollection.Get(typeface); skTypeface = typefaceCollection.Get(typeface);
break; break;
} }
case FontFamily.DefaultFontFamilyName:
case "Noto Mono": case "Noto Mono":
{ {
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily);

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

@ -130,7 +130,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{ {
using (Start()) using (Start())
{ {
const string text = "1234الدولي"; const string text = "ABCDالدولي";
var defaultProperties = new GenericTextRunProperties(Typeface.Default); var defaultProperties = new GenericTextRunProperties(Typeface.Default);

5
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.UnitTests namespace Avalonia.UnitTests
@ -26,9 +25,9 @@ namespace Avalonia.UnitTests
} }
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out FontKey fontKey) CultureInfo culture, out Typeface fontKey)
{ {
fontKey = default; fontKey = new Typeface(_defaultFamilyName);
return false; return false;
} }

7
tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

@ -1,6 +1,5 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -15,9 +14,11 @@ namespace Avalonia.Visuals.UnitTests.Media
{ {
var fontFamily = new FontFamily("MyFont"); var fontFamily = new FontFamily("MyFont");
var typeface = FontManager.Current.GetOrAddTypeface(fontFamily); var typeface = new Typeface(fontFamily);
Assert.Same(typeface, FontManager.Current.GetOrAddTypeface(fontFamily)); var glyphTypeface = FontManager.Current.GetOrAddGlyphTypeface(typeface);
Assert.Same(glyphTypeface, FontManager.Current.GetOrAddGlyphTypeface(typeface));
} }
} }

Loading…
Cancel
Save