Browse Source

Merge branch 'master' into patch-1

pull/5377/head
Benedikt Stebner 5 years ago
committed by GitHub
parent
commit
6f4bb48f2b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 8
      .github/ISSUE_TEMPLATE/config.yml
  3. 1
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 22
      native/Avalonia.Native/src/OSX/cursor.mm
  5. BIN
      samples/ControlCatalog/Assets/avalonia-32.png
  6. 4
      samples/ControlCatalog/MainView.xaml
  7. 29
      samples/ControlCatalog/Pages/CursorPage.xaml
  8. 20
      samples/ControlCatalog/Pages/CursorPage.xaml.cs
  9. 44
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  10. 30
      src/Avalonia.Base/EnumExtensions.cs
  11. 4
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  12. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  13. 6
      src/Avalonia.Controls/ApiCompatBaseline.txt
  14. 2
      src/Avalonia.Controls/ComboBox.cs
  15. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  16. 45
      src/Avalonia.Controls/Grid.cs
  17. 8
      src/Avalonia.Controls/ListBox.cs
  18. 41
      src/Avalonia.Controls/MenuItem.cs
  19. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  20. 12
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  21. 21
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  22. 48
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  23. 8
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  24. 4
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  25. 4
      src/Avalonia.Controls/TextBox.cs
  26. 2
      src/Avalonia.Controls/TopLevel.cs
  27. 12
      src/Avalonia.Controls/TreeView.cs
  28. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  29. 12
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  30. 8
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  31. 4
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  32. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  33. 8
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  34. 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  35. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  36. 4
      src/Avalonia.Input/ApiCompatBaseline.txt
  37. 39
      src/Avalonia.Input/Cursor.cs
  38. 12
      src/Avalonia.Input/Platform/ICursorFactory.cs
  39. 14
      src/Avalonia.Input/Platform/ICursorImpl.cs
  40. 9
      src/Avalonia.Input/Platform/IStandardCursorFactory.cs
  41. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  42. 25
      src/Avalonia.Native/Cursor.cs
  43. 6
      src/Avalonia.Native/WindowImplBase.cs
  44. 1
      src/Avalonia.Native/avn.idl
  45. 56
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  46. 27
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
  47. 540
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  48. 74
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs
  49. 73
      src/Avalonia.X11/X11CursorFactory.cs
  50. 2
      src/Avalonia.X11/X11Platform.cs
  51. 2
      src/Avalonia.X11/X11Structs.cs
  52. 4
      src/Avalonia.X11/X11Window.Ime.cs
  53. 26
      src/Avalonia.X11/X11Window.cs
  54. 10
      src/Avalonia.X11/XI2Manager.cs
  55. 7
      src/Avalonia.X11/XLib.cs
  56. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  57. 9
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  58. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  59. 9
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  60. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  61. 49
      src/Markup/Avalonia.Markup/Data/Binding.cs
  62. 49
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  63. 14
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  64. 2
      src/Markup/Avalonia.Markup/Data/RelativeSource.cs
  65. 17
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  66. 26
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  67. 21
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  68. 22
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  69. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  70. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs
  71. 16
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs
  72. 36
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  73. 42
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  74. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  75. 6
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  76. 12
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  77. 6
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  78. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  79. 9
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
  80. 2
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  81. 14
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  82. 127
      src/Windows/Avalonia.Win32/CursorFactory.cs
  83. 6
      src/Windows/Avalonia.Win32/DataObject.cs
  84. 16
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  85. 24
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  86. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  87. 4
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  88. 25
      src/Windows/Avalonia.Win32/WindowImpl.cs
  89. 4
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  90. 2
      src/iOS/Avalonia.iOS/Platform.cs
  91. 12
      src/iOS/Avalonia.iOS/Stubs.cs
  92. 9
      tests/Avalonia.Benchmarks/NullCursorFactory.cs
  93. 2
      tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
  94. 19
      tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs
  95. 2
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  96. 4
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  97. 30
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  98. 4
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  99. 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  100. 2
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

7
.github/ISSUE_TEMPLATE/bug_report.md

@ -4,7 +4,6 @@ about: Create a report to help us improve Avalonia
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -24,8 +24,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/config.yml

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Questions, Discussions, Ideas
url: https://github.com/AvaloniaUI/Avalonia/discussions/new
about: Please ask and answer questions here.
- name: Avalonia Community Support on Gitter
url: https://gitter.im/AvaloniaUI/Avalonia
about: Please ask and answer questions here.

1
.github/ISSUE_TEMPLATE/feature_request.md

@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**

22
native/Avalonia.Native/src/OSX/cursor.mm

@ -62,6 +62,28 @@ public:
return S_OK;
}
virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override
{
if(bitmapData == nullptr || retOut == nullptr)
{
return E_POINTER;
}
NSData *imageData = [NSData dataWithBytes:bitmapData length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];
NSPoint hotSpot;
hotSpot.x = hotPixel.Width;
hotSpot.y = hotPixel.Height;
*retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]);
(*retOut)->AddRef();
return S_OK;
}
};
extern IAvnCursorFactory* CreateCursorFactory()

BIN
samples/ControlCatalog/Assets/avalonia-32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

4
samples/ControlCatalog/MainView.xaml

@ -22,6 +22,10 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="Cursor"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CursorPage/>
</TabItem>
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">

29
samples/ControlCatalog/Pages/CursorPage.xaml

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CursorPage">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*">
<StackPanel Grid.ColumnSpan="2" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Cursor</TextBlock>
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}"/>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1" Margin="8 8 0 8">
<Button Cursor="{Binding CustomCursor}" Margin="0 8" Padding="16">
<TextBlock>Custom Cursor</TextBlock>
</Button>
</StackPanel>
</Grid>
</UserControl>

20
samples/ControlCatalog/Pages/CursorPage.xaml.cs

@ -0,0 +1,20 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class CursorPage : UserControl
{
public CursorPage()
{
this.InitializeComponent();
DataContext = new CursorPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

44
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class CursorPageViewModel : ViewModelBase
{
public CursorPageViewModel()
{
StandardCursors = Enum.GetValues(typeof(StandardCursorType))
.Cast<StandardCursorType>()
.Select(x => new StandardCursorModel(x))
.ToList();
var loader = AvaloniaLocator.Current.GetService<IAssetLoader>();
var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
var bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));
}
public IEnumerable<StandardCursorModel> StandardCursors { get; }
public Cursor CustomCursor { get; }
public class StandardCursorModel
{
public StandardCursorModel(StandardCursorType type)
{
Type = type;
Cursor = new Cursor(type);
}
public StandardCursorType Type { get; }
public Cursor Cursor { get; }
}
}
}

30
src/Avalonia.Base/EnumExtensions.cs

@ -11,10 +11,32 @@ namespace Avalonia
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
{
var intValue = *(int*)&value;
var intFlag = *(int*)&flag;
return (intValue & intFlag) == intFlag;
if (sizeof(T) == 1)
{
var byteValue = Unsafe.As<T, byte>(ref value);
var byteFlag = Unsafe.As<T, byte>(ref flag);
return (byteValue & byteFlag) == byteFlag;
}
else if (sizeof(T) == 2)
{
var shortValue = Unsafe.As<T, short>(ref value);
var shortFlag = Unsafe.As<T, short>(ref flag);
return (shortValue & shortFlag) == shortFlag;
}
else if (sizeof(T) == 4)
{
var intValue = Unsafe.As<T, int>(ref value);
var intFlag = Unsafe.As<T, int>(ref flag);
return (intValue & intFlag) == intFlag;
}
else if (sizeof(T) == 8)
{
var longValue = Unsafe.As<T, long>(ref value);
var longFlag = Unsafe.As<T, long>(ref flag);
return (longValue & longFlag) == longFlag;
}
else
throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");
}
}
}

4
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -372,8 +372,8 @@ namespace Avalonia.Utilities
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";
bool allowImplicit = (operatorType & OperatorType.Implicit) != 0;
bool allowExplicit = (operatorType & OperatorType.Explicit) != 0;
bool allowImplicit = operatorType.HasFlagCustom(OperatorType.Implicit);
bool allowExplicit = operatorType.HasFlagCustom(OperatorType.Explicit);
foreach (MethodInfo method in fromType.GetMethods())
{

2
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -2595,7 +2595,7 @@ namespace Avalonia.Collections
/// <returns>Whether the specified flag is set</returns>
private bool CheckFlag(CollectionViewFlags flags)
{
return (_flags & flags) != 0;
return _flags.HasFlagCustom(flags);
}
/// <summary>

6
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -0,0 +1,6 @@
Compat issues with assembly Avalonia.Controls:
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 4

2
src/Avalonia.Controls/ComboBox.cs

@ -188,7 +188,7 @@ namespace Avalonia.Controls
return;
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.KeyModifiers & KeyModifiers.Alt) != 0)))
((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public virtual void SetCursor(IPlatformHandle cursor)
public virtual void SetCursor(ICursorImpl cursor)
{
}

45
src/Avalonia.Controls/Grid.cs

@ -637,7 +637,7 @@ namespace Avalonia.Controls
/// </summary>
internal bool MeasureOverrideInProgress
{
get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); }
get { return CheckFlags(Flags.MeasureOverrideInProgress); }
set { SetFlags(value, Flags.MeasureOverrideInProgress); }
}
@ -646,7 +646,7 @@ namespace Avalonia.Controls
/// </summary>
internal bool ArrangeOverrideInProgress
{
get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); }
get { return CheckFlags(Flags.ArrangeOverrideInProgress); }
set { SetFlags(value, Flags.ArrangeOverrideInProgress); }
}
@ -2350,25 +2350,12 @@ namespace Avalonia.Controls
}
/// <summary>
/// CheckFlagsAnd returns <c>true</c> if all the flags in the
/// CheckFlags returns <c>true</c> if all the flags in the
/// given bitmask are set on the object.
/// </summary>
private bool CheckFlagsAnd(Flags flags)
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
/// <summary>
/// CheckFlagsOr returns <c>true</c> if at least one flag in the
/// given bitmask is set.
/// </summary>
/// <remarks>
/// If no bits are set in the given bitmask, the method returns
/// <c>true</c>.
/// </remarks>
private bool CheckFlagsOr(Flags flags)
{
return (flags == 0 || (_flags & flags) != 0);
return _flags.HasFlagCustom(flags);
}
private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
@ -2535,7 +2522,7 @@ namespace Avalonia.Controls
/// </summary>
private bool CellsStructureDirty
{
get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); }
get { return !CheckFlags(Flags.ValidCellsStructure); }
set { SetFlags(!value, Flags.ValidCellsStructure); }
}
@ -2544,7 +2531,7 @@ namespace Avalonia.Controls
/// </summary>
private bool ListenToNotifications
{
get { return (CheckFlagsAnd(Flags.ListenToNotifications)); }
get { return CheckFlags(Flags.ListenToNotifications); }
set { SetFlags(value, Flags.ListenToNotifications); }
}
@ -2553,7 +2540,7 @@ namespace Avalonia.Controls
/// </summary>
private bool SizeToContentU
{
get { return (CheckFlagsAnd(Flags.SizeToContentU)); }
get { return CheckFlags(Flags.SizeToContentU); }
set { SetFlags(value, Flags.SizeToContentU); }
}
@ -2562,7 +2549,7 @@ namespace Avalonia.Controls
/// </summary>
private bool SizeToContentV
{
get { return (CheckFlagsAnd(Flags.SizeToContentV)); }
get { return CheckFlags(Flags.SizeToContentV); }
set { SetFlags(value, Flags.SizeToContentV); }
}
@ -2571,7 +2558,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasStarCellsU
{
get { return (CheckFlagsAnd(Flags.HasStarCellsU)); }
get { return CheckFlags(Flags.HasStarCellsU); }
set { SetFlags(value, Flags.HasStarCellsU); }
}
@ -2580,7 +2567,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasStarCellsV
{
get { return (CheckFlagsAnd(Flags.HasStarCellsV)); }
get { return CheckFlags(Flags.HasStarCellsV); }
set { SetFlags(value, Flags.HasStarCellsV); }
}
@ -2589,7 +2576,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasGroup3CellsInAutoRows
{
get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); }
get { return CheckFlags(Flags.HasGroup3CellsInAutoRows); }
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
}
@ -2803,10 +2790,10 @@ namespace Avalonia.Controls
internal LayoutTimeSizeType SizeTypeU;
internal LayoutTimeSizeType SizeTypeV;
internal int Next;
internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
internal bool IsStarU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Star);
internal bool IsAutoU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Auto);
internal bool IsStarV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Star);
internal bool IsAutoV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Auto);
}
/// <summary>

