Browse Source

Merge branch 'master' into exported-menus

pull/2978/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
0bb3971fda
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  2. 23
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  3. 83
      src/Avalonia.Controls/Button.cs
  4. 9
      src/Avalonia.Controls/Primitives/Popup.cs
  5. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  6. 2
      src/Avalonia.Controls/TreeView.cs
  7. 2
      src/Avalonia.Controls/TreeViewItem.cs
  8. 37
      src/Avalonia.FreeDesktop/NativeMethods.cs
  9. 2
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  10. 5
      src/Avalonia.X11/X11CursorFactory.cs
  11. 40
      src/Avalonia.X11/XI2Manager.cs
  12. 7
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  13. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  14. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  15. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  16. 25
      src/Windows/Avalonia.Win32/DataObject.cs
  17. 7
      src/Windows/Avalonia.Win32/DragSource.cs
  18. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  19. 43
      src/Windows/Avalonia.Win32/OleDataObject.cs
  20. 12
      src/Windows/Avalonia.Win32/WindowImpl.cs
  21. 9
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  22. 22
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  23. 74
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -15,10 +15,10 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface
{
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false)
@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this };
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels);
}
@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] {this};
public IEnumerable<object> Surfaces { get; }
public IRenderer CreateRenderer(IRenderRoot root)
{

23
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;
@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop
{
public class Utf8Buffer : SafeHandle
{
private GCHandle _gchandle;
private GCHandle _gcHandle;
private byte[] _data;
public Utf8Buffer(string s) : base(IntPtr.Zero, true)
@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop
if (s == null)
return;
_data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject();
_gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gcHandle.AddrOfPinnedObject();
}
public int ByteLen => _data.Length;
@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop
{
handle = IntPtr.Zero;
_data = null;
_gchandle.Free();
_gcHandle.Free();
}
return true;
}
@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop
return null;
int len;
for (len = 0; pstr[len] != 0; len++) ;
var bytes = new byte[len];
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len);
var bytes = ArrayPool<byte>.Shared.Rent(len);
try
{
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len);
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
}
}
}
}

83
src/Avalonia.Controls/Button.cs

@ -64,6 +64,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsDefaultProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsDefault));
/// <summary>
/// Defines the <see cref="IsCancelProperty"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsCancelProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsCancel));
/// <summary>
/// Defines the <see cref="Click"/> event.
/// </summary>
@ -84,6 +90,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged);
PseudoClass<Button>(IsPressedProperty, ":pressed");
}
@ -142,6 +149,16 @@ namespace Avalonia.Controls
set { SetValue(IsDefaultProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the button is the Cancel button for the
/// window.
/// </summary>
public bool IsCancel
{
get { return GetValue(IsCancelProperty); }
set { SetValue(IsCancelProperty, value); }
}
public bool IsPressed
{
get { return GetValue(IsPressedProperty); }
@ -162,6 +179,13 @@ namespace Avalonia.Controls
ListenForDefault(inputElement);
}
}
if (IsCancel)
{
if (e.Root is IInputElement inputElement)
{
ListenForCancel(inputElement);
}
}
}
/// <inheritdoc/>
@ -351,6 +375,28 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="IsCancel"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void IsCancelChanged(AvaloniaPropertyChangedEventArgs e)
{
var button = e.Sender as Button;
var isCancel = (bool)e.NewValue;
if (button?.VisualRoot is IInputElement inputRoot)
{
if (isCancel)
{
button.ListenForCancel(inputRoot);
}
else
{
button.StopListeningForCancel(inputRoot);
}
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>
@ -373,7 +419,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param>
private void ListenForDefault(IInputElement root)
{
root.AddHandler(KeyDownEvent, RootKeyDown);
root.AddHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Starts listening for the Escape key when the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void ListenForCancel(IInputElement root)
{
root.AddHandler(KeyDownEvent, RootCancelKeyDown);
}
/// <summary>
@ -382,7 +437,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param>
private void StopListeningForDefault(IInputElement root)
{
root.RemoveHandler(KeyDownEvent, RootKeyDown);
root.RemoveHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Stops listening for the Escape key when the button is no longer <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void StopListeningForCancel(IInputElement root)
{
root.RemoveHandler(KeyDownEvent, RootCancelKeyDown);
}
/// <summary>
@ -390,12 +454,25 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void RootKeyDown(object sender, KeyEventArgs e)
private void RootDefaultKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && IsVisible && IsEnabled)
{
OnClick();
}
}
/// <summary>
/// Called when a key is pressed on the input root and the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void RootCancelKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && IsVisible && IsEnabled)
{
OnClick();
}
}
}
}

