Browse Source

Merge branch 'master' into feature/invariantFamilyNames

pull/10620/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
80d40bba1e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
@ -41,6 +41,7 @@
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
@ -63,4 +64,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
}
}

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

@ -324,9 +324,9 @@ namespace ControlCatalog.Pages
mappedResults.Add("+> " + FullPathOrName(selectedItem));
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)
{
var items = await folder.GetItemsAsync();
contentStr += $"Folder {item.Name}: items {items.Count}{Environment.NewLine}{Environment.NewLine}";
var childrenCount = 0;
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());
}
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return Array.Empty<IStorageItem>();
yield break;
}
List<IStorageItem> files = new List<IStorageItem>();
var contentResolver = Activity.ContentResolver;
if (contentResolver == null)
{
return files;
yield break;
}
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(Uri!, DocumentsContract.GetTreeDocumentId(Uri));
@ -168,12 +166,10 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
continue;
}
files.Add(mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
new AndroidStorageFile(Activity, uri));
yield return mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
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.Core;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia
@ -20,12 +19,20 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
/// <summary>
/// Provides a metadata object for types which have no metadata of their own.
/// </summary>
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> _metadataCache = new Dictionary<Type, AvaloniaPropertyMetadata>();
private bool _hasMetadataOverrides;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// </summary>
@ -57,7 +64,8 @@ namespace Avalonia
Id = s_nextId++;
_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
_defaultMetadata = metadata;
_defaultMetadata = metadata.GenerateTypeSafeMetadata();
_singleMetadata = new(ownerType, metadata);
}
/// <summary>
@ -80,9 +88,6 @@ namespace Avalonia
Id = source.Id;
_defaultMetadata = source._defaultMetadata;
// Properties that have different owner can't use fast path for metadata.
_hasMetadataOverrides = true;
if (metadata != null)
{
_metadata.Add(ownerType, metadata);
@ -453,33 +458,14 @@ namespace Avalonia
}
/// <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>
/// <typeparam name="T">The type.</typeparam>
/// <returns>
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject
{
return GetMetadata(typeof(T));
}
/// <typeparam name="T">The type for which to retrieve metadata.</typeparam>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject => GetMetadata(typeof(T));
/// <summary>
/// Gets the property metadata for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata(Type type)
{
if (!_hasMetadataOverrides)
{
return _defaultMetadata;
}
return GetMetadataWithOverrides(type);
}
/// <inheritdoc cref="GetMetadata{T}"/>
/// <param name="type">The type for which to retrieve metadata.</param>
public AvaloniaPropertyMetadata GetMetadata(Type type) => GetMetadataWithOverrides(type);
/// <summary>
/// Checks whether the <paramref name="value"/> is valid for the property.
@ -578,7 +564,7 @@ namespace Avalonia
_metadata.Add(type, metadata);
_metadataCache.Clear();
_hasMetadataOverrides = true;
_singleMetadata = null;
}
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
@ -595,7 +581,12 @@ namespace Avalonia
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)
{
@ -609,13 +600,11 @@ namespace Avalonia
currentType = currentType.BaseType;
}
_metadataCache[type] = _defaultMetadata;
return _defaultMetadata;
return _metadataCache[type] = _defaultMetadata;
}
bool IPropertyInfo.CanGet => true;
bool IPropertyInfo.CanSet => true;
bool IPropertyInfo.CanSet => !IsReadOnly;
object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
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>
/// Base class for avalonia property metadata.
/// </summary>
public class AvaloniaPropertyMetadata
public abstract class AvaloniaPropertyMetadata
{
private BindingMode _defaultBindingMode;
@ -61,5 +61,13 @@ namespace Avalonia
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;
}
}
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);
}
public Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
var items = DirectoryInfo.GetDirectories()
var items = DirectoryInfo.EnumerateDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
.Concat(DirectoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
.ToArray();
.Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)));
return Task.FromResult<IReadOnlyList<IStorageItem>>(items);
foreach (var item in items)
{
yield return item;
}
}
public virtual Task<string?> SaveBookmarkAsync()

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