8
src/Avalonia.Controls/ListBox.cs

@ -135,8 +135,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0);
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control));
}
}
@ -154,8 +154,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
point.Properties.IsRightButtonPressed);
}
}

41
src/Avalonia.Controls/MenuItem.cs

@ -103,6 +103,7 @@ namespace Avalonia.Controls
private bool _commandCanExecute = true;
private Popup? _popup;
private KeyGesture _hotkey;
private bool _isEmbeddedInMenu;
/// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class.
@ -112,6 +113,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
PressedMixin.Attach<MenuItem>();
CommandProperty.Changed.Subscribe(CommandChanged);
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
@ -146,7 +148,7 @@ namespace Avalonia.Controls
{
var parent = x as Control;
return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ??
Observable.Return<DefinitionBase.SharedSizeScope?>(null);
Observable.Return<DefinitionBase.SharedSizeScope?>(null);
});
this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope);
@ -274,7 +276,7 @@ namespace Avalonia.Controls
public bool IsTopLevel => Parent is Menu;
/// <inheritdoc/>
bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
/// <inheritdoc/>
IMenuElement? IMenuItem.Parent => Parent as IMenuElement;
@ -309,7 +311,7 @@ namespace Avalonia.Controls
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
}
/// <summary>
/// Opens the submenu.
@ -336,6 +338,18 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (!_isEmbeddedInMenu)
{
//Normally the Menu's IMenuInteractionHandler is sending the click events for us
//However when the item is not embedded into a menu we need to send them ourselves.
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
@ -349,6 +363,15 @@ namespace Avalonia.Controls
{
Command.CanExecuteChanged += CanExecuteChanged;
}
var parent = Parent;
while (parent is MenuItem)
{
parent = parent.Parent;
}
_isEmbeddedInMenu = parent is IMenu;
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -506,6 +529,18 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="CommandParameter"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
{
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>

2
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -98,7 +98,7 @@ namespace Avalonia.Platform
/// Sets the cursor associated with the toplevel.
/// </summary>
/// <param name="cursor">The cursor. Use null for default cursor</param>
void SetCursor(IPlatformHandle cursor);
void SetCursor(ICursorImpl cursor);
/// <summary>
/// Gets or sets a method called when the underlying implementation is destroyed.

12
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -73,20 +73,20 @@ namespace Avalonia.Platform
{
if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
return effect; // No need to check for the modifiers.
if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(RawInputModifiers.Alt))
if (effect.HasFlagCustom(DragDropEffects.Link) && modifiers.HasFlagCustom(RawInputModifiers.Alt))
return DragDropEffects.Link;
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(RawInputModifiers.Control))
if (effect.HasFlagCustom(DragDropEffects.Copy) && modifiers.HasFlagCustom(RawInputModifiers.Control))
return DragDropEffects.Copy;
return DragDropEffects.Move;
}
private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
{
if (effects.HasFlag(DragDropEffects.Copy))
if (effects.HasFlagCustom(DragDropEffects.Copy))
return StandardCursorType.DragCopy;
if (effects.HasFlag(DragDropEffects.Move))
if (effects.HasFlagCustom(DragDropEffects.Move))
return StandardCursorType.DragMove;
if (effects.HasFlag(DragDropEffects.Link))
if (effects.HasFlagCustom(DragDropEffects.Link))
return StandardCursorType.DragLink;
return StandardCursorType.No;
}
@ -161,7 +161,7 @@ namespace Avalonia.Platform
void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
{
if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
if (_initialInputModifiers.Value.HasFlagCustom(changedMouseButton))
{
var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
UpdateCursor(null, DragDropEffects.None);

21
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -253,9 +253,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
public static void ValidateEdge(this PopupAnchor edge)
{
if (((edge & PopupAnchor.Left) != 0 && (edge & PopupAnchor.Right) != 0)
||
((edge & PopupAnchor.Top) != 0 && (edge & PopupAnchor.Bottom) != 0))
if (edge.HasFlagCustom(PopupAnchor.Left) && edge.HasFlagCustom(PopupAnchor.Right) ||
edge.HasFlagCustom(PopupAnchor.Top) && edge.HasFlagCustom(PopupAnchor.Bottom))
throw new ArgumentException("Opposite edges specified");
}
@ -266,25 +265,25 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public static PopupAnchor Flip(this PopupAnchor edge)
{
var hmask = PopupAnchor.Left | PopupAnchor.Right;
var vmask = PopupAnchor.Top | PopupAnchor.Bottom;
if ((edge & hmask) != 0)
edge ^= hmask;
if ((edge & vmask) != 0)
edge ^= vmask;
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask;
if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask;
return edge;
}
public static PopupAnchor FlipX(this PopupAnchor edge)
{
if ((edge & PopupAnchor.HorizontalMask) != 0)
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask;
return edge;
}
public static PopupAnchor FlipY(this PopupAnchor edge)
{
if ((edge & PopupAnchor.VerticalMask) != 0)
if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask;
return edge;
}

48
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -42,16 +42,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge)
{
double x, y;
if ((edge & PopupAnchor.Left) != 0)
if (edge.HasFlagCustom(PopupAnchor.Left))
x = anchorRect.X;
else if ((edge & PopupAnchor.Right) != 0)
else if (edge.HasFlagCustom(PopupAnchor.Right))
x = anchorRect.Right;
else
x = anchorRect.X + anchorRect.Width / 2;
if ((edge & PopupAnchor.Top) != 0)
if (edge.HasFlagCustom(PopupAnchor.Top))
y = anchorRect.Y;
else if ((edge & PopupAnchor.Bottom) != 0)
else if (edge.HasFlagCustom(PopupAnchor.Bottom))
y = anchorRect.Bottom;
else
y = anchorRect.Y + anchorRect.Height / 2;
@ -61,16 +61,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity)
{
double x, y;
if ((gravity & PopupGravity.Left) != 0)
if (gravity.HasFlagCustom(PopupGravity.Left))
x = -size.Width;
else if ((gravity & PopupGravity.Right) != 0)
else if (gravity.HasFlagCustom(PopupGravity.Right))
x = 0;
else
x = -size.Width / 2;
if ((gravity & PopupGravity.Top) != 0)
if (gravity.HasFlagCustom(PopupGravity.Top))
y = -size.Height;
else if ((gravity & PopupGravity.Bottom) != 0)
else if (gravity.HasFlagCustom(PopupGravity.Bottom))
y = 0;
else
y = -size.Height / 2;
@ -125,21 +125,13 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask)
{
if ((edge & PopupAnchor.Left) != 0
&& rc.X < bounds.X)
return false;
if ((edge & PopupAnchor.Top) != 0
&& rc.Y < bounds.Y)
return false;
if ((edge & PopupAnchor.Right) != 0
&& rc.Right > bounds.Right)
return false;
if ((edge & PopupAnchor.Bottom) != 0
&& rc.Bottom > bounds.Bottom)
if (edge.HasFlagCustom(PopupAnchor.Left) && rc.X < bounds.X ||
edge.HasFlagCustom(PopupAnchor.Top) && rc.Y < bounds.Y ||
edge.HasFlagCustom(PopupAnchor.Right) && rc.Right > bounds.Right ||
edge.HasFlagCustom(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom)
{
return false;
}
return true;
}
@ -155,7 +147,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.HorizontalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipX))
{
var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
if (FitsInBounds(flipped, PopupAnchor.HorizontalMask))
@ -163,7 +155,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideX))
{
geo = geo.WithX(Math.Max(geo.X, bounds.X));
if (geo.Right > bounds.Right)
@ -171,7 +163,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// Resize the rect horizontally if allowed.
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeX) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeX))
{
var unconstrainedRect = geo;
@ -194,7 +186,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.VerticalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipY))
{
var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
if (FitsInBounds(flipped, PopupAnchor.VerticalMask))
@ -202,7 +194,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideY))
{
geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
if (geo.Bottom > bounds.Bottom)
@ -210,7 +202,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// Resize the rect vertically if allowed.
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeY) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeY))
{
var unconstrainedRect = geo;

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

@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set.
/// </summary>
protected bool AlwaysSelected => (SelectionMode & SelectionMode.AlwaysSelected) != 0;
protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected);
/// <inheritdoc/>
public override void BeginInit()
@ -487,7 +487,7 @@ namespace Avalonia.Controls.Primitives
if (ItemCount > 0 &&
Match(keymap.SelectAll) &&
SelectionMode.HasFlag(SelectionMode.Multiple))
SelectionMode.HasFlagCustom(SelectionMode.Multiple))
{
Selection.SelectAll();
e.Handled = true;
@ -577,8 +577,8 @@ namespace Avalonia.Controls.Primitives
}
var mode = SelectionMode;
var multi = (mode & SelectionMode.Multiple) != 0;
var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0);
var multi = mode.HasFlagCustom(SelectionMode.Multiple);
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
var range = multi && rangeModifier;
if (!select)

4
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

@ -53,8 +53,8 @@ namespace Avalonia.Controls
{
return _owner.GetElementImpl(
index,
(options & ElementRealizationOptions.ForceCreate) != 0,
(options & ElementRealizationOptions.SuppressAutoRecycle) != 0);
options.HasFlagCustom(ElementRealizationOptions.ForceCreate),
options.HasFlagCustom(ElementRealizationOptions.SuppressAutoRecycle));
}
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);

4
src/Avalonia.Controls/TextBox.cs

@ -585,7 +585,7 @@ namespace Avalonia.Controls
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
bool DetectSelection() => e.KeyModifiers.HasFlagCustom(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{
@ -703,7 +703,7 @@ namespace Avalonia.Controls
}
else
{
bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
bool hasWholeWordModifiers = modifiers.HasFlagCustom(keymap.WholeWordTextActionModifiers);
switch (e.Key)
{
case Key.Left:

2
src/Avalonia.Controls/TopLevel.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls
this.GetObservable(PointerOverElementProperty)
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl));
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{

12
src/Avalonia.Controls/TreeView.cs

@ -412,7 +412,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0);
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift));
}
}
@ -521,8 +521,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
point.Properties.IsRightButtonPressed);
}
}
@ -558,9 +558,9 @@ namespace Avalonia.Controls
}
var mode = SelectionMode;
var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0;
var multi = (mode & SelectionMode.Multiple) != 0;
var range = multi && selectedContainer != null && rangeModifier;
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
var multi = mode.HasFlagCustom(SelectionMode.Multiple);
var range = multi && rangeModifier && selectedContainer != null;
if (rightButton)
{

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -47,7 +47,7 @@ namespace Avalonia.DesignerSupport.Remote
var threading = new InternalPlatformThreadingInterface();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(threading)

12
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -73,7 +73,7 @@ namespace Avalonia.DesignerSupport.Remote
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}
@ -192,9 +192,15 @@ namespace Avalonia.DesignerSupport.Remote
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
}
class CursorFactoryStub : IStandardCursorFactory
class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB");
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
private class CursorStub : ICursorImpl
{
public void Dispose() { }
}
}
class IconLoaderStub : IPlatformIconLoader