9
src/Avalonia.Controls/Primitives/Popup.cs

@ -450,7 +450,14 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child)
{
return _popupHost != null && ((IVisual)_popupHost).FindCommonVisualAncestor(child) == _popupHost;
IVisual root = child.GetVisualRoot();
while (root is IHostedVisualTreeRoot hostedRoot )
{
if (root == this._popupHost)
return true;
root = hostedRoot.Host?.GetVisualRoot();
}
return false;
}
public bool IsInsidePopup(IVisual visual)

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

@ -304,6 +304,11 @@ namespace Avalonia.Controls.Primitives
{
base.ItemsCollectionChanged(sender, e);
if (_updateCount > 0)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@ -1071,13 +1076,20 @@ namespace Avalonia.Controls.Primitives
private void UpdateFinished()
{
if (_updateSelectedIndex != int.MinValue)
if (_updateSelectedItem != null)
{
SelectedIndex = _updateSelectedIndex;
SelectedItem = _updateSelectedItem;
}
else if (_updateSelectedItem != null)
else
{
SelectedItem = _updateSelectedItem;
if (ItemCount == 0 && SelectedIndex != -1)
{
SelectedIndex = -1;
}
else
{
SelectedIndex = _updateSelectedIndex != int.MinValue ? _updateSelectedIndex : 0;
}
}
}

2
src/Avalonia.Controls/TreeView.cs

@ -50,7 +50,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
ListBox.SelectionModeProperty.AddOwner<TreeView>();
private static readonly IList Empty = new object[0];
private static readonly IList Empty = Array.Empty<object>();
private object _selectedItem;
private IList _selectedItems;

2
src/Avalonia.Controls/TreeViewItem.cs

@ -173,7 +173,7 @@ namespace Avalonia.Controls
{
var result = 0;
while (logical != null && logical.GetType() != typeof(T))
while (logical != null && !(logical is T))
{
++result;
logical = logical.LogicalParent;

37
src/Avalonia.FreeDesktop/NativeMethods.cs

@ -1,12 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Controls.Platform;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
@ -21,11 +14,27 @@ namespace Avalonia.FreeDesktop
public static string ReadLink(string path)
{
var symlink = Encoding.UTF8.GetBytes(path);
var result = new byte[4095];
readlink(symlink, result, result.Length);
var rawstr = Encoding.UTF8.GetString(result);
return rawstr.Substring(0, rawstr.IndexOf('\0'));
var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length);
var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated
var symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1);
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
symlink[symlinkSize] = 0;
var size = readlink(symlink, buffer, bufferSize);
Debug.Assert(size < bufferSize); // if this fails, we need to increase the buffer size (dynamically?)
return Encoding.UTF8.GetString(buffer, 0, (int)size);
}
finally
{
ArrayPool<byte>.Shared.Return(symlink);
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
}

2
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs → src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@ -3,7 +3,7 @@ using Avalonia.Reactive;
namespace Avalonia.Controls
{
public static class ResourceProviderExtensions
public static class ResourceNodeExtensions
{
/// <summary>
/// Finds the specified resource by searching up the logical tree and then global styles.

5
src/Avalonia.X11/X11CursorFactory.cs

@ -8,6 +8,8 @@ namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
private static IntPtr _nullCursor;
private readonly IntPtr _display;
@ -68,9 +70,8 @@ namespace Avalonia.X11
private static IntPtr GetNullCursor(IntPtr display)
{
XColor color = new XColor();
byte[] data = new byte[] { 0 };
IntPtr window = XLib.XRootWindow(display, 0);
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1);
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1);
return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
}
}

40
src/Avalonia.X11/XI2Manager.cs

@ -1,18 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Input;
using Avalonia.Input.Raw;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class XI2Manager
{
private static readonly XiEventType[] DefaultEventTypes = new XiEventType[]
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease
};
private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[]
{
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd
};
private X11Info _x11;
private bool _multitouch;
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
class DeviceInfo
{
public int Id { get; }
@ -125,20 +139,18 @@ namespace Avalonia.X11
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
var events = new List<XiEventType>
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease
};
var eventsLength = DefaultEventTypes.Length;
if (_multitouch)
events.AddRange(new[]
{
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd
});
eventsLength += MultiTouchEventTypes.Length;
var events = new List<XiEventType>(eventsLength);
events.AddRange(DefaultEventTypes);
if (_multitouch)
events.AddRange(MultiTouchEventTypes);
XiSelectEvents(_x11.Display, xid,
new Dictionary<int, List<XiEventType>> {[_pointerDevice.Id] = events});

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