@ -16,5 +16,5 @@ public interface IStorageFolder : IStorageItem
/// <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.
/// </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="metadata">The property metadata.</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>
public StyledProperty(
string name,
@ -41,7 +44,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the value validation callback for the property.
/// A method which returns "false" for values that are never valid for this property.
/// </summary>
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;
for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++)
{
var cellContent = clipboardRowContent[cellIndex];
text.Append(cellContent.Content);
var cellContent = clipboardRowContent[cellIndex].Content?.ToString();
cellContent = cellContent?.Replace("\"", "\"\"");
text.Append($"\"{cellContent}\"");
if (cellIndex < numberOfItem - 1)
{
text.Append('\t');

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

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

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

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

9
src/Avalonia.Controls/ToolTipService.cs

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

76
src/Avalonia.X11/X11Window.cs

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

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

@ -1,4 +1,5 @@
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
namespace Avalonia.Browser.Interop;
@ -8,15 +9,22 @@ internal static partial class GeneralHelpers
public static partial JSObject[] ItemsArrayAt(JSObject jsObject, string 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)]
public static partial string[] ItemsArrayAtAsStrings(JSObject jsObject, string key);
public static string[] GetPropertyAsStringArray(this JSObject jsObject, string key) => ItemsArrayAtAsStrings(jsObject, key);
[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)]
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, string arg1) => IntCallMethodStringString(jsObject, name, arg1);
public static string CallMethodString(this JSObject jsObject, string name) => IntCallMethodStr(jsObject, name);
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)]
public static partial Task<JSObject> OpenRead(JSObject item);
[JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs<JSType.Promise<JSType.Object>>]
public static partial Task<JSObject?> GetItems(JSObject item);
[JSImport("StorageItem.getItemsIterator", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs<JSType.Object>]
public static partial JSObject? GetItemsIterator(JSObject item);
[JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)]
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);
if (items is null)
using var itemsIterator = StorageHelper.GetItemsIterator(FileHandle);
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
.Select(reference => reference.GetPropertyAsString("kind") switch
var valArray = nextResult.GetPropertyAsJSObject("value");
var storageItem = valArray?.GetArrayItem(1); // 0 - item name, 1 - item instance
if (storageItem is null)
{
"directory" => (IStorageItem)new JSStorageFolder(reference),
"file" => new JSStorageFile(reference),
_ => null
})
.Where(i => i is not null)
.ToArray()!;
yield break;
}
var kind = storageItem.GetPropertyAsString("kind");
switch (kind)
{
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 {
public static itemsArrayAt(instance: any, key: string): any[] {
public static itemsArrayAt(instance: any, key: any): any[] {
const items = instance[key];
if (!items) {
return [];
@ -12,6 +12,11 @@ export class GeneralHelpers {
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 {
const args = Array.prototype.slice.call(arguments, 2);
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) {
return new StorageItems([]);
return null;
}
const items: StorageItem[] = [];
for await (const [, value] of (item.handle as any).entries()) {
items.push(new StorageItem(value));
}
return new StorageItems(items);
return (item.handle as any).entries();
}
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>>();
new NSFileCoordinator().CoordinateRead(Url,
@ -142,6 +143,10 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
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 Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Base.UnitTests
@ -29,7 +27,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void GetMetadata_Returns_Supplied_Value()
{
var metadata = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Same(metadata, target.GetMetadata<Class1>());
@ -38,26 +36,30 @@ namespace Avalonia.Base.UnitTests
[Fact]
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);
Assert.Same(metadata, target.GetMetadata<Class2>());
}
[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);
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]
public void GetMetadata_Returns_Overridden_Value()
{
var metadata = new AvaloniaPropertyMetadata();
var overridden = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata();
var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideMetadata<Class2>(overridden);
@ -68,9 +70,9 @@ namespace Avalonia.Base.UnitTests
[Fact]
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 overridden = new AvaloniaPropertyMetadata();
var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);
target.OverrideMetadata<Class2>(overridden);
@ -131,15 +133,31 @@ namespace Avalonia.Base.UnitTests
[Fact]
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);
}
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>
{
public TestProperty(string name, Type ownerType, AvaloniaPropertyMetadata metadata = null)
: base(name, ownerType, metadata ?? new AvaloniaPropertyMetadata())
public TestProperty(string name, Type ownerType, TestMetadata metadata = null)
: base(name, ownerType, metadata ?? new TestMetadata())
{
}

Loading…
Cancel
Save