8
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop
return null;
var lst = new List<string>();
var mod = item.Gesture;
if ((mod.KeyModifiers & KeyModifiers.Control) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Control))
lst.Add("Control");
if ((mod.KeyModifiers & KeyModifiers.Alt) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
lst.Add("Alt");
if ((mod.KeyModifiers & KeyModifiers.Shift) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
lst.Add("Shift");
if ((mod.KeyModifiers & KeyModifiers.Meta) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
lst.Add("Super");
lst.Add(item.Gesture.Key.ToString());
return new[] { lst.ToArray() };

4
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -33,11 +33,11 @@ namespace Avalonia.Headless.Vnc
{
Window?.MouseMove(pt);
foreach (var btn in CheckedButtons)
if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn))
if (_previousButtons.HasFlagCustom(btn) && !buttons.HasFlagCustom(btn))
Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons)
if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn))
if (!_previousButtons.HasFlagCustom(btn) && buttons.HasFlagCustom(btn))
Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons;
}, DispatcherPriority.Input);

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -58,7 +58,7 @@ namespace Avalonia.Headless
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToSingleton<HeadlessSystemDialogsStub>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()

8
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -52,12 +52,14 @@ namespace Avalonia.Headless
}
}
class HeadlessCursorFactoryStub : IStandardCursorFactory
class HeadlessCursorFactoryStub : ICursorFactory
{
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
public IPlatformHandle GetCursor(StandardCursorType cursorType)
private class CursorStub : ICursorImpl
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
public void Dispose() { }
}
}

2
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -67,7 +67,7 @@ namespace Avalonia.Headless
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -177,7 +177,7 @@ namespace Avalonia.Input
{
bool menuIsOpen = MainMenu?.IsOpen == true;
if ((e.KeyModifiers & KeyModifiers.Alt) != 0 || menuIsOpen)
if (e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt) || menuIsOpen)
{
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.

4
src/Avalonia.Input/ApiCompatBaseline.txt

@ -0,0 +1,4 @@
Compat issues with assembly Avalonia.Input:
MembersMustExist : Member 'public Avalonia.Platform.IPlatformHandle Avalonia.Input.Cursor.PlatformCursor.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract.
Total Issues: 2

39
src/Avalonia.Input/Cursors.cs → src/Avalonia.Input/Cursor.cs

@ -1,15 +1,11 @@
using System;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Input
{
/*
=========================================================================================
NOTE: Cursors are NOT disposable and are cached in platform implementation.
To support loading custom cursors some measures about that should be taken beforehand
=========================================================================================
*/
public enum StandardCursorType
{
Arrow,
@ -46,21 +42,28 @@ namespace Avalonia.Input
// SizeNorthEastSouthWest,
}
public class Cursor
public class Cursor : IDisposable
{
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
internal Cursor(IPlatformHandle platformCursor)
internal Cursor(ICursorImpl platformImpl)
{
PlatformCursor = platformCursor;
PlatformImpl = platformImpl;
}
public Cursor(StandardCursorType cursorType)
: this(GetCursor(cursorType))
: this(GetCursorFactory().GetCursor(cursorType))
{
}
public Cursor(IBitmap cursor, PixelPoint hotSpot)
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot))
{
}
public IPlatformHandle PlatformCursor { get; }
public ICursorImpl PlatformImpl { get; }
public void Dispose() => PlatformImpl.Dispose();
public static Cursor Parse(string s)
{
@ -69,16 +72,10 @@ namespace Avalonia.Input
throw new ArgumentException($"Unrecognized cursor type '{s}'.");
}
private static IPlatformHandle GetCursor(StandardCursorType type)
private static ICursorFactory GetCursorFactory()
{
var platform = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
if (platform == null)
{
throw new Exception("Could not create Cursor: IStandardCursorFactory not registered.");
}
return platform.GetCursor(type);
return AvaloniaLocator.Current.GetService<ICursorFactory>() ??
throw new Exception("Could not create Cursor: ICursorFactory not registered.");
}
}
}

12
src/Avalonia.Input/Platform/ICursorFactory.cs

@ -0,0 +1,12 @@
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
public interface ICursorFactory
{
ICursorImpl GetCursor(StandardCursorType cursorType);
ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot);
}
}

14
src/Avalonia.Input/Platform/ICursorImpl.cs

@ -0,0 +1,14 @@
using System;
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
/// <summary>
/// Represents a platform implementation of a <see cref="Cursor"/>.
/// </summary>
public interface ICursorImpl : IDisposable
{
}
}

9
src/Avalonia.Input/Platform/IStandardCursorFactory.cs

@ -1,9 +0,0 @@
using Avalonia.Input;
namespace Avalonia.Platform
{
public interface IStandardCursorFactory
{
IPlatformHandle GetCursor(StandardCursorType cursorType);
}
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -97,7 +97,7 @@ namespace Avalonia.Native
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToConstant(this)

25
src/Avalonia.Native/Cursor.cs

@ -1,11 +1,12 @@
using System;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Native.Interop;
namespace Avalonia.Native
{
class AvaloniaNativeCursor : IPlatformHandle, IDisposable
class AvaloniaNativeCursor : ICursorImpl, IDisposable
{
public IAvnCursor Cursor { get; private set; }
public IntPtr Handle => IntPtr.Zero;
@ -24,7 +25,7 @@ namespace Avalonia.Native
}
}
class CursorFactory : IStandardCursorFactory
class CursorFactory : ICursorFactory
{
IAvnCursorFactory _native;
@ -33,10 +34,28 @@ namespace Avalonia.Native
_native = native;
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
var cursor = _native.GetCursor((AvnStandardCursorType)cursorType);
return new AvaloniaNativeCursor( cursor );
}
public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
using(var ms = new MemoryStream())
{
cursor.Save(ms);
var imageData = ms.ToArray();
fixed(void* ptr = imageData)
{
var avnCursor = _native.CreateCustomCursor(ptr, new IntPtr(imageData.Length),
new AvnPixelSize { Width = hotSpot.X, Height = hotSpot.Y });
return new AvaloniaNativeCursor(avnCursor);
}
}
}
}
}

6
src/Avalonia.Native/WindowImplBase.cs

@ -53,7 +53,7 @@ namespace Avalonia.Native
private bool _gpu = false;
private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private readonly IStandardCursorFactory _cursorFactory;
private readonly ICursorFactory _cursorFactory;
private Size _savedLogicalSize;
private Size _lastRenderedLogicalSize;
private double _savedScaling;
@ -68,7 +68,7 @@ namespace Avalonia.Native
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
}
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
@ -398,7 +398,7 @@ namespace Avalonia.Native
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
if (_native == null)
{

1
src/Avalonia.Native/avn.idl

@ -619,6 +619,7 @@ interface IAvnCursor : IUnknown
interface IAvnCursorFactory : IUnknown
{
HRESULT GetCursor(AvnStandardCursorType cursorType, IAvnCursor** retOut);
HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut);
}
[uuid(60452465-8616-40af-bc00-042e69828ce7)]

56
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs

@ -1,56 +0,0 @@
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class BreakPairTable
{
private static readonly byte[][] s_breakPairTable =
{
new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
};
public static PairBreakType Map(LineBreakClass first, LineBreakClass second)
{
return (PairBreakType)s_breakPairTable[(int)first][(int)second];
}
}
internal enum PairBreakType : byte
{
DI = 0, // Direct break opportunity
IN = 1, // Indirect break opportunity
CI = 2, // Indirect break opportunity for combining marks
CP = 3, // Prohibited break for combining marks
PR = 4 // Prohibited break
}
}

27
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs

@ -9,37 +9,40 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// </summary>
public static readonly Codepoint ReplacementCodepoint = new Codepoint('\uFFFD');
private readonly int _value;
public Codepoint(int value)
{
_value = value;
Value = value;
}
/// <summary>
/// Get the codepoint's value.
/// </summary>
public int Value { get; }
/// <summary>
/// Gets the <see cref="Unicode.GeneralCategory"/>.
/// </summary>
public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(_value);
public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(Value);
/// <summary>
/// Gets the <see cref="Unicode.Script"/>.
/// </summary>
public Script Script => UnicodeData.GetScript(_value);
public Script Script => UnicodeData.GetScript(Value);
/// <summary>
/// Gets the <see cref="Unicode.BiDiClass"/>.
/// </summary>
public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(_value);
public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(Value);
/// <summary>
/// Gets the <see cref="Unicode.LineBreakClass"/>.
/// </summary>
public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(_value);
public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(Value);
/// <summary>
/// Gets the <see cref="GraphemeBreakClass"/>.
/// </summary>
public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(_value);
public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(Value);
/// <summary>
/// Determines whether this <see cref="Codepoint"/> is a break char.
@ -51,7 +54,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
get
{
switch (_value)
switch (Value)
{
case '\u000A':
case '\u000B':
@ -93,12 +96,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
public static implicit operator int(Codepoint codepoint)
{
return codepoint._value;
return codepoint.Value;
}
public static implicit operator uint(Codepoint codepoint)
{
return (uint)codepoint._value;
return (uint)codepoint.Value;
}
/// <summary>
@ -112,7 +115,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
count = 1;
if (index > text.Length)
if (index >= text.Length)
{
return ReplacementCodepoint;
}

540
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@ -1,160 +1,460 @@
// RichTextKit
// Copyright © 2019 Topten Software. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// Ported from: https://github.com/foliojs/linebreak
// Copied from: https://github.com/toptensoftware/RichTextKit
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
/// <summary>
/// Implementation of the Unicode Line Break Algorithm
/// Implementation of the Unicode Line Break Algorithm. UAX:14
/// <see href="https://www.unicode.org/reports/tr14/tr14-37.html"/>
/// </summary>
public ref struct LineBreakEnumerator
{
// State
private readonly ReadOnlySlice<char> _text;
private int _pos;
private int _lastPos;
private LineBreakClass? _curClass;
private LineBreakClass? _nextClass;
private int _position;
private int _lastPosition;
private LineBreakClass _currentClass;
private LineBreakClass _nextClass;
private bool _first;
private int _alphaNumericCount;
private bool _lb8a;
private bool _lb21a;
private bool _lb22ex;
private bool _lb24ex;
private bool _lb25ex;
private bool _lb30;
private int _lb30a;
private bool _lb31;
public LineBreakEnumerator(ReadOnlySlice<char> text)
: this()
{
_text = text;
_pos = 0;
_lastPos = 0;
_curClass = null;
_nextClass = null;
Current = default;
_position = 0;
_currentClass = LineBreakClass.Unknown;
_nextClass = LineBreakClass.Unknown;
_first = true;
_lb8a = false;
_lb21a = false;
_lb22ex = false;
_lb24ex = false;
_lb25ex = false;
_alphaNumericCount = 0;
_lb31 = false;
_lb30 = false;
_lb30a = 0;
}
public LineBreak Current { get; private set; }
public bool MoveNext()
{
// get the first char if we're at the beginning of the string
if (!_curClass.HasValue)
// Get the first char if we're at the beginning of the string.
if (_first)
{
_curClass = PeekCharClass() == LineBreakClass.Space ? LineBreakClass.WordJoiner : MapFirst(ReadCharClass());
var firstClass = NextCharClass();
_first = false;
_currentClass = MapFirst(firstClass);
_nextClass = firstClass;
_lb8a = firstClass == LineBreakClass.ZWJ;
_lb30a = 0;
}
while (_pos < _text.Length)
while (_position < _text.Length)
{
_lastPos = _pos;
_lastPosition = _position;
var lastClass = _nextClass;
_nextClass = ReadCharClass();
_nextClass = NextCharClass();
// explicit newline
if (_curClass.HasValue && (_curClass == LineBreakClass.MandatoryBreak || _curClass == LineBreakClass.CarriageReturn && _nextClass != LineBreakClass.LineFeed))
// Explicit newline
switch (_currentClass)
{
_curClass = MapFirst(MapClass(_nextClass.Value));
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
case LineBreakClass.MandatoryBreak:
case LineBreakClass.CarriageReturn when _nextClass != LineBreakClass.LineFeed:
{
_currentClass = MapFirst(_nextClass);
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition, true);
return true;
}
}
var shouldBreak = GetSimpleBreak() ?? (bool?)GetPairTableBreak(lastClass);
// Rule LB8a
_lb8a = _nextClass == LineBreakClass.ZWJ;
if (shouldBreak.Value)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition);
return true;
}
}
// handle classes not handled by the pair table
LineBreakClass? cur = null;
switch (_nextClass.Value)
if (_position >= _text.Length)
{
if (_lastPosition < _text.Length)
{
case LineBreakClass.Space:
cur = _curClass;
break;
_lastPosition = _text.Length;
var required = false;
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.CarriageReturn when _nextClass != LineBreakClass.LineFeed:
required = true;
break;
}
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition, required);
return true;
}
}
Current = default;
return false;
}
private static LineBreakClass MapClass(Codepoint cp)
{
if (cp.Value == 327685)
{
return LineBreakClass.Alphabetic;
}
// LB 1
// ==========================================
// Resolved Original General_Category
// ==========================================
// AL AI, SG, XX Any
// CM SA Only Mn or Mc
// AL SA Any except Mn and Mc
// NS CJ Any
switch (cp.LineBreakClass)
{
case LineBreakClass.Ambiguous:
case LineBreakClass.Surrogate:
case LineBreakClass.Unknown:
return LineBreakClass.Alphabetic;
case LineBreakClass.ComplexContext:
return cp.GeneralCategory == GeneralCategory.NonspacingMark || cp.GeneralCategory == GeneralCategory.SpacingMark
? LineBreakClass.CombiningMark
: LineBreakClass.Alphabetic;
case LineBreakClass.ConditionalJapaneseStarter:
return LineBreakClass.Nonstarter;
default:
return cp.LineBreakClass;
}
}
private static LineBreakClass MapFirst(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
return LineBreakClass.MandatoryBreak;
case LineBreakClass.Space:
return LineBreakClass.WordJoiner;
default:
return c;
}
}
private static bool IsAlphaNumeric(LineBreakClass cls)
=> cls == LineBreakClass.Alphabetic
|| cls == LineBreakClass.HebrewLetter
|| cls == LineBreakClass.Numeric;
private LineBreakClass PeekNextCharClass()
{
var cp = Codepoint.ReadAt(_text, _position, out _);
return MapClass(cp);
}
// Get the next character class
private LineBreakClass NextCharClass()
{
var cp = Codepoint.ReadAt(_text, _position, out var count);
var cls = MapClass(cp);
_position += count;
// Keep track of alphanumeric + any combining marks.
// This is used for LB22 and LB30.
if (IsAlphaNumeric(_currentClass) || _alphaNumericCount > 0 && cls == LineBreakClass.CombiningMark)
{
_alphaNumericCount++;
}
// Track combining mark exceptions. LB22
if (cls == LineBreakClass.CombiningMark)
{
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
cur = LineBreakClass.MandatoryBreak;
break;
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
case LineBreakClass.CarriageReturn:
cur = LineBreakClass.CarriageReturn;
_lb22ex = true;
break;
}
}
// Track combining mark exceptions. LB31
if (_first && cls == LineBreakClass.CombiningMark)
{
_lb31 = true;
}
if (cls == LineBreakClass.CombiningMark)
{
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
cur = LineBreakClass.BreakAfter;
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
case LineBreakClass.CarriageReturn:
case LineBreakClass.ZWJ:
_lb31 = true;
break;
}
}
if (_first
&& (cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space))
{
_lb31 = true;
}
if (_currentClass == LineBreakClass.Alphabetic &&
(cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space))
{
_lb31 = true;
}
// Reset LB31 if next is U+0028 (Left Opening Parenthesis)
if (_lb31
&& _currentClass != LineBreakClass.PostfixNumeric
&& _currentClass != LineBreakClass.PrefixNumeric
&& cls == LineBreakClass.OpenPunctuation && cp.Value == 0x0028)
{
_lb31 = false;
}
// Rule LB24
if (_first && (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.CloseParenthesis))
{
_lb24ex = true;
}
if (cur != null)
// Rule LB25
if (_first
&& (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.InfixNumeric || cls == LineBreakClass.BreakSymbols))
{
_lb25ex = true;
}
if (cls == LineBreakClass.Space || cls == LineBreakClass.WordJoiner || cls == LineBreakClass.Alphabetic)
{
var next = PeekNextCharClass();
if (next == LineBreakClass.ClosePunctuation || next == LineBreakClass.InfixNumeric || next == LineBreakClass.BreakSymbols)
{
_curClass = cur;
_lb25ex = true;
}
}
// AlphaNumeric + and combining marks can break for OP except.
// - U+0028 (Left Opening Parenthesis)
// - U+005B (Opening Square Bracket)
// - U+007B (Left Curly Bracket)
// See custom colums|rules in the text pair table.
// https://www.unicode.org/Public/13.0.0/ucd/auxiliary/LineBreakTest.html
_lb30 = _alphaNumericCount > 0
&& cls == LineBreakClass.OpenPunctuation
&& cp.Value != 0x0028
&& cp.Value != 0x005B
&& cp.Value != 0x007B;
return cls;
}
private bool? GetSimpleBreak()
{
// handle classes not handled by the pair table
switch (_nextClass)
{
case LineBreakClass.Space:
return false;
if (_nextClass.Value == LineBreakClass.MandatoryBreak)
case LineBreakClass.MandatoryBreak:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
_currentClass = LineBreakClass.MandatoryBreak;
return false;
case LineBreakClass.CarriageReturn:
_currentClass = LineBreakClass.CarriageReturn;
return false;
}
return null;
}
private bool GetPairTableBreak(LineBreakClass lastClass)
{
// If not handled already, use the pair table
bool shouldBreak = false;
switch (LineBreakPairTable.Table[(int)_currentClass][(int)_nextClass])
{
case LineBreakPairTable.DIBRK: // Direct break
shouldBreak = true;
break;
// TODO: Rewrite this so that it defaults to true and rules are set as exceptions.
case LineBreakPairTable.INBRK: // Possible indirect break
// LB31
if (_lb31 && _nextClass == LineBreakClass.OpenPunctuation)
{
_lastPos = _pos;
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true;
shouldBreak = true;
_lb31 = false;
break;
}
continue;
}
// if not handled already, use the pair table
var shouldBreak = false;
switch (BreakPairTable.Map(_curClass.Value,_nextClass.Value))
{
case PairBreakType.DI: // Direct break
// LB30
if (_lb30)
{
shouldBreak = true;
_lb30 = false;
_alphaNumericCount = 0;
break;
}
case PairBreakType.IN: // possible indirect break
shouldBreak = lastClass.HasValue && lastClass.Value == LineBreakClass.Space;
// LB25
if (_lb25ex && (_nextClass == LineBreakClass.PrefixNumeric || _nextClass == LineBreakClass.Numeric))
{
shouldBreak = true;
_lb25ex = false;
break;
}
case PairBreakType.CI:
shouldBreak = lastClass.HasValue && lastClass.Value == LineBreakClass.Space;
if (!shouldBreak)
{
continue;
}
// LB24
if (_lb24ex && (_nextClass == LineBreakClass.PostfixNumeric || _nextClass == LineBreakClass.PrefixNumeric))
{
shouldBreak = true;
_lb24ex = false;
break;
}
// LB18
shouldBreak = lastClass == LineBreakClass.Space;
break;
case LineBreakPairTable.CIBRK:
shouldBreak = lastClass == LineBreakClass.Space;
if (!shouldBreak)
{
return false;
}
case PairBreakType.CP: // prohibited for combining marks
if (!lastClass.HasValue || lastClass.Value != LineBreakClass.Space)
break;
case LineBreakPairTable.CPBRK: // prohibited for combining marks
if (lastClass != LineBreakClass.Space)
{
return false;
}
break;
case LineBreakPairTable.PRBRK:
break;
}
// Rule LB22
if (_nextClass == LineBreakClass.Inseparable)
{
switch (lastClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
// Allow break
break;
case LineBreakClass.CombiningMark:
if (_lb22ex)
{
continue;
// Allow break
_lb22ex = false;
break;
}
shouldBreak = false;
break;
default:
shouldBreak = false;
break;
}
}
_curClass = _nextClass;
if (_lb8a)
{
shouldBreak = false;
}
if (shouldBreak)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos);
return true;
}
// Rule LB21a
if (_lb21a && (_currentClass == LineBreakClass.Hyphen || _currentClass == LineBreakClass.BreakAfter))
{
shouldBreak = false;
_lb21a = false;
}
else
{
_lb21a = _currentClass == LineBreakClass.HebrewLetter;
}
if (_pos >= _text.Length)
// Rule LB30a
if (_currentClass == LineBreakClass.RegionalIndicator)
{
if (_lastPos < _text.Length)
_lb30a++;
if (_lb30a == 2 && _nextClass == LineBreakClass.RegionalIndicator)
{
_lastPos = _text.Length;
var cls = Codepoint.ReadAt(_text, _text.Length - 1, out _).LineBreakClass;
bool required = cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed || cls == LineBreakClass.CarriageReturn;
Current = new LineBreak(FindPriorNonWhitespace(_text.Length), _text.Length, required);
return true;
shouldBreak = true;
_lb30a = 0;
}
}
else
{
_lb30a = 0;
}
return false;
}
_currentClass = _nextClass;
return shouldBreak;
}
private int FindPriorNonWhitespace(int from)
{
if (from > 0)
@ -163,7 +463,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
var cls = cp.LineBreakClass;
if (cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed || cls == LineBreakClass.CarriageReturn)
if (cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed ||
cls == LineBreakClass.CarriageReturn)
{
from -= count;
}
@ -184,61 +485,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
break;
}
}
return from;
}
// Get the next character class
private LineBreakClass ReadCharClass()
{
var cp = Codepoint.ReadAt(_text, _pos, out var count);
_pos += count;
return MapClass(cp.LineBreakClass);
}
private LineBreakClass PeekCharClass()
{
return MapClass(Codepoint.ReadAt(_text, _pos, out _).LineBreakClass);
}
private static LineBreakClass MapClass(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.Ambiguous:
return LineBreakClass.Alphabetic;
case LineBreakClass.ComplexContext:
case LineBreakClass.Surrogate:
case LineBreakClass.Unknown:
return LineBreakClass.Alphabetic;
case LineBreakClass.ConditionalJapaneseStarter:
return LineBreakClass.Nonstarter;
default:
return c;
}
}
private static LineBreakClass MapFirst(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
return LineBreakClass.MandatoryBreak;
case LineBreakClass.ContingentBreak:
return LineBreakClass.BreakAfter;
case LineBreakClass.Space:
return LineBreakClass.WordJoiner;
default:
return c;
}
return from;
}
}
}