@ -6,7 +6,6 @@ using Avalonia.LinuxFramebuffer.Input;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
{
@ -14,6 +13,7 @@ namespace Avalonia.LinuxFramebuffer
{
private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend;
private bool _renderQueued;
public IInputRoot InputRoot { get; private set; }
@ -21,6 +21,9 @@ namespace Avalonia.LinuxFramebuffer
{
_outputBackend = outputBackend;
_inputBackend = inputBackend;
Surfaces = new object[] { _outputBackend };
Invalidate(default(Rect));
_inputBackend.Initialize(this, e => Input?.Invoke(e));
}
@ -62,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
public IPopupImpl CreatePopup() => null;
public double Scaling => _outputBackend.Scaling;
public IEnumerable<object> Surfaces => new object[] {_outputBackend};
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
}
public DynamicResourceExtension(string resourceKey)
public DynamicResourceExtension(object resourceKey)
{
ResourceKey = resourceKey;
}

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -16,12 +16,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
}
public StaticResourceExtension(string resourceKey)
public StaticResourceExtension(object resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public object ResourceKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{

1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Direct2D1</PackageId>
</PropertyGroup>
<ItemGroup>

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

@ -1,5 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -254,9 +256,18 @@ namespace Avalonia.Win32
return WriteFileListToHGlobal(ref hGlobal, files);
if (data is Stream stream)
{
byte[] buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return WriteBytesToHGlobal(ref hGlobal, buffer);
var length = (int)(stream.Length - stream.Position);
var buffer = ArrayPool<byte>.Shared.Rent(length);
try
{
stream.Read(buffer, 0, length);
return WriteBytesToHGlobal(ref hGlobal, buffer.AsSpan(0, length));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
if (data is IEnumerable<byte> bytes)
{
@ -277,7 +288,7 @@ namespace Avalonia.Win32
}
}
private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data)
private unsafe int WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data)
{
int required = data.Length;
if (hGlobal == IntPtr.Zero)
@ -287,10 +298,12 @@ namespace Avalonia.Win32
if (required > available)
return STG_E_MEDIUMFULL;
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
var ptr = UnmanagedMethods.GlobalLock(hGlobal);
Debug.Assert(ptr == hGlobal);
try
{
Marshal.Copy(data, 0, ptr, data.Length);
data.CopyTo(new Span<byte>((void*)ptr, data.Length));
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
finally

7
src/Windows/Avalonia.Win32/DragSource.cs

@ -17,9 +17,8 @@ namespace Avalonia.Win32
DataObject dataObject = new DataObject(data);
int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects);
int[] finalEffect = new int[1];
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect);
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));}
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, out var finalEffect);
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect));
}
}
}

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

@ -1051,10 +1051,10 @@ namespace Avalonia.Win32.Interop
public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
[DllImport("user32")]
public static extern bool GetTouchInputInfo(
public static extern unsafe bool GetTouchInputInfo(
IntPtr hTouchInput,
uint cInputs,
[Out]TOUCHINPUT[] pInputs,
TOUCHINPUT* pInputs,
int cbSize
);
@ -1115,7 +1115,7 @@ namespace Avalonia.Win32.Interop
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect);

