From 01edb8c8b128318a3c12d85be433af429dd1639f Mon Sep 17 00:00:00 2001 From: Adir Date: Wed, 7 Oct 2020 19:25:18 +0300 Subject: [PATCH 01/79] Added potential fix for hotkey manager instances not clearing up after detaching from root --- src/Avalonia.Controls/HotkeyManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 95752e7875..9a2b4f2d0a 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -55,6 +55,11 @@ namespace Avalonia.Controls private void OnParentChanged(TopLevel control) { + if (control == null) + { + Stop(); + return; + } Unregister(); _root = control; Register(); From 188192c1259c34abcbcaaca1f5c4ede0d7259850 Mon Sep 17 00:00:00 2001 From: Adir Date: Wed, 7 Oct 2020 20:57:41 +0300 Subject: [PATCH 02/79] Changed HotkeyManager.cs to stop/init based on detaching/attaching to visual tree --- src/Avalonia.Controls/HotkeyManager.cs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 9a2b4f2d0a..bff25d6dad 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; using Avalonia.Input; +using Avalonia.LogicalTree; namespace Avalonia.Controls { @@ -45,6 +46,8 @@ namespace Avalonia.Controls { _control = control; _wrapper = new HotkeyCommandWrapper(_control); + _control.DetachedFromVisualTree += ControlOnDetachedFromVisualTree; + _control.AttachedToLogicalTree += ControlOnAttachedToLogicalTree; } public void Init() @@ -53,13 +56,19 @@ namespace Avalonia.Controls _parentSub = AncestorFinder.Create(_control).Subscribe(OnParentChanged); } + private void ControlOnAttachedToLogicalTree(object sender, LogicalTreeAttachmentEventArgs e) + { + Stop(); + Init(); + } + + private void ControlOnDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + Stop(); + } + private void OnParentChanged(TopLevel control) { - if (control == null) - { - Stop(); - return; - } Unregister(); _root = control; Register(); @@ -89,7 +98,7 @@ namespace Avalonia.Controls { if (_root != null && _hotkey != null) { - _binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper}; + _binding = new KeyBinding() { Gesture = _hotkey, Command = _wrapper }; _root.KeyBindings.Add(_binding); } } @@ -107,11 +116,12 @@ namespace Avalonia.Controls HotKeyProperty.Changed.Subscribe(args => { var control = args.Sender as IControl; - if (args.OldValue != null|| control == null) + if (args.OldValue != null || control == null) return; new Manager(control).Init(); }); } + public static void SetHotKey(AvaloniaObject target, KeyGesture value) => target.SetValue(HotKeyProperty, value); public static KeyGesture GetHotKey(AvaloniaObject target) => target.GetValue(HotKeyProperty); } From ecba5f65f05da4917b6a454e81abf3f99b76ceb4 Mon Sep 17 00:00:00 2001 From: Adir Date: Wed, 7 Oct 2020 23:11:50 +0300 Subject: [PATCH 03/79] Reverted HotkeyManager.cs Changed Button.cs to remove it's HotKey property when detaching from visual tree --- src/Avalonia.Controls/Button.cs | 12 ++++++++++-- src/Avalonia.Controls/HotkeyManager.cs | 14 -------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index e94d00b2ff..c541a384b0 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -80,6 +80,7 @@ namespace Avalonia.Controls private ICommand _command; private bool _commandCanExecute = true; + private KeyGesture _hotkey; /// /// Initializes static members of the class. @@ -168,11 +169,13 @@ namespace Avalonia.Controls private set { SetValue(IsPressedProperty, value); } } - protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { + if (_hotkey != null) + HotKey = _hotkey; base.OnAttachedToVisualTree(e); if (IsDefault) @@ -182,6 +185,7 @@ namespace Avalonia.Controls ListenForDefault(inputElement); } } + if (IsCancel) { if (e.Root is IInputElement inputElement) @@ -194,6 +198,8 @@ namespace Avalonia.Controls /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { + _hotkey = HotKey; + HotKey = null; base.OnDetachedFromVisualTree(e); if (IsDefault) @@ -239,6 +245,7 @@ namespace Avalonia.Controls { OnClick(); } + IsPressed = true; e.Handled = true; } @@ -255,6 +262,7 @@ namespace Avalonia.Controls { OnClick(); } + IsPressed = false; e.Handled = true; } @@ -309,7 +317,7 @@ namespace Avalonia.Controls } } } - + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { IsPressed = false; diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index bff25d6dad..5972a0fdd9 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -2,7 +2,6 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; using Avalonia.Input; -using Avalonia.LogicalTree; namespace Avalonia.Controls { @@ -46,8 +45,6 @@ namespace Avalonia.Controls { _control = control; _wrapper = new HotkeyCommandWrapper(_control); - _control.DetachedFromVisualTree += ControlOnDetachedFromVisualTree; - _control.AttachedToLogicalTree += ControlOnAttachedToLogicalTree; } public void Init() @@ -56,17 +53,6 @@ namespace Avalonia.Controls _parentSub = AncestorFinder.Create(_control).Subscribe(OnParentChanged); } - private void ControlOnAttachedToLogicalTree(object sender, LogicalTreeAttachmentEventArgs e) - { - Stop(); - Init(); - } - - private void ControlOnDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) - { - Stop(); - } - private void OnParentChanged(TopLevel control) { Unregister(); From 7c952cfc5fb3063b03b3860d7faf1b69e6db1b46 Mon Sep 17 00:00:00 2001 From: Adir Date: Thu, 8 Oct 2020 16:33:32 +0300 Subject: [PATCH 04/79] Changed Hotkey reset to detatch from logical tree (was visual tree) --- src/Avalonia.Controls/Button.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index c541a384b0..046253b3cd 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -174,8 +174,6 @@ namespace Avalonia.Controls /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - if (_hotkey != null) - HotKey = _hotkey; base.OnAttachedToVisualTree(e); if (IsDefault) @@ -198,8 +196,6 @@ namespace Avalonia.Controls /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - _hotkey = HotKey; - HotKey = null; base.OnDetachedFromVisualTree(e); if (IsDefault) @@ -213,6 +209,8 @@ namespace Avalonia.Controls protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { + if (_hotkey != null) + HotKey = _hotkey; base.OnAttachedToLogicalTree(e); if (Command != null) @@ -223,6 +221,12 @@ namespace Avalonia.Controls protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { + if (HotKey != null) + { + _hotkey = HotKey; + HotKey = null; + } + base.OnDetachedFromLogicalTree(e); if (Command != null) From 3c302f72f236a94d5b1d3f31fcab52d351d31625 Mon Sep 17 00:00:00 2001 From: Adir Date: Thu, 8 Oct 2020 18:03:52 +0300 Subject: [PATCH 05/79] Added docs explaining why need to change Hotkey on attach/detach from logical tree Added the same logic to MenuItem.cs Reverted auto indentation in Button.cs --- src/Avalonia.Controls/Button.cs | 9 ++++----- src/Avalonia.Controls/MenuItem.cs | 11 +++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 046253b3cd..87fabf5ed7 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -169,7 +169,7 @@ namespace Avalonia.Controls private set { SetValue(IsPressedProperty, value); } } - protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -183,7 +183,6 @@ namespace Avalonia.Controls ListenForDefault(inputElement); } } - if (IsCancel) { if (e.Root is IInputElement inputElement) @@ -210,6 +209,7 @@ namespace Avalonia.Controls protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { if (_hotkey != null) + // Control attached again, set Hotkey to create a hotkey manager for this control HotKey = _hotkey; base.OnAttachedToLogicalTree(e); @@ -223,6 +223,7 @@ namespace Avalonia.Controls { if (HotKey != null) { + // This will cause the hotkey manager to dispose the observer and the reference to this control _hotkey = HotKey; HotKey = null; } @@ -249,7 +250,6 @@ namespace Avalonia.Controls { OnClick(); } - IsPressed = true; e.Handled = true; } @@ -266,7 +266,6 @@ namespace Avalonia.Controls { OnClick(); } - IsPressed = false; e.Handled = true; } @@ -321,7 +320,7 @@ namespace Avalonia.Controls } } } - + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { IsPressed = false; diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 7d4fef009d..faf92c97fe 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -102,6 +102,7 @@ namespace Avalonia.Controls private ICommand? _command; private bool _commandCanExecute = true; private Popup? _popup; + private KeyGesture _hotkey; /// /// Initializes static members of the class. @@ -337,6 +338,9 @@ namespace Avalonia.Controls protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { + if (_hotkey != null) + // Control attached again, set Hotkey to create a hotkey manager for this control + HotKey = _hotkey; base.OnAttachedToLogicalTree(e); if (Command != null) @@ -347,6 +351,13 @@ namespace Avalonia.Controls protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { + if (HotKey != null) + { + // This will cause the hotkey manager to dispose the observer and the reference to this control + _hotkey = HotKey; + HotKey = null; + } + base.OnDetachedFromLogicalTree(e); if (Command != null) From 68f67f703401c50105ed63250266f44ec7f190a7 Mon Sep 17 00:00:00 2001 From: Adir Date: Thu, 8 Oct 2020 18:05:46 +0300 Subject: [PATCH 06/79] Reverted auto indentation in HotkeyManager.cs --- src/Avalonia.Controls/HotkeyManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 5972a0fdd9..95752e7875 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -84,7 +84,7 @@ namespace Avalonia.Controls { if (_root != null && _hotkey != null) { - _binding = new KeyBinding() { Gesture = _hotkey, Command = _wrapper }; + _binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper}; _root.KeyBindings.Add(_binding); } } @@ -102,12 +102,11 @@ namespace Avalonia.Controls HotKeyProperty.Changed.Subscribe(args => { var control = args.Sender as IControl; - if (args.OldValue != null || control == null) + if (args.OldValue != null|| control == null) return; new Manager(control).Init(); }); } - public static void SetHotKey(AvaloniaObject target, KeyGesture value) => target.SetValue(HotKeyProperty, value); public static KeyGesture GetHotKey(AvaloniaObject target) => target.GetValue(HotKeyProperty); } From fd2da7aa49b2ba59289d205e35b39cff177b43db Mon Sep 17 00:00:00 2001 From: Adir Date: Thu, 8 Oct 2020 21:26:21 +0300 Subject: [PATCH 07/79] Added unit tests for enforcing manager releasing reference to the controls Changed AncestorFinder.cs to dispose child nodes and subject (need review on this) --- src/Avalonia.Controls/Utils/AncestorFinder.cs | 2 + .../Utils/HotKeyManagerTests.cs | 113 +++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index 529aec1393..42e0d85dcd 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs @@ -47,6 +47,8 @@ namespace Avalonia.Controls.Utils public void Dispose() { + _child?.Dispose(); + _subject.Dispose(); _disposable.Dispose(); } } diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 9f2712c93c..6425c5c8f4 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -1,9 +1,14 @@ -using Moq; +using System; +using System.Collections.Generic; +using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Styling; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Utils @@ -50,10 +55,116 @@ namespace Avalonia.Controls.UnitTests.Utils HotKeyManager.SetHotKey(button, null); Assert.Empty(tl.KeyBindings); + } + } + + [Fact] + public void HotKeyManager_Release_Reference_When_Control_Detached() + { + using (AvaloniaLocator.EnterScope()) + { + var styler = new Mock(); + + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(new WindowingPlatformMock()) + .Bind().ToConstant(styler.Object); + + var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); + + WeakReference reference = null; + + var tl = new Window(); + + new Action(() => + { + var button = new Button(); + reference = new WeakReference(button, true); + tl.Content = button; + tl.Template = CreateWindowTemplate(); + tl.ApplyTemplate(); + tl.Presenter.ApplyTemplate(); + HotKeyManager.SetHotKey(button, gesture1); + + // Detach the button from the logical tree, so there is no reference to it + tl.Content = null; + tl.ApplyTemplate(); + })(); + + // The button should be collected since it's detached from the listbox + GC.Collect(); + GC.WaitForPendingFinalizers(); + + Assert.Null(reference?.Target); + } + } + + [Fact] + public void HotKeyManager_Release_Reference_When_Control_In_Item_Template_Detached() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var styler = new Mock(); + + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(new WindowingPlatformMock()) + .Bind().ToConstant(styler.Object); + + var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); + + var weakReferences = new List(); + var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true }; + var lm = tl.LayoutManager; + + var keyGestures = new AvaloniaList { gesture1 }; + var listBox = new ListBox + { + Width = 100, + Height = 100, + VirtualizationMode = ItemVirtualizationMode.None, + // Create a button with binding to the KeyGesture in the template and add it to references list + ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) => + { + var keyGesture = o as KeyGesture; + var button = new Button + { + DataContext = keyGesture, [!Button.HotKeyProperty] = new Binding("") + }; + weakReferences.Add(new WeakReference(button, true)); + return button; + }) + }; + // Add the listbox and render it + tl.Content = listBox; + lm.ExecuteInitialLayoutPass(); + listBox.Items = keyGestures; + lm.ExecuteLayoutPass(); + + // Let the button detach when clearing the source items + keyGestures.Clear(); + lm.ExecuteLayoutPass(); + + // Add it again to double check,and render + keyGestures.Add(gesture1); + lm.ExecuteLayoutPass(); + + keyGestures.Clear(); + lm.ExecuteLayoutPass(); + + // The button should be collected since it's detached from the listbox + GC.Collect(); + GC.WaitForPendingFinalizers(); + + + Assert.True(weakReferences.Count > 0); + foreach (var weakReference in weakReferences) + { + Assert.Null(weakReference.Target); + } } } + private FuncControlTemplate CreateWindowTemplate() { return new FuncControlTemplate((parent, scope) => From d58d80d11b8c8e7d038899e99c62e75da59fbc2c Mon Sep 17 00:00:00 2001 From: Adir Date: Fri, 9 Oct 2020 01:34:14 +0300 Subject: [PATCH 08/79] Added another GC collect to the HotKeyManagerTests.cs memory leak tests Renamed tests --- .../Utils/HotKeyManagerTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 6425c5c8f4..2e23de366e 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests.Utils } [Fact] - public void HotKeyManager_Release_Reference_When_Control_Detached() + public void HotKeyManager_Should_Release_Reference_When_Control_Detached() { using (AvaloniaLocator.EnterScope()) { @@ -94,13 +94,15 @@ namespace Avalonia.Controls.UnitTests.Utils // The button should be collected since it's detached from the listbox GC.Collect(); GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); Assert.Null(reference?.Target); } } [Fact] - public void HotKeyManager_Release_Reference_When_Control_In_Item_Template_Detached() + public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -154,7 +156,8 @@ namespace Avalonia.Controls.UnitTests.Utils // The button should be collected since it's detached from the listbox GC.Collect(); GC.WaitForPendingFinalizers(); - + GC.Collect(); + GC.WaitForPendingFinalizers(); Assert.True(weakReferences.Count > 0); foreach (var weakReference in weakReferences) From fda517d13e7371c4577974dbc4ea3d6bc5d78201 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 14:24:52 +0200 Subject: [PATCH 09/79] Make filename match contained class. --- src/Avalonia.Input/{Cursors.cs => Cursor.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Input/{Cursors.cs => Cursor.cs} (100%) diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursor.cs similarity index 100% rename from src/Avalonia.Input/Cursors.cs rename to src/Avalonia.Input/Cursor.cs From 3124152913cf91658836e7d9b4faef246a573178 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 16:37:27 +0200 Subject: [PATCH 10/79] Enable NRT for cursor API. --- src/Avalonia.Input/Cursor.cs | 2 ++ src/Avalonia.Input/Platform/IStandardCursorFactory.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 920b598eac..0c858aacdd 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -1,6 +1,8 @@ using System; using Avalonia.Platform; +#nullable enable + namespace Avalonia.Input { /* diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs index 51845cf03e..ede19e91b4 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs @@ -1,5 +1,7 @@ using Avalonia.Input; +#nullable enable + namespace Avalonia.Platform { public interface IStandardCursorFactory From 03a18f56bbc6c70542db99e0056d94b360a78d91 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 16:38:24 +0200 Subject: [PATCH 11/79] Initial support for custom cursors on Win32. --- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + .../HeadlessPlatformStubs.cs | 6 +- src/Avalonia.Input/Cursor.cs | 20 ++--- .../Platform/IStandardCursorFactory.cs | 1 + src/Avalonia.Native/Cursor.cs | 5 ++ src/Avalonia.X11/X11CursorFactory.cs | 5 ++ src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 5 ++ src/Windows/Avalonia.Win32/CursorFactory.cs | 80 +++++++++++++++++++ .../Interop/UnmanagedMethods.cs | 13 +++ .../CursorFactoryMock.cs | 5 ++ 10 files changed, 130 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f377b1bcd1..a400102877 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -195,6 +195,7 @@ namespace Avalonia.DesignerSupport.Remote class CursorFactoryStub : IStandardCursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new PlatformHandle(IntPtr.Zero, "STUB"); } class IconLoaderStub : IPlatformIconLoader diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 4c0e2982f4..43e6731eee 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -54,11 +54,15 @@ namespace Avalonia.Headless class HeadlessCursorFactoryStub : IStandardCursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) { return new PlatformHandle(new IntPtr((int)cursorType), "STUB"); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, "STUB"); + } } class HeadlessPlatformSettingsStub : IPlatformSettings diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 0c858aacdd..21c2b1a326 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media.Imaging; using Avalonia.Platform; #nullable enable @@ -58,7 +59,12 @@ namespace Avalonia.Input } public Cursor(StandardCursorType cursorType) - : this(GetCursor(cursorType)) + : this(GetCursorFactory().GetCursor(cursorType)) + { + } + + public Cursor(IBitmap cursor, PixelPoint hotSpot) + : this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot)) { } @@ -71,16 +77,10 @@ namespace Avalonia.Input throw new ArgumentException($"Unrecognized cursor type '{s}'."); } - private static IPlatformHandle GetCursor(StandardCursorType type) + private static IStandardCursorFactory GetCursorFactory() { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform == null) - { - throw new Exception("Could not create Cursor: IStandardCursorFactory not registered."); - } - - return platform.GetCursor(type); + return AvaloniaLocator.Current.GetService() ?? + throw new Exception("Could not create Cursor: ICursorFactory not registered."); } } } diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs index ede19e91b4..12c3ec7b74 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs @@ -7,5 +7,6 @@ namespace Avalonia.Platform public interface IStandardCursorFactory { IPlatformHandle GetCursor(StandardCursorType cursorType); + IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); } } diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index 3c65367c12..fffff51bce 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -38,5 +38,10 @@ namespace Avalonia.Native var cursor = _native.GetCursor((AvnStandardCursorType)cursorType); return new AvaloniaNativeCursor( cursor ); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + throw new NotImplementedException(); + } } } diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 1c995475ae..a82fe76cd5 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -67,6 +67,11 @@ namespace Avalonia.X11 return new PlatformHandle(handle, "XCURSOR"); } + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + throw new NotImplementedException(); + } + private static IntPtr GetNullCursor(IntPtr display) { XColor color = new XColor(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index 7a257da0dd..8763f18cc0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -10,6 +10,11 @@ namespace Avalonia.LinuxFramebuffer { return new PlatformHandle(IntPtr.Zero, null); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, null); + } } internal class PlatformSettings : IPlatformSettings { diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 8c40eaa7b8..844f230a2e 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -1,8 +1,15 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; using Avalonia.Input; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Win32.Interop; +using SdBitmap = System.Drawing.Bitmap; +using SdPixelFormat = System.Drawing.Imaging.PixelFormat; namespace Avalonia.Win32 { @@ -87,5 +94,78 @@ namespace Avalonia.Win32 return rv; } + + public IPlatformHandle 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 PlatformHandle( + UnmanagedMethods.CreateIconIndirect(ref info), + PlatformConstants.CursorHandleType); + } + + 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.Format32bppArgb && + source.PixelFormat != SdPixelFormat.Format32bppPArgb) + { + 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); + } + } } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 5feb6c9e46..c18a5c07c5 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1031,6 +1031,9 @@ 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 PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); @@ -1743,6 +1746,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 { diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index da02cccdc5..66c6033fb4 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -10,5 +10,10 @@ namespace Avalonia.Controls.UnitTests { return new PlatformHandle(IntPtr.Zero, cursorType.ToString()); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, "Custom"); + } } } From 193cdb3324d82a3dc7207a5bf980fa45bd9b639e Mon Sep 17 00:00:00 2001 From: grokys Date: Mon, 19 Oct 2020 13:44:54 +0200 Subject: [PATCH 12/79] Implement custom cursors on X11. --- src/Avalonia.X11/X11CursorFactory.cs | 59 +++++++++++++++++++++++++++- src/Avalonia.X11/X11Structs.cs | 2 +- src/Avalonia.X11/XLib.cs | 7 ++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index a82fe76cd5..176878ed5f 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -1,8 +1,13 @@ 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 { @@ -67,9 +72,9 @@ namespace Avalonia.X11 return new PlatformHandle(handle, "XCURSOR"); } - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - throw new NotImplementedException(); + return new XImageCursor(_display, cursor, hotSpot); } private static IntPtr GetNullCursor(IntPtr display) @@ -79,5 +84,55 @@ 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 : IFramebufferPlatformSurface, IPlatformHandle, IDisposable + { + private readonly PixelSize _pixelSize; + private readonly IUnmanagedBlob _blob; + + public XImageCursor(IntPtr display, IBitmapImpl bitmap, PixelPoint hotSpot) + { + var size = Marshal.SizeOf() + + (bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4); + + _pixelSize = bitmap.PixelSize; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(size); + + var image = (XcursorImage*)_blob.Address; + image->version = 1; + image->size = Marshal.SizeOf(); + 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().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 IntPtr Handle { get; } + public string HandleDescriptor => "XCURSOR"; + + public void Dispose() + { + XLib.XcursorImageDestroy(Handle); + _blob.Dispose(); + } + + public ILockedFramebuffer Lock() + { + return new LockedFramebuffer( + _blob.Address + Marshal.SizeOf(), + _pixelSize, _pixelSize.Width * 4, + new Vector(96, 96), PixelFormat.Bgra8888, null); + } + } } } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 0ae5c16aef..7ee764e8a3 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1684,7 +1684,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 */ diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 85aa4862b7..f5da691caa 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -19,6 +19,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); @@ -512,6 +513,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); From a832d639155ea05b84d35fa17a48c32b1e0366b1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 13:46:47 +0200 Subject: [PATCH 13/79] IStandardCursorFactory -> ICursorFactory. --- .../Remote/PreviewerWindowingPlatform.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs | 2 +- src/Avalonia.Headless/HeadlessPlatformStubs.cs | 2 +- src/Avalonia.Input/Cursor.cs | 4 ++-- .../{IStandardCursorFactory.cs => ICursorFactory.cs} | 2 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 2 +- src/Avalonia.Native/Cursor.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 ++-- src/Avalonia.X11/X11CursorFactory.cs | 2 +- src/Avalonia.X11/X11Platform.cs | 2 +- .../Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs | 2 +- src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 2 +- src/Windows/Avalonia.Win32/CursorFactory.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- .../CalendarDatePickerTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs | 2 +- tests/Avalonia.Controls.UnitTests/DatePickerTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 4 ++-- .../TextBoxTests_DataValidation.cs | 2 +- tests/Avalonia.Controls.UnitTests/TimePickerTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 8 ++++---- tests/Avalonia.UnitTests/UnitTestApplication.cs | 2 +- 24 files changed, 31 insertions(+), 31 deletions(-) rename src/Avalonia.Input/Platform/{IStandardCursorFactory.cs => ICursorFactory.cs} (83%) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index fe4c580bbb..67b832318a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -47,7 +47,7 @@ namespace Avalonia.DesignerSupport.Remote var threading = new InternalPlatformThreadingInterface(); AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(Keyboard) .Bind().ToConstant(instance) .Bind().ToConstant(threading) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a400102877..df1e5d6e98 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -192,7 +192,7 @@ namespace Avalonia.DesignerSupport.Remote public Task GetDataAsync(string format) => Task.FromResult((object)null); } - class CursorFactoryStub : IStandardCursorFactory + class CursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new PlatformHandle(IntPtr.Zero, "STUB"); diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 1f750a0309..fca2a1336f 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -58,7 +58,7 @@ namespace Avalonia.Headless AvaloniaLocator.CurrentMutable .Bind().ToConstant(new HeadlessPlatformThreadingInterface()) .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new HeadlessPlatformSettingsStub()) .Bind().ToSingleton() .Bind().ToSingleton() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 43e6731eee..efad5133b9 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -52,7 +52,7 @@ namespace Avalonia.Headless } } - class HeadlessCursorFactoryStub : IStandardCursorFactory + class HeadlessCursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 21c2b1a326..f018041a94 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -77,9 +77,9 @@ namespace Avalonia.Input throw new ArgumentException($"Unrecognized cursor type '{s}'."); } - private static IStandardCursorFactory GetCursorFactory() + private static ICursorFactory GetCursorFactory() { - return AvaloniaLocator.Current.GetService() ?? + return AvaloniaLocator.Current.GetService() ?? throw new Exception("Could not create Cursor: ICursorFactory not registered."); } } diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/ICursorFactory.cs similarity index 83% rename from src/Avalonia.Input/Platform/IStandardCursorFactory.cs rename to src/Avalonia.Input/Platform/ICursorFactory.cs index 12c3ec7b74..e531015d40 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/ICursorFactory.cs @@ -4,7 +4,7 @@ using Avalonia.Input; namespace Avalonia.Platform { - public interface IStandardCursorFactory + public interface ICursorFactory { IPlatformHandle GetCursor(StandardCursorType cursorType); IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index e8b2f065c7..c56a8404fa 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -99,7 +99,7 @@ namespace Avalonia.Native AvaloniaLocator.CurrentMutable .Bind() .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) - .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) + .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(this) diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index fffff51bce..ebee4baafe 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native } } - class CursorFactory : IStandardCursorFactory + class CursorFactory : ICursorFactory { IAvnCursorFactory _native; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 5d35c773d7..e4643bf675 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/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(); _mouse = new MouseDevice(); - _cursorFactory = AvaloniaLocator.Current.GetService(); + _cursorFactory = AvaloniaLocator.Current.GetService(); } protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 176878ed5f..7f08407239 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -11,7 +11,7 @@ using Avalonia.Utilities; namespace Avalonia.X11 { - class X11CursorFactory : IStandardCursorFactory + class X11CursorFactory : ICursorFactory { private static readonly byte[] NullCursorData = new byte[] { 0 }; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index c6db146f7b..ffd7c75608 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -52,7 +52,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) - .Bind().ToConstant(new X11CursorFactory(Display)) + .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToConstant(new X11Clipboard(this)) .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new X11IconLoader(Info)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 8801f71f9a..340d10517d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -38,7 +38,7 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) - .Bind().ToTransient() + .Bind().ToTransient() .Bind().ToConstant(new KeyboardDevice()) .Bind().ToSingleton() .Bind().ToConstant(new RenderLoop()) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index 8763f18cc0..def5f6b817 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer { - internal class CursorFactoryStub : IStandardCursorFactory + internal class CursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 844f230a2e..aca5f42771 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -13,7 +13,7 @@ using SdPixelFormat = System.Drawing.Imaging.PixelFormat; namespace Avalonia.Win32 { - internal class CursorFactory : IStandardCursorFactory + internal class CursorFactory : ICursorFactory { public static CursorFactory Instance { get; } = new CursorFactory(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 61c1a4f45e..ebc69072fc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -90,7 +90,7 @@ namespace Avalonia.Win32 Options = options; AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToConstant(CursorFactory.Instance) + .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index f41a3e7581..d77c7b87fa 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -74,7 +74,7 @@ namespace Avalonia.Controls.UnitTests } private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private CalendarDatePicker CreateControl() { diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index 66c6033fb4..0c5a395ba4 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { - public class CursorFactoryMock : IStandardCursorFactory + public class CursorFactoryMock : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index 7bcb120850..3105263290 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/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(), + standardCursorFactory: Mock.Of(), textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index f2b6b0db4b..72ccead783 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -11,8 +11,8 @@ namespace Avalonia.Controls.UnitTests { public GridSplitterTests() { - var cursorFactoryImpl = new Mock(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); + var cursorFactoryImpl = new Mock(); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index fe25fa7346..6ac7799828 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/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()); + standardCursorFactory: Mock.Of()); private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 78b6bf01ec..570b9ee4ea 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/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()); + standardCursorFactory: Mock.Of()); private IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index 682f0eaadb..c39a963dae 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/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(), + standardCursorFactory: Mock.Of(), textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 012cab23dc..8d27562146 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -23,7 +23,7 @@ namespace Avalonia.UnitTests assetLoader: new AssetLoader(), platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), - standardCursorFactory: Mock.Of(), + standardCursorFactory: Mock.Of(), styler: new Styler(), theme: () => CreateDefaultTheme(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), @@ -70,7 +70,7 @@ namespace Avalonia.UnitTests IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, IScheduler scheduler = null, - IStandardCursorFactory standardCursorFactory = null, + ICursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, @@ -111,7 +111,7 @@ namespace Avalonia.UnitTests public IFontManagerImpl FontManagerImpl { get; } public ITextShaperImpl TextShaperImpl { get; } public IScheduler Scheduler { get; } - public IStandardCursorFactory StandardCursorFactory { get; } + public ICursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } @@ -130,7 +130,7 @@ namespace Avalonia.UnitTests IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, IScheduler scheduler = null, - IStandardCursorFactory standardCursorFactory = null, + ICursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index e4a65f105d..6fc82088e9 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -62,7 +62,7 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.TextShaperImpl) .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.Scheduler) - .Bind().ToConstant(Services.StandardCursorFactory) + .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); From ca408e55b5091449158cfae7536960464389f6fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:32:28 +0200 Subject: [PATCH 14/79] Added ICursorImpl. --- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 11 ++-- .../HeadlessPlatformStubs.cs | 10 ++-- src/Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- src/Avalonia.Input/Cursor.cs | 6 +-- src/Avalonia.Input/Platform/ICursorFactory.cs | 4 +- src/Avalonia.Input/Platform/ICursorImpl.cs | 14 +++++ src/Avalonia.Native/Cursor.cs | 6 +-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11CursorFactory.cs | 19 ++++--- src/Avalonia.X11/X11Window.cs | 8 ++- .../FramebufferToplevelImpl.cs | 2 +- src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 10 ++-- .../Wpf/WpfTopLevelImpl.cs | 6 +-- src/Windows/Avalonia.Win32/CursorFactory.cs | 52 ++++++++++++------- .../Interop/UnmanagedMethods.cs | 3 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 15 ++++-- .../CursorFactoryMock.cs | 16 ++++-- 20 files changed, 121 insertions(+), 71 deletions(-) create mode 100644 src/Avalonia.Input/Platform/ICursorImpl.cs diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 522103c7bd..ca0e9d48b8 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/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) { } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 7514f214aa..09f38042a1 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -98,7 +98,7 @@ namespace Avalonia.Platform /// Sets the cursor associated with the toplevel. /// /// The cursor. Use null for default cursor - void SetCursor(IPlatformHandle cursor); + void SetCursor(ICursorImpl cursor); /// /// Gets or sets a method called when the underlying implementation is destroyed. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 3d24f60463..ffccdeb3e2 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) - .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor)); + .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl)); if (((IStyleHost)this).StylingParent is IResourceHost applicationResources) { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index df1e5d6e98..751c5c08f2 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/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) { } @@ -194,8 +194,13 @@ namespace Avalonia.DesignerSupport.Remote class CursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => 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 diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index efad5133b9..ce4c31e27e 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -54,14 +54,12 @@ namespace Avalonia.Headless class HeadlessCursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(new IntPtr((int)cursorType), "STUB"); - } + public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub(); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub(); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + private class CursorStub : ICursorImpl { - return new PlatformHandle(IntPtr.Zero, "STUB"); + public void Dispose() { } } } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 8f4fa5e304..2c52438743 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/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) { } diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index f018041a94..052ef5e9a0 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -53,9 +53,9 @@ namespace Avalonia.Input { 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) @@ -68,7 +68,7 @@ namespace Avalonia.Input { } - public IPlatformHandle PlatformCursor { get; } + public ICursorImpl PlatformImpl { get; } public static Cursor Parse(string s) { diff --git a/src/Avalonia.Input/Platform/ICursorFactory.cs b/src/Avalonia.Input/Platform/ICursorFactory.cs index e531015d40..fff1f92d53 100644 --- a/src/Avalonia.Input/Platform/ICursorFactory.cs +++ b/src/Avalonia.Input/Platform/ICursorFactory.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform { public interface ICursorFactory { - IPlatformHandle GetCursor(StandardCursorType cursorType); - IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); + ICursorImpl GetCursor(StandardCursorType cursorType); + ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); } } diff --git a/src/Avalonia.Input/Platform/ICursorImpl.cs b/src/Avalonia.Input/Platform/ICursorImpl.cs new file mode 100644 index 0000000000..14235869f7 --- /dev/null +++ b/src/Avalonia.Input/Platform/ICursorImpl.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Input; + +#nullable enable + +namespace Avalonia.Platform +{ + /// + /// Represents a platform implementation of a . + /// + public interface ICursorImpl : IDisposable + { + } +} diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index ebee4baafe..2cb085bc7e 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -5,7 +5,7 @@ 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; @@ -33,13 +33,13 @@ 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 IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index e4643bf675..ad2b7411eb 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -389,7 +389,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) { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 7f08407239..f95d4320fe 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -56,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) @@ -69,10 +69,10 @@ namespace Avalonia.X11 ? _cursors[shape] : _cursors[CursorFontShape.XC_top_left_arrow]; } - return new PlatformHandle(handle, "XCURSOR"); + return new CursorImpl(handle); } - public unsafe IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { return new XImageCursor(_display, cursor, hotSpot); } @@ -85,7 +85,7 @@ namespace Avalonia.X11 return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } - private unsafe class XImageCursor : IFramebufferPlatformSurface, IPlatformHandle, IDisposable + private unsafe class XImageCursor : CursorImpl, IFramebufferPlatformSurface, IPlatformHandle { private readonly PixelSize _pixelSize; private readonly IUnmanagedBlob _blob; @@ -117,10 +117,9 @@ namespace Avalonia.X11 Handle = XLib.XcursorImageLoadCursor(display, _blob.Address); } - public IntPtr Handle { get; } public string HandleDescriptor => "XCURSOR"; - public void Dispose() + public override void Dispose() { XLib.XcursorImageDestroy(Handle); _blob.Dispose(); @@ -135,4 +134,12 @@ namespace Avalonia.X11 } } } + + class CursorImpl : ICursorImpl + { + public CursorImpl() { } + public CursorImpl(IntPtr handle) => Handle = handle; + public IntPtr Handle { get; protected set; } + public virtual void Dispose() { } + } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2cd3b973d8..32c455571f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -878,15 +878,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); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 0a101eec7a..4bbb58e53e 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/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) { } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index def5f6b817..642be28c69 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -6,14 +6,12 @@ namespace Avalonia.LinuxFramebuffer { internal class CursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(IntPtr.Zero, null); - } + public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub(); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub(); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + private class CursorStub : ICursorImpl { - return new PlatformHandle(IntPtr.Zero, null); + public void Dispose() { } } } internal class PlatformSettings : IPlatformSettings diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 3467a33d16..3bb29f4e23 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -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 ITopLevelImpl.Input { get; set; } //TODO diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index aca5f42771..10878ba7b5 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Runtime.InteropServices; using Avalonia.Input; -using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Win32.Interop; using SdBitmap = System.Drawing.Bitmap; @@ -36,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)); } } } @@ -77,25 +74,23 @@ namespace Avalonia.Win32 {StandardCursorType.DragLink, 32516}, }; - private static readonly Dictionary Cache = - new Dictionary(); + private static readonly Dictionary Cache = + new Dictionary(); - 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 IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { using var source = LoadSystemDrawingBitmap(cursor); using var mask = AlphaToMask(source); @@ -109,9 +104,7 @@ namespace Avalonia.Win32 ColorBitmap = source.GetHbitmap(), }; - return new PlatformHandle( - UnmanagedMethods.CreateIconIndirect(ref info), - PlatformConstants.CursorHandleType); + return new CursorImpl(UnmanagedMethods.CreateIconIndirect(ref info), true); } private SdBitmap LoadSystemDrawingBitmap(IBitmapImpl bitmap) @@ -168,4 +161,27 @@ namespace Avalonia.Win32 } } } + + 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; + } + } + } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c18a5c07c5..3b11aecc2d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1034,6 +1034,9 @@ namespace Avalonia.Win32.Interop [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); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7079a0120c..755570bff1 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -557,14 +557,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); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index 0c5a395ba4..ee4264e6b9 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.Input; using Avalonia.Platform; @@ -6,14 +5,21 @@ namespace Avalonia.Controls.UnitTests { 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 IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - return new PlatformHandle(IntPtr.Zero, "Custom"); + return new MockCursorImpl(); + } + + private class MockCursorImpl : ICursorImpl + { + public void Dispose() + { + } } } } From f0d097ed1234a9a45ab42451c52bba8afc4ac37d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:34:50 +0200 Subject: [PATCH 15/79] Make Cursor disposable. --- src/Avalonia.Input/Cursor.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 052ef5e9a0..2b99c51472 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -6,13 +6,6 @@ using Avalonia.Platform; 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, @@ -49,7 +42,7 @@ namespace Avalonia.Input // SizeNorthEastSouthWest, } - public class Cursor + public class Cursor : IDisposable { public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); @@ -70,6 +63,8 @@ namespace Avalonia.Input public ICursorImpl PlatformImpl { get; } + public void Dispose() => PlatformImpl.Dispose(); + public static Cursor Parse(string s) { return Enum.TryParse(s, true, out var t) ? From 04f5d3a252f1c029ccc64f16684c07eb8cd6c798 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:38:01 +0200 Subject: [PATCH 16/79] Don't allow premultipied alpha for the moment. --- src/Windows/Avalonia.Win32/CursorFactory.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 10878ba7b5..0a0e45b03a 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -118,8 +118,13 @@ namespace Avalonia.Win32 { var dest = new SdBitmap(source.Width, source.Height, SdPixelFormat.Format1bppIndexed); - if (source.PixelFormat != SdPixelFormat.Format32bppArgb && - source.PixelFormat != SdPixelFormat.Format32bppPArgb) + 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; } From 98974af13e7e08662c32b4744e6df9f89490d97c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Oct 2020 14:44:24 +0200 Subject: [PATCH 17/79] Added cursor page to control catalog. --- samples/ControlCatalog/Assets/avalonia-32.png | Bin 0 -> 1322 bytes samples/ControlCatalog/MainView.xaml | 4 ++ samples/ControlCatalog/Pages/CursorPage.xaml | 29 ++++++++++++ .../ControlCatalog/Pages/CursorPage.xaml.cs | 20 +++++++++ .../ViewModels/CursorPageViewModel.cs | 42 ++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 samples/ControlCatalog/Assets/avalonia-32.png create mode 100644 samples/ControlCatalog/Pages/CursorPage.xaml create mode 100644 samples/ControlCatalog/Pages/CursorPage.xaml.cs create mode 100644 samples/ControlCatalog/ViewModels/CursorPageViewModel.cs diff --git a/samples/ControlCatalog/Assets/avalonia-32.png b/samples/ControlCatalog/Assets/avalonia-32.png new file mode 100644 index 0000000000000000000000000000000000000000..7b443e7a250846dc898e6467cddeed8c55880766 GIT binary patch literal 1322 zcmV+_1=aeAP)A zL5V<&1`?F`W{`LZcsE5cBpMzF4;mv6L;;~9STsnu#G;m4u_XwFBGRT;C>`6GUgn(T zL)$4c=S({l;{UQ|_FDV=|6Y6Tb=DCggsQrL1;9dJ29OIR^qV_@TA&g*4xAK`-bkN4 z9C5m;mZ)ljMvf*`-Kwf%?}yk>)mK&3GXmmJbg1eZ{m~l{d7`S8-Uo3gep1zxNXaLv zYSjZIAHbigniI+=A>>IwIZ!Z^$o9rgT58+rX!W5AiD_}9PaZ`|X5w)By8#r7NUKeN zsv5v|z%r+mmfzx!BR9Bs_y#St?T$}Rn>32TB~y5G>114PBbTZ98d$;Vp#KA9s55xh1snb zVql0XA`$Lr7fUu3F?C*+li3I4iAdc*oHjU-H`Lr_?|aAS_Vr+?iKSLxRCGuI{vMMb zK0ZUk2-=%=h?IY zi`~EzPRh&oS7NDYn_skkCi9*z44TU>OeecAopT4SaeU`R+nUNljsgG{8T7qa0_gPk zXlwS6nU~go-h*d9ZQcYM`MsF6BA5Jy*|vF4lb7b}cb%5ZFn}oo|JrZ=!;Uw1vwh<( zW-ZO<@uv%dM9f`1WPJjlXysJL`Q~em0&)x>8Nl1o#aATvT{Op4xk1SOI5BuQ<-fC&z>p5V$uYLPByp$*GA{ zp16$P^aoYHvq!3^ID9EAdG)bc&K+_VkT53JX<7~XdN>>AE`KbD+)@+MviN388T+;# zrTOMyiM?Id&f(9>`L^T)rsa?TLqJqD7H9y1I+mWUUfx>%DV|nu*isxDAA`$q)9W*X zY)MfmgjVRHRe3B}Z@b2pAtGJC?odl~Y!ok+tVCScA~7&_dAsp-`bPYsx)%e5ejCso zYRoU1!kTxMgP~zX432}pM*;&tL~atM8JV+eCL4CFC1G@I#3CZ87oiaV_z*bhHmSh z$FX-7ktX^Yl}~^N^O5O{ABXi&)f`p*LRC8-@RyQDRdXYjs;Wt<`dq)5v#Q$iA4n}# g?NZe;Rhb%7 literal 0 HcmV?d00001 diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index bd5beafe29..34ff4b1a54 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -22,6 +22,10 @@ + + + diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml b/samples/ControlCatalog/Pages/CursorPage.xaml new file mode 100644 index 0000000000..a28039ea3f --- /dev/null +++ b/samples/ControlCatalog/Pages/CursorPage.xaml @@ -0,0 +1,29 @@ + + + + Cursor + Defines a cursor (mouse pointer) + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml.cs b/samples/ControlCatalog/Pages/CursorPage.xaml.cs new file mode 100644 index 0000000000..9e9e9ba8b9 --- /dev/null +++ b/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); + } + } +} diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs new file mode 100644 index 0000000000..a4c643dd04 --- /dev/null +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Input; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using ReactiveUI; + +namespace ControlCatalog.ViewModels +{ + public class CursorPageViewModel : ReactiveObject + { + public CursorPageViewModel() + { + StandardCursors = Enum.GetValues(typeof(StandardCursorType)) + .Cast() + .Select(x => new StandardCursorModel(x)) + .ToList(); + + var loader = AvaloniaLocator.Current.GetService(); + 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 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; } + } + } +} From 41c4630bbb2fac9e9247f864d510079a9e60327b Mon Sep 17 00:00:00 2001 From: Adir Date: Fri, 23 Oct 2020 14:08:05 +0300 Subject: [PATCH 18/79] Changed if statements to have braces Moved comments above if --- src/Avalonia.Controls/Button.cs | 8 +++++--- src/Avalonia.Controls/MenuItem.cs | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 87fabf5ed7..9cd7a19e7e 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -208,9 +208,11 @@ namespace Avalonia.Controls protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - if (_hotkey != null) - // Control attached again, set Hotkey to create a hotkey manager for this control + if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control + { HotKey = _hotkey; + } + base.OnAttachedToLogicalTree(e); if (Command != null) @@ -221,9 +223,9 @@ namespace Avalonia.Controls protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { + // This will cause the hotkey manager to dispose the observer and the reference to this control if (HotKey != null) { - // This will cause the hotkey manager to dispose the observer and the reference to this control _hotkey = HotKey; HotKey = null; } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index faf92c97fe..8612f696bc 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -338,9 +338,11 @@ namespace Avalonia.Controls protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - if (_hotkey != null) - // Control attached again, set Hotkey to create a hotkey manager for this control + if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control + { HotKey = _hotkey; + } + base.OnAttachedToLogicalTree(e); if (Command != null) @@ -351,9 +353,9 @@ namespace Avalonia.Controls protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { + // This will cause the hotkey manager to dispose the observer and the reference to this control if (HotKey != null) { - // This will cause the hotkey manager to dispose the observer and the reference to this control _hotkey = HotKey; HotKey = null; } From bb0a8b698567addeb835341504c2a8aff2125bc2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 23 Nov 2020 23:19:16 -0500 Subject: [PATCH 19/79] Fix compiled PropertyElement.ToString() for first Property element --- .../CompiledBindings/CompiledBindingPath.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index f6636664c1..11489c39aa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings internal object RawSource { get; } public override string ToString() - => string.Concat(_elements.Select(e => e.ToString())); + => string.Concat(_elements); } public class CompiledBindingPathBuilder @@ -88,7 +88,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CompiledBindingPathBuilder Property(IPropertyInfo info, Func, IPropertyInfo, IPropertyAccessor> accessorFactory) { - _elements.Add(new PropertyElement(info, accessorFactory)); + _elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0)); return this; } @@ -161,10 +161,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings internal class PropertyElement : ICompiledBindingPathElement { - public PropertyElement(IPropertyInfo property, Func, IPropertyInfo, IPropertyAccessor> accessorFactory) + private readonly bool _isFirstElement; + + public PropertyElement(IPropertyInfo property, Func, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement) { Property = property; AccessorFactory = accessorFactory; + _isFirstElement = isFirstElement; } public IPropertyInfo Property { get; } @@ -172,7 +175,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public Func, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; } public override string ToString() - => $".{Property.Name}"; + => _isFirstElement ? Property.Name : $".{Property.Name}"; } internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement From 14e3799641fc55a8c7fd9dd183bd3a718fa570f6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 23 Nov 2020 23:20:51 -0500 Subject: [PATCH 20/79] Use BindingBase in DataGrid instead of Binding and support CompiledBinding --- .../DataGridBoundColumn.cs | 21 ++++++++++++------- .../DataGridColumn.cs | 16 ++++++++------ .../DataGridColumns.cs | 5 +++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index 1e72a07760..90401a00a2 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -10,7 +10,8 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; using Avalonia.Reactive; using System.Diagnostics; -using Avalonia.Controls.Utils; +using Avalonia.Controls.Utils; +using Avalonia.Markup.Xaml.MarkupExtensions; namespace Avalonia.Controls { @@ -47,14 +48,15 @@ namespace Avalonia.Controls if (_binding != null) { - if(_binding is Avalonia.Data.Binding binding) + if(_binding is BindingBase binding) { if (binding.Mode == BindingMode.OneWayToSource) { throw new InvalidOperationException("DataGridColumn doesn't support BindingMode.OneWayToSource. Use BindingMode.TwoWay instead."); } - if (!String.IsNullOrEmpty(binding.Path) && binding.Mode == BindingMode.Default) + var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); + if (!string.IsNullOrEmpty(path) && binding.Mode == BindingMode.Default) { binding.Mode = BindingMode.TwoWay; } @@ -136,13 +138,16 @@ namespace Avalonia.Controls internal void SetHeaderFromBinding() { if (OwningGrid != null && OwningGrid.DataConnection.DataType != null - && Header == null && Binding != null && Binding is Binding binding - && !String.IsNullOrWhiteSpace(binding.Path)) + && Header == null && Binding != null && Binding is BindingBase binding) { - string header = OwningGrid.DataConnection.DataType.GetDisplayName(binding.Path); - if (header != null) + var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); + if (!string.IsNullOrWhiteSpace(path)) { - Header = header; + var header = OwningGrid.DataConnection.DataType.GetDisplayName(path); + if (header != null) + { + Header = header; + } } } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 92ddd4e736..407d6ff058 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -12,6 +12,7 @@ using System; using System.Linq; using System.Diagnostics; using Avalonia.Controls.Utils; +using Avalonia.Markup.Xaml.MarkupExtensions; namespace Avalonia.Controls { @@ -1033,13 +1034,16 @@ namespace Avalonia.Controls if (String.IsNullOrEmpty(result)) { - - if(this is DataGridBoundColumn boundColumn && - boundColumn.Binding != null && - boundColumn.Binding is Binding binding && - binding.Path != null) + if (this is DataGridBoundColumn boundColumn) { - result = binding.Path; + if (boundColumn.Binding is Binding binding) + { + result = binding.Path; + } + else if (boundColumn.Binding is CompiledBindingExtension compiledBinding) + { + result = compiledBinding.Path.ToString(); + } } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 46bcd0d347..a4577ee952 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Utils; using Avalonia.Data; +using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Utilities; using System; using System.Collections.Generic; @@ -141,9 +142,9 @@ namespace Avalonia.Controls Debug.Assert(dataGridColumn != null); if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && - dataGridBoundColumn.Binding is Binding binding) + dataGridBoundColumn.Binding is BindingBase binding) { - string path = binding.Path; + var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); if (string.IsNullOrWhiteSpace(path)) { From 26fa71bddce33848565dfd4c42df314364d7cec3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 23 Nov 2020 23:23:55 -0500 Subject: [PATCH 21/79] DataGrid add CompiledBinding example --- samples/ControlCatalog/Pages/DataGridPage.xaml | 5 +++-- samples/ControlCatalog/Pages/DataGridPage.xaml.cs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index cacc2204bd..6dc50087f8 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,5 +1,5 @@ @@ -26,7 +26,8 @@ - + + diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 2a30f4d91b..dc5cc49a90 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -24,8 +24,10 @@ namespace ControlCatalog.Pages dg1.LoadingRow += Dg1_LoadingRow; dg1.Sorting += (s, a) => { - var property = ((a.Column as DataGridBoundColumn)?.Binding as Binding).Path; - if (property == dataGridSortDescription.PropertyPath + var binding = (a.Column as DataGridBoundColumn)?.Binding as Binding; + + if (binding?.Path is string property + && property == dataGridSortDescription.PropertyPath && !collectionView1.SortDescriptions.Contains(dataGridSortDescription)) { collectionView1.SortDescriptions.Add(dataGridSortDescription); From 1321c7996e81d34f19fc6bf3704e09939960a076 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 10 Jan 2021 10:39:46 +0100 Subject: [PATCH 22/79] Fix various Direct2D resource leak issues, as well as an unclosed geometry stream. --- .../Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 12 +++++++++++- src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs | 6 +++--- .../Media/Imaging/WicBitmapImpl.cs | 6 +++--- .../Avalonia.Direct2D1/Media/StreamGeometryImpl.cs | 9 ++++++--- .../Avalonia.Direct2D1/PrimitiveExtensions.cs | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 136ff63f3d..47a19aad8c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/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(); + _ownsDeviceContext = true; } _deviceContext.BeginDraw(); @@ -96,6 +99,13 @@ namespace Avalonia.Direct2D1.Media { throw new RenderTargetCorruptedException(ex); } + finally + { + if (_ownsDeviceContext) + { + _deviceContext.Dispose(); + } + } } /// @@ -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) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 636309ad1a..d04e2b3110 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -33,13 +33,13 @@ namespace Avalonia.Direct2D1.Media /// 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); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 49193afd78..90592ea806 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media /// 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 /// The filename of the bitmap to load. 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 /// The Direct2D bitmap. public override OptionalDispose 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.FromWicBitmap(renderTarget, converter), true); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 9104be64b2..2bc2b2db71 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/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); } diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index 31e9c260e0..669e139d8f 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -111,7 +111,7 @@ namespace Avalonia.Direct2D1 /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, SharpDX.Direct2D1.RenderTarget renderTarget) { - return pen.ToDirect2DStrokeStyle(renderTarget.Factory); + return pen.ToDirect2DStrokeStyle(Direct2D1Platform.Direct2D1Factory); } /// From afbff15978baef52d2715bc2af4155c0417b1632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 20 Jan 2021 19:47:19 +0000 Subject: [PATCH 23/79] Enabled nullable reference types on Avalonia.Markup. --- .../Avalonia.Markup/Avalonia.Markup.csproj | 1 + src/Markup/Avalonia.Markup/Data/Binding.cs | 16 ++++---- .../Avalonia.Markup/Data/BindingBase.cs | 38 ++++++++----------- .../Avalonia.Markup/Data/MultiBinding.cs | 12 +++--- .../Avalonia.Markup/Data/RelativeSource.cs | 2 +- .../Avalonia.Markup/Data/TemplateBinding.cs | 17 ++++----- .../Parsers/BindingExpressionGrammar.cs | 24 ++++++------ .../Parsers/ExpressionObserverBuilder.cs | 16 ++++---- .../Markup/Parsers/ExpressionParser.cs | 22 +++++------ .../Markup/Parsers/Nodes/ElementNameNode.cs | 2 +- .../Markup/Parsers/Nodes/FindAncestorNode.cs | 6 +-- .../Markup/Parsers/Nodes/StringIndexerNode.cs | 16 ++++---- .../Markup/Parsers/PropertyPathGrammar.cs | 24 ++++++------ .../Markup/Parsers/SelectorGrammar.cs | 20 +++++----- .../Markup/Parsers/SelectorParser.cs | 6 +-- 15 files changed, 108 insertions(+), 114 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 89e2c096ee..ce104b3609 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -2,6 +2,7 @@ netstandard2.0 Avalonia + Enable diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index bf43730481..554c27b85a 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -39,17 +39,17 @@ namespace Avalonia.Data /// /// Gets or sets the name of the element to use as the binding source. /// - public string ElementName { get; set; } + public string? ElementName { get; set; } /// /// Gets or sets the relative source for the binding. /// - public RelativeSource RelativeSource { get; set; } + public RelativeSource? RelativeSource { get; set; } /// /// Gets or sets the source for the binding. /// - public object Source { get; set; } + public object? Source { get; set; } /// /// Gets or sets the binding path. @@ -59,20 +59,21 @@ namespace Avalonia.Data /// /// Gets or sets a function used to resolve types from names in the binding path. /// - public Func TypeResolver { get; set; } + public Func? 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(target != null); + Contract.Requires(target is not null); anchor = 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); +#nullable disable if (ElementName != null) { return CreateElementObserver( @@ -138,5 +139,6 @@ namespace Avalonia.Data throw new NotSupportedException(); } } +#nullable enable } } diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 3dbc83a7df..b900a043d7 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -38,22 +38,22 @@ namespace Avalonia.Data /// /// Gets or sets the to use. /// - public IValueConverter Converter { get; set; } + public IValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the value to use when the binding is unable to produce a value. /// - public object FallbackValue { get; set; } + public object? FallbackValue { get; set; } /// /// Gets or sets the value to use when the binding result is null. /// - public object TargetNullValue { get; set; } + public object? TargetNullValue { get; set; } /// /// Gets or sets the binding mode. @@ -68,23 +68,23 @@ namespace Avalonia.Data /// /// Gets or sets the string format. /// - public string StringFormat { get; set; } + public string? StringFormat { get; set; } - public WeakReference DefaultAnchor { get; set; } + public WeakReference? DefaultAnchor { get; set; } - public WeakReference NameScope { get; set; } + public WeakReference? NameScope { get; set; } protected abstract ExpressionObserver CreateExpressionObserver( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor, + object? anchor, bool enableDataValidation); /// public InstancedBinding Initiate( IAvaloniaObject target, AvaloniaProperty targetProperty, - object anchor = null, + object? anchor = null, bool enableDataValidation = false) { Contract.Requires(target != null); @@ -133,18 +133,13 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node, bool targetIsDataContext, - object anchor) + object? anchor) { Contract.Requires(target != null); 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) @@ -173,8 +168,7 @@ namespace Avalonia.Data { Contract.Requires(target != null); - 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), @@ -190,7 +184,7 @@ namespace Avalonia.Data { Contract.Requires(target != null); - IObservable controlLocator; + IObservable controlLocator; switch (relativeSource.Tree) { @@ -229,7 +223,7 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node) { - Contract.Requires(target != null); + Contract.Requires(target is not null); var result = new ExpressionObserver( () => target.GetValue(StyledElement.TemplatedParentProperty), @@ -240,7 +234,7 @@ namespace Avalonia.Data return result; } - protected IObservable GetParentDataContext(IAvaloniaObject target) + protected IObservable 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 +246,7 @@ namespace Avalonia.Data .Select(x => { return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ?? - Observable.Return((object)null); + Observable.Return((object?)null); }).Switch(); } diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index cbc5f414f2..1a33536fa3 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -22,12 +22,12 @@ namespace Avalonia.Data /// /// Gets or sets the to use. /// - public IMultiValueConverter Converter { get; set; } + public IMultiValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the value to use when the binding is unable to produce a value. @@ -52,12 +52,12 @@ namespace Avalonia.Data /// /// Gets or sets the relative source for the binding. /// - public RelativeSource RelativeSource { get; set; } + public RelativeSource? RelativeSource { get; set; } /// /// Gets or sets the string format. /// - 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 values, Type targetType, IMultiValueConverter converter) + private object ConvertValue(IList values, Type targetType, IMultiValueConverter? converter) { for (var i = 0; i < values.Count; ++i) { diff --git a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs index e3d2dd4aaa..037175a137 100644 --- a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs +++ b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs @@ -94,7 +94,7 @@ namespace Avalonia.Data /// /// Gets the type of ancestor to look for when in mode. /// - public Type AncestorType { get; set; } + public Type? AncestorType { get; set; } /// /// Gets or sets a value that describes the type of relative source lookup. diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 83f02c52aa..b6c723d68c 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/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 /// /// Gets or sets the to use. /// - public IValueConverter Converter { get; set; } + public IValueConverter? Converter { get; set; } /// /// Gets or sets a parameter to pass to . /// - public object ConverterParameter { get; set; } + public object? ConverterParameter { get; set; } /// /// Gets or sets the binding mode. @@ -83,7 +83,7 @@ namespace Avalonia.Data /// /// Gets or sets the name of the source property on the templated parent. /// - public AvaloniaProperty Property { get; set; } + public AvaloniaProperty? Property { get; set; } /// 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; } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 7c362e24cc..4da6a7f3ae 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -271,8 +271,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 +424,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 Arguments { get; set; } + public IList Arguments { get; set; } = Array.Empty(); } public class NotNode : INode, ITransformNode { } @@ -447,20 +447,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; } } } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index f957bcab1e..e615923ff0 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/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 typeResolver = null, - INameScope nameScope = null) + internal static (ExpressionNode? Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func? 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 typeResolver = null) + string? description = null, + Func? typeResolver = null) { return new ExpressionObserver( root, @@ -45,8 +45,8 @@ namespace Avalonia.Markup.Parsers IObservable rootObservable, string expression, bool enableDataValidation = false, - string description = null, - Func typeResolver = null) + string? description = null, + Func? typeResolver = null) { Contract.Requires(rootObservable != null); return new ExpressionObserver( @@ -61,8 +61,8 @@ namespace Avalonia.Markup.Parsers string expression, IObservable update, bool enableDataValidation = false, - string description = null, - Func typeResolver = null) + string? description = null, + Func? typeResolver = null) { Contract.Requires(rootGetter != null); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index 558130e23f..0a6f3f82ba 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs +++ b/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 _typeResolver; - private readonly INameScope _nameScope; + private readonly Func? _typeResolver; + private readonly INameScope? _nameScope; - public ExpressionParser(bool enableValidation, Func typeResolver, INameScope nameScope) + public ExpressionParser(bool enableValidation, Func? 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) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index 97198145a8..d6068a15d4 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Markup.Parsers.Nodes { private readonly WeakReference _nameScope; private readonly string _name; - private IDisposable _subscription; + private IDisposable? _subscription; public ElementNameNode(INameScope nameScope, string name) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs index f304d1e9a2..4124032113 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs +++ b/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; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs index f3abd6a5c5..2f1756fc55 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs +++ b/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 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; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index c5953b514c..8e0a40ccf4 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -30,7 +30,7 @@ namespace Avalonia.Markup.Parsers var parsed = new List(); 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 +53,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 +106,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 +147,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,7 +184,7 @@ 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) => obj is PropertySyntax other @@ -193,9 +193,9 @@ namespace Avalonia.Markup.Parsers 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) => obj is TypeQualifiedPropertySyntax other @@ -212,8 +212,8 @@ namespace Avalonia.Markup.Parsers public class EnsureTypeSyntax : ISyntax { - public string TypeName { get; set; } - public string TypeNamespace { get; set; } + 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 @@ -222,8 +222,8 @@ namespace Avalonia.Markup.Parsers public class CastTypeSyntax : ISyntax { - public string TypeName { get; set; } - public string TypeNamespace { get; set; } + 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 diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index b25e9490cd..d821a04d93 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -39,7 +39,7 @@ namespace Avalonia.Markup.Parsers var selector = new List(); while (!r.End && state != State.End) { - ISyntax syntax = null; + ISyntax? syntax = null; switch (state) { case State.Start: @@ -110,7 +110,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 +190,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,7 +325,7 @@ 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; @@ -338,7 +338,7 @@ 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; @@ -351,7 +351,7 @@ 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) { @@ -361,7 +361,7 @@ 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) { @@ -371,9 +371,9 @@ 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) { @@ -409,7 +409,7 @@ namespace Avalonia.Markup.Parsers public class NotSyntax : ISyntax { - public IEnumerable Argument { get; set; } + public IEnumerable Argument { get; set; } = Enumerable.Empty(); public override bool Equals(object obj) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 04519bf2bb..92ba744ee1 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -31,13 +31,13 @@ namespace Avalonia.Markup.Parsers /// /// The string. /// The parsed selector. - public Selector Parse(string s) + public Selector? Parse(string s) { var syntax = SelectorGrammar.Parse(s); return Create(syntax); } - private Selector Create(IEnumerable syntax) + private Selector? Create(IEnumerable syntax) { var result = default(Selector); var results = default(List); @@ -110,7 +110,7 @@ namespace Avalonia.Markup.Parsers results = new List(); } - results.Add(result); + results.Add(result ?? throw new NotSupportedException("Invalid selector!")); result = null; break; default: From fe6f66e77d9ac0f363d9bdc8e741e8970f89b9dc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 28 Jan 2021 19:00:20 +0100 Subject: [PATCH 24/79] Try all font styles to find a fallback typeface --- src/Skia/Avalonia.Skia/SKTypefaceCollection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 71deb1235f..7c4ff4edc0 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/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; } } } From b8d3f85636d15b39c05e5b3cd2869dfd20532047 Mon Sep 17 00:00:00 2001 From: MichalPetryka <35800402+MichalPetryka@users.noreply.github.com> Date: Thu, 28 Jan 2021 19:57:49 +0100 Subject: [PATCH 25/79] Correct RtlGetVersion usage --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b21741453c..1559c7e794 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1432,15 +1432,15 @@ namespace Avalonia.Win32.Interop } [DllImport("ntdll")] - private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); + private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation); internal static Version RtlGetVersion() { RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX(); v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(v); - if (RtlGetVersion(out v) == 0) + if (RtlGetVersion(ref v) == 0) { - return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber, (int)v.dwPlatformId); + return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber); } else { From 4bebe777d907ca07a39a4eb181dc1018384c9356 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 28 Jan 2021 20:06:52 +0100 Subject: [PATCH 26/79] Fix mock font manager and add typeface collection tests --- .../Media/CustomFontManagerImpl.cs | 4 +-- .../Media/SKTypefaceCollectionCacheTests.cs | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index a0fe348166..cc4b727bd9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media continue; } - typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight); + typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight); return true; } @@ -83,7 +83,7 @@ namespace Avalonia.Skia.UnitTests.Media case "Noto Mono": { var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); - skTypeface = typefaceCollection.Get(typeface); + skTypeface = typefaceCollection.Get(_defaultTypeface); break; } default: diff --git a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs b/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs index f9f924e782..68813f28ab 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs @@ -7,25 +7,33 @@ namespace Avalonia.Skia.UnitTests.Media public class SKTypefaceCollectionCacheTests { [Fact] - public void Should_Load_Typefaces_From_Invalid_Name() + public void Should_Get_Near_Matching_Typeface() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { var notoMono = new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); - var colorEmoji = - new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Twitter Color Emoji"); - var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono); - var typeface = new Typeface("ABC", FontStyle.Italic, FontWeight.Bold); - - Assert.Equal("Noto Mono", notoMonoCollection.Get(typeface).FamilyName); - - var notoColorEmojiCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(colorEmoji); + Assert.Equal("Noto Mono", + notoMonoCollection.Get(new Typeface(notoMono, weight: FontWeight.Bold)).FamilyName); + } + } + + [Fact] + public void Should_Get_Null_For_Invalid_FamilyName() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var notoMono = + new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); + + var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono); - Assert.Equal("Twitter Color Emoji", notoColorEmojiCollection.Get(typeface).FamilyName); + var typeface = notoMonoCollection.Get(new Typeface("ABC")); + + Assert.Null(typeface); } } } From faf9aa1f96e710fd648737c60776aa877c7675ee Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 28 Jan 2021 20:23:45 +0100 Subject: [PATCH 27/79] CanExecute reevaluation on parameter change --- src/Avalonia.Controls/MenuItem.cs | 13 ++++++++ .../MenuItemTests.cs | 30 +++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 7d4fef009d..bad1e1d45c 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -111,6 +111,7 @@ namespace Avalonia.Controls SelectableMixin.Attach(IsSelectedProperty); PressedMixin.Attach(); CommandProperty.Changed.Subscribe(CommandChanged); + CommandParameterProperty.Changed.Subscribe(CommandParameterChanged); FocusableProperty.OverrideDefaultValue(true); HeaderProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); IconProperty.Changed.AddClassHandler((x, e) => x.IconChanged(e)); @@ -493,6 +494,18 @@ namespace Avalonia.Controls } } + /// + /// Called when the property changes. + /// + /// The event args. + private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is MenuItem menuItem) + { + menuItem.CanExecuteChanged(menuItem, EventArgs.Empty); + } + } + /// /// Called when the event fires. /// diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index 34371916df..5cb32f2b85 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/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 _canExecute; + private readonly Action _execute; private EventHandler _canExecuteChanged; public TestCommand(bool enabled = true) + : this(_ => enabled, _ => { }) + { + } + + public TestCommand(Func canExecute, Action execute) { - _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); } } } From a767773eb203b7ea6fb1ffd74bb2e8f7e4899123 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 28 Jan 2021 21:37:12 +0100 Subject: [PATCH 28/79] Fixed tests --- tests/Avalonia.Controls.UnitTests/MenuItemTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index 5cb32f2b85..ebe471f303 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -181,10 +181,10 @@ namespace Avalonia.Controls.UnitTests { } - public TestCommand(Func canExecute, Action execute) + public TestCommand(Func canExecute, Action execute = null) { _canExecute = canExecute; - _execute = execute; + _execute = execute ?? (_ => { }); } public int SubscriptionCount { get; private set; } From 817ef37b806ef5876e7305f49e85d2fceff22bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Tue, 2 Feb 2021 13:59:31 +0000 Subject: [PATCH 29/79] Fixed NativeControlHost to prevent calling ShowInBounds when control bounds are empty. --- src/Avalonia.Controls/NativeControlHost.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 20eac11c2c..64414b1f47 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -157,10 +157,14 @@ namespace Avalonia.Controls var needsShow = IsEffectivelyVisible && bounds.HasValue; if (needsShow) + { + if (bounds.Value.IsEmpty) + return false; _attachment?.ShowInBounds(bounds.Value); + } else _attachment?.HideWithSize(Bounds.Size); - return false; + return true; } private void CheckDestruction() From 1371cf56121938c150b14c808ec6cbe6cc3773f0 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 4 Feb 2021 17:03:13 +0100 Subject: [PATCH 30/79] Fix LineBreakEnumerator --- .../TextFormatting/Unicode/BreakPairTable.cs | 4 +- .../Media/TextFormatting/Unicode/Codepoint.cs | 27 +- .../Unicode/LineBreakEnumerator.cs | 540 +++++++++++++----- .../Unicode/LineBreakPairTable.cs | 74 +++ .../Media/TextFormatting/BreakPairTable.txt | 4 +- .../LineBreakEnumuratorTests.cs | 259 +++++++++ .../Media/TextFormatting/LineBreakerTests.cs | 56 -- .../UnicodeDataGeneratorTests.cs | 25 +- 8 files changed, 747 insertions(+), 242 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs delete mode 100644 tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs index 86d39a4283..76c94b324a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs @@ -5,8 +5,8 @@ namespace Avalonia.Media.TextFormatting.Unicode 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[] {0,4,4,1,1,4,4,4,4,1,1,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,4,4,4,4,1,1,1,1,1,0,0,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}, diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs index 2f46fdd9d0..43a95310c6 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs @@ -9,37 +9,40 @@ namespace Avalonia.Media.TextFormatting.Unicode /// public static readonly Codepoint ReplacementCodepoint = new Codepoint('\uFFFD'); - private readonly int _value; - public Codepoint(int value) { - _value = value; + Value = value; } + /// + /// Get the codepoint's value. + /// + public int Value { get; } + /// /// Gets the . /// - public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(_value); + public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(Value); /// /// Gets the . /// - public Script Script => UnicodeData.GetScript(_value); + public Script Script => UnicodeData.GetScript(Value); /// /// Gets the . /// - public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(_value); + public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(Value); /// /// Gets the . /// - public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(_value); + public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(Value); /// /// Gets the . /// - public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(_value); + public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(Value); /// /// Determines whether this 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; } /// @@ -112,7 +115,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { count = 1; - if (index > text.Length) + if (index >= text.Length) { return ReplacementCodepoint; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 76bb9ac44f..03433200c5 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/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 { /// - /// Implementation of the Unicode Line Break Algorithm + /// Implementation of the Unicode Line Break Algorithm. UAX:14 + /// /// public ref struct LineBreakEnumerator { - // State private readonly ReadOnlySlice _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 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.CombiningMark: + 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; + } + + // Rule LB25 + if (_first + && (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.InfixNumeric || cls == LineBreakClass.BreakSymbols)) + { + _lb25ex = true; + } - if (cur != null) + 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; + } + } - if (_nextClass.Value == LineBreakClass.MandatoryBreak) + // 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; + + 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 PairBreakType.CP: // prohibited for combining marks - if (!lastClass.HasValue || lastClass.Value != LineBreakClass.Space) + case LineBreakPairTable.CIBRK: + shouldBreak = lastClass == LineBreakClass.Space; + if (!shouldBreak) + { + return false; + } + + 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; } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs new file mode 100644 index 0000000000..22ef3f8fc2 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace Avalonia.Media.TextFormatting.Unicode +{ + internal static class LineBreakPairTable + { + /// + /// Direct break opportunity + /// + public const byte DIBRK = 0; + + /// + /// Indirect break opportunity + /// + public const byte INBRK = 1; + + /// + /// Indirect break opportunity for combining marks + /// + public const byte CIBRK = 2; + + /// + /// Prohibited break for combining marks + /// + public const byte CPBRK = 3; + + /// + /// Prohibited break + /// + 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; } = new byte[][] + { + // . 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 + }; + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt index 814ce15d0a..93d531c700 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt @@ -1,7 +1,7 @@ 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 OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ -CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs new file mode 100644 index 0000000000..5b30992891 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using Avalonia.Media.TextFormatting.Unicode; +using Xunit; +using Xunit.Abstractions; + +namespace Avalonia.Visuals.UnitTests.Media.TextFormatting +{ + public class LineBreakEnumeratorTests + { + private readonly ITestOutputHelper _outputHelper; + + public LineBreakEnumeratorTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + [Fact] + public void BasicLatinTest() + { + var lineBreaker = new LineBreakEnumerator("Hello World\r\nThis is a test.".AsMemory()); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(6, lineBreaker.Current.PositionWrap); + Assert.False(lineBreaker.Current.Required); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(13, lineBreaker.Current.PositionWrap); + Assert.True(lineBreaker.Current.Required); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(18, lineBreaker.Current.PositionWrap); + Assert.False(lineBreaker.Current.Required); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(21, lineBreaker.Current.PositionWrap); + Assert.False(lineBreaker.Current.Required); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(23, lineBreaker.Current.PositionWrap); + Assert.False(lineBreaker.Current.Required); + + Assert.True(lineBreaker.MoveNext()); + Assert.Equal(28, lineBreaker.Current.PositionWrap); + Assert.False(lineBreaker.Current.Required); + + Assert.False(lineBreaker.MoveNext()); + } + + + [Fact] + public void ForwardTextWithOuterWhitespace() + { + var lineBreaker = new LineBreakAlgorithm(" Apples Pears Bananas ".AsMemory()); + var positionsF = GetBreaks(lineBreaker); + Assert.Equal(1, positionsF[0].PositionWrap); + Assert.Equal(0, positionsF[0].PositionMeasure); + Assert.Equal(8, positionsF[1].PositionWrap); + Assert.Equal(7, positionsF[1].PositionMeasure); + Assert.Equal(14, positionsF[2].PositionWrap); + Assert.Equal(13, positionsF[2].PositionMeasure); + Assert.Equal(24, positionsF[3].PositionWrap); + Assert.Equal(21, positionsF[3].PositionMeasure); + } + + private static List GetBreaks(LineBreakAlgorithm lineBreaker) + { + var breaks = new List(); + + while (lineBreaker.MoveNext()) + { + breaks.Add(lineBreaker.Current); + } + + return breaks; + } + + [Fact] + public void ForwardTest() + { + var lineBreaker = new LineBreakAlgorithm("Apples Pears Bananas".AsMemory()); + + var positionsF = GetBreaks(lineBreaker); + Assert.Equal(7, positionsF[0].PositionWrap); + Assert.Equal(6, positionsF[0].PositionMeasure); + Assert.Equal(13, positionsF[1].PositionWrap); + Assert.Equal(12, positionsF[1].PositionMeasure); + Assert.Equal(20, positionsF[2].PositionWrap); + Assert.Equal(20, positionsF[2].PositionMeasure); + } + + [Theory(Skip = "Only run when the Unicode spec changes.")] + [ClassData(typeof(LineBreakTestDataGenerator))] + public void ShouldFindBreaks(int lineNumber, int[] codePoints, int[] breakPoints) + { + var text = string.Join(null, codePoints.Select(char.ConvertFromUtf32)); + + var lineBreaker = new LineBreakAlgorithm(text.AsMemory()); + + var foundBreaks = new List(); + + while (lineBreaker.MoveNext()) + { + foundBreaks.Add(lineBreaker.Current.PositionWrap); + } + + // Check the same + var pass = true; + + if (foundBreaks.Count != breakPoints.Length) + { + pass = false; + } + else + { + for (var i = 0; i < foundBreaks.Count; i++) + { + if (foundBreaks[i] != breakPoints[i]) + { + pass = false; + } + } + } + + if (!pass) + { + _outputHelper.WriteLine($"Failed test on line {lineNumber}"); + _outputHelper.WriteLine(""); + _outputHelper.WriteLine($" Code Points: {string.Join(" ", codePoints)}"); + _outputHelper.WriteLine($"Expected Breaks: {string.Join(" ", breakPoints)}"); + _outputHelper.WriteLine($" Actual Breaks: {string.Join(" ", foundBreaks)}"); + _outputHelper.WriteLine($" Text: {text}"); + _outputHelper.WriteLine($" Char Props: {string.Join(" ", codePoints.Select(x => new Codepoint(x).LineBreakClass))}"); + _outputHelper.WriteLine(""); + } + + Assert.True(pass); + } + + private class LineBreakTestDataGenerator : IEnumerable + { + private readonly List _testData; + + public LineBreakTestDataGenerator() + { + _testData = GenerateTestData(); + } + + public IEnumerator GetEnumerator() + { + return _testData.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private static List GenerateTestData() + { + // Process each line + var tests = new List(); + + // Read the test file + var url = Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/LineBreakTest.txt"); + + using (var client = new HttpClient()) + using (var result = client.GetAsync(url).GetAwaiter().GetResult()) + { + if (!result.IsSuccessStatusCode) + { + return tests; + } + + using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) + using (var reader = new StreamReader(stream)) + { + var lineNumber = 1; + + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + + if (line is null) + { + break; + } + + // Get the line, remove comments + line = line.Split('#')[0].Trim(); + + // Ignore blank/comment only lines + if (string.IsNullOrWhiteSpace(line)) + { + lineNumber++; + continue; + } + + var codePoints = new List(); + var breakPoints = new List(); + + // Parse the test + var p = 0; + + while (p < line.Length) + { + // Ignore white space + if (char.IsWhiteSpace(line[p])) + { + p++; + continue; + } + + if (line[p] == '×') + { + p++; + continue; + } + + if (line[p] == '÷') + { + breakPoints.Add(codePoints.Select(x=> x > ushort.MaxValue ? 2 : 1).Sum()); + p++; + continue; + } + + var codePointPos = p; + + while (p < line.Length && IsHexDigit(line[p])) + { + p++; + } + + var codePointStr = line.Substring(codePointPos, p - codePointPos); + var codePoint = Convert.ToInt32(codePointStr, 16); + codePoints.Add(codePoint); + } + + tests.Add(new object[] { lineNumber, codePoints.ToArray(), breakPoints.ToArray() }); + + lineNumber++; + } + } + } + + return tests; + } + + private static bool IsHexDigit(char ch) + { + return char.IsDigit(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); + } + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs deleted file mode 100644 index 3d489af3a2..0000000000 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using Avalonia.Media.TextFormatting.Unicode; -using Avalonia.Utilities; -using Xunit; - -namespace Avalonia.Visuals.UnitTests.Media.TextFormatting -{ - public class LineBreakerTests - { - [Fact] - public void Should_Split_Text_By_Explicit_Breaks() - { - //ABC [0 3] - //DEF\r[4 7] - //\r[8] - //Hello\r\n[9 15] - const string text = "ABC DEF\r\rHELLO\r\n"; - - var buffer = new ReadOnlySlice(text.AsMemory()); - - var lineBreaker = new LineBreakEnumerator(buffer); - - var current = 0; - - Assert.True(lineBreaker.MoveNext()); - - var a = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1); - - Assert.Equal("ABC ", a); - - current += a.Length; - - Assert.True(lineBreaker.MoveNext()); - - var b = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1); - - Assert.Equal("DEF\r", b); - - current += b.Length; - - Assert.True(lineBreaker.MoveNext()); - - var c = text.Substring(current, lineBreaker.Current.PositionMeasure - current + 1); - - Assert.Equal("\r", c); - - current += c.Length; - - Assert.True(lineBreaker.MoveNext()); - - var d = text.Substring(current, text.Length - current); - - Assert.Equal("HELLO\r\n", d); - } - } -} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs index 5c705ba0c7..47aef84533 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs @@ -1,6 +1,4 @@ -using System; -using Avalonia.Media.TextFormatting.Unicode; -using Xunit; +using Xunit; namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { @@ -15,26 +13,5 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { UnicodeDataGenerator.Execute(); } - [Theory(Skip = "Only run when we update the trie.")] - [ClassData(typeof(LineBreakTestDataGenerator))] - - public void Should_Enumerate_LineBreaks(string text, int expectedLength) - { - var textMemory = text.AsMemory(); - - var enumerator = new LineBreakEnumerator(textMemory); - - Assert.True(enumerator.MoveNext()); - - Assert.Equal(expectedLength, enumerator.Current.PositionWrap); - } - - private class LineBreakTestDataGenerator : TestDataGenerator - { - public LineBreakTestDataGenerator() - : base("auxiliary/LineBreakTest.txt") - { - } - } } } From bd9aaae4c0edf3f7b1303d3faf6f3e4e27253f71 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 4 Feb 2021 18:32:28 +0100 Subject: [PATCH 31/79] Fix Typo --- .../Media/TextFormatting/Unicode/LineBreakEnumerator.cs | 2 +- .../Media/TextFormatting/LineBreakEnumuratorTests.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 03433200c5..4d02f94cad 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -201,7 +201,7 @@ namespace Avalonia.Media.TextFormatting.Unicode switch (_currentClass) { case LineBreakClass.MandatoryBreak: - case LineBreakClass.CombiningMark: + case LineBreakClass.ContingentBreak: case LineBreakClass.Exclamation: case LineBreakClass.LineFeed: case LineBreakClass.NextLine: diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs index 5b30992891..a90be6d519 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs @@ -55,7 +55,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting [Fact] public void ForwardTextWithOuterWhitespace() { - var lineBreaker = new LineBreakAlgorithm(" Apples Pears Bananas ".AsMemory()); + var lineBreaker = new LineBreakEnumerator(" Apples Pears Bananas ".AsMemory()); var positionsF = GetBreaks(lineBreaker); Assert.Equal(1, positionsF[0].PositionWrap); Assert.Equal(0, positionsF[0].PositionMeasure); @@ -67,7 +67,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting Assert.Equal(21, positionsF[3].PositionMeasure); } - private static List GetBreaks(LineBreakAlgorithm lineBreaker) + private static List GetBreaks(LineBreakEnumerator lineBreaker) { var breaks = new List(); @@ -82,7 +82,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting [Fact] public void ForwardTest() { - var lineBreaker = new LineBreakAlgorithm("Apples Pears Bananas".AsMemory()); + var lineBreaker = new LineBreakEnumerator("Apples Pears Bananas".AsMemory()); var positionsF = GetBreaks(lineBreaker); Assert.Equal(7, positionsF[0].PositionWrap); @@ -99,7 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { var text = string.Join(null, codePoints.Select(char.ConvertFromUtf32)); - var lineBreaker = new LineBreakAlgorithm(text.AsMemory()); + var lineBreaker = new LineBreakEnumerator(text.AsMemory()); var foundBreaks = new List(); From ef146125d202203f5d9cd16157786bcb5164fc93 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 7 Feb 2021 10:13:31 +0300 Subject: [PATCH 32/79] Add redirects for Q/A and discussions in issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 7 ++++--- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 1 - 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6b910fc615..4e34d4b132 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.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. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..b5f5bda6fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491ef1..5f0a04cee3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.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.** From aeaccd4535e5a0cbd2e45be89ce2441de47d06dc Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 7 Feb 2021 16:42:06 +0300 Subject: [PATCH 33/79] Resolve conversation --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b5f5bda6fe..687355d825 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - name: Questions, Discussions, Ideas url: https://github.com/AvaloniaUI/Avalonia/discussions/new From 28fa72185c3f71a1e590fd886bada1d38d3527d6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 16:19:51 +0000 Subject: [PATCH 34/79] add native side implementation for osx. --- native/Avalonia.Native/src/OSX/cursor.mm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index b6f9ed5071..1732d6e71f 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/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() From 304c49821cab0cfa92de90c1e09026e96fa90406 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 17:12:56 +0000 Subject: [PATCH 35/79] fix control catalog. --- samples/ControlCatalog/ViewModels/CursorPageViewModel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs index a4c643dd04..f1cc0637dc 100644 --- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -5,11 +5,11 @@ using Avalonia; using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Platform; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class CursorPageViewModel : ReactiveObject + public class CursorPageViewModel : ViewModelBase { public CursorPageViewModel() { @@ -25,6 +25,7 @@ namespace ControlCatalog.ViewModels } public IEnumerable StandardCursors { get; } + public Cursor CustomCursor { get; } public class StandardCursorModel @@ -36,6 +37,7 @@ namespace ControlCatalog.ViewModels } public StandardCursorType Type { get; } + public Cursor Cursor { get; } } } From e90fe543f0466602687dbea6f568a8f42a0832b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 17:16:46 +0000 Subject: [PATCH 36/79] add full osx implementation for custom cursors. --- src/Avalonia.Native/Cursor.cs | 18 ++++++++++++++++-- src/Avalonia.Native/avn.idl | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index 2cb085bc7e..ae218e270c 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Native.Interop; @@ -39,9 +40,22 @@ namespace Avalonia.Native return new AvaloniaNativeCursor( cursor ); } - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - throw new NotImplementedException(); + 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); + } + } } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 166046ca24..3627ff6894 100644 --- a/src/Avalonia.Native/avn.idl +++ b/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)] From 1d7690c05ddf65d83aec4023704c87ee44a7a505 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 18:51:10 +0000 Subject: [PATCH 37/79] Api changes. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 6 ++++++ src/Avalonia.Input/ApiCompatBaseline.txt | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 src/Avalonia.Controls/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Input/ApiCompatBaseline.txt diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt new file mode 100644 index 0000000000..e5adc8c6ed --- /dev/null +++ b/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 diff --git a/src/Avalonia.Input/ApiCompatBaseline.txt b/src/Avalonia.Input/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fff2fb2806 --- /dev/null +++ b/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 From 0f6e9a86aa1070d6571266d2d94661a3a88add25 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 20:26:49 +0000 Subject: [PATCH 38/79] Update licence.md --- licence.md | 187 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 22 deletions(-) diff --git a/licence.md b/licence.md index d18aef99ad..0a041280bd 100644 --- a/licence.md +++ b/licence.md @@ -1,22 +1,165 @@ -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors -All Rights Reserved - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. From 2ab5f8b8932e0acac078a58c30ff8db908d9a9d8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 20:29:15 +0000 Subject: [PATCH 39/79] Revert "Update licence.md" This reverts commit 0f6e9a86aa1070d6571266d2d94661a3a88add25. --- licence.md | 187 +++++++---------------------------------------------- 1 file changed, 22 insertions(+), 165 deletions(-) diff --git a/licence.md b/licence.md index 0a041280bd..d18aef99ad 100644 --- a/licence.md +++ b/licence.md @@ -1,165 +1,22 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors +All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 4e3af8c9c1c5a4d79c6bdbf232264319016b2a1a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:24:51 +0100 Subject: [PATCH 40/79] Fix non-compiling assemblies. --- src/iOS/Avalonia.iOS/Stubs.cs | 12 +++++++++--- tests/Avalonia.Benchmarks/NullCursorFactory.cs | 9 ++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/iOS/Avalonia.iOS/Stubs.cs b/src/iOS/Avalonia.iOS/Stubs.cs index a35b301a7f..c2526d7d9f 100644 --- a/src/iOS/Avalonia.iOS/Stubs.cs +++ b/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); } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Benchmarks/NullCursorFactory.cs b/tests/Avalonia.Benchmarks/NullCursorFactory.cs index 012adce0f2..9aeb353151 100644 --- a/tests/Avalonia.Benchmarks/NullCursorFactory.cs +++ b/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() { } } } } From e1051b7ce00d25359567e3b290b516c0f4174d3b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:39:09 +0100 Subject: [PATCH 41/79] Fix another compiler error. --- src/iOS/Avalonia.iOS/AvaloniaView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 7d367c99d1..36a70ea410 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/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; } } -} \ No newline at end of file +} From 75d59d28a6bce51aa6f584f3184ae922682d9dd5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:57:13 +0100 Subject: [PATCH 42/79] Remove Contract usages. --- src/Markup/Avalonia.Markup/Data/Binding.cs | 3 ++- src/Markup/Avalonia.Markup/Data/BindingBase.cs | 13 +++++++------ .../Markup/Parsers/ExpressionObserverBuilder.cs | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 554c27b85a..71d2050e9e 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -63,7 +63,8 @@ namespace Avalonia.Data protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation) { - Contract.Requires(target is not null); + _ = target ?? throw new ArgumentNullException(nameof(target)); + anchor = anchor ?? DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index b900a043d7..c25ef49167 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -87,7 +87,8 @@ namespace Avalonia.Data object? anchor = null, bool enableDataValidation = false) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); + anchor = anchor ?? DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; @@ -135,7 +136,7 @@ namespace Avalonia.Data bool targetIsDataContext, object? anchor) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); if (!(target is IDataContextProvider)) { @@ -166,7 +167,7 @@ namespace Avalonia.Data string elementName, ExpressionNode node) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null) throw new InvalidOperationException("Name scope is null or was already collected"); @@ -182,7 +183,7 @@ namespace Avalonia.Data RelativeSource relativeSource, ExpressionNode node) { - Contract.Requires(target != null); + _ = target ?? throw new ArgumentNullException(nameof(target)); IObservable controlLocator; @@ -214,7 +215,7 @@ namespace Avalonia.Data object source, ExpressionNode node) { - Contract.Requires(source != null); + _ = source ?? throw new ArgumentNullException(nameof(source)); return new ExpressionObserver(source, node); } @@ -223,7 +224,7 @@ namespace Avalonia.Data IAvaloniaObject target, ExpressionNode node) { - Contract.Requires(target is not null); + _ = target ?? throw new ArgumentNullException(nameof(target)); var result = new ExpressionObserver( () => target.GetValue(StyledElement.TemplatedParentProperty), diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index e615923ff0..ebdad881c3 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -48,7 +48,8 @@ namespace Avalonia.Markup.Parsers string? description = null, Func? typeResolver = null) { - Contract.Requires(rootObservable != null); + _ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable)); + return new ExpressionObserver( rootObservable, Parse(expression, enableDataValidation, typeResolver).Node, @@ -64,7 +65,7 @@ namespace Avalonia.Markup.Parsers string? description = null, Func? typeResolver = null) { - Contract.Requires(rootGetter != null); + _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); return new ExpressionObserver( rootGetter, From f3a9f509d26f46a56de262a13fe1018b8228fa33 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:10:35 +0100 Subject: [PATCH 43/79] Fix nullable issues in Binding.cs, --- src/Markup/Avalonia.Markup/Data/Binding.cs | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 71d2050e9e..50be598d7f 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -65,8 +65,7 @@ namespace Avalonia.Data { _ = target ?? throw new ArgumentNullException(nameof(target)); - anchor = anchor ?? DefaultAnchor?.Target; - + anchor ??= DefaultAnchor?.Target; enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; INameScope? nameScope = null; @@ -74,11 +73,22 @@ namespace Avalonia.Data var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope); -#nullable disable + 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); } @@ -98,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) @@ -113,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) { @@ -130,16 +134,12 @@ 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 { throw new NotSupportedException(); } } -#nullable enable } } From f56e4e0d2c91011c37258a3f962aba8deda2d3b5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:19:33 +0100 Subject: [PATCH 44/79] Fix nullable annotation. --- src/Markup/Avalonia.Markup/Data/MultiBinding.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 1a33536fa3..17b033f238 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -105,7 +105,7 @@ namespace Avalonia.Data } } - private object ConvertValue(IList values, Type targetType, IMultiValueConverter? converter) + private object ConvertValue(IList 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(values); + values = new System.Collections.ObjectModel.ReadOnlyCollection(values); object converted; if (converter != null) { From 311dbfa0889b01f8cf014c483f999148ceb83271 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:46:59 +0100 Subject: [PATCH 45/79] Add #nullable enable on shared files. Some files in Avalonia.Markup are linked into other projects and their use of `?` was causing warnings in the other projects. Explicitly add `#nullable enable` in these cases for now. --- .../Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs | 2 ++ .../Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs | 2 ++ src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 4da6a7f3ae..439bc15243 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/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 diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 8e0a40ccf4..9b67f9c7bc 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/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 diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index d821a04d93..af307ca1ac 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/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 From 6b0f3a26eb99a2db42a719d58bbf7f12db0cabd2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:52:57 +0100 Subject: [PATCH 46/79] Fix nullability of Equals methods. --- .../Markup/Parsers/PropertyPathGrammar.cs | 10 +++++----- .../Markup/Parsers/SelectorGrammar.cs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 9b67f9c7bc..250eca1852 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -188,7 +188,7 @@ namespace Avalonia.Markup.Parsers { 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; } @@ -199,7 +199,7 @@ namespace Avalonia.Markup.Parsers 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 @@ -209,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; } = string.Empty; public string? TypeNamespace { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is EnsureTypeSyntax other && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; @@ -226,7 +226,7 @@ namespace Avalonia.Markup.Parsers { 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 CastTypeSyntax other && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index af307ca1ac..9d03341f92 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -331,7 +331,7 @@ namespace Avalonia.Markup.Parsers 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; @@ -344,7 +344,7 @@ namespace Avalonia.Markup.Parsers 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; @@ -355,7 +355,7 @@ namespace Avalonia.Markup.Parsers { 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; } @@ -365,7 +365,7 @@ namespace Avalonia.Markup.Parsers { 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; } @@ -377,7 +377,7 @@ namespace Avalonia.Markup.Parsers 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 && @@ -387,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; } @@ -395,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; } @@ -403,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; } @@ -413,7 +413,7 @@ namespace Avalonia.Markup.Parsers { public IEnumerable Argument { get; set; } = Enumerable.Empty(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument); } @@ -421,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; } From 922f765af64964fc3af33871142e74f7da8b9ded Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 12:53:52 +0100 Subject: [PATCH 47/79] Promote nullable warnings to errors. --- src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index ce104b3609..7b9cd0212e 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -3,6 +3,7 @@ netstandard2.0 Avalonia Enable + CS8600;CS8602;CS8603 From f207a6a61ec256e6992a8a69dda7b1f1cd2e45bc Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 9 Feb 2021 12:55:23 +0100 Subject: [PATCH 48/79] Fix issue with MenuItem.Click not called when the item is not embdded into a Menu --- src/Avalonia.Controls/MenuItem.cs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 2d91a2ee7e..94099a970e 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/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; /// /// Initializes static members of the class. @@ -147,7 +148,7 @@ namespace Avalonia.Controls { var parent = x as Control; return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ?? - Observable.Return(null); + Observable.Return(null); }); this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope); @@ -275,7 +276,7 @@ namespace Avalonia.Controls public bool IsTopLevel => Parent is Menu; /// - bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false; + bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false; /// IMenuElement? IMenuItem.Parent => Parent as IMenuElement; @@ -310,7 +311,7 @@ namespace Avalonia.Controls .Select(x => x.ContainerControl) .OfType(); } - } + } /// /// Opens the submenu. @@ -337,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 @@ -350,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) From ef52b6cfd7dc07c0e69906b528fe71d8a7f0b189 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 13:11:24 +0100 Subject: [PATCH 49/79] And another compiler error. --- src/iOS/Avalonia.iOS/Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 28bccb6637..2cac5e6bcf 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -27,7 +27,7 @@ namespace Avalonia.iOS var softKeyboard = new SoftKeyboardHelper(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(GlFeature) - .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) .Bind().ToConstant(new PlatformSettings()) From da0145fae010a76455eb1eac11e386fe5893a9b3 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 9 Feb 2021 16:14:48 +0100 Subject: [PATCH 50/79] Remove redundant file Add porting comment --- .../TextFormatting/Unicode/BreakPairTable.cs | 56 --------------- .../Unicode/LineBreakPairTable.cs | 70 +++++++++---------- 2 files changed, 35 insertions(+), 91 deletions(-) delete mode 100644 src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs deleted file mode 100644 index 76c94b324a..0000000000 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs +++ /dev/null @@ -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,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,4,4,4,4,1,1,1,1,1,0,0,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 - } -} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs index 22ef3f8fc2..fd37eed68d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs @@ -1,5 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +// Ported from: https://github.com/SixLabors/Fonts/ namespace Avalonia.Media.TextFormatting.Unicode { @@ -33,42 +34,41 @@ namespace Avalonia.Media.TextFormatting.Unicode // 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; } = new byte[][] - { + 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 byte[] { 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 + 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 }; } } From 1abf9964e6f6cdd52be1e6f24da50f84559ab8c2 Mon Sep 17 00:00:00 2001 From: Peter Davis Date: Wed, 10 Feb 2021 11:13:41 +1030 Subject: [PATCH 51/79] Set initial input focus using LinuxFrameBufferPlatform --- .../Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 340d10517d..3c8d3b5e06 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading; using Avalonia.Controls; @@ -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; From e6bd7d5e0189c9cecc2333bab3bd71347ca4f414 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 10 Feb 2021 00:33:56 -0500 Subject: [PATCH 52/79] Remove Maximum setter from Fluent ProgressBar --- src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index 036595b61c..53eca197d3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -12,7 +12,6 @@ - From 3417cebae1b8fe44b3ff54fc7cd34b1f0dd8cf22 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 10 Feb 2021 00:34:52 -0500 Subject: [PATCH 53/79] Update progress bar samples to include Maximum/Minimum examples --- samples/ControlCatalog/Pages/ProgressBarPage.xaml | 7 +++++++ src/Avalonia.Themes.Default/ProgressBar.xaml | 5 ++++- src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index 2ec0b48c76..da8ef6cf07 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -15,6 +15,13 @@ + + + + + + + diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml index d9c407245b..fb4b23b9ad 100644 --- a/src/Avalonia.Themes.Default/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/ProgressBar.xaml @@ -1,8 +1,11 @@ - + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index 53eca197d3..0dfd3e414a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -1,8 +1,11 @@ - + + + + From 0a7ca87e3bb386943c7af516f31beff955442763 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 21:21:13 +0300 Subject: [PATCH 54/79] HasFlagCustom made safe and supports all enums --- src/Avalonia.Base/EnumExtensions.cs | 30 ++++++++++-- src/Avalonia.Base/Utilities/TypeUtilities.cs | 4 +- .../Collections/DataGridCollectionView.cs | 2 +- src/Avalonia.Controls/ComboBox.cs | 2 +- src/Avalonia.Controls/Grid.cs | 45 +++++++---------- src/Avalonia.Controls/ListBox.cs | 8 ++-- .../Platform/InProcessDragSource.cs | 12 ++--- .../PopupPositioning/IPopupPositioner.cs | 21 ++++---- .../ManagedPopupPositioner.cs | 48 ++++++++----------- .../Primitives/SelectingItemsControl.cs | 8 ++-- .../Repeater/RepeaterLayoutContext.cs | 4 +- src/Avalonia.Controls/TextBox.cs | 4 +- src/Avalonia.Controls/TreeView.cs | 12 ++--- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 8 ++-- .../HeadlessVncFramebufferSource.cs | 4 +- src/Avalonia.Input/AccessKeyHandler.cs | 2 +- src/Avalonia.X11/X11Window.Ime.cs | 4 +- src/Avalonia.X11/X11Window.cs | 18 +++---- src/Avalonia.X11/XI2Manager.cs | 10 ++-- .../Output/DrmBindings.cs | 2 +- .../Wpf/WpfTopLevelImpl.cs | 8 ++-- src/Windows/Avalonia.Win32/DataObject.cs | 6 +-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 24 +++++----- .../WindowImpl.CustomCaptionProc.cs | 4 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++-- 25 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs index 1e4864283f..bc1f8d36a9 100644 --- a/src/Avalonia.Base/EnumExtensions.cs +++ b/src/Avalonia.Base/EnumExtensions.cs @@ -11,10 +11,32 @@ namespace Avalonia [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasFlagCustom(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(ref value); + var byteFlag = Unsafe.As(ref flag); + return (byteValue & byteFlag) == byteFlag; + } + else if (sizeof(T) == 2) + { + var shortValue = Unsafe.As(ref value); + var shortFlag = Unsafe.As(ref flag); + return (shortValue & shortFlag) == shortFlag; + } + else if (sizeof(T) == 4) + { + var intValue = Unsafe.As(ref value); + var intFlag = Unsafe.As(ref flag); + return (intValue & intFlag) == intFlag; + } + else if (sizeof(T) == 8) + { + var longValue = Unsafe.As(ref value); + var longFlag = Unsafe.As(ref flag); + return (longValue & longFlag) == longFlag; + } + else + throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf() + " are not supported"); } } } diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index d0d88166a7..097731bc60 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/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()) { diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 92734b128d..b97f2a2bcb 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -2595,7 +2595,7 @@ namespace Avalonia.Collections /// Whether the specified flag is set private bool CheckFlag(CollectionViewFlags flags) { - return (_flags & flags) != 0; + return _flags.HasFlagCustom(flags); } /// diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 7f2acb58fe..20ca41bc57 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/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; diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 6357ec98a8..66266c3b61 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -637,7 +637,7 @@ namespace Avalonia.Controls /// 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 /// 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 } /// - /// CheckFlagsAnd returns true if all the flags in the + /// CheckFlags returns true if all the flags in the /// given bitmask are set on the object. /// - private bool CheckFlagsAnd(Flags flags) + private bool CheckFlags(Flags flags) { - return ((_flags & flags) == flags); - } - - /// - /// CheckFlagsOr returns true if at least one flag in the - /// given bitmask is set. - /// - /// - /// If no bits are set in the given bitmask, the method returns - /// true. - /// - 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// 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); } /// diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index d1b8038581..b6b3cc786c 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/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); } } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 9d644aaa00..7e1a7378c7 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/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); diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index aed7dff0fe..545034239f 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/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; } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 7f1dbdf592..80b32b5ef8 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/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; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 280f46be9f..2cd69793dc 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets a value indicating whether is set. /// - protected bool AlwaysSelected => (SelectionMode & SelectionMode.AlwaysSelected) != 0; + protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected); /// 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) diff --git a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs index fd33eeaf39..4eadced423 100644 --- a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs +++ b/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); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 90064fad57..54d3af9b59 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -585,7 +585,7 @@ namespace Avalonia.Controls var keymap = AvaloniaLocator.Current.GetService(); bool Match(List 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: diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 09742412d9..c8150cc200 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/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) { diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index e93ca64d3a..2cf533195e 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop return null; var lst = new List(); 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() }; diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs b/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs index 32f9f99709..fb61f25cea 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs +++ b/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); diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index 660584e2ed..731a409090 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/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. diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index f469ff7455..ac626f5825 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/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(); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 92d1e37efe..8f3f412578 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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; } diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 8cdf24cc7b..2874c517a9 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/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); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index b5ebc4bcb7..34cc261187 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/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; } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 3bb29f4e23..7d86116f38 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/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) { diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 6c1b4ef5d9..5f02796d30 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/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); diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 37d047689c..d038e341b8 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/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; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 68bd40da79..0a8648aa9a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/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 }; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5e67c0d7f3..2f980cfe86 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -836,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) { @@ -853,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) { @@ -901,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); } @@ -1239,7 +1239,7 @@ namespace Avalonia.Win32 public Action ExtendClientAreaToDecorationsChanged { get; set; } /// - public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome); + public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlagCustom(ExtendClientAreaChromeHints.PreferSystemChrome); /// public Thickness ExtendedMargins => _extendedMargins; From 4003b1e40d2c17bb51ed48c58189da0dc33d8817 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2021 13:37:26 +0100 Subject: [PATCH 55/79] Pass clicked control as placement target. Moved the implementation of `ContextMenu.Open` into a private method that accepts a control and a placement target. When calling `Open` due to a right-click in `ControlPointerReleased`, call this method with the clicked control as the placement target. Fixes #5101 --- src/Avalonia.Controls/ContextMenu.cs | 77 ++++++++++++++-------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fb8080f0d4..bf8db9a4ec 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -269,7 +269,43 @@ namespace Avalonia.Controls } control ??= _attachedControls![0]; + Open(control, control); + } + + /// + /// Closes the menu. + /// + public override void Close() + { + if (!IsOpen) + { + return; + } + if (_popup != null && _popup.IsVisible) + { + _popup.IsOpen = false; + } + } + + void ISetterValue.Initialize(ISetter setter) + { + // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides + // the behavior defined in Control which requires controls to be wrapped in a