74
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class LineBreakPairTable
{
/// <summary>
/// Direct break opportunity
/// </summary>
public const byte DIBRK = 0;
/// <summary>
/// Indirect break opportunity
/// </summary>
public const byte INBRK = 1;
/// <summary>
/// Indirect break opportunity for combining marks
/// </summary>
public const byte CIBRK = 2;
/// <summary>
/// Prohibited break for combining marks
/// </summary>
public const byte CPBRK = 3;
/// <summary>
/// Prohibited break
/// </summary>
public const byte PRBRK = 4;
// Based on example pair table from https://www.unicode.org/reports/tr14/tr14-37.html#Table2
// - ZWJ special processing for LB8a
// - CB manually added as per Rule LB20
public static byte[][] Table { get; } = {
// . OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB
new[] { PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, CPBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK }, // OP
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CP
new[] { PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // QU
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // GL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // NS
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // EX
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, DIBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // SY
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // IS
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK }, // PR
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // PO
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // NU
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // AL
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // HL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // ID
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // IN
new[] { DIBRK, PRBRK, PRBRK, INBRK, DIBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // HY
new[] { DIBRK, PRBRK, PRBRK, INBRK, DIBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // BA
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK }, // BB
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, PRBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // B2
new[] { DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK }, // ZW
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CM
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // WJ
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // H2
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // H3
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JV
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JT
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, INBRK, DIBRK }, // RI
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK }, // EB
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // EM
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // ZWJ
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, DIBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK } // CB
};
}
}

73
src/Avalonia.X11/X11CursorFactory.cs

@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
class X11CursorFactory : ICursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
@ -51,7 +56,7 @@ namespace Avalonia.X11
.ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id));
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
IntPtr handle;
if (cursorType == StandardCursorType.None)
@ -64,7 +69,12 @@ namespace Avalonia.X11
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
}
return new PlatformHandle(handle, "XCURSOR");
return new CursorImpl(handle);
}
public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
return new XImageCursor(_display, cursor, hotSpot);
}
private static IntPtr GetNullCursor(IntPtr display)
@ -74,5 +84,62 @@ namespace Avalonia.X11
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1);
return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
}
private unsafe class XImageCursor : CursorImpl, IFramebufferPlatformSurface, IPlatformHandle
{
private readonly PixelSize _pixelSize;
private readonly IUnmanagedBlob _blob;
public XImageCursor(IntPtr display, IBitmapImpl bitmap, PixelPoint hotSpot)
{
var size = Marshal.SizeOf<XcursorImage>() +
(bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4);
_pixelSize = bitmap.PixelSize;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
var image = (XcursorImage*)_blob.Address;
image->version = 1;
image->size = Marshal.SizeOf<XcursorImage>();
image->width = bitmap.PixelSize.Width;
image->height = bitmap.PixelSize.Height;
image->xhot = hotSpot.X;
image->yhot = hotSpot.Y;
image->pixels = (IntPtr)(image + 1);
using (var renderTarget = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[] { this }))
using (var ctx = renderTarget.CreateDrawingContext(null))
{
var r = new Rect(_pixelSize.ToSize(1));
ctx.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, r, r);
}
Handle = XLib.XcursorImageLoadCursor(display, _blob.Address);
}
public string HandleDescriptor => "XCURSOR";
public override void Dispose()
{
XLib.XcursorImageDestroy(Handle);
_blob.Dispose();
}
public ILockedFramebuffer Lock()
{
return new LockedFramebuffer(
_blob.Address + Marshal.SizeOf<XcursorImage>(),
_pixelSize, _pixelSize.Width * 4,
new Vector(96, 96), PixelFormat.Bgra8888, null);
}
}
}
class CursorImpl : ICursorImpl
{
public CursorImpl() { }
public CursorImpl(IntPtr handle) => Handle = handle;
public IntPtr Handle { get; protected set; }
public virtual void Dispose() { }
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -71,7 +71,7 @@ namespace Avalonia.X11
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))

2
src/Avalonia.X11/X11Structs.cs

@ -1693,7 +1693,7 @@ namespace Avalonia.X11 {
[StructLayout (LayoutKind.Sequential)]
internal struct XcursorImage
{
private int version;
public int version;
public int size; /* nominal size for matching */
public int width; /* actual width */
public int height; /* actual height */

4
src/Avalonia.X11/X11Window.Ime.cs

@ -96,14 +96,14 @@ namespace Avalonia.X11
void HandleKeyEvent(ref XEvent ev)
{
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
var index = ev.KeyEvent.state.HasFlagCustom(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
if (ev.KeyEvent.state.HasFlagCustom(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();

26
src/Avalonia.X11/X11Window.cs

@ -639,23 +639,23 @@ namespace Avalonia.X11
RawInputModifiers TranslateModifiers(XModifierMask state)
{
var rv = default(RawInputModifiers);
if (state.HasFlag(XModifierMask.Button1Mask))
if (state.HasFlagCustom(XModifierMask.Button1Mask))
rv |= RawInputModifiers.LeftMouseButton;
if (state.HasFlag(XModifierMask.Button2Mask))
if (state.HasFlagCustom(XModifierMask.Button2Mask))
rv |= RawInputModifiers.RightMouseButton;
if (state.HasFlag(XModifierMask.Button3Mask))
if (state.HasFlagCustom(XModifierMask.Button3Mask))
rv |= RawInputModifiers.MiddleMouseButton;
if (state.HasFlag(XModifierMask.Button4Mask))
if (state.HasFlagCustom(XModifierMask.Button4Mask))
rv |= RawInputModifiers.XButton1MouseButton;
if (state.HasFlag(XModifierMask.Button5Mask))
if (state.HasFlagCustom(XModifierMask.Button5Mask))
rv |= RawInputModifiers.XButton2MouseButton;
if (state.HasFlag(XModifierMask.ShiftMask))
if (state.HasFlagCustom(XModifierMask.ShiftMask))
rv |= RawInputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
if (state.HasFlagCustom(XModifierMask.ControlMask))
rv |= RawInputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
if (state.HasFlagCustom(XModifierMask.Mod1Mask))
rv |= RawInputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
if (state.HasFlagCustom(XModifierMask.Mod4Mask))
rv |= RawInputModifiers.Meta;
return rv;
}
@ -872,15 +872,13 @@ namespace Avalonia.X11
UpdateSizeHints(null);
}
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
if (cursor == null)
XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor);
else
else if (cursor is CursorImpl impl)
{
if (cursor.HandleDescriptor != "XCURSOR")
throw new ArgumentException("Expected XCURSOR handle type");
XDefineCursor(_x11.Display, _handle, cursor.Handle);
XDefineCursor(_x11.Display, _handle, impl.Handle);
}
}

10
src/Avalonia.X11/XI2Manager.cs

@ -342,13 +342,13 @@ namespace Avalonia.X11
Type = ev->evtype;
Timestamp = (ulong)ev->time.ToInt64();
var state = (XModifierMask)ev->mods.Effective;
if (state.HasFlag(XModifierMask.ShiftMask))
if (state.HasFlagCustom(XModifierMask.ShiftMask))
Modifiers |= RawInputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
if (state.HasFlagCustom(XModifierMask.ControlMask))
Modifiers |= RawInputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
if (state.HasFlagCustom(XModifierMask.Mod1Mask))
Modifiers |= RawInputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
if (state.HasFlagCustom(XModifierMask.Mod4Mask))
Modifiers |= RawInputModifiers.Meta;
Modifiers |= ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask);
@ -364,7 +364,7 @@ namespace Avalonia.X11
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
Detail = ev->detail;
Emulated = ev->flags.HasFlag(XiDeviceEventFlags.XIPointerEmulated);
Emulated = ev->flags.HasFlagCustom(XiDeviceEventFlags.XIPointerEmulated);
}
}

7
src/Avalonia.X11/XLib.cs

@ -20,6 +20,7 @@ namespace Avalonia.X11
const string libX11Randr = "libXrandr.so.2";
const string libX11Ext = "libXext.so.6";
const string libXInput = "libXi.so.6";
const string libXCursor = "libXcursor.so.1";
[DllImport(libX11)]
public static extern IntPtr XOpenDisplay(IntPtr display);
@ -569,6 +570,12 @@ namespace Avalonia.X11
[DllImport(libXInput)]
public static extern void XIFreeDeviceInfo(XIDeviceInfo* info);
[DllImport(libXCursor)]
public static extern IntPtr XcursorImageLoadCursor(IntPtr display, IntPtr image);
[DllImport(libXCursor)]
public static extern IntPtr XcursorImageDestroy(IntPtr image);
public static void XISetMask(ref int mask, XiEventType ev)
{
mask |= (1 << (int)ev);

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -57,7 +57,7 @@ namespace Avalonia.LinuxFramebuffer
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}

9
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Threading;
using Avalonia.Controls;
@ -38,7 +38,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
@ -79,6 +79,11 @@ namespace Avalonia.LinuxFramebuffer
tl.Prepare();
_topLevel = tl;
_topLevel.Renderer.Start();
if (_topLevel is IFocusScope scope)
{
FocusManager.Instance?.SetFocusScope(scope);
}
}
_topLevel.Content = value;

2
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@ -54,7 +54,7 @@ namespace Avalonia.LinuxFramebuffer.Output
}
public PixelSize Resolution => new PixelSize(Mode.hdisplay, Mode.vdisplay);
public bool IsPreferred => Mode.type.HasFlag(DrmModeType.DRM_MODE_TYPE_PREFERRED);
public bool IsPreferred => Mode.type.HasFlagCustom(DrmModeType.DRM_MODE_TYPE_PREFERRED);
public string Name { get; }
}

9
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -4,11 +4,14 @@ using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer
{
internal class CursorFactoryStub : IStandardCursorFactory
internal class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
private class CursorStub : ICursorImpl
{
return new PlatformHandle(IntPtr.Zero, null);
public void Dispose() { }
}
}
internal class PlatformSettings : IPlatformSettings

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />

49
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -39,17 +39,17 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string ElementName { get; set; }
public string? ElementName { get; set; }
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the source for the binding.
/// </summary>
public object Source { get; set; }
public object? Source { get; set; }
/// <summary>
/// Gets or sets the binding path.
@ -59,24 +59,36 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string, string, Type> TypeResolver { get; set; }
public Func<string, string, Type>? TypeResolver { get; set; }
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor ??= DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
INameScope nameScope = null;
INameScope? nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
if (node is null)
{
throw new InvalidOperationException("Could not parse binding expression.");
}
IStyledElement GetSource()
{
return target as IStyledElement ??
anchor as IStyledElement ??
throw new ArgumentException("Could not find binding source: either target or anchor must be an IStyledElement.");
}
if (ElementName != null)
{
return CreateElementObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
GetSource(),
ElementName,
node);
}
@ -96,9 +108,7 @@ namespace Avalonia.Data
}
else
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
}
else if (RelativeSource.Mode == RelativeSourceMode.DataContext)
@ -111,15 +121,11 @@ namespace Avalonia.Data
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
return CreateTemplatedParentObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateTemplatedParentObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
@ -128,10 +134,7 @@ namespace Avalonia.Data
throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
return CreateFindAncestorObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
RelativeSource,
node);
return CreateFindAncestorObserver(GetSource(), RelativeSource, node);
}
else
{

49
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@ -38,22 +38,22 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object FallbackValue { get; set; }
public object? FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object TargetNullValue { get; set; }
public object? TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
@ -68,26 +68,27 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
public WeakReference? DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
public WeakReference<INameScope>? NameScope { get; set; }
protected abstract ExpressionObserver CreateExpressionObserver(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor,
object? anchor,
bool enableDataValidation);
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor = anchor ?? DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
@ -133,18 +134,13 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node,
bool targetIsDataContext,
object anchor)
object? anchor)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
if (!(target is IDataContextProvider))
{
target = anchor as IDataContextProvider;
if (target == null)
{
throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
target = anchor as IDataContextProvider ?? throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
if (!targetIsDataContext)
@ -171,10 +167,9 @@ namespace Avalonia.Data
string elementName,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
NameScope.TryGetTarget(out var scope);
if (scope == null)
if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null)
throw new InvalidOperationException("Name scope is null or was already collected");
var result = new ExpressionObserver(
NameScopeLocator.Track(scope, elementName),
@ -188,9 +183,9 @@ namespace Avalonia.Data
RelativeSource relativeSource,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
IObservable<object> controlLocator;
IObservable<object?> controlLocator;
switch (relativeSource.Tree)
{
@ -220,7 +215,7 @@ namespace Avalonia.Data
object source,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(source != null);
_ = source ?? throw new ArgumentNullException(nameof(source));
return new ExpressionObserver(source, node);
}
@ -229,7 +224,7 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
var result = new ExpressionObserver(
() => target.GetValue(StyledElement.TemplatedParentProperty),
@ -240,7 +235,7 @@ namespace Avalonia.Data
return result;
}
protected IObservable<object> GetParentDataContext(IAvaloniaObject target)
protected IObservable<object?> GetParentDataContext(IAvaloniaObject target)
{
// The DataContext is based on the visual parent and not the logical parent: this may
// seem counter intuitive considering the fact that property inheritance works on the logical
@ -252,7 +247,7 @@ namespace Avalonia.Data
.Select(x =>
{
return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ??
Observable.Return((object)null);
Observable.Return((object?)null);
}).Switch();
}

14
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -22,12 +22,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IMultiValueConverter"/> to use.
/// </summary>
public IMultiValueConverter Converter { get; set; }
public IMultiValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
@ -52,12 +52,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public MultiBinding()
{
@ -69,7 +69,7 @@ namespace Avalonia.Data
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
var targetType = targetProperty?.PropertyType ?? typeof(object);
@ -105,7 +105,7 @@ namespace Avalonia.Data
}
}
private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter)
private object ConvertValue(IList<object?> values, Type targetType, IMultiValueConverter? converter)
{
for (var i = 0; i < values.Count; ++i)
{
@ -116,7 +116,7 @@ namespace Avalonia.Data
}
var culture = CultureInfo.CurrentCulture;
values = new System.Collections.ObjectModel.ReadOnlyCollection<object>(values);
values = new System.Collections.ObjectModel.ReadOnlyCollection<object?>(values);
object converted;
if (converter != null)
{

2
src/Markup/Avalonia.Markup/Data/RelativeSource.cs

@ -94,7 +94,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets the type of ancestor to look for when in <see cref="RelativeSourceMode.FindAncestor"/> mode.
/// </summary>
public Type AncestorType { get; set; }
public Type? AncestorType { get; set; }
/// <summary>
/// Gets or sets a value that describes the type of relative source lookup.

17
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -17,8 +17,8 @@ namespace Avalonia.Data
ISetterValue
{
private bool _isSetterValue;
private IStyledElement _target;
private Type _targetType;
private IStyledElement _target = default!;
private Type? _targetType;
public TemplateBinding()
{
@ -33,7 +33,7 @@ namespace Avalonia.Data
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
@ -68,12 +68,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the binding mode.
@ -83,7 +83,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the source property on the templated parent.
/// </summary>
public AvaloniaProperty Property { get; set; }
public AvaloniaProperty? Property { get; set; }
/// <inheritdoc/>
public string Description => "TemplateBinding: " + Property;
@ -164,10 +164,7 @@ namespace Avalonia.Data
{
if (e.Property == StyledElement.TemplatedParentProperty)
{
var oldValue = (IAvaloniaObject)e.OldValue;
var newValue = (IAvaloniaObject)e.OldValue;
if (oldValue != null)
if (e.OldValue is IAvaloniaObject oldValue)
{
oldValue.PropertyChanged -= TemplatedParentPropertyChanged;
}

26
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@ -6,6 +6,8 @@ using Avalonia.Utilities;
using System;
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Markup.Parsers
{
internal enum SourceMode
@ -271,8 +273,8 @@ namespace Avalonia.Markup.Parsers
}
else if (mode.SequenceEqual("parent".AsSpan()))
{
string ancestorNamespace = null;
string ancestorType = null;
string? ancestorNamespace = null;
string? ancestorType = null;
var ancestorLevel = 0;
if (PeekOpenBracket(ref r))
{
@ -424,19 +426,19 @@ namespace Avalonia.Markup.Parsers
public class PropertyNameNode : INode
{
public string PropertyName { get; set; }
public string PropertyName { get; set; } = string.Empty;
}
public class AttachedPropertyNameNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string PropertyName { get; set; }
public string Namespace { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public string PropertyName { get; set; } = string.Empty;
}
public class IndexerNode : INode
{
public IList<string> Arguments { get; set; }
public IList<string> Arguments { get; set; } = Array.Empty<string>();
}
public class NotNode : INode, ITransformNode { }
@ -447,20 +449,20 @@ namespace Avalonia.Markup.Parsers
public class NameNode : INode
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
}
public class AncestorNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string? Namespace { get; set; }
public string? TypeName { get; set; }
public int Level { get; set; }
}
public class TypeCastNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string Namespace { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
}
}
}

21
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@ -8,8 +8,8 @@ namespace Avalonia.Markup.Parsers
{
public static class ExpressionObserverBuilder
{
internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null,
INameScope nameScope = null)
internal static (ExpressionNode? Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type>? typeResolver = null,
INameScope? nameScope = null)
{
if (string.IsNullOrWhiteSpace(expression))
{
@ -32,8 +32,8 @@ namespace Avalonia.Markup.Parsers
object root,
string expression,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
return new ExpressionObserver(
root,
@ -45,10 +45,11 @@ namespace Avalonia.Markup.Parsers
IObservable<object> rootObservable,
string expression,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
_ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable));
return new ExpressionObserver(
rootObservable,
Parse(expression, enableDataValidation, typeResolver).Node,
@ -61,10 +62,10 @@ namespace Avalonia.Markup.Parsers
string expression,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
_ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
return new ExpressionObserver(
rootGetter,

22
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -11,25 +11,25 @@ namespace Avalonia.Markup.Parsers
internal class ExpressionParser
{
private readonly bool _enableValidation;
private readonly Func<string, string, Type> _typeResolver;
private readonly INameScope _nameScope;
private readonly Func<string, string, Type>? _typeResolver;
private readonly INameScope? _nameScope;
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver, INameScope nameScope)
public ExpressionParser(bool enableValidation, Func<string, string, Type>? typeResolver, INameScope? nameScope)
{
_typeResolver = typeResolver;
_nameScope = nameScope;
_enableValidation = enableValidation;
}
public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r)
public (ExpressionNode? Node, SourceMode Mode) Parse(ref CharacterReader r)
{
ExpressionNode rootNode = null;
ExpressionNode node = null;
ExpressionNode? rootNode = null;
ExpressionNode? node = null;
var (astNodes, mode) = BindingExpressionGrammar.Parse(ref r);
foreach (var astNode in astNodes)
{
ExpressionNode nextNode = null;
ExpressionNode? nextNode = null;
switch (astNode)
{
case BindingExpressionGrammar.EmptyExpressionNode _:
@ -57,13 +57,13 @@ namespace Avalonia.Markup.Parsers
nextNode = ParseFindAncestor(ancestor);
break;
case BindingExpressionGrammar.NameNode elementName:
nextNode = new ElementNameNode(_nameScope, elementName.Name);
nextNode = new ElementNameNode(_nameScope ?? throw new NotSupportedException("Invalid element name binding with null name scope!"), elementName.Name);
break;
case BindingExpressionGrammar.TypeCastNode typeCast:
nextNode = ParseTypeCastNode(typeCast);
break;
}
if (rootNode is null)
if (node is null)
{
rootNode = node = nextNode;
}
@ -79,7 +79,7 @@ namespace Avalonia.Markup.Parsers
private FindAncestorNode ParseFindAncestor(BindingExpressionGrammar.AncestorNode node)
{
Type ancestorType = null;
Type? ancestorType = null;
var ancestorLevel = node.Level;
if (!(node.Namespace is null) && !(node.TypeName is null))
@ -97,7 +97,7 @@ namespace Avalonia.Markup.Parsers
private TypeCastNode ParseTypeCastNode(BindingExpressionGrammar.TypeCastNode node)
{
Type castType = null;
Type? castType = null;
if (!(node.Namespace is null) && !(node.TypeName is null))
{
if (_typeResolver == null)

2
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Markup.Parsers.Nodes
{
private readonly WeakReference<INameScope> _nameScope;
private readonly string _name;
private IDisposable _subscription;
private IDisposable? _subscription;
public ElementNameNode(INameScope nameScope, string name)
{

6
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs

@ -7,10 +7,10 @@ namespace Avalonia.Markup.Parsers.Nodes
public class FindAncestorNode : ExpressionNode
{
private readonly int _level;
private readonly Type _ancestorType;
private IDisposable _subscription;
private readonly Type? _ancestorType;
private IDisposable? _subscription;
public FindAncestorNode(Type ancestorType, int level)
public FindAncestorNode(Type? ancestorType, int level)
{
_level = level;
_ancestorType = ancestorType;

16
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs

@ -29,15 +29,15 @@ namespace Avalonia.Markup.Parsers.Nodes
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
ParameterInfo[] indexerParameters;
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count)
{
var convertedObjectArray = new object[indexerParameters.Length];
for (int i = 0; i < Arguments.Count; i++)
{
object temp = null;
object? temp = null;
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp))
{
@ -125,7 +125,7 @@ namespace Avalonia.Markup.Parsers.Nodes
public IList<string> Arguments { get; }
public override Type PropertyType
public override Type? PropertyType
{
get
{
@ -144,15 +144,15 @@ namespace Avalonia.Markup.Parsers.Nodes
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
ParameterInfo[] indexerParameters;
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count)
{
var convertedObjectArray = new object[indexerParameters.Length];
for (int i = 0; i < Arguments.Count; i++)
{
object temp = null;
object? temp = null;
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp))
{
@ -246,7 +246,7 @@ namespace Avalonia.Markup.Parsers.Nodes
return true;
}
private static PropertyInfo GetIndexer(TypeInfo typeInfo)
private static PropertyInfo? GetIndexer(TypeInfo? typeInfo)
{
PropertyInfo indexer;

36
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using Avalonia.Data.Core;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Markup.Parsers
{
#if !BUILDTASK
@ -30,7 +32,7 @@ namespace Avalonia.Markup.Parsers
var parsed = new List<ISyntax>();
while (state != State.End)
{
ISyntax syntax = null;
ISyntax? syntax = null;
if (state == State.Start)
(state, syntax) = ParseStart(ref r);
else if (state == State.Next)
@ -53,7 +55,7 @@ namespace Avalonia.Markup.Parsers
return parsed;
}
private static (State, ISyntax) ParseNext(ref CharacterReader r)
private static (State, ISyntax?) ParseNext(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.End)
@ -106,7 +108,7 @@ namespace Avalonia.Markup.Parsers
});
}
static (string ns, string name) ParseXamlIdentifier(ref CharacterReader r)
static (string? ns, string name) ParseXamlIdentifier(ref CharacterReader r)
{
var ident = r.ParseIdentifier();
if (ident.IsEmpty)
@ -147,7 +149,7 @@ namespace Avalonia.Markup.Parsers
return true;
}
private static (State, ISyntax) ParseAfterProperty(ref CharacterReader r)
private static (State, ISyntax?) ParseAfterProperty(ref CharacterReader r)
{
if (TryParseCasts(ref r, out var rv))
return rv;
@ -184,20 +186,20 @@ namespace Avalonia.Markup.Parsers
public class PropertySyntax : ISyntax
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is PropertySyntax other
&& other.Name == Name;
}
public class TypeQualifiedPropertySyntax : ISyntax
{
public string Name { get; set; }
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public string Name { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is TypeQualifiedPropertySyntax other
&& other.Name == Name
&& other.TypeName == TypeName
@ -207,14 +209,14 @@ namespace Avalonia.Markup.Parsers
public class ChildTraversalSyntax : ISyntax
{
public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax();
public override bool Equals(object obj) => obj is ChildTraversalSyntax;
public override bool Equals(object? obj) => obj is ChildTraversalSyntax;
}
public class EnsureTypeSyntax : ISyntax
{
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public override bool Equals(object obj)
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object? obj)
=> obj is EnsureTypeSyntax other
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
@ -222,9 +224,9 @@ namespace Avalonia.Markup.Parsers
public class CastTypeSyntax : ISyntax
{
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public override bool Equals(object obj)
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object? obj)
=> obj is CastTypeSyntax other
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;

42
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -4,6 +4,8 @@ using System.Linq;
using Avalonia.Data.Core;
using Avalonia.Utilities;
#nullable enable
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable 659
@ -39,7 +41,7 @@ namespace Avalonia.Markup.Parsers
var selector = new List<ISyntax>();
while (!r.End && state != State.End)
{
ISyntax syntax = null;
ISyntax? syntax = null;
switch (state)
{
case State.Start:
@ -110,7 +112,7 @@ namespace Avalonia.Markup.Parsers
return State.TypeName;
}
private static (State, ISyntax) ParseMiddle(ref CharacterReader r, char? end)
private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
{
if (r.TakeIf(':'))
{
@ -190,7 +192,7 @@ namespace Avalonia.Markup.Parsers
}
}
private static (State, ISyntax) ParseTraversal(ref CharacterReader r)
private static (State, ISyntax?) ParseTraversal(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.TakeIf('>'))
@ -325,11 +327,11 @@ namespace Avalonia.Markup.Parsers
public class OfTypeSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; }
public string TypeName { get; set; } = string.Empty;
public string Xmlns { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
var other = obj as OfTypeSyntax;
return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
@ -338,11 +340,11 @@ namespace Avalonia.Markup.Parsers
public class IsSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; }
public string TypeName { get; set; } = string.Empty;
public string Xmlns { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
var other = obj as IsSyntax;
return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
@ -351,9 +353,9 @@ namespace Avalonia.Markup.Parsers
public class ClassSyntax : ISyntax
{
public string Class { get; set; }
public string Class { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class;
}
@ -361,9 +363,9 @@ namespace Avalonia.Markup.Parsers
public class NameSyntax : ISyntax
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is NameSyntax && ((NameSyntax)obj).Name == Name;
}
@ -371,11 +373,11 @@ namespace Avalonia.Markup.Parsers
public class PropertySyntax : ISyntax
{
public string Property { get; set; }
public string Property { get; set; } = string.Empty;
public string Value { get; set; }
public string Value { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is PropertySyntax &&
((PropertySyntax)obj).Property == Property &&
@ -385,7 +387,7 @@ namespace Avalonia.Markup.Parsers
public class ChildSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is ChildSyntax;
}
@ -393,7 +395,7 @@ namespace Avalonia.Markup.Parsers
public class DescendantSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is DescendantSyntax;
}
@ -401,7 +403,7 @@ namespace Avalonia.Markup.Parsers
public class TemplateSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is TemplateSyntax;
}
@ -409,9 +411,9 @@ namespace Avalonia.Markup.Parsers
public class NotSyntax : ISyntax
{
public IEnumerable<ISyntax> Argument { get; set; }
public IEnumerable<ISyntax> Argument { get; set; } = Enumerable.Empty<ISyntax>();
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument);
}
@ -419,7 +421,7 @@ namespace Avalonia.Markup.Parsers
public class CommaSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is CommaSyntax or;
}

6
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -31,13 +31,13 @@ namespace Avalonia.Markup.Parsers
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The parsed selector.</returns>
public Selector Parse(string s)
public Selector? Parse(string s)
{
var syntax = SelectorGrammar.Parse(s);
return Create(syntax);
}
private Selector Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
private Selector? Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
{
var result = default(Selector);
var results = default(List<Selector>);
@ -110,7 +110,7 @@ namespace Avalonia.Markup.Parsers
results = new List<Selector>();
}
results.Add(result);
results.Add(result ?? throw new NotSupportedException("Invalid selector!"));
result = null;
break;
default:

6
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -32,7 +32,7 @@ namespace Avalonia.Skia
weight -= weight % 100; // make sure we start at a full weight
for (var i = (int)key.Style; i < 2; i++)
for (var i = 0; i < 2; i++)
{
// only try 2 font weights in each direction
for (var j = 0; j < 200; j += 100)
@ -57,8 +57,8 @@ namespace Avalonia.Skia
}
}
//Nothing was found so we use the first typeface we can get.
return typefaces.Values.FirstOrDefault();
//Nothing was found so we try to get a regular typeface.
return typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface) ? typeface : null;
}
}
}

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

@ -21,6 +21,7 @@ namespace Avalonia.Direct2D1.Media
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly DeviceContext _deviceContext;
private readonly bool _ownsDeviceContext;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
@ -51,10 +52,12 @@ namespace Avalonia.Direct2D1.Media
if (_renderTarget is DeviceContext deviceContext)
{
_deviceContext = deviceContext;
_ownsDeviceContext = false;
}
else
{
_deviceContext = _renderTarget.QueryInterface<DeviceContext>();
_ownsDeviceContext = true;
}
_deviceContext.BeginDraw();
@ -96,6 +99,13 @@ namespace Avalonia.Direct2D1.Media
{
throw new RenderTargetCorruptedException(ex);
}
finally
{
if (_ownsDeviceContext)
{
_deviceContext.Dispose();
}
}
}
/// <summary>
@ -151,7 +161,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D()))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
{

6
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@ -33,13 +33,13 @@ namespace Avalonia.Direct2D1.Media
/// <inheritdoc/>
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
var result = new PathGeometry(Geometry.Factory);
var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
using (var sink = result.Open())
{
Geometry.Combine(((GeometryImpl)geometry).Geometry, CombineMode.Intersect, sink);
return new StreamGeometryImpl(result);
sink.Close();
}
return new StreamGeometryImpl(result);
}
/// <inheritdoc/>

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private BitmapDecoder _decoder;
private readonly BitmapDecoder _decoder;
private static BitmapInterpolationMode ConvertInterpolationMode(Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode)
{
@ -41,7 +41,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(string fileName)
{
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
using (var decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
Dpi = new Vector(96, 96);
@ -177,7 +177,7 @@ namespace Avalonia.Direct2D1.Media
/// <returns>The Direct2D bitmap.</returns>
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}

9
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs

@ -29,9 +29,12 @@ namespace Avalonia.Direct2D1.Media
public IStreamGeometryImpl Clone()
{
var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
var sink = result.Open();
((PathGeometry)Geometry).Stream(sink);
sink.Close();
using (var sink = result.Open())
{
((PathGeometry)Geometry).Stream(sink);
sink.Close();
}
return new StreamGeometryImpl(result);
}

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

@ -111,7 +111,7 @@ namespace Avalonia.Direct2D1
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, SharpDX.Direct2D1.RenderTarget renderTarget)
{
return pen.ToDirect2DStrokeStyle(renderTarget.Factory);
return pen.ToDirect2DStrokeStyle(Direct2D1Platform.Direct2D1Factory);
}
/// <summary>

14
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -147,13 +147,13 @@ namespace Avalonia.Win32.Interop.Wpf
{
var state = Keyboard.Modifiers;
var rv = default(RawInputModifiers);
if (state.HasFlag(ModifierKeys.Windows))
if (state.HasFlagCustom(ModifierKeys.Windows))
rv |= RawInputModifiers.Meta;
if (state.HasFlag(ModifierKeys.Alt))
if (state.HasFlagCustom(ModifierKeys.Alt))
rv |= RawInputModifiers.Alt;
if (state.HasFlag(ModifierKeys.Control))
if (state.HasFlagCustom(ModifierKeys.Control))
rv |= RawInputModifiers.Control;
if (state.HasFlag(ModifierKeys.Shift))
if (state.HasFlagCustom(ModifierKeys.Shift))
rv |= RawInputModifiers.Shift;
if (e != null)
{
@ -225,12 +225,12 @@ namespace Avalonia.Win32.Interop.Wpf
protected override void OnTextInput(TextCompositionEventArgs e)
=> _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text));
void ITopLevelImpl.SetCursor(IPlatformHandle cursor)
void ITopLevelImpl.SetCursor(ICursorImpl cursor)
{
if (cursor == null)
Cursor = Cursors.Arrow;
else if (cursor.HandleDescriptor == "HCURSOR")
Cursor = CursorShim.FromHCursor(cursor.Handle);
else if (cursor is IPlatformHandle handle)
Cursor = CursorShim.FromHCursor(handle.Handle);
}
Action<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO

127
src/Windows/Avalonia.Win32/CursorFactory.cs

@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using SdBitmap = System.Drawing.Bitmap;
using SdPixelFormat = System.Drawing.Imaging.PixelFormat;
namespace Avalonia.Win32
{
internal class CursorFactory : IStandardCursorFactory
internal class CursorFactory : ICursorFactory
{
public static CursorFactory Instance { get; } = new CursorFactory();
@ -29,8 +34,7 @@ namespace Avalonia.Win32
IntPtr cursor = UnmanagedMethods.LoadCursor(mh, new IntPtr(id));
if (cursor != IntPtr.Zero)
{
PlatformHandle phCursor = new PlatformHandle(cursor, PlatformConstants.CursorHandleType);
Cache.Add(cursorType, phCursor);
Cache.Add(cursorType, new CursorImpl(cursor, false));
}
}
}
@ -70,22 +74,119 @@ namespace Avalonia.Win32
{StandardCursorType.DragLink, 32516},
};
private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =
new Dictionary<StandardCursorType, IPlatformHandle>();
private static readonly Dictionary<StandardCursorType, CursorImpl> Cache =
new Dictionary<StandardCursorType, CursorImpl>();
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
IPlatformHandle rv;
if (!Cache.TryGetValue(cursorType, out rv))
if (!Cache.TryGetValue(cursorType, out var rv))
{
Cache[cursorType] =
rv =
new PlatformHandle(
UnmanagedMethods.LoadCursor(IntPtr.Zero, new IntPtr(CursorTypeMapping[cursorType])),
PlatformConstants.CursorHandleType);
rv = new CursorImpl(
UnmanagedMethods.LoadCursor(IntPtr.Zero, new IntPtr(CursorTypeMapping[cursorType])),
false);
Cache.Add(cursorType, rv);
}
return rv;
}
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
using var source = LoadSystemDrawingBitmap(cursor);
using var mask = AlphaToMask(source);
var info = new UnmanagedMethods.ICONINFO
{
IsIcon = false,
xHotspot = hotSpot.X,
yHotspot = hotSpot.Y,
MaskBitmap = mask.GetHbitmap(),
ColorBitmap = source.GetHbitmap(),
};
return new CursorImpl(UnmanagedMethods.CreateIconIndirect(ref info), true);
}
private SdBitmap LoadSystemDrawingBitmap(IBitmapImpl bitmap)
{
using var memoryStream = new MemoryStream();
bitmap.Save(memoryStream);
return new SdBitmap(memoryStream);
}
private unsafe SdBitmap AlphaToMask(SdBitmap source)
{
var dest = new SdBitmap(source.Width, source.Height, SdPixelFormat.Format1bppIndexed);
if (source.PixelFormat == SdPixelFormat.Format32bppPArgb)
{
throw new NotSupportedException(
"Images with premultiplied alpha not yet supported as cursor images.");
}
if (source.PixelFormat != SdPixelFormat.Format32bppArgb)
{
return dest;
}
var sourceData = source.LockBits(
new Rectangle(default, source.Size),
ImageLockMode.ReadOnly,
SdPixelFormat.Format32bppArgb);
var destData = dest.LockBits(
new Rectangle(default, source.Size),
ImageLockMode.ReadOnly,
SdPixelFormat.Format1bppIndexed);
try
{
var pSource = (byte*)sourceData.Scan0.ToPointer();
var pDest = (byte*)destData.Scan0.ToPointer();
for (var y = 0; y < dest.Height; ++y)
{
for (var x = 0; x < dest.Width; ++x)
{
if (pSource[x * 4] == 0)
{
pDest[x / 8] |= (byte)(1 << (x % 8));
}
}
pSource += sourceData.Stride;
pDest += destData.Stride;
}
return dest;
}
finally
{
source.UnlockBits(sourceData);
dest.UnlockBits(destData);
}
}
}
internal class CursorImpl : ICursorImpl, IPlatformHandle
{
private readonly bool _isCustom;
public CursorImpl(IntPtr handle, bool isCustom)
{
Handle = handle;
_isCustom = isCustom;
}
public IntPtr Handle { get; private set; }
public string HandleDescriptor => PlatformConstants.CursorHandleType;
public void Dispose()
{
if (_isCustom && Handle != IntPtr.Zero)
{
UnmanagedMethods.DestroyIcon(Handle);
Handle = IntPtr.Zero;
}
}
}
}

6
src/Windows/Avalonia.Win32/DataObject.cs

@ -181,7 +181,7 @@ namespace Avalonia.Win32
ole.GetData(ref format, out medium);
return;
}
if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
if(!format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
@ -205,7 +205,7 @@ namespace Avalonia.Win32
return;
}
if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
@ -228,7 +228,7 @@ namespace Avalonia.Win32
return ole.QueryGetData(ref format);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
return DV_E_DVASPECT;
if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
if (!format.tymed.HasFlagCustom(TYMED.TYMED_HGLOBAL))
return DV_E_TYMED;
string dataFormat = ClipboardFormats.GetFormat(format.cfFormat);

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

@ -1035,6 +1035,12 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
[DllImport("user32.dll")]
public static extern IntPtr CreateIconIndirect([In] ref ICONINFO iconInfo);
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("user32.dll")]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
@ -1762,6 +1768,16 @@ namespace Avalonia.Win32.Interop
public int CyContact;
}
[StructLayout(LayoutKind.Sequential)]
public struct ICONINFO
{
public bool IsIcon;
public int xHotspot;
public int yHotspot;
public IntPtr MaskBitmap;
public IntPtr ColorBitmap;
};
[Flags]
public enum TouchInputFlags
{

24
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -24,11 +24,11 @@ namespace Avalonia.Win32
public static DropEffect ConvertDropEffect(DragDropEffects operation)
{
DropEffect result = DropEffect.None;
if (operation.HasFlag(DragDropEffects.Copy))
if (operation.HasFlagCustom(DragDropEffects.Copy))
result |= DropEffect.Copy;
if (operation.HasFlag(DragDropEffects.Move))
if (operation.HasFlagCustom(DragDropEffects.Move))
result |= DropEffect.Move;
if (operation.HasFlag(DragDropEffects.Link))
if (operation.HasFlagCustom(DragDropEffects.Link))
result |= DropEffect.Link;
return result;
}
@ -36,11 +36,11 @@ namespace Avalonia.Win32
public static DragDropEffects ConvertDropEffect(DropEffect effect)
{
DragDropEffects result = DragDropEffects.None;
if (effect.HasFlag(DropEffect.Copy))
if (effect.HasFlagCustom(DropEffect.Copy))
result |= DragDropEffects.Copy;
if (effect.HasFlag(DropEffect.Move))
if (effect.HasFlagCustom(DropEffect.Move))
result |= DragDropEffects.Move;
if (effect.HasFlag(DropEffect.Link))
if (effect.HasFlagCustom(DropEffect.Link))
result |= DragDropEffects.Link;
return result;
}
@ -50,17 +50,17 @@ namespace Avalonia.Win32
var modifiers = RawInputModifiers.None;
var state = (UnmanagedMethods.ModifierKeys)grfKeyState;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_LBUTTON))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_LBUTTON))
modifiers |= RawInputModifiers.LeftMouseButton;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
modifiers |= RawInputModifiers.MiddleMouseButton;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_RBUTTON))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_RBUTTON))
modifiers |= RawInputModifiers.RightMouseButton;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_SHIFT))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_SHIFT))
modifiers |= RawInputModifiers.Shift;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_CONTROL))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_CONTROL))
modifiers |= RawInputModifiers.Control;
if (state.HasFlag(UnmanagedMethods.ModifierKeys.MK_ALT))
if (state.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_ALT))
modifiers |= RawInputModifiers.Alt;
return modifiers;
}

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