43
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -1,5 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -86,15 +88,8 @@ namespace Avalonia.Win32
return null;
}
private bool IsSerializedObject(byte[] data)
{
if (data.Length < DataObject.SerializedObjectGUID.Length)
return false;
for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++)
if (data[i] != DataObject.SerializedObjectGUID[i])
return false;
return true;
}
private static bool IsSerializedObject(ReadOnlySpan<byte> data) =>
data.StartsWith(DataObject.SerializedObjectGUID);
private static IEnumerable<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
{
@ -148,21 +143,33 @@ namespace Avalonia.Win32
private IEnumerable<string> GetDataFormatsCore()
{
var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET);
if (enumFormat != null)
{
enumFormat.Reset();
FORMATETC[] formats = new FORMATETC[1];
int[] fetched = { 1 };
while (fetched[0] > 0)
var formats = ArrayPool<FORMATETC>.Shared.Rent(1);
var fetched = ArrayPool<int>.Shared.Rent(1);
try
{
fetched[0] = 0;
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
do
{
if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
fetched[0] = 0;
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
{
if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
}
}
while (fetched[0] > 0);
}
finally
{
ArrayPool<FORMATETC>.Shared.Return(formats);
ArrayPool<int>.Shared.Return(fetched);
}
}
}

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

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
@ -13,7 +12,6 @@ using Avalonia.Input.Raw;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -430,7 +428,7 @@ namespace Avalonia.Win32
}
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
@ -637,8 +635,12 @@ namespace Avalonia.Win32
new Point(0, 0), GetMouseModifiers(wParam));
break;
case WindowsMessage.WM_TOUCH:
var touchInputs = new TOUCHINPUT[wParam.ToInt32()];
if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf<TOUCHINPUT>()))
var touchInputCount = wParam.ToInt32();
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount);
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>()))
{
foreach (var touchInput in touchInputs)
{

9
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -16,14 +16,17 @@ namespace Avalonia.iOS
[Adopts("UIKeyInput")]
class TopLevelImpl : UIView, ITopLevelImpl, IFramebufferPlatformSurface
{
private IInputRoot _inputRoot;
private readonly KeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private IInputRoot _inputRoot;
public TopLevelImpl()
{
_keyboardHelper = new KeyboardEventsHelper<TopLevelImpl>(this);
AutoresizingMask = UIViewAutoresizing.All;
_keyboardHelper.ActivateAutoShowKeyboard();
Surfaces = new object[] { this };
}
[Export("hasText")]
@ -76,8 +79,8 @@ namespace Avalonia.iOS
{
//Not supported
}
public IEnumerable<object> Surfaces => new object[] { this };
public IEnumerable<object> Surfaces { get; }
public override void TouchesEnded(NSSet touches, UIEvent evt)
{

22
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -109,6 +109,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(items[1].IsSelected);
}
[Fact]
public void Setting_SelectedIndex_During_Initialize_Should_Select_Item_When_AlwaysSelected_Is_Used()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected
};
listBox.BeginInit();
listBox.SelectedIndex = 1;
var items = new AvaloniaList<string>();
listBox.Items = items;
items.Add("A");
items.Add("B");
items.Add("C");
listBox.EndInit();
Assert.Equal("B", listBox.SelectedItem);
}
[Fact]
public void Setting_SelectedIndex_Before_ApplyTemplate_Should_Set_Item_IsSelected_True()
{

74
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
@ -843,6 +844,54 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, target.SelectedItems.Count);
}
[Fact]
public void TreeViewItems_Level_Should_Be_Set()
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
Assert.Equal(0, GetItem(target, 0).Level);
Assert.Equal(1, GetItem(target, 0, 0).Level);
Assert.Equal(1, GetItem(target, 0, 1).Level);
Assert.Equal(1, GetItem(target, 0, 2).Level);
Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
}
[Fact]
public void TreeViewItems_Level_Should_Be_Set_For_Derived_TreeView()
{
var tree = CreateTestTreeData();
var target = new DerivedTreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
Assert.Equal(0, GetItem(target, 0).Level);
Assert.Equal(1, GetItem(target, 0, 0).Level);
Assert.Equal(1, GetItem(target, 0, 1).Level);
Assert.Equal(1, GetItem(target, 0, 2).Level);
Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
}
private void ApplyTemplates(TreeView tree)
{
tree.ApplyTemplate();
@ -862,6 +911,19 @@ namespace Avalonia.Controls.UnitTests
}
}
private TreeViewItem GetItem(TreeView target, params int[] indexes)
{
var c = (ItemsControl)target;
foreach (var index in indexes)
{
var item = ((IList)c.Items)[index];
c = (ItemsControl)target.ItemContainerGenerator.Index.ContainerFromItem(item);
}
return (TreeViewItem)c;
}
private IList<Node> CreateTestTreeData()
{
return new AvaloniaList<Node>
@ -929,6 +991,14 @@ namespace Avalonia.Controls.UnitTests
});
}
private void ExpandAll(TreeView tree)
{
foreach (var i in tree.ItemContainerGenerator.Containers)
{
tree.ExpandSubTree((TreeViewItem)i.ContainerControl);
}
}
private List<string> ExtractItemHeader(TreeView tree, int level)
{
return ExtractItemContent(tree.Presenter.Panel, 0, level)
@ -1019,5 +1089,9 @@ namespace Avalonia.Controls.UnitTests
return data is Node;
}
}
private class DerivedTreeView : TreeView
{
}
}
}

Loading…
Cancel
Save