Browse Source

Merge branch 'feature/invariantFamilyNames' of https://github.com/Gillibald/Avalonia into feature/invariantFamilyNames

pull/10620/head
Benedikt Stebner 4 years ago
parent
commit
ab40d4e9fa
  1. 5
      Avalonia.Desktop.slnf
  2. 4
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  3. 8
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  4. 16
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  5. 65
      src/Avalonia.Base/AvaloniaProperty.cs
  6. 10
      src/Avalonia.Base/AvaloniaPropertyMetadata.cs
  7. 2
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  8. 12
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  9. 2
      src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
  10. 7
      src/Avalonia.Base/StyledProperty.cs
  11. 2
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  12. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  13. 2
      src/Avalonia.Controls/Platform/IPopupImpl.cs
  14. 2
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  15. 9
      src/Avalonia.Controls/ToolTipService.cs
  16. 76
      src/Avalonia.X11/X11Window.cs
  17. 16
      src/Browser/Avalonia.Browser/Interop/GeneralHelpers.cs
  18. 6
      src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
  19. 47
      src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
  20. 7
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/generalHelpers.ts
  21. 10
      src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
  22. 9
      src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
  23. 46
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

5
Avalonia.Desktop.slnf

@ -8,9 +8,9 @@
"samples\\GpuInterop\\GpuInterop.csproj", "samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj", "samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj", "samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj", "samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj", "samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj", "src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
@ -41,6 +41,7 @@
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj", "src\\tools\\DevGenerators\\DevGenerators.csproj",
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj", "src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
@ -63,4 +64,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj" "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
] ]
} }
} }

4
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -324,9 +324,9 @@ namespace ControlCatalog.Pages
mappedResults.Add("+> " + FullPathOrName(selectedItem)); mappedResults.Add("+> " + FullPathOrName(selectedItem));
if (selectedItem is IStorageFolder folder) if (selectedItem is IStorageFolder folder)
{ {
foreach (var innerItems in await folder.GetItemsAsync()) await foreach (var innerItem in folder.GetItemsAsync())
{ {
mappedResults.Add("++> " + FullPathOrName(innerItems)); mappedResults.Add("++> " + FullPathOrName(innerItem));
} }
} }
} }

8
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -104,8 +104,12 @@ namespace ControlCatalog.Pages
} }
else if (item is IStorageFolder folder) else if (item is IStorageFolder folder)
{ {
var items = await folder.GetItemsAsync(); var childrenCount = 0;
contentStr += $"Folder {item.Name}: items {items.Count}{Environment.NewLine}{Environment.NewLine}"; await foreach (var _ in folder.GetItemsAsync())
{
childrenCount++;
}
contentStr += $"Folder {item.Name}: items {childrenCount}{Environment.NewLine}{Environment.NewLine}";
} }
} }

16
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -131,19 +131,17 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
return Task.FromResult(new StorageItemProperties()); return Task.FromResult(new StorageItemProperties());
} }
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync() public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{ {
if (!await EnsureExternalFilesPermission(false)) if (!await EnsureExternalFilesPermission(false))
{ {
return Array.Empty<IStorageItem>(); yield break;
} }
List<IStorageItem> files = new List<IStorageItem>();
var contentResolver = Activity.ContentResolver; var contentResolver = Activity.ContentResolver;
if (contentResolver == null) if (contentResolver == null)
{ {
return files; yield break;
} }
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(Uri!, DocumentsContract.GetTreeDocumentId(Uri)); var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(Uri!, DocumentsContract.GetTreeDocumentId(Uri));
@ -168,12 +166,10 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
continue; continue;
} }
files.Add(mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) : yield return mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
new AndroidStorageFile(Activity, uri)); new AndroidStorageFile(Activity, uri);
} }
} }
return files;
} }
} }

65
src/Avalonia.Base/AvaloniaProperty.cs