@ -104,7 +104,7 @@ namespace Avalonia.Win32
Options = options;
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<ICursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)

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

@ -23,13 +23,13 @@ namespace Avalonia.Win32
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
var borderThickness = new RECT();
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME))
if (GetStyle().HasFlagCustom(WindowStyles.WS_THICKFRAME))
{
AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0);
borderThickness.left *= -1;
borderThickness.top *= -1;
}
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER))
else if (GetStyle().HasFlagCustom(WindowStyles.WS_BORDER))
{
borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
}

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

@ -608,14 +608,19 @@ namespace Avalonia.Win32
SetWindowText(_hwnd, title);
}
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
var hCursor = cursor?.Handle ?? DefaultCursor;
SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor);
var impl = cursor as CursorImpl;
if (_owner.IsPointerOver)
if (cursor is null || impl is object)
{
UnmanagedMethods.SetCursor(hCursor);
var hCursor = impl?.Handle ?? DefaultCursor;
SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor);
if (_owner.IsPointerOver)
{
UnmanagedMethods.SetCursor(hCursor);
}
}
}
@ -831,7 +836,7 @@ namespace Avalonia.Win32
borderCaptionThickness.left *= -1;
borderCaptionThickness.top *= -1;
bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
bool wantsTitleBar = _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
if (!wantsTitleBar)
{
@ -848,7 +853,7 @@ namespace Avalonia.Win32
borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
}
margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
margins.cyTopHeight = _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
if (WindowState == WindowState.Maximized)
{
@ -896,8 +901,8 @@ namespace Avalonia.Win32
_extendedMargins = new Thickness();
}
if(!_isClientAreaExtended || (_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) &&
!_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome)))
if(!_isClientAreaExtended || (_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.SystemChrome) &&
!_extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome)))
{
EnableCloseButton(_hwnd);
}
@ -1234,7 +1239,7 @@ namespace Avalonia.Win32
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
/// <inheritdoc/>
public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome);
public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome);
/// <inheritdoc/>
public Thickness ExtendedMargins => _extendedMargins;

