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
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
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
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
Global
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|iPhoneSimulator.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

7
azure-pipelines.yml

@ -56,13 +56,6 @@ jobs:
xcodeVersion: '10' # Options: 8, 9, default, specifyPath
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
displayName: 'Install Nuke'
inputs:

2
build/NetFX.props

@ -1,7 +1,7 @@
<Project>
<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>
</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.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.
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 '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.
Total Issues: 16
Total Issues: 18

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

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

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

@ -8,6 +8,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// </summary>
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>
/// 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.

10
src/Avalonia.Controls/ColumnDefinitions.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Avalonia.Collections;
namespace Avalonia.Controls
@ -13,7 +14,7 @@ namespace Avalonia.Controls
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinitions"/> class.
/// </summary>
public ColumnDefinitions() : base ()
public ColumnDefinitions()
{
}
@ -27,6 +28,11 @@ namespace Avalonia.Controls
AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
}
public override string ToString()
{
return string.Join(",", this.Select(x => x.Width));
}
/// <summary>
/// Parses a string representation of column definitions collection.
/// </summary>
@ -34,4 +40,4 @@ namespace Avalonia.Controls
/// <returns>The <see cref="ColumnDefinitions"/>.</returns>
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
{
Constraint = constraint,
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
@ -499,7 +499,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontStyle, FontWeight),
FontSize = FontSize,
TextAlignment = TextAlignment,
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.")
};
SetValue(IsVisibleProperty, isVisible, BindingPriority.Style);
SetValue(IsVisibleProperty, isVisible);
}
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.Specialized;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -681,8 +681,15 @@ namespace Avalonia.Controls
if (oldValue != null)
{
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
foreach (var element in Children)
@ -699,8 +706,15 @@ namespace Avalonia.Controls
if (newValue != null)
{
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;

2
src/Avalonia.Controls/TextBlock.cs

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

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

@ -38,11 +38,13 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
public HtmlWebSocketTransport(IAvaloniaRemoteTransportConnection signalTransport, Uri listenUri)
{
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.";
_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 =>
{

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

@ -8,8 +8,8 @@ namespace Avalonia.Diagnostics.ViewModels
internal abstract class PropertyViewModel : ViewModelBase
{
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
private static readonly Type[] StringParameter = new[] { typeof(string) };
private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) };
private static readonly Type[] StringParameter = { typeof(string) };
private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) };
public abstract object Key { get; }
public abstract string Name { get; }
@ -26,35 +26,46 @@ namespace Avalonia.Diagnostics.ViewModels
}
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);
if (converter != null && converter.CanConvertFrom(typeof(string)))
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
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)
{
return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture });
}
throw new InvalidCastException("Unable to convert value.");
}
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null);
protected static object ConvertFromString(string s, Type targetType)
{
var converter = TypeDescriptor.GetConverter(targetType);
if (method != null)
{
return method.Invoke(null, new object[] { s });
}
if (converter.CanConvertFrom(typeof(string)))
{
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" };
}
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;
}
}

22
src/Avalonia.Input/AccessKeyHandler.cs

