From 229c8b7f12762ce4f37c241c13c15e50bde83c1b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 29 May 2020 00:36:15 +0200 Subject: [PATCH 01/15] Benchmark and simple optimization for INPC accessor. --- .../Plugins/InpcPropertyAccessorPlugin.cs | 8 ++- .../Data/PropertyAccessorBenchmarks.cs | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index f2ed86d2aa..84ef0fb695 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,7 +1,5 @@ using System; using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; using System.Reflection; using Avalonia.Utilities; @@ -31,7 +29,11 @@ namespace Avalonia.Data.Core.Plugins Contract.Requires(propertyName != null); reference.TryGetTarget(out object instance); - var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName); + + const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | + BindingFlags.Static | BindingFlags.Instance; + + var p = instance.GetType().GetProperty(propertyName, bindingFlags); if (p != null) { diff --git a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs new file mode 100644 index 0000000000..5c2502422a --- /dev/null +++ b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Avalonia.Data.Core.Plugins; +using BenchmarkDotNet.Attributes; +using JetBrains.Annotations; + +namespace Avalonia.Benchmarks.Data +{ + [MemoryDiagnoser, InProcess] + public class PropertyAccessorBenchmarks + { + private readonly InpcPropertyAccessorPlugin _plugin = new InpcPropertyAccessorPlugin(); + private readonly TestObject _targetStrongRef = new TestObject(); + private readonly WeakReference _targetWeakRef; + + public PropertyAccessorBenchmarks() + { + _targetWeakRef = new WeakReference(_targetStrongRef); + } + + [Benchmark] + public void InpcAccessor() + { + _plugin.Start(_targetWeakRef, nameof(TestObject.Test)); + } + + private class TestObject : INotifyPropertyChanged + { + private string _test; + + public string Test + { + get => _test; + set + { + if (_test == value) + { + return; + } + + _test = value; + + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} From 996e602bf85f05df71987a119ec706dde2257c83 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 29 May 2020 18:47:42 +0300 Subject: [PATCH 02/15] Implemented custom format support for clipboard --- native/Avalonia.Native/inc/avalonia-native.h | 3 + native/Avalonia.Native/src/OSX/AvnString.h | 1 + native/Avalonia.Native/src/OSX/AvnString.mm | 12 ++ native/Avalonia.Native/src/OSX/clipboard.mm | 34 +++++ .../Platform/ClipboardImpl.cs | 6 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 4 + src/Avalonia.Input/Platform/IClipboard.cs | 6 + src/Avalonia.Native/AvnString.cs | 15 ++ src/Avalonia.Native/ClipboardImpl.cs | 32 +++++ src/Avalonia.X11/X11Atoms.cs | 35 ++++- src/Avalonia.X11/X11Clipboard.cs | 135 +++++++++++++++--- src/Avalonia.X11/XLib.cs | 4 + src/Windows/Avalonia.Win32/ClipboardImpl.cs | 76 +++++++--- .../Interop/UnmanagedMethods.cs | 6 + src/iOS/Avalonia.iOS/Clipboard.cs | 6 + .../TextBoxTests.cs | 5 + 16 files changed, 335 insertions(+), 45 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index b86c679397..f9bfaf0b47 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -387,6 +387,9 @@ AVNCOM(IAvnClipboard, 0f) : IUnknown virtual HRESULT SetText (char* type, void* utf8Text) = 0; virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0; virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0; + virtual HRESULT SetBytes(char* type, void* utf8Text, int len) = 0; + virtual HRESULT GetBytes(char* type, IAvnString**ppv) = 0; + virtual HRESULT Clear() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/AvnString.h b/native/Avalonia.Native/src/OSX/AvnString.h index 88bc4e6963..5d299374e5 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.h +++ b/native/Avalonia.Native/src/OSX/AvnString.h @@ -12,4 +12,5 @@ extern IAvnString* CreateAvnString(NSString* string); extern IAvnStringArray* CreateAvnStringArray(NSArray* array); extern IAvnStringArray* CreateAvnStringArray(NSString* string); +extern IAvnString* CreateByteArray(void* data, int len); #endif /* AvnString_h */ diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 6445a9fef1..00b748ef63 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -29,6 +29,13 @@ public: memcpy((void*)_cstring, (void*)cstring, _length); } + AvnStringImpl(void*ptr, int len) + { + _length = len; + _cstring = (const char*)malloc(_length); + memcpy((void*)_cstring, ptr, len); + } + virtual ~AvnStringImpl() { free((void*)_cstring); @@ -114,3 +121,8 @@ IAvnStringArray* CreateAvnStringArray(NSString* string) { return new AvnStringArrayImpl(string); } + +IAvnString* CreateByteArray(void* data, int len) +{ + return new AvnStringImpl(data, len); +} diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 18d60d3853..116a08670e 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -82,6 +82,40 @@ public: return S_OK; } + + virtual HRESULT SetBytes(char* type, void* bytes, int len) override + { + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + auto data = [NSData dataWithBytes:bytes length:len]; + if(_item == nil) + [_pb setData:data forType:typeString]; + else + [_item setData:data forType:typeString]; + return S_OK; + } + + virtual HRESULT GetBytes(char* type, IAvnString**ppv) override + { + *ppv = nil; + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + NSData*data; + @try + { + if(_item) + data = [_item dataForType:typeString]; + else + data = [_pb dataForType:typeString]; + if(data == nil) + return E_FAIL; + } + @catch(NSException* e) + { + return E_FAIL; + } + *ppv = CreateByteArray((void*)data.bytes, (int)data.length); + return S_OK; + } + virtual HRESULT Clear() override { diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs index 51e0a1e799..b07dce8df4 100644 --- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs +++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs @@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform return Task.FromResult(null); } + + public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException(); + + public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); + + public Task GetFormatAsync(string format) => throw new PlatformNotSupportedException(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a9665b1519..b5f3b66aaa 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -156,6 +156,10 @@ namespace Avalonia.DesignerSupport.Remote public Task SetTextAsync(string text) => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask; + public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; + public Task GetFormatsAsync() => Task.FromResult(new string[0]); + + public Task GetFormatAsync(string format) => Task.FromResult((object)null); } class CursorFactoryStub : IStandardCursorFactory diff --git a/src/Avalonia.Input/Platform/IClipboard.cs b/src/Avalonia.Input/Platform/IClipboard.cs index b195050fa8..4a3c61cacd 100644 --- a/src/Avalonia.Input/Platform/IClipboard.cs +++ b/src/Avalonia.Input/Platform/IClipboard.cs @@ -9,5 +9,11 @@ namespace Avalonia.Input.Platform Task SetTextAsync(string text); Task ClearAsync(); + + Task SetDataObjectAsync(IDataObject data); + + Task GetFormatsAsync(); + + Task GetFormatAsync(string format); } } diff --git a/src/Avalonia.Native/AvnString.cs b/src/Avalonia.Native/AvnString.cs index ba427b6aac..11b1a33276 100644 --- a/src/Avalonia.Native/AvnString.cs +++ b/src/Avalonia.Native/AvnString.cs @@ -5,6 +5,7 @@ namespace Avalonia.Native.Interop unsafe partial class IAvnString { private string _managed; + private byte[] _bytes; public string String { @@ -22,6 +23,20 @@ namespace Avalonia.Native.Interop } } + public byte[] Bytes + { + get + { + if (_bytes == null) + { + _bytes = new byte[Length()]; + Marshal.Copy(Pointer(), _bytes, 0, _bytes.Length); + } + + return _bytes; + } + } + public override string ToString() => String; } diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 2a24869513..0430c4c638 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -82,6 +82,38 @@ namespace Avalonia.Native using (var strings = _native.GetStrings(NSFilenamesPboardType)) return strings.ToStringArray(); } + + public unsafe Task SetDataObjectAsync(IDataObject data) + { + _native.Clear(); + foreach (var fmt in data.GetDataFormats()) + { + var o = data.Get(fmt); + if(o is string s) + using (var b = new Utf8Buffer(s)) + _native.SetText(fmt, b.DangerousGetHandle()); + else if(o is byte[] bytes) + fixed (byte* pbytes = bytes) + _native.SetBytes(fmt, new IntPtr(pbytes), bytes.Length); + } + return Task.CompletedTask; + } + + public Task GetFormatsAsync() + { + using (var n = _native.ObtainFormats()) + return Task.FromResult(n.ToStringArray()); + } + + public async Task GetFormatAsync(string format) + { + if (format == DataFormats.Text) + return await GetTextAsync(); + if (format == DataFormats.FileNames) + return GetFileNames(); + using (var n = _native.GetBytes(format)) + return n.Bytes; + } } class ClipboardDataObject : IDataObject, IDisposable diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index dd16fae386..32f1822e51 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -22,6 +22,7 @@ // using System; +using System.Collections.Generic; using System.Linq; using static Avalonia.X11.XLib; // ReSharper disable FieldCanBeMadeReadOnly.Global @@ -40,8 +41,9 @@ namespace Avalonia.X11 internal class X11Atoms { + private readonly IntPtr _display; -// Our atoms + // Our atoms public readonly IntPtr AnyPropertyType = (IntPtr)0; public readonly IntPtr XA_PRIMARY = (IntPtr)1; public readonly IntPtr XA_SECONDARY = (IntPtr)2; @@ -187,10 +189,13 @@ namespace Avalonia.X11 public readonly IntPtr ATOM_PAIR; public readonly IntPtr MANAGER; public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION; + public readonly IntPtr INCR; - + private readonly Dictionary _namesToAtoms = new Dictionary(); + private readonly Dictionary _atomsToNames = new Dictionary(); public X11Atoms(IntPtr display) { + _display = display; // make sure this array stays in sync with the statements below @@ -204,7 +209,33 @@ namespace Avalonia.X11 XInternAtoms(display, atomNames, atomNames.Length, true, atoms); for (var c = 0; c < fields.Length; c++) + { + _namesToAtoms[fields[c].Name] = atoms[c]; + _atomsToNames[atoms[c]] = fields[c].Name; fields[c].SetValue(this, atoms[c]); + } + } + + public IntPtr GetAtom(string name) + { + if (_namesToAtoms.TryGetValue(name, out var rv)) + return rv; + var atom = XInternAtom(_display, name, false); + _namesToAtoms[name] = atom; + _atomsToNames[atom] = name; + return atom; + } + + public string GetAtomName(IntPtr atom) + { + if (_atomsToNames.TryGetValue(atom, out var rv)) + return rv; + var name = XLib.GetAtomName(_display, atom); + if (name == null) + return null; + _atomsToNames[atom] = name; + _namesToAtoms[name] = atom; + return name; } } } diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index a431ffcc1a..40198be17f 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Avalonia.Input; using Avalonia.Input.Platform; using static Avalonia.X11.XLib; namespace Avalonia.X11 @@ -10,12 +12,14 @@ namespace Avalonia.X11 class X11Clipboard : IClipboard { private readonly X11Info _x11; - private string _storedString; + private IDataObject _storedDataObject; private IntPtr _handle; private TaskCompletionSource _requestedFormatsTcs; - private TaskCompletionSource _requestedTextTcs; + private TaskCompletionSource _requestedDataTcs; private readonly IntPtr[] _textAtoms; private readonly IntPtr _avaloniaSaveTargetsAtom; + private readonly Dictionary _formatAtoms = new Dictionary(); + private readonly Dictionary _atomFormats = new Dictionary(); public X11Clipboard(AvaloniaX11Platform platform) { @@ -31,6 +35,11 @@ namespace Avalonia.X11 }.Where(a => a != IntPtr.Zero).ToArray(); } + bool IsStringAtom(IntPtr atom) + { + return _textAtoms.Contains(atom); + } + Encoding GetStringEncoding(IntPtr atom) { return (atom == _x11.Atoms.XA_STRING @@ -75,21 +84,31 @@ namespace Avalonia.X11 Encoding textEnc; if (target == _x11.Atoms.TARGETS) { - var atoms = _textAtoms; - atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) - .ToArray(); + var atoms = new HashSet { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE }; + foreach (var fmt in _storedDataObject.GetDataFormats()) + { + if (fmt == DataFormats.Text) + foreach (var ta in _textAtoms) + atoms.Add(ta); + else + atoms.Add(_x11.Atoms.GetAtom(fmt)); + } + XChangeProperty(_x11.Display, window, property, - _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); + _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms.ToArray(), atoms.Count); return property; } else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) { return property; } - else if ((textEnc = GetStringEncoding(target)) != null) + else if ((textEnc = GetStringEncoding(target)) != null + && _storedDataObject?.Contains(DataFormats.Text) == true) { - - var data = textEnc.GetBytes(_storedString ?? ""); + var text = _storedDataObject.GetText(); + if(text == null) + return IntPtr.Zero; + var data = textEnc.GetBytes(text); fixed (void* pdata = data) XChangeProperty(_x11.Display, window, property, target, 8, PropertyMode.Replace, @@ -121,6 +140,23 @@ namespace Avalonia.X11 return property; } + else if(_storedDataObject?.Contains(_x11.Atoms.GetAtomName(target)) == true) + { + var objValue = _storedDataObject.Get(_x11.Atoms.GetAtomName(target)); + + if(!(objValue is byte[] bytes)) + { + if (objValue is string s) + bytes = Encoding.UTF8.GetBytes(s); + else + return IntPtr.Zero; + } + + XChangeProperty(_x11.Display, window, property, target, 8, + PropertyMode.Replace, + bytes, bytes.Length); + return property; + } else return IntPtr.Zero; } @@ -131,15 +167,15 @@ namespace Avalonia.X11 if (sel.property == IntPtr.Zero) { _requestedFormatsTcs?.TrySetResult(null); - _requestedTextTcs?.TrySetResult(null); + _requestedDataTcs?.TrySetResult(null); } XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType, - out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); + out var actualTypeAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); Encoding textEnc = null; if (nitems == IntPtr.Zero) { _requestedFormatsTcs?.TrySetResult(null); - _requestedTextTcs?.TrySetResult(null); + _requestedDataTcs?.TrySetResult(null); } else { @@ -154,10 +190,24 @@ namespace Avalonia.X11 _requestedFormatsTcs?.TrySetResult(formats); } } - else if ((textEnc = GetStringEncoding(sel.property)) != null) + else if ((textEnc = GetStringEncoding(actualTypeAtom)) != null) { var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32()); - _requestedTextTcs?.TrySetResult(text); + _requestedDataTcs?.TrySetResult(text); + } + else + { + if (actualTypeAtom == _x11.Atoms.INCR) + { + // TODO: Actually implement that monstrosity + _requestedDataTcs.TrySetResult(null); + } + else + { + var data = new byte[(int)nitems * (actualFormat / 8)]; + Marshal.Copy(prop, data, 0, data.Length); + _requestedDataTcs?.TrySetResult(data); + } } } @@ -174,17 +224,19 @@ namespace Avalonia.X11 return _requestedFormatsTcs.Task; } - Task SendTextRequest(IntPtr format) + Task SendDataRequest(IntPtr format) { - if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted) - _requestedTextTcs = new TaskCompletionSource(); + if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted) + _requestedDataTcs = new TaskCompletionSource(); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); - return _requestedTextTcs.Task; + return _requestedDataTcs.Task; } + + bool HasOwner => XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) != IntPtr.Zero; public async Task GetTextAsync() { - if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero) + if (!HasOwner) return null; var res = await SendFormatRequest(); var target = _x11.Atoms.UTF8_STRING; @@ -199,7 +251,7 @@ namespace Avalonia.X11 } } - return await SendTextRequest(target); + return (string)await SendDataRequest(target); } void StoreAtomsInClipboardManager(IntPtr[] atoms) @@ -220,15 +272,52 @@ namespace Avalonia.X11 public Task SetTextAsync(string text) { - _storedString = text; + var data = new DataObject(); + data.Set(DataFormats.Text, text); + return SetDataObjectAsync(data); + } + + public Task ClearAsync() + { + return SetTextAsync(null); + } + + public Task SetDataObjectAsync(IDataObject data) + { + _storedDataObject = data; XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); StoreAtomsInClipboardManager(_textAtoms); return Task.CompletedTask; } - public Task ClearAsync() + public async Task GetFormatsAsync() { - return SetTextAsync(null); + if (!HasOwner) + return null; + var res = await SendFormatRequest(); + if (res == null) + return null; + var rv = new List(); + if (_textAtoms.Any(res.Contains)) + rv.Add(DataFormats.Text); + foreach (var t in res) + rv.Add(_x11.Atoms.GetAtomName(t)); + return rv.ToArray(); + } + + public async Task GetFormatAsync(string format) + { + if (!HasOwner) + return null; + if (format == DataFormats.Text) + return await GetTextAsync(); + + var formatAtom = _x11.Atoms.GetAtom(format); + var res = await SendFormatRequest(); + if (!res.Contains(formatAtom)) + return null; + + return await SendDataRequest(formatAtom); } } } diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 3c41f7bdde..85aa4862b7 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -236,6 +236,10 @@ namespace Avalonia.X11 public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, PropertyMode mode, ref IntPtr value, int nelements); + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, byte[] data, int nelements); + [DllImport(libX11)] public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, PropertyMode mode, uint[] data, int nelements); diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 1c94d7a446..b579a7275e 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -1,25 +1,30 @@ using System; +using System.Linq; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.Threading; using Avalonia.Win32.Interop; namespace Avalonia.Win32 { internal class ClipboardImpl : IClipboard { - private async Task OpenClipboard() + private async Task OpenClipboard() { while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) { await Task.Delay(100); } + + return Disposable.Create(() => UnmanagedMethods.CloseClipboard()); } public async Task GetTextAsync() { - await OpenClipboard(); - try + using(await OpenClipboard()) { IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); if (hText == IntPtr.Zero) @@ -37,10 +42,6 @@ namespace Avalonia.Win32 UnmanagedMethods.GlobalUnlock(hText); return rv; } - finally - { - UnmanagedMethods.CloseClipboard(); - } } public async Task SetTextAsync(string text) @@ -50,31 +51,66 @@ namespace Avalonia.Win32 throw new ArgumentNullException(nameof(text)); } - await OpenClipboard(); - - UnmanagedMethods.EmptyClipboard(); - - try + using(await OpenClipboard()) { + UnmanagedMethods.EmptyClipboard(); + var hGlobal = Marshal.StringToHGlobalUni(text); UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); } - finally - { - UnmanagedMethods.CloseClipboard(); - } } public async Task ClearAsync() { - await OpenClipboard(); - try + using(await OpenClipboard()) { UnmanagedMethods.EmptyClipboard(); } - finally + } + + public async Task SetDataObjectAsync(IDataObject data) + { + Dispatcher.UIThread.VerifyAccess(); + var wrapper = new DataObject(data); + while (true) { - UnmanagedMethods.CloseClipboard(); + if (UnmanagedMethods.OleSetClipboard(wrapper) == 0) + break; + await Task.Delay(100); + } + } + + public async Task GetFormatsAsync() + { + Dispatcher.UIThread.VerifyAccess(); + while (true) + { + if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + { + var wrapper = new OleDataObject(dataObject); + var formats = wrapper.GetDataFormats().ToArray(); + Marshal.ReleaseComObject(dataObject); + return formats; + } + + await Task.Delay(100); + } + } + + public async Task GetFormatAsync(string format) + { + Dispatcher.UIThread.VerifyAccess(); + while (true) + { + if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + { + var wrapper = new OleDataObject(dataObject); + var rv = wrapper.Get(format); + Marshal.ReleaseComObject(dataObject); + return rv; + } + + await Task.Delay(100); } } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index da52c3116e..ba3775200b 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1169,6 +1169,12 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); + [DllImport("ole32.dll", PreserveSig = false)] + public static extern int OleGetClipboard(out IOleDataObject dataObject); + + [DllImport("ole32.dll", PreserveSig = true)] + public static extern int OleSetClipboard(IOleDataObject dataObject); + [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalLock(IntPtr handle); diff --git a/src/iOS/Avalonia.iOS/Clipboard.cs b/src/iOS/Avalonia.iOS/Clipboard.cs index c66b379453..e505190413 100644 --- a/src/iOS/Avalonia.iOS/Clipboard.cs +++ b/src/iOS/Avalonia.iOS/Clipboard.cs @@ -22,5 +22,11 @@ namespace Avalonia.iOS UIPasteboard.General.String = ""; return Task.FromResult(0); } + + public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException(); + + public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); + + public Task GetFormatAsync(string format) => throw new PlatformNotSupportedException(); } } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index d2f62cde04..2d65e147c5 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -654,6 +654,11 @@ namespace Avalonia.Controls.UnitTests public Task SetTextAsync(string text) => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask; + public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; + + public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); + + public Task GetFormatAsync(string format) => Task.FromResult((object)null); } } } From 3e04ef4d7214c4d1a65f4b7b03fd242742810ff8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 29 May 2020 21:03:07 +0300 Subject: [PATCH 03/15] Naming --- src/Android/Avalonia.Android/Platform/ClipboardImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Input/Platform/IClipboard.cs | 2 +- src/Avalonia.Native/ClipboardImpl.cs | 2 +- src/Avalonia.X11/X11Clipboard.cs | 2 +- src/Windows/Avalonia.Win32/ClipboardImpl.cs | 2 +- src/iOS/Avalonia.iOS/Clipboard.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs index b07dce8df4..7802f336fb 100644 --- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs +++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs @@ -48,6 +48,6 @@ namespace Avalonia.Android.Platform public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); - public Task GetFormatAsync(string format) => throw new PlatformNotSupportedException(); + public Task GetDataAsync(string format) => throw new PlatformNotSupportedException(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index b5f3b66aaa..b0e628e747 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -159,7 +159,7 @@ namespace Avalonia.DesignerSupport.Remote public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; public Task GetFormatsAsync() => Task.FromResult(new string[0]); - public Task GetFormatAsync(string format) => Task.FromResult((object)null); + public Task GetDataAsync(string format) => Task.FromResult((object)null); } class CursorFactoryStub : IStandardCursorFactory diff --git a/src/Avalonia.Input/Platform/IClipboard.cs b/src/Avalonia.Input/Platform/IClipboard.cs index 4a3c61cacd..eb880904eb 100644 --- a/src/Avalonia.Input/Platform/IClipboard.cs +++ b/src/Avalonia.Input/Platform/IClipboard.cs @@ -14,6 +14,6 @@ namespace Avalonia.Input.Platform Task GetFormatsAsync(); - Task GetFormatAsync(string format); + Task GetDataAsync(string format); } } diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 0430c4c638..554e7a497a 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -105,7 +105,7 @@ namespace Avalonia.Native return Task.FromResult(n.ToStringArray()); } - public async Task GetFormatAsync(string format) + public async Task GetDataAsync(string format) { if (format == DataFormats.Text) return await GetTextAsync(); diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 40198be17f..7023bb3ef3 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -305,7 +305,7 @@ namespace Avalonia.X11 return rv.ToArray(); } - public async Task GetFormatAsync(string format) + public async Task GetDataAsync(string format) { if (!HasOwner) return null; diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index b579a7275e..7d9e0a8bd2 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Win32 } } - public async Task GetFormatAsync(string format) + public async Task GetDataAsync(string format) { Dispatcher.UIThread.VerifyAccess(); while (true) diff --git a/src/iOS/Avalonia.iOS/Clipboard.cs b/src/iOS/Avalonia.iOS/Clipboard.cs index e505190413..2deb49473f 100644 --- a/src/iOS/Avalonia.iOS/Clipboard.cs +++ b/src/iOS/Avalonia.iOS/Clipboard.cs @@ -27,6 +27,6 @@ namespace Avalonia.iOS public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); - public Task GetFormatAsync(string format) => throw new PlatformNotSupportedException(); + public Task GetDataAsync(string format) => throw new PlatformNotSupportedException(); } } From 9237a398e956e4b8c6e17e84a6c85e5e4b426cfa Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Jun 2020 02:07:35 -0400 Subject: [PATCH 04/15] Make ToolTip background transparent using IPopupImpl --- src/Avalonia.Controls/ToolTip.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index 2f11746891..4ad5a6e60d 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -238,6 +238,9 @@ namespace Avalonia.Controls _popup.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); + + WindowManagerAddShadowHintChanged(_popup, false); + _popup.Show(); } @@ -250,5 +253,13 @@ namespace Avalonia.Controls _popup = null; } } + + private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) + { + if (host is PopupRoot pr) + { + pr.PlatformImpl.SetWindowManagerAddShadowHint(hint); + } + } } } From 7574a7506663759d2c973ea58c217c69bc6899b0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Jun 2020 03:06:58 -0400 Subject: [PATCH 05/15] Add tooltip fluent resources --- .../Accents/FluentBaseDark.xaml | 10 ++++++++++ .../Accents/FluentBaseLight.xaml | 10 ++++++++++ .../Accents/FluentControlResourcesDark.xaml | 11 +++++++++++ .../Accents/FluentControlResourcesLight.xaml | 11 +++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml index 7a715bbde7..a9369c569f 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml @@ -312,5 +312,15 @@ + + + 12 + 1 + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml index 5c6286a0bc..731ac1d0c7 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml @@ -313,5 +313,15 @@ + + + 12 + 1 + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 7364c339f1..d076df4cc9 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -343,5 +343,16 @@ + + + 12 + 1 + + + + + + + 8,5,8,7 diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 15e157f573..430e9f61cf 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -343,5 +343,16 @@ + + + 12 + 1 + + + + + + + 8,5,8,7 From 1fa96f049c85b24cab9e57ff10a21d4790c4e1b4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Jun 2020 03:07:28 -0400 Subject: [PATCH 06/15] Implement ToolTip :open pseudo class --- src/Avalonia.Controls/ToolTip.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index 4ad5a6e60d..b458b15c64 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -204,13 +204,15 @@ namespace Avalonia.Controls private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) { var control = (Control)e.Sender; + var newValue = (bool)e.NewValue; + ToolTip toolTip; - if ((bool)e.NewValue) + if (newValue) { var tip = GetTip(control); if (tip == null) return; - var toolTip = control.GetValue(ToolTipProperty); + toolTip = control.GetValue(ToolTipProperty); if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) { toolTip?.Close(); @@ -223,9 +225,11 @@ namespace Avalonia.Controls } else { - var toolTip = control.GetValue(ToolTipProperty); + toolTip = control.GetValue(ToolTipProperty); toolTip?.Close(); } + + toolTip?.UpdatePseudoClasses(newValue); } private void Open(Control control) @@ -261,5 +265,10 @@ namespace Avalonia.Controls pr.PlatformImpl.SetWindowManagerAddShadowHint(hint); } } + + private void UpdatePseudoClasses(bool newValue) + { + PseudoClasses.Set(":open", newValue); + } } } From 5721702fadbd43619600d55fcfcd05c6be2956e6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Jun 2020 03:08:27 -0400 Subject: [PATCH 07/15] Fluent ToolTip style --- src/Avalonia.Themes.Fluent/ToolTip.xaml | 91 ++++++++++++++++++++----- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml index 1fc0202dd3..f3c4266f77 100644 --- a/src/Avalonia.Themes.Fluent/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml @@ -1,17 +1,74 @@ - \ No newline at end of file + + + + + Hover Here + + + + + + ToolTip + A control which pops up a hint when a control is hovered + + + ToolTip bottom placement + + + + + + + + + + From 41719d8f1c39003e0cd9ba974f44d8542f2f4127 Mon Sep 17 00:00:00 2001 From: Yatao Li Date: Wed, 3 Jun 2020 16:00:12 +0800 Subject: [PATCH 08/15] build.sh: add downloaded dotnet to PATH --- build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sh b/build.sh index 40b1c225a6..a40e00f815 100755 --- a/build.sh +++ b/build.sh @@ -67,6 +67,8 @@ else fi fi +export PATH=$DOTNET_DIRECTORY:$PATH + echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} From 999d5945c10a9c0296d43a73a0041ed5cb4257a8 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 18:20:38 +0200 Subject: [PATCH 09/15] Optimize method accessor plugin --- .../Data/Core/Plugins/MethodAccessorPlugin.cs | 93 ++++++++++++++----- ...sorBenchmarks.cs => AccessorBenchmarks.cs} | 37 +++++++- 2 files changed, 105 insertions(+), 25 deletions(-) rename tests/Avalonia.Benchmarks/Data/{PropertyAccessorBenchmarks.cs => AccessorBenchmarks.cs} (58%) diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index c19ee8dba7..4aa6434de2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -1,13 +1,16 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; namespace Avalonia.Data.Core.Plugins { - class MethodAccessorPlugin : IPropertyAccessorPlugin + public class MethodAccessorPlugin : IPropertyAccessorPlugin { - public bool Match(object obj, string methodName) - => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); + private readonly Dictionary<(Type, string), MethodInfo> _methodLookup = + new Dictionary<(Type, string), MethodInfo>(); + + public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null; public IPropertyAccessor Start(WeakReference reference, string methodName) { @@ -15,17 +18,22 @@ namespace Avalonia.Data.Core.Plugins Contract.Requires(methodName != null); reference.TryGetTarget(out object instance); - var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); + + var method = GetFirstMethodWithName(instance.GetType(), methodName); if (method != null) { - if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) + var parameters = method.GetParameters(); + + if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) { - var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(methodName)); + var exception = new ArgumentException( + "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", + nameof(methodName)); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } - return new Accessor(reference, method); + return new Accessor(reference, method, parameters); } else { @@ -35,31 +43,72 @@ namespace Avalonia.Data.Core.Plugins } } + private MethodInfo GetFirstMethodWithName(Type type, string methodName) + { + var key = (type, methodName); + + if (!_methodLookup.TryGetValue(key, out MethodInfo methodInfo)) + { + methodInfo = TryFindAndCacheMethod(type, methodName); + } + + return methodInfo; + } + + private MethodInfo TryFindAndCacheMethod(Type type, string methodName) + { + MethodInfo found = null; + + const BindingFlags bindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + var methods = type.GetMethods(bindingFlags); + + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name == methodName) + { + found = methodInfo; + + break; + } + } + + _methodLookup.Add((type, methodName), found); + + return found; + } + private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method) + public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) { Contract.Requires(reference != null); Contract.Requires(method != null); - var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); var returnType = method.ReturnType; - - if (returnType == typeof(void)) + bool hasReturn = returnType != typeof(void); + + var signatureTypeCount = hasReturn ? 1 : 0 + parameters.Length; + + var paramTypes = new Type[signatureTypeCount]; + + for (var i = 0; i < parameters.Length; i++) { - if (paramTypes.Length == 0) - { - PropertyType = typeof(Action); - } - else - { - PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); - } + ParameterInfo parameter = parameters[i]; + + paramTypes[i] = parameter.ParameterType; + } + + if (hasReturn) + { + paramTypes[paramTypes.Length - 1] = returnType; + + PropertyType = Expression.GetFuncType(paramTypes); } else { - var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); - PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); + PropertyType = Expression.GetActionType(paramTypes); } if (method.IsStatic) diff --git a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs similarity index 58% rename from tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs rename to tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs index 5c2502422a..3d5a4029bb 100644 --- a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs @@ -10,7 +10,8 @@ namespace Avalonia.Benchmarks.Data [MemoryDiagnoser, InProcess] public class PropertyAccessorBenchmarks { - private readonly InpcPropertyAccessorPlugin _plugin = new InpcPropertyAccessorPlugin(); + private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin(); + private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin(); private readonly TestObject _targetStrongRef = new TestObject(); private readonly WeakReference _targetWeakRef; @@ -20,9 +21,27 @@ namespace Avalonia.Benchmarks.Data } [Benchmark] - public void InpcAccessor() + public void InpcAccessorMatch() { - _plugin.Start(_targetWeakRef, nameof(TestObject.Test)); + _inpcPlugin.Match(_targetWeakRef, nameof(TestObject.Test)); + } + + [Benchmark] + public void InpcAccessorStart() + { + _inpcPlugin.Start(_targetWeakRef, nameof(TestObject.Test)); + } + + [Benchmark] + public void MethodAccessorMatch() + { + _methodPlugin.Match(_targetWeakRef, nameof(TestObject.Execute)); + } + + [Benchmark] + public void MethodAccessorStart() + { + _methodPlugin.Start(_targetWeakRef, nameof(TestObject.Execute)); } private class TestObject : INotifyPropertyChanged @@ -45,6 +64,18 @@ namespace Avalonia.Benchmarks.Data } } + public void Execute() + { + } + + public void Execute(object p0) + { + } + + public void Execute(object p0, object p1) + { + } + public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] From 0f23da0a79a90be415de2e4959f62e509b5aaf86 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 18:34:05 +0200 Subject: [PATCH 10/15] Use correct file name. --- .../Data/{AccessorBenchmarks.cs => PropertyAccessorBenchmarks.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Avalonia.Benchmarks/Data/{AccessorBenchmarks.cs => PropertyAccessorBenchmarks.cs} (100%) diff --git a/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs similarity index 100% rename from tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs rename to tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs From 4b6d601bb70f95635d02edb785a62c8037b938b6 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 19:11:38 +0200 Subject: [PATCH 11/15] Fix ternary op. --- src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 4aa6434de2..5d694f4cf9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -89,7 +89,7 @@ namespace Avalonia.Data.Core.Plugins var returnType = method.ReturnType; bool hasReturn = returnType != typeof(void); - var signatureTypeCount = hasReturn ? 1 : 0 + parameters.Length; + var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; var paramTypes = new Type[signatureTypeCount]; From d2a5f525dafd7d8310947a169320b817511ace5f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 4 Jun 2020 22:20:31 -0400 Subject: [PATCH 12/15] Add text wrapping to Fluent ToolTip --- src/Avalonia.Themes.Fluent/ToolTip.xaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml index f3c4266f77..fa528ee6cf 100644 --- a/src/Avalonia.Themes.Fluent/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml @@ -8,7 +8,7 @@ Background="{DynamicResource ThemeAccentBrush}" Margin="5" Padding="50" - ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."> + ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> Hover Here - + + + From bcd215e3db09e8c5a67774ff4b78599b2b9cf38b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 4 Jun 2020 22:31:40 -0400 Subject: [PATCH 13/15] 150 ms opacity transition duration for ToolTip --- src/Avalonia.Themes.Fluent/ToolTip.xaml | 76 ++++++++++++------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml index fa528ee6cf..cf6f32f9bc 100644 --- a/src/Avalonia.Themes.Fluent/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml @@ -1,39 +1,39 @@ - - + + Hover Here + + - Hover Here - - - - - - ToolTip - A control which pops up a hint when a control is hovered - - - ToolTip bottom placement - - + Grid.Row="0" + IsChecked="{Binding ElementName=Border, Path=(ToolTip.IsOpen)}" + Content="ToolTip Open" /> + + + + ToolTip + A control which pops up a hint when a control is hovered + + + ToolTip bottom placement + + - + - + From 53cb9d266d4f87789c30b37b55e6c5bf7cc095ae Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 5 Jun 2020 03:46:02 -0400 Subject: [PATCH 14/15] Add ToolTip :open pseudo class tests --- .../ToolTipTests.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index dc5b574db7..34b37e7635 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -102,5 +102,76 @@ namespace Avalonia.Controls.UnitTests Assert.True(ToolTip.GetIsOpen(target)); } } + + [Fact] + public void Open_Class_Should_Not_Initially_Be_Added() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var toolTip = new ToolTip(); + var window = new Window(); + + var decorator = new Decorator() + { + [ToolTip.TipProperty] = toolTip + }; + + window.Content = decorator; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + Assert.Empty(toolTip.Classes); + } + } + + [Fact] + public void Setting_IsOpen_Should_Add_Open_Class() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var toolTip = new ToolTip(); + var window = new Window(); + + var decorator = new Decorator() + { + [ToolTip.TipProperty] = toolTip + }; + + window.Content = decorator; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + ToolTip.SetIsOpen(decorator, true); + + Assert.Equal(new[] { ":open" }, toolTip.Classes); + } + } + + [Fact] + public void Clearing_IsOpen_Should_Remove_Open_Class() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var toolTip = new ToolTip(); + var window = new Window(); + + var decorator = new Decorator() + { + [ToolTip.TipProperty] = toolTip + }; + + window.Content = decorator; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + ToolTip.SetIsOpen(decorator, true); + ToolTip.SetIsOpen(decorator, false); + + Assert.Empty(toolTip.Classes); + } + } } } From 165da1c6787b77378ea6fb02395f9a7661bde9db Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 5 Jun 2020 13:59:23 +0300 Subject: [PATCH 15/15] Naming --- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 2d65e147c5..217dec2d6d 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -658,7 +658,7 @@ namespace Avalonia.Controls.UnitTests public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); - public Task GetFormatAsync(string format) => Task.FromResult((object)null); + public Task GetDataAsync(string format) => Task.FromResult((object)null); } } }