4
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -74,7 +74,7 @@ namespace Avalonia.iOS
public PixelPoint PointToScreen(Point point) => new PixelPoint((int) point.X, (int) point.Y);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl _)
{
// no-op
}
@ -136,4 +136,4 @@ namespace Avalonia.iOS
set => _topLevel.Content = value;
}
}
}
}

2
src/iOS/Avalonia.iOS/Platform.cs

@ -27,7 +27,7 @@ namespace Avalonia.iOS
var softKeyboard = new SoftKeyboardHelper();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformOpenGlInterface>().ToConstant(GlFeature)
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToConstant(new PlatformSettings())

12
src/iOS/Avalonia.iOS/Stubs.cs

@ -5,9 +5,15 @@ using Avalonia.Platform;
namespace Avalonia.iOS
{
class CursorFactoryStub : IStandardCursorFactory
class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "NULL");
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorImplStub();
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new CursorImplStub();
private class CursorImplStub : ICursorImpl
{
public void Dispose() { }
}
}
class WindowingPlatformStub : IWindowingPlatform
@ -57,4 +63,4 @@ namespace Avalonia.iOS
_ms.CopyTo(outputStream);
}
}
}
}

9
tests/Avalonia.Benchmarks/NullCursorFactory.cs

@ -4,11 +4,14 @@ using Avalonia.Platform;
namespace Avalonia.Benchmarks
{
internal class NullCursorFactory : IStandardCursorFactory
internal class NullCursorFactory : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new NullCursorImpl();
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new NullCursorImpl();
private class NullCursorImpl : ICursorImpl
{
return new PlatformHandle(IntPtr.Zero, "null");
public void Dispose() { }
}
}
}

2
tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs

@ -74,7 +74,7 @@ namespace Avalonia.Controls.UnitTests
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private CalendarDatePicker CreateControl()
{

19
tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs

@ -1,14 +1,25 @@
using System;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Controls.UnitTests
{
public class CursorFactoryMock : IStandardCursorFactory
public class CursorFactoryMock : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
return new PlatformHandle(IntPtr.Zero, cursorType.ToString());
return new MockCursorImpl();
}
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
return new MockCursorImpl();
}
private class MockCursorImpl : ICursorImpl
{
public void Dispose()
{
}
}
}
}

2
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -204,7 +204,7 @@ namespace Avalonia.Controls.UnitTests
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()

4
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -11,8 +11,8 @@ namespace Avalonia.Controls.UnitTests
{
public GridSplitterTests()
{
var cursorFactoryImpl = new Mock<IStandardCursorFactory>();
AvaloniaLocator.CurrentMutable.Bind<IStandardCursorFactory>().ToConstant(cursorFactoryImpl.Object);
var cursorFactoryImpl = new Mock<ICursorFactory>();
AvaloniaLocator.CurrentMutable.Bind<ICursorFactory>().ToConstant(cursorFactoryImpl.Object);
}
[Fact]

30
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -156,15 +156,35 @@ namespace Avalonia.Controls.UnitTests
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Invokes_CanExecute_When_CommandParameter_Changed()
{
var command = new TestCommand(p => p is bool value && value);
var target = new MenuItem { Command = command };
target.CommandParameter = true;
Assert.True(target.IsEffectivelyEnabled);
target.CommandParameter = false;
Assert.False(target.IsEffectivelyEnabled);
}
private class TestCommand : ICommand
{
private bool _enabled;
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
private EventHandler _canExecuteChanged;
public TestCommand(bool enabled = true)
: this(_ => enabled, _ => { })
{
}
public TestCommand(Func<object, bool> canExecute, Action<object> execute = null)
{
_enabled = enabled;
_canExecute = canExecute;
_execute = execute ?? (_ => { });
}
public int SubscriptionCount { get; private set; }
@ -175,11 +195,9 @@ namespace Avalonia.Controls.UnitTests
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => _enabled;
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter)
{
}
public void Execute(object parameter) => _execute(parameter);
}
}
}

4
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -698,10 +698,10 @@ namespace Avalonia.Controls.UnitTests
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private IControlTemplate CreateTemplate()
{

2
tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls.UnitTests
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private IControlTemplate CreateTemplate()
{

2
tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()

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

Loading…
Cancel
Save