@ -28,7 +28,7 @@ namespace Avalonia.Input
/// <summary>
/// The window to which the handler belongs.
/// </summary>
private IInputRoot _owner;
private IInputRoot? _owner;
/// <summary>
/// Whether access keys are currently being shown;
@ -48,17 +48,17 @@ namespace Avalonia.Input
/// <summary>
/// Element to restore following AltKey taking focus.
/// </summary>
private IInputElement _restoreFocusElement;
private IInputElement? _restoreFocusElement;
/// <summary>
/// The window's main menu.
/// </summary>
private IMainMenu _mainMenu;
private IMainMenu? _mainMenu;
/// <summary>
/// Gets or sets the window's main menu.
/// </summary>
public IMainMenu MainMenu
public IMainMenu? MainMenu
{
get => _mainMenu;
set
@ -86,14 +86,12 @@ namespace Avalonia.Input
/// </remarks>
public void SetOwner(IInputRoot owner)
{
Contract.Requires<ArgumentNullException>(owner != null);
if (_owner != null)
{
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, 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
// access key markers in the window (i.e. "_File").
_owner.ShowAccessKeys = _showingAccessKeys = true;
_owner!.ShowAccessKeys = _showingAccessKeys = true;
}
else
{
@ -241,7 +239,7 @@ namespace Avalonia.Input
{
if (_showingAccessKeys)
{
_owner.ShowAccessKeys = false;
_owner!.ShowAccessKeys = false;
}
}
@ -250,13 +248,13 @@ namespace Avalonia.Input
/// </summary>
private void CloseMenu()
{
MainMenu.Close();
_owner.ShowAccessKeys = _showingAccessKeys = false;
MainMenu!.Close();
_owner!.ShowAccessKeys = _showingAccessKeys = false;
}
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">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

6
src/Avalonia.Input/DataObject.cs

@ -11,7 +11,7 @@ namespace Avalonia.Input
return _items.ContainsKey(dataFormat);
}
public object Get(string dataFormat)
public object? Get(string dataFormat)
{
if (_items.ContainsKey(dataFormat))
return _items[dataFormat];
@ -23,12 +23,12 @@ namespace Avalonia.Input
return _items.Keys;
}
public IEnumerable<string> GetFileNames()
public IEnumerable<string>? GetFileNames()
{
return Get(DataFormats.FileNames) as IEnumerable<string>;
}
public string GetText()
public string? GetText()
{
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();
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();
if (target != null && DragDrop.GetAllowDrop(target))
@ -19,7 +19,7 @@ namespace Avalonia.Input
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)
return DragDropEffects.None;

47
src/Avalonia.Input/FocusManager.cs

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

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

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

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

@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers
@ -11,9 +10,9 @@ namespace Avalonia.Input.GestureRecognizers
{
private bool _scrolling;
private Point _trackedRootPoint;
private IPointer _tracking;
private IInputElement _target;
private IGestureRecognizerActionsDispatcher _actions;
private IPointer? _tracking;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private int _gestureId;
@ -95,7 +94,7 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true;
if (_scrolling)
{
_actions.Capture(e.Pointer, this);
_actions!.Capture(e.Pointer, this);
}
}
@ -110,7 +109,7 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint = rootPoint;
if (elapsed.TotalSeconds > 0)
_inertia = vector / elapsed.TotalSeconds;
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
}
@ -128,7 +127,7 @@ namespace Avalonia.Input.GestureRecognizers
{
_inertia = default;
_scrolling = false;
_target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0;
_lastMoveTimestamp = null;
}
@ -165,7 +164,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(0.15, st.Elapsed.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>(
"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);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
static Gestures()
{
@ -69,6 +71,11 @@ namespace Avalonia.Input
private static void PointerPressed(RoutedEventArgs ev)
{
if (ev.Source is null)
{
return;
}
if (ev.Route == RoutingStrategies.Bubble)
{
var e = (PointerPressedEventArgs)ev;
@ -76,7 +83,7 @@ namespace Avalonia.Input
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)
{

2
src/Avalonia.Input/IAccessKeyHandler.cs

@ -8,7 +8,7 @@ namespace Avalonia.Input
/// <summary>
/// Gets or sets the window's main menu.
/// </summary>
IMainMenu MainMenu { get; set; }
IMainMenu? MainMenu { get; set; }
/// <summary>
/// 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.
/// <seealso cref="DataFormats.Text"/>
/// </summary>
string GetText();
string? GetText();
/// <summary>
/// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/>
/// </summary>
IEnumerable<string> GetFileNames();
IEnumerable<string>? GetFileNames();
/// <summary>
/// Tries to get the data of the given DataFormat.
/// </summary>
object Get(string dataFormat);
object? Get(string dataFormat);
}
}

6
src/Avalonia.Input/IFocusManager.cs

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

2
src/Avalonia.Input/IInputElement.cs

@ -78,7 +78,7 @@ namespace Avalonia.Input
/// <summary>
/// Gets or sets the associated mouse cursor.
/// </summary>
Cursor Cursor { get; }
Cursor? Cursor { get; }
/// <summary>
/// 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>
/// Gets or sets the input element that the pointer is currently over.
/// </summary>
IInputElement PointerOverElement { get; set; }
IInputElement? PointerOverElement { get; set; }
/// <summary>
/// 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
/// </summary>
[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
{
IInputElement FocusedElement { get; }
IInputElement? FocusedElement { get; }
void SetFocusedElement(
IInputElement element,
IInputElement? element,
NavigationMethod method,
KeyModifiers modifiers);
}

4
src/Avalonia.Input/IPointer.cs

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

4
src/Avalonia.Input/IPointerDevice.cs

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

8
src/Avalonia.Input/InputElement.cs

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

2
src/Avalonia.Input/KeyEventArgs.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
public IKeyboardDevice Device { get; set; }
public IKeyboardDevice? Device { 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
{
private IInputElement _focusedElement;
private IInputElement? _focusedElement;
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -18,7 +18,7 @@ namespace Avalonia.Input
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IInputElement FocusedElement
public IInputElement? FocusedElement
{
get
{
@ -33,7 +33,7 @@ namespace Avalonia.Input
}
public void SetFocusedElement(
IInputElement element,
IInputElement? element,
NavigationMethod method,
KeyModifiers keyModifiers)
{

13
src/Avalonia.Input/KeyboardNavigationHandler.cs

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

73
src/Avalonia.Input/MouseDevice.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input
private readonly Pointer _pointer;
private bool _disposed;
public MouseDevice(Pointer pointer = null)
public MouseDevice(Pointer? pointer = null)
{
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
}
@ -34,7 +34,7 @@ namespace Avalonia.Input
/// <see cref="Capture"/> method.
/// </remarks>
[Obsolete("Use IPointer instead")]
public IInputElement Captured => _pointer.Captured;
public IInputElement? Captured => _pointer.Captured;
/// <summary>
/// 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
/// by the <see cref="Captured"/> property.
/// </remarks>
public void Capture(IInputElement control)
public void Capture(IInputElement? control)
{
_pointer.Capture(control);
}
@ -66,7 +66,7 @@ namespace Avalonia.Input
/// <returns>The mouse position in the control's coordinates.</returns>
public Point GetPosition(IVisual relativeTo)
{
Contract.Requires<ArgumentNullException>(relativeTo != null);
relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
if (relativeTo.VisualRoot == null)
{
@ -75,7 +75,7 @@ namespace Avalonia.Input
var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform.Value;
return rootPoint * transform!.Value;
}
public void ProcessRawEvent(RawInputEventArgs e)
@ -126,7 +126,7 @@ namespace Avalonia.Input
private void ProcessRawEvent(RawPointerEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
e = e ?? throw new ArgumentNullException(nameof(e));
var mouse = (MouseDevice)e.Device;
if(mouse._disposed)
@ -173,8 +173,8 @@ namespace Avalonia.Input
private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
ClearPointerOver(this, timestamp, root, properties, inputModifiers);
}
@ -214,8 +214,8 @@ namespace Avalonia.Input
PointerPointProperties properties,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
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,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
IInputElement source;
IInputElement? source;
if (_pointer.Captured == null)
{
@ -265,18 +265,23 @@ namespace Avalonia.Input
source = _pointer.Captured;
}
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
p, timestamp, properties, inputModifiers);
if (source is object)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
p, timestamp, properties, inputModifiers);
source?.RaiseEvent(e);
return e.Handled;
source.RaiseEvent(e);
return e.Handled;
}
return false;
}
private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p);
@ -298,8 +303,8 @@ namespace Avalonia.Input
PointerPointProperties props,
Vector delta, KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p);
@ -317,21 +322,21 @@ namespace Avalonia.Input
private IInteractive GetSource(IVisual hit)
{
Contract.Requires<ArgumentNullException>(hit != null);
hit = hit ?? throw new ArgumentNullException(nameof(hit));
return _pointer.Captured ??
(hit as IInteractive) ??
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);
}
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive source,
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source,
PointerPointProperties properties,
KeyModifiers inputModifiers)
{
@ -343,8 +348,8 @@ namespace Avalonia.Input
PointerPointProperties properties,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.PointerOverElement;
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,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.InputHitTest(p);
@ -412,11 +417,11 @@ namespace Avalonia.Input
PointerPointProperties properties,
KeyModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(element != null);
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
element = element ?? throw new ArgumentNullException(nameof(element));
IInputElement branch = null;
IInputElement? branch = null;
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"/>
/// was the last in the requested direction.
/// </returns>
public static IInputElement GetNextInTabOrder(
public static IInputElement? GetNextInTabOrder(
IInputElement element,
NavigationDirection direction,
bool outsideElement = false)
{
Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>(
direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous);
element = element ?? throw new ArgumentNullException(nameof(element));
if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous)
{
throw new ArgumentException("Invalid direction: must be Next or Previous.");
}
var container = element.GetVisualParent<IInputElement>();
@ -110,7 +112,7 @@ namespace Avalonia.Input.Navigation
if (customNext.handled)
{
yield return customNext.next;
yield return customNext.next!;
}
else
{
@ -143,12 +145,14 @@ namespace Avalonia.Input.Navigation
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement GetNextInContainer(
private static IInputElement? GetNextInContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction,
bool outsideElement)
{
IInputElement? e = element;
if (direction == NavigationDirection.Next && !outsideElement)
{
var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
@ -167,13 +171,13 @@ namespace Avalonia.Input.Navigation
// INavigableContainer.
if (navigable != null)
{
while (element != null)
while (e != null)
{
element = navigable.GetControl(direction, element, false);
e = navigable.GetControl(direction, e, false);
if (element != null &&
element.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) element))
if (e != null &&
e.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement)e))
{
break;
}
@ -183,12 +187,12 @@ namespace Avalonia.Input.Navigation
{
// TODO: Do a spatial search here if the container doesn't implement
// 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)
{
@ -196,7 +200,7 @@ namespace Avalonia.Input.Navigation
}
}
return element;
return e;
}
return null;
@ -209,13 +213,13 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
private static IInputElement? GetFirstInNextContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction)
{
var parent = container.GetVisualParent<IInputElement>();
IInputElement next = null;
IInputElement? next = null;
if (parent != null)
{
@ -268,7 +272,7 @@ namespace Avalonia.Input.Navigation
return next;
}
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element,
private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element,
NavigationDirection direction)
{
if (element is ICustomKeyboardNavigation custom)

9
src/Avalonia.Input/Pointer.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -20,7 +19,7 @@ namespace Avalonia.Input
public int Id { get; }
IInputElement FindCommonParent(IInputElement control1, IInputElement control2)
IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2)
{
if (control1 == null || control2 == null)
return null;
@ -28,12 +27,12 @@ namespace Avalonia.Input
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)
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 bool IsPrimary { get; }

14
src/Avalonia.Input/PointerEventArgs.cs

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

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

@ -7,4 +7,4 @@
DragLeave,
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>
public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
{
Contract.Requires<ArgumentNullException>(device != null);
device = device ?? throw new ArgumentNullException(nameof(device));
Device = device;
Timestamp = timestamp;

4
src/Avalonia.Input/TextInputEventArgs.cs

@ -4,8 +4,8 @@ namespace Avalonia.Input
{
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)
{
base.OnDetachedFromVisualTreeCore(e);
if (e.Root is ILayoutRoot r)
{
if (_layoutUpdated is object)
@ -772,6 +770,8 @@ namespace Avalonia.Layout
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
}
base.OnDetachedFromVisualTreeCore(e);
}
/// <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
}
public List<PlatformApi> AllowedPlatformApis = new List<PlatformApi>
{
PlatformApi.DirectX9
};
public IList<PlatformApi> AllowedPlatformApis { get; set; } = null;
}
}

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_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
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
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_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B;
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;
public IntPtr Handle => _display;
private AngleOptions.PlatformApi? _angleApi;
private int _sampleCount;
private int _stencilSize;
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 (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
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);
if (display == IntPtr.Zero)
display = egl.GetDisplay(IntPtr.Zero);
}
else
{
if (_egl.GetPlatformDisplayEXT == null)
if (egl.GetPlatformDisplayEXT == null)
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)
throw OpenGlException.GetFormattedException("eglGetDisplay", _egl);
public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
: 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))
throw OpenGlException.GetFormattedException("eglInitialize", _egl);
@ -172,5 +156,15 @@ namespace Avalonia.OpenGL
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl);
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)
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
{
var disp = new EglDisplay();
var disp = displayFactory();
return new EglGlPlatformFeature
{
_display = disp,

84
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -3,33 +3,26 @@ using System.Threading;
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 EglContext _context;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info)
public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base()
{
_display = context.Display;
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
var glSurface = _display.CreateWindowSurface(_info.Handle);
return new RenderTarget(_display, _context, glSurface, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTargetWithCorruptionInfo
class RenderTarget : EglPlatformSurfaceRenderTargetBase
{
private readonly EglDisplay _display;
private readonly EglContext _context;
@ -38,7 +31,7 @@ namespace Avalonia.OpenGL
private PixelSize _initialSize;
public RenderTarget(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info)
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context)
{
_display = display;
_context = context;
@ -47,70 +40,11 @@ namespace Avalonia.OpenGL
_initialSize = info.Size;
}
public void Dispose() => _glSurface.Dispose();
public override void Dispose() => _glSurface.Dispose();
public 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;
}
}
public override bool IsCorrupted => _initialSize != _info.Size;
class Session : IGlPlatformSurfaceRenderingSession
{
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; }
}
public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info);
}
}
}

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))
{
}
[DllImport("libegl.dll", CharSet = CharSet.Ansi)]
static extern IntPtr eglGetProcAddress(string proc);
static Func<string, IntPtr> Load()
{
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
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();
}
@ -147,6 +143,21 @@ namespace Avalonia.OpenGL
return null;
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
}

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