@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.PropertyStore; using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -20,12 +19,20 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType(); public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId; private static int s_nextId;
/// <summary>
/// Provides a metadata object for types which have no metadata of their own.
/// </summary>
private readonly AvaloniaPropertyMetadata _defaultMetadata; private readonly AvaloniaPropertyMetadata _defaultMetadata;
/// <summary>
/// Provides a fast path when the property has no metadata overrides.
/// </summary>
private KeyValuePair<Type, AvaloniaPropertyMetadata>? _singleMetadata;
private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadata; private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadata;
private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadataCache = new Dictionary<Type, AvaloniaPropertyMetadata>(); private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadataCache = new Dictionary<Type, AvaloniaPropertyMetadata>();
private bool _hasMetadataOverrides;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class. /// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// </summary> /// </summary>
@ -57,7 +64,8 @@ namespace Avalonia
Id = s_nextId++; Id = s_nextId++;
_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata))); _metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
_defaultMetadata = metadata; _defaultMetadata = metadata.GenerateTypeSafeMetadata();
_singleMetadata = new(ownerType, metadata);
} }
/// <summary> /// <summary>
@ -80,9 +88,6 @@ namespace Avalonia
Id = source.Id; Id = source.Id;
_defaultMetadata = source._defaultMetadata; _defaultMetadata = source._defaultMetadata;
// Properties that have different owner can't use fast path for metadata.
_hasMetadataOverrides = true;
if (metadata != null) if (metadata != null)
{ {
_metadata.Add(ownerType, metadata); _metadata.Add(ownerType, metadata);
@ -453,33 +458,14 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the property metadata for the specified type. /// Gets the <see cref="AvaloniaPropertyMetadata"/> which applies to this property when it is used with the specified type.
/// </summary> /// </summary>
/// <typeparam name="T">The type.</typeparam> /// <typeparam name="T">The type for which to retrieve metadata.</typeparam>
/// <returns> public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject => GetMetadata(typeof(T));
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject
{
return GetMetadata(typeof(T));
}
/// <summary> /// <inheritdoc cref="GetMetadata{T}"/>
/// Gets the property metadata for the specified type. /// <param name="type">The type for which to retrieve metadata.</param>
/// </summary> public AvaloniaPropertyMetadata GetMetadata(Type type) => GetMetadataWithOverrides(type);
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata(Type type)
{
if (!_hasMetadataOverrides)
{
return _defaultMetadata;
}
return GetMetadataWithOverrides(type);
}
/// <summary> /// <summary>
/// Checks whether the <paramref name="value"/> is valid for the property. /// Checks whether the <paramref name="value"/> is valid for the property.
@ -578,7 +564,7 @@ namespace Avalonia
_metadata.Add(type, metadata); _metadata.Add(type, metadata);
_metadataCache.Clear(); _metadataCache.Clear();
_hasMetadataOverrides = true; _singleMetadata = null;
} }
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged(); protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
@ -595,7 +581,12 @@ namespace Avalonia
return result; return result;
} }
Type? currentType = type; if (_singleMetadata is { } singleMetadata)
{
return _metadataCache[type] = singleMetadata.Key.IsAssignableFrom(type) ? singleMetadata.Value : _defaultMetadata;
}
var currentType = type;
while (currentType != null) while (currentType != null)
{ {
@ -609,13 +600,11 @@ namespace Avalonia
currentType = currentType.BaseType; currentType = currentType.BaseType;
} }
_metadataCache[type] = _defaultMetadata; return _metadataCache[type] = _defaultMetadata;
return _defaultMetadata;
} }
bool IPropertyInfo.CanGet => true; bool IPropertyInfo.CanGet => true;
bool IPropertyInfo.CanSet => true; bool IPropertyInfo.CanSet => !IsReadOnly;
object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this); object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
void IPropertyInfo.Set(object target, object? value) => ((AvaloniaObject)target).SetValue(this, value); void IPropertyInfo.Set(object target, object? value) => ((AvaloniaObject)target).SetValue(this, value);
} }

10
src/Avalonia.Base/AvaloniaPropertyMetadata.cs

@ -5,7 +5,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Base class for avalonia property metadata. /// Base class for avalonia property metadata.
/// </summary> /// </summary>
public class AvaloniaPropertyMetadata public abstract class AvaloniaPropertyMetadata
{ {
private BindingMode _defaultBindingMode; private BindingMode _defaultBindingMode;
@ -61,5 +61,13 @@ namespace Avalonia
EnableDataValidation ??= baseMetadata.EnableDataValidation; EnableDataValidation ??= baseMetadata.EnableDataValidation;
} }
/// <summary>
/// Gets a copy of this object configured for use with any owner type.
/// </summary>
/// <remarks>
/// For example, delegates which receive the owner object should be removed.
/// </remarks>
public abstract AvaloniaPropertyMetadata GenerateTypeSafeMetadata();
} }
} }

2
src/Avalonia.Base/DirectPropertyMetadata`1.cs

@ -45,5 +45,7 @@ namespace Avalonia
UnsetValue ??= src.UnsetValue; UnsetValue ??= src.UnsetValue;
} }
} }
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new DirectPropertyMetadata<TValue>(UnsetValue, DefaultBindingMode, EnableDataValidation);
} }
} }

12
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@ -57,14 +57,16 @@ internal class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult<IStorageFolder?>(null); return Task.FromResult<IStorageFolder?>(null);
} }
public Task<IReadOnlyList<IStorageItem>> GetItemsAsync() public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{ {
var items = DirectoryInfo.GetDirectories() var items = DirectoryInfo.EnumerateDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d)) .Select(d => (IStorageItem)new BclStorageFolder(d))
.Concat(DirectoryInfo.GetFiles().Select(f => new BclStorageFile(f))) .Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)));
.ToArray();
return Task.FromResult<IReadOnlyList<IStorageItem>>(items); foreach (var item in items)
{
yield return item;
}
} }
public virtual Task<string?> SaveBookmarkAsync() public virtual Task<string?> SaveBookmarkAsync()

2
src/Avalonia.Base/Platform/Storage/IStorageFolder.cs

@ -16,5 +16,5 @@ public interface IStorageFolder : IStorageItem
/// <returns> /// <returns>
/// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an <see cref="IStorageItem"/> implementation object. /// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an <see cref="IStorageItem"/> implementation object.
/// </returns> /// </returns>
Task<IReadOnlyList<IStorageItem>> GetItemsAsync(); IAsyncEnumerable<IStorageItem> GetItemsAsync();
} }

7
src/Avalonia.Base/StyledProperty.cs

@ -18,7 +18,10 @@ namespace Avalonia
/// <param name="ownerType">The type of the class that registers the property.</param> /// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param> /// <param name="validate">
/// <para>A method which returns "false" for values that are never valid for this property.</para>
/// <para>This method is not part of the property's metadata and so cannot be changed after registration.</para>
/// </param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param> /// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
public StyledProperty( public StyledProperty(
string name, string name,
@ -41,7 +44,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the value validation callback for the property. /// A method which returns "false" for values that are never valid for this property.
/// </summary> /// </summary>
public Func<TValue, bool>? ValidateValue { get; } public Func<TValue, bool>? ValidateValue { get; }

2
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -58,5 +58,7 @@ namespace Avalonia
} }
} }
} }
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new StyledPropertyMetadata<TValue>(DefaultValue, DefaultBindingMode, enableDataValidation: EnableDataValidation ?? false);
} }
} }

5
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -6066,8 +6066,9 @@ namespace Avalonia.Controls
var numberOfItem = clipboardRowContent.Count; var numberOfItem = clipboardRowContent.Count;
for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++) for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++)
{ {
var cellContent = clipboardRowContent[cellIndex]; var cellContent = clipboardRowContent[cellIndex].Content?.ToString();
text.Append(cellContent.Content); cellContent = cellContent?.Replace("\"", "\"\"");
text.Append($"\"{cellContent}\"");
if (cellIndex < numberOfItem - 1) if (cellIndex < numberOfItem - 1)
{ {
text.Append('\t'); text.Append('\t');

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

@ -9,7 +9,7 @@ namespace Avalonia.Platform
[Unstable] [Unstable]
public interface IPopupImpl : IWindowBaseImpl public interface IPopupImpl : IWindowBaseImpl
{ {
IPopupPositioner PopupPositioner { get; } IPopupPositioner? PopupPositioner { get; }
void SetWindowManagerAddShadowHint(bool enabled); void SetWindowManagerAddShadowHint(bool enabled);
} }

2
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -95,7 +95,7 @@ namespace Avalonia.Controls.Primitives
private void UpdatePosition() private void UpdatePosition()
{ {
PlatformImpl?.PopupPositioner.Update(_positionerParameters); PlatformImpl?.PopupPositioner?.Update(_positionerParameters);
} }
public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, public void ConfigurePosition(Visual target, PlacementMode placement, Point offset,

9
src/Avalonia.Controls/ToolTipService.cs

@ -42,11 +42,12 @@ namespace Avalonia.Controls
{ {
Close(control); Close(control);
} }
else else
{ {
var tip = control.GetValue(ToolTip.ToolTipProperty); if (control.GetValue(ToolTip.ToolTipProperty) is { } tip)
{
tip!.Content = e.NewValue; tip.Content = e.NewValue;
}
} }
} }
} }

76
src/Avalonia.X11/X11Window.cs

@ -25,6 +25,9 @@ using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
// ReSharper disable IdentifierTypo // ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo // ReSharper disable StringLiteralTypo
#nullable enable
namespace Avalonia.X11 namespace Avalonia.X11
{ {
internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client
@ -35,11 +38,11 @@ namespace Avalonia.X11
private XConfigureEvent? _configure; private XConfigureEvent? _configure;
private PixelPoint? _configurePoint; private PixelPoint? _configurePoint;
private bool _triggeredExpose; private bool _triggeredExpose;
private IInputRoot _inputRoot; private IInputRoot? _inputRoot;
private readonly MouseDevice _mouse; private readonly MouseDevice _mouse;
private readonly TouchDevice _touch; private readonly TouchDevice _touch;
private readonly IKeyboardDevice _keyboard; private readonly IKeyboardDevice _keyboard;
private readonly ITopLevelNativeMenuExporter _nativeMenuExporter; private readonly ITopLevelNativeMenuExporter? _nativeMenuExporter;
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
private readonly X11NativeControlHost _nativeControlHost; private readonly X11NativeControlHost _nativeControlHost;
private PixelPoint? _position; private PixelPoint? _position;
@ -54,8 +57,8 @@ namespace Avalonia.X11
private bool _wasMappedAtLeastOnce = false; private bool _wasMappedAtLeastOnce = false;
private double? _scalingOverride; private double? _scalingOverride;
private bool _disabled; private bool _disabled;
private TransparencyHelper _transparencyHelper; private TransparencyHelper? _transparencyHelper;
private RawEventGrouper _rawEventGrouper; private RawEventGrouper? _rawEventGrouper;
private bool _useRenderWindow = false; private bool _useRenderWindow = false;
private bool _usePositioningFlags = false; private bool _usePositioningFlags = false;
@ -66,7 +69,7 @@ namespace Avalonia.X11
WaitPaint WaitPaint
} }
public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent)
{ {
_platform = platform; _platform = platform;
_popup = popupParent != null; _popup = popupParent != null;
@ -196,7 +199,7 @@ namespace Avalonia.X11
XFlush(_x11.Display); XFlush(_x11.Display);
if(_popup) if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize)); PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent!, MoveResize));
if (platform.Options.UseDBusMenu) if (platform.Options.UseDBusMenu)
_nativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle); _nativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
_nativeControlHost = new X11NativeControlHost(_platform, this); _nativeControlHost = new X11NativeControlHost(_platform, this);
@ -214,7 +217,7 @@ namespace Avalonia.X11
_storageProvider = new CompositeStorageProvider(new[] _storageProvider = new CompositeStorageProvider(new[]
{ {
() => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult<IStorageProvider>(null), () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult<IStorageProvider?>(null),
() => GtkSystemDialog.TryCreate(this) () => GtkSystemDialog.TryCreate(this)
}); });
} }
@ -351,17 +354,17 @@ namespace Avalonia.X11
public double DesktopScaling => RenderScaling; public double DesktopScaling => RenderScaling;
public IEnumerable<object> Surfaces { get; } public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs>? Input { get; set; }
public Action<Rect> Paint { get; set; } public Action<Rect>? Paint { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; } public Action<Size, PlatformResizeReason>? Resized { get; set; }
//TODO //TODO
public Action<double> ScalingChanged { get; set; } public Action<double>? ScalingChanged { get; set; }
public Action Deactivated { get; set; } public Action? Deactivated { get; set; }
public Action Activated { get; set; } public Action? Activated { get; set; }
public Func<WindowCloseReason, bool> Closing { get; set; } public Func<WindowCloseReason, bool>? Closing { get; set; }
public Action<WindowState> WindowStateChanged { get; set; } public Action<WindowState>? WindowStateChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged public Action<WindowTransparencyLevel>? TransparencyLevelChanged
{ {
get => _transparencyHelper?.TransparencyLevelChanged; get => _transparencyHelper?.TransparencyLevelChanged;
set set
@ -371,7 +374,7 @@ namespace Avalonia.X11
} }
} }
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; } public Action<bool>? ExtendClientAreaToDecorationsChanged { get; set; }
public Thickness ExtendedMargins { get; } = new Thickness(); public Thickness ExtendedMargins { get; } = new Thickness();
@ -379,15 +382,18 @@ namespace Avalonia.X11
public bool IsClientAreaExtendedToDecorations { get; } public bool IsClientAreaExtendedToDecorations { get; }
public Action Closed { get; set; } public Action? Closed { get; set; }
public Action<PixelPoint> PositionChanged { get; set; } public Action<PixelPoint>? PositionChanged { get; set; }
public Action LostFocus { get; set; } public Action? LostFocus { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) => public IRenderer CreateRenderer(IRenderRoot root) =>
new CompositingRenderer(root, _platform.Compositor, () => Surfaces); new CompositingRenderer(root, _platform.Compositor, () => Surfaces);
private void OnEvent(ref XEvent ev) private void OnEvent(ref XEvent ev)
{ {
if (_inputRoot is null)
return;
if (ev.type == XEventName.MapNotify) if (ev.type == XEventName.MapNotify)
{ {
_mapped = true; _mapped = true;
@ -434,7 +440,8 @@ namespace Avalonia.X11
2 => RawPointerEventType.MiddleButtonDown, 2 => RawPointerEventType.MiddleButtonDown,
3 => RawPointerEventType.RightButtonDown, 3 => RawPointerEventType.RightButtonDown,
8 => RawPointerEventType.XButton1Down, 8 => RawPointerEventType.XButton1Down,
9 => RawPointerEventType.XButton2Down 9 => RawPointerEventType.XButton2Down,
_ => throw new NotSupportedException("Unexepected RawPointerEventType.")
}, },
ref ev, ev.ButtonEvent.state); ref ev, ev.ButtonEvent.state);
else else
@ -462,7 +469,8 @@ namespace Avalonia.X11
2 => RawPointerEventType.MiddleButtonUp, 2 => RawPointerEventType.MiddleButtonUp,
3 => RawPointerEventType.RightButtonUp, 3 => RawPointerEventType.RightButtonUp,
8 => RawPointerEventType.XButton1Up, 8 => RawPointerEventType.XButton1Up,
9 => RawPointerEventType.XButton2Up 9 => RawPointerEventType.XButton2Up,
_ => throw new NotSupportedException("Unexepected RawPointerEventType.")
}, },
ref ev, ev.ButtonEvent.state); ref ev, ev.ButtonEvent.state);
} }
@ -618,7 +626,7 @@ namespace Avalonia.X11
{ {
// Occurs once the window has been mapped, which is the earliest the extents // Occurs once the window has been mapped, which is the earliest the extents
// can be retrieved, so invoke event to force update of TopLevel.FrameSize. // can be retrieved, so invoke event to force update of TopLevel.FrameSize.
Resized.Invoke(ClientSize, PlatformResizeReason.Unspecified); Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified);
} }
if (atom == _x11.Atoms._NET_WM_STATE) if (atom == _x11.Atoms._NET_WM_STATE)
@ -712,6 +720,8 @@ namespace Avalonia.X11
private void DispatchInput(RawInputEventArgs args) private void DispatchInput(RawInputEventArgs args)
{ {
if (_inputRoot is null)
return;
Input?.Invoke(args); Input?.Invoke(args);
if (!args.Handled && args is RawKeyEventArgsWithText text && !string.IsNullOrEmpty(text.Text)) if (!args.Handled && args is RawKeyEventArgsWithText text && !string.IsNullOrEmpty(text.Text))
Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text.Text)); Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text.Text));
@ -744,11 +754,13 @@ namespace Avalonia.X11
if (args is RawDragEvent drag) if (args is RawDragEvent drag)
drag.Location = drag.Location / RenderScaling; drag.Location = drag.Location / RenderScaling;
_rawEventGrouper.HandleEvent(args); _rawEventGrouper?.HandleEvent(args);
} }
private void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods) private void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods)
{ {
if (_inputRoot is null)
return;
var mev = new RawPointerEventArgs( var mev = new RawPointerEventArgs(
_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot, _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods)); type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
@ -783,7 +795,7 @@ namespace Avalonia.X11
} }
public IInputRoot InputRoot => _inputRoot; public IInputRoot? InputRoot => _inputRoot;
public void SetInputRoot(IInputRoot inputRoot) public void SetInputRoot(IInputRoot inputRoot)
{ {
@ -795,7 +807,7 @@ namespace Avalonia.X11
Cleanup(); Cleanup();
} }
public virtual object TryGetFeature(Type featureType) public virtual object? TryGetFeature(Type featureType)
{ {
if (featureType == typeof(ITopLevelNativeMenuExporter)) if (featureType == typeof(ITopLevelNativeMenuExporter))
{ {
@ -953,7 +965,7 @@ namespace Avalonia.X11
UpdateSizeHints(null); UpdateSizeHints(null);
} }
public void SetCursor(ICursorImpl cursor) public void SetCursor(ICursorImpl? cursor)
{ {
if (cursor == null) if (cursor == null)
XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor); XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor);
@ -996,7 +1008,7 @@ namespace Avalonia.X11
public IMouseDevice MouseDevice => _mouse; public IMouseDevice MouseDevice => _mouse;
public TouchDevice TouchDevice => _touch; public TouchDevice TouchDevice => _touch;
public IPopupImpl CreatePopup() public IPopupImpl? CreatePopup()
=> _platform.Options.OverlayPopups ? null : new X11Window(_platform, this); => _platform.Options.OverlayPopups ? null : new X11Window(_platform, this);
public void Activate() public void Activate()
@ -1082,7 +1094,7 @@ namespace Avalonia.X11
BeginMoveResize(side, e); BeginMoveResize(side, e);
} }
public void SetTitle(string title) public void SetTitle(string? title)
{ {
if (string.IsNullOrEmpty(title)) if (string.IsNullOrEmpty(title))
{ {
@ -1161,9 +1173,9 @@ namespace Avalonia.X11
{ {
} }
public Action GotInputWhenDisabled { get; set; } public Action? GotInputWhenDisabled { get; set; }
public void SetIcon(IWindowIconImpl icon) public void SetIcon(IWindowIconImpl? icon)
{ {
if (icon != null) if (icon != null)
{ {
@ -1218,7 +1230,7 @@ namespace Avalonia.X11
); );
} }
public IPopupPositioner PopupPositioner { get; } public IPopupPositioner? PopupPositioner { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper?.SetTransparencyRequest(transparencyLevel); _transparencyHelper?.SetTransparencyRequest(transparencyLevel);

16
src/Browser/Avalonia.Browser/Interop/GeneralHelpers.cs

@ -1,4 +1,5 @@
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
namespace Avalonia.Browser.Interop; namespace Avalonia.Browser.Interop;
@ -8,15 +9,22 @@ internal static partial class GeneralHelpers
public static partial JSObject[] ItemsArrayAt(JSObject jsObject, string key); public static partial JSObject[] ItemsArrayAt(JSObject jsObject, string key);
public static JSObject[] GetPropertyAsJSObjectArray(this JSObject jsObject, string key) => ItemsArrayAt(jsObject, key); public static JSObject[] GetPropertyAsJSObjectArray(this JSObject jsObject, string key) => ItemsArrayAt(jsObject, key);
[JSImport("GeneralHelpers.itemAt", AvaloniaModule.MainModuleName)]
public static partial JSObject ItemAtInt(JSObject jsObject, int key);
public static JSObject GetArrayItem(this JSObject jsObject, int key) => ItemAtInt(jsObject, key);
[JSImport("GeneralHelpers.itemsArrayAt", AvaloniaModule.MainModuleName)] [JSImport("GeneralHelpers.itemsArrayAt", AvaloniaModule.MainModuleName)]
public static partial string[] ItemsArrayAtAsStrings(JSObject jsObject, string key); public static partial string[] ItemsArrayAtAsStrings(JSObject jsObject, string key);
public static string[] GetPropertyAsStringArray(this JSObject jsObject, string key) => ItemsArrayAtAsStrings(jsObject, key); public static string[] GetPropertyAsStringArray(this JSObject jsObject, string key) => ItemsArrayAtAsStrings(jsObject, key);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)] [JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodString(JSObject jsObject, string name); public static partial string IntCallMethodStr(JSObject jsObject, string name);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodStrStr(JSObject jsObject, string name, string arg1);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)] [JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodStringString(JSObject jsObject, string name, string arg1); public static partial Task<JSObject?> IntCallMethodPromiseObj(JSObject jsObject, string name);
public static string CallMethodString(this JSObject jsObject, string name) => IntCallMethodString(jsObject, name); public static string CallMethodString(this JSObject jsObject, string name) => IntCallMethodStr(jsObject, name);
public static string CallMethodString(this JSObject jsObject, string name, string arg1) => IntCallMethodStringString(jsObject, name, arg1); public static string CallMethodString(this JSObject jsObject, string name, string arg1) => IntCallMethodStrStr(jsObject, name, arg1);
public static Task<JSObject?> CallMethodObjectAsync(this JSObject jsObject, string name) => IntCallMethodPromiseObj(jsObject, name);
} }

6
src/Browser/Avalonia.Browser/Interop/StorageHelper.cs

@ -40,9 +40,9 @@ internal static partial class StorageHelper
[JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)] [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)]
public static partial Task<JSObject> OpenRead(JSObject item); public static partial Task<JSObject> OpenRead(JSObject item);
[JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)] [JSImport("StorageItem.getItemsIterator", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs<JSType.Promise<JSType.Object>>] [return: JSMarshalAs<JSType.Object>]
public static partial Task<JSObject?> GetItems(JSObject item); public static partial JSObject? GetItemsIterator(JSObject item);
[JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)] [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)]
public static partial JSObject[] ItemsArray(JSObject item); public static partial JSObject[] ItemsArray(JSObject item);

47
src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs

@ -258,24 +258,45 @@ internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder
{ {
} }
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync() public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{ {
using var items = await StorageHelper.GetItems(FileHandle); using var itemsIterator = StorageHelper.GetItemsIterator(FileHandle);
if (items is null) if (itemsIterator is null)
{ {
return Array.Empty<IStorageItem>(); yield break;
} }
var itemsArray = StorageHelper.ItemsArray(items); while (true)
{
var nextResult = await itemsIterator.CallMethodObjectAsync("next");
if (nextResult is null)
{
yield break;
}
var isDone = nextResult.GetPropertyAsBoolean("done");
if (isDone)
{
yield break;
}
return itemsArray var valArray = nextResult.GetPropertyAsJSObject("value");
.Select(reference => reference.GetPropertyAsString("kind") switch var storageItem = valArray?.GetArrayItem(1); // 0 - item name, 1 - item instance
if (storageItem is null)
{ {
"directory" => (IStorageItem)new JSStorageFolder(reference), yield break;
"file" => new JSStorageFile(reference), }
_ => null
}) var kind = storageItem.GetPropertyAsString("kind");
.Where(i => i is not null) switch (kind)
.ToArray()!; {
case "directory":
yield return new JSStorageFolder(storageItem);
break;
case "file":
yield return new JSStorageFile(storageItem);
break;
}
}
} }
} }

7
src/Browser/Avalonia.Browser/webapp/modules/avalonia/generalHelpers.ts

@ -1,5 +1,5 @@
export class GeneralHelpers { export class GeneralHelpers {
public static itemsArrayAt(instance: any, key: string): any[] { public static itemsArrayAt(instance: any, key: any): any[] {
const items = instance[key]; const items = instance[key];
if (!items) { if (!items) {
return []; return [];
@ -12,6 +12,11 @@ export class GeneralHelpers {
return retItems; return retItems;
} }
public static itemAt(instance: any, key: any): any {
const item = instance[key];
return item;
}
public static callMethod(instance: any, name: string /*, args */): any { public static callMethod(instance: any, name: string /*, args */): any {
const args = Array.prototype.slice.call(arguments, 2); const args = Array.prototype.slice.call(arguments, 2);
return instance[name].apply(instance, args); return instance[name].apply(instance, args);

10
src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts

@ -89,16 +89,12 @@ export class StorageItem {
} }
} }
public static async getItems(item: StorageItem): Promise<StorageItems> { public static getItemsIterator(item: StorageItem): any | null {
if (item.kind !== "directory" || !item.handle) { if (item.kind !== "directory" || !item.handle) {
return new StorageItems([]); return null;
} }
const items: StorageItem[] = []; return (item.handle as any).entries();
for await (const [, value] of (item.handle as any).entries()) {
items.push(new StorageItem(value));
}
return new StorageItems(items);
} }
private async verityPermissions(mode: "read" | "readwrite"): Promise<void | never> { private async verityPermissions(mode: "read" | "readwrite"): Promise<void | never> {

9
src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs

@ -114,8 +114,9 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
{ {
} }
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync() public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{ {
// TODO: find out if it can be lazily enumerated.
var tcs = new TaskCompletionSource<IReadOnlyList<IStorageItem>>(); var tcs = new TaskCompletionSource<IReadOnlyList<IStorageItem>>();
new NSFileCoordinator().CoordinateRead(Url, new NSFileCoordinator().CoordinateRead(Url,
@ -142,6 +143,10 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
throw new NSErrorException(error); throw new NSErrorException(error);
} }
return await tcs.Task; var items = await tcs.Task;
foreach (var item in items)
{
yield return item;
}
} }
} }

46
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.PropertyStore; using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -29,7 +27,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void GetMetadata_Returns_Supplied_Value() public void GetMetadata_Returns_Supplied_Value()
{ {
var metadata = new AvaloniaPropertyMetadata(); var metadata = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata); var target = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Same(metadata, target.GetMetadata<Class1>()); Assert.Same(metadata, target.GetMetadata<Class1>());
@ -38,26 +36,30 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void GetMetadata_Returns_Supplied_Value_For_Derived_Class() public void GetMetadata_Returns_Supplied_Value_For_Derived_Class()
{ {
var metadata = new AvaloniaPropertyMetadata(); var metadata = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata); var target = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Same(metadata, target.GetMetadata<Class2>()); Assert.Same(metadata, target.GetMetadata<Class2>());
} }
[Fact] [Fact]
public void GetMetadata_Returns_Supplied_Value_For_Unrelated_Class() public void GetMetadata_Returns_TypeSafe_Metadata_For_Unrelated_Class()
{ {
var metadata = new AvaloniaPropertyMetadata(); var metadata = new TestMetadata(BindingMode.OneWayToSource, true, x => { _ = (StyledElement)x; });
var target = new TestProperty<string>("test", typeof(Class3), metadata); var target = new TestProperty<string>("test", typeof(Class3), metadata);
Assert.Same(metadata, target.GetMetadata<Class2>()); var targetMetadata = (TestMetadata)target.GetMetadata<Class2>();
Assert.Equal(metadata.DefaultBindingMode, targetMetadata.DefaultBindingMode);
Assert.Equal(metadata.EnableDataValidation, targetMetadata.EnableDataValidation);
Assert.Equal(null, targetMetadata.OwnerSpecificAction);
} }
[Fact] [Fact]
public void GetMetadata_Returns_Overridden_Value() public void GetMetadata_Returns_Overridden_Value()
{ {
var metadata = new AvaloniaPropertyMetadata(); var metadata = new TestMetadata();
var overridden = new AvaloniaPropertyMetadata(); var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata); var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideMetadata<Class2>(overridden); target.OverrideMetadata<Class2>(overridden);
@ -68,9 +70,9 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void OverrideMetadata_Should_Merge_Values() public void OverrideMetadata_Should_Merge_Values()
{ {
var metadata = new AvaloniaPropertyMetadata(BindingMode.TwoWay); var metadata = new TestMetadata(BindingMode.TwoWay);
var notify = (Action<AvaloniaObject, bool>)((a, b) => { }); var notify = (Action<AvaloniaObject, bool>)((a, b) => { });
var overridden = new AvaloniaPropertyMetadata(); var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata); var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideMetadata<Class2>(overridden); target.OverrideMetadata<Class2>(overridden);
@ -131,15 +133,31 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void PropertyMetadata_BindingMode_Default_Returns_OneWay() public void PropertyMetadata_BindingMode_Default_Returns_OneWay()
{ {
var data = new AvaloniaPropertyMetadata(defaultBindingMode: BindingMode.Default); var data = new TestMetadata(defaultBindingMode: BindingMode.Default);
Assert.Equal(BindingMode.OneWay, data.DefaultBindingMode); Assert.Equal(BindingMode.OneWay, data.DefaultBindingMode);
} }
private class TestMetadata : AvaloniaPropertyMetadata
{
public Action<AvaloniaObject> OwnerSpecificAction { get; }
public TestMetadata(BindingMode defaultBindingMode = BindingMode.Default,
bool? enableDataValidation = null,
Action<AvaloniaObject> ownerSpecificAction = null)
: base(defaultBindingMode, enableDataValidation)
{
OwnerSpecificAction = ownerSpecificAction;
}
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() =>
new TestMetadata(DefaultBindingMode, EnableDataValidation, null);
}
private class TestProperty<TValue> : AvaloniaProperty<TValue> private class TestProperty<TValue> : AvaloniaProperty<TValue>
{ {
public TestProperty(string name, Type ownerType, AvaloniaPropertyMetadata metadata = null) public TestProperty(string name, Type ownerType, TestMetadata metadata = null)
: base(name, ownerType, metadata ?? new AvaloniaPropertyMetadata()) : base(name, ownerType, metadata ?? new TestMetadata())
{ {
} }

Loading…
Cancel
Save