@ -8,11 +8,11 @@
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
<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="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="MinWidth" Value="120" />
<Setter Property="MinHeight" Value="32" />

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

@ -13,11 +13,11 @@
<Setter Property="Background" Value="{DynamicResource RadioButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForeground}" />
<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="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="MinWidth" Value="120" />
<Setter Property="Template">

12
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,5 +1,15 @@
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.
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.
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>
public sealed class FontManager
{
private readonly ConcurrentDictionary<FontKey, Typeface> _typefaceCache =
new ConcurrentDictionary<FontKey, Typeface>();
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>();
private readonly FontFamily _defaultFontFamily;
public FontManager(IFontManagerImpl platformImpl)
@ -76,79 +76,52 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <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>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="typeface">The typeface.</param>
/// <returns>
/// The typeface.
/// The <see cref="GlyphTypeface"/>.
/// </returns>
public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal)
public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{
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 (_typefaceCache.TryAdd(key, typeface))
{
return typeface;
}
if (fontFamily == _defaultFontFamily)
if (typeface.FontFamily == _defaultFontFamily)
{
return null;
}
fontFamily = _defaultFontFamily;
typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
}
}
/// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties.
/// Returns <c>null</c> if no fallback was found.
/// Tries to match a specified character to a <see cref="Typeface"/> that supports specified font properties.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontStyle">The font style.</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="culture">The culture.</param>
/// <param name="typeface">The matching <see cref="Typeface"/>.</param>
/// <returns>
/// The matched typeface.
/// <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
/// </returns>
public Typeface MatchCharacter(int codepoint,
FontStyle fontStyle = FontStyle.Normal,
FontWeight fontWeight = FontWeight.Normal,
FontFamily fontFamily = null, CultureInfo culture = null)
{
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;
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out Typeface typeface) =>
PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
}
}

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 _);
//ToDo: Fix FontFamily fallback
currentTypeface =
FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily);
var matchFound =
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
return new ShapeableTextCharacters(text.Take(count),

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

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

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

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

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

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

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

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

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

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

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

@ -2,31 +2,28 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
private readonly ConcurrentDictionary<FontKey, SKTypeface> _typefaces =
new ConcurrentDictionary<FontKey, SKTypeface>();
private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces =
new ConcurrentDictionary<Typeface, SKTypeface>();
public void AddTypeface(FontKey key, SKTypeface typeface)
public void AddTypeface(Typeface key, SKTypeface typeface)
{
_typefaces.TryAdd(key, typeface);
}
public SKTypeface Get(Typeface typeface)
{
var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight);
return GetNearestMatch(_typefaces, key);
return GetNearestMatch(_typefaces, typeface);
}
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;
}
@ -42,7 +39,7 @@ namespace Avalonia.Skia
{
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;
}
@ -53,7 +50,7 @@ namespace Avalonia.Skia
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;
}

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

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

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

@ -16,6 +16,10 @@ namespace Avalonia
/// <summary>
/// The maximum number of bytes for video memory to store textures and resources.
/// </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.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
@ -34,7 +33,7 @@ namespace Avalonia.Direct2D1.Media
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out FontKey fontKey)
FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
@ -51,12 +50,12 @@ namespace Avalonia.Direct2D1.Media
var fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight);
typeface = new Typeface(fontFamilyName, fontStyle, fontWeight);
return true;
}
fontKey = default;
typeface = default;
return false;
}

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

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

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

@ -853,7 +853,7 @@ namespace Avalonia.Win32
private void ShowWindow(WindowState state)
{
ShowWindowCommand command;
ShowWindowCommand? command;
var newWindowProperties = _windowProperties;
@ -875,8 +875,8 @@ namespace Avalonia.Win32
case WindowState.FullScreen:
newWindowProperties.IsFullScreen = true;
UpdateWindowProperties(newWindowProperties);
return;
command = IsWindowVisible(_hwnd) ? (ShowWindowCommand?)null : ShowWindowCommand.Restore;
break;
default:
throw new ArgumentException("Invalid WindowState.");
@ -884,7 +884,10 @@ namespace Avalonia.Win32
UpdateWindowProperties(newWindowProperties);
UnmanagedMethods.ShowWindow(_hwnd, command);
if (command.HasValue)
{
UnmanagedMethods.ShowWindow(_hwnd, command.Value);
}
if (state == WindowState.Maximized)
{
@ -1007,10 +1010,12 @@ namespace Avalonia.Win32
if (newProperties.IsResizable)
{
style |= WindowStyles.WS_SIZEFRAME;
style |= WindowStyles.WS_MAXIMIZEBOX;
}
else
{
style &= ~WindowStyles.WS_SIZEFRAME;
style &= ~WindowStyles.WS_MAXIMIZEBOX;
}
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()
{
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>();
return r.CreateFormattedText(text,
FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight),
new Typeface(fontFamily, fontStyle, fontWeight),
fontSize,
textAlignment,
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 };
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)
{
@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue;
}
fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight);
typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
return true;
}
@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media
var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
return true;
}
@ -73,13 +73,13 @@ namespace Avalonia.Skia.UnitTests.Media
skTypeface = typefaceCollection.Get(typeface);
break;
}
case "Noto Sans":
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily);
skTypeface = typefaceCollection.Get(typeface);
break;
}
case FontFamily.DefaultFontFamilyName:
case "Noto Mono":
{
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())
{
const string text = "1234الدولي";
const string text = "ABCDالدولي";
var defaultProperties = new GenericTextRunProperties(Typeface.Default);

5
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
namespace Avalonia.UnitTests
@ -26,9 +25,9 @@ namespace Avalonia.UnitTests
}
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;
}

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

@ -1,6 +1,5 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Xunit;
@ -15,9 +14,11 @@ namespace Avalonia.Visuals.UnitTests.Media
{
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