Browse Source

Merge remote-tracking branch 'origin/master' into feature/wpf-like-dispatcher

# Conflicts:
#	Avalonia.Desktop.slnf
pull/10691/head
Nikita Tsukanov 3 years ago
parent
commit
0b20b8e90f
  1. 1
      .gitignore
  2. 5
      .ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
  3. 5
      .ncrunch/Generators.Sandbox.v3.ncrunchproject
  4. 1
      Avalonia.Desktop.slnf
  5. 2
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  6. 2
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  7. 2
      native/Avalonia.Native/src/OSX/WindowImpl.h
  8. 12
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  9. 4
      packages/Avalonia/Avalonia.csproj
  10. 1
      packages/Avalonia/Avalonia.props
  11. 4
      readme.md
  12. 2
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  13. 2
      samples/ControlCatalog/MainView.xaml
  14. 60
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  15. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  16. 2
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  17. 2
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  18. 4
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  19. 2
      samples/ControlCatalog/Pages/CursorPage.xaml
  20. 8
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  21. 16
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  22. 8
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  23. 2
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  24. 6
      samples/ControlCatalog/Pages/MenuPage.xaml
  25. 8
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  26. 6
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  27. 2
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml
  28. 4
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml
  29. 4
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  30. 2
      samples/ControlCatalog/Pages/TabStripPage.xaml
  31. 2
      samples/ControlCatalog/Pages/ThemePage.axaml.cs
  32. 2
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  33. 2
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  34. 11
      samples/IntegrationTestApp/MainWindow.axaml
  35. 6
      samples/IntegrationTestApp/MainWindow.axaml.cs
  36. 24
      samples/IntegrationTestApp/ShowWindowTest.axaml
  37. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  38. 2
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  39. 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  40. 247
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  41. 78
      src/Avalonia.Base/AvaloniaProperty.cs
  42. 10
      src/Avalonia.Base/AvaloniaPropertyMetadata.cs
  43. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  44. 9
      src/Avalonia.Base/CornerRadius.cs
  45. 2
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  46. 2
      src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
  47. 11
      src/Avalonia.Base/Media/BoxShadow.cs
  48. 4
      src/Avalonia.Base/Media/BoxShadows.cs
  49. 6
      src/Avalonia.Base/Media/DrawingGroup.cs
  50. 5
      src/Avalonia.Base/Media/FontFamily.cs
  51. 10
      src/Avalonia.Base/Media/FontManager.cs
  52. 15
      src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
  53. 2
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  54. 5
      src/Avalonia.Base/Media/FormattedText.cs
  55. 2
      src/Avalonia.Base/Media/ImageDrawing.cs
  56. 2
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  57. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  58. 2
      src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
  59. 19
      src/Avalonia.Base/PixelRect.cs
  60. 1
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  61. 18
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  62. 47
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  63. 17
      src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
  64. 16
      src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
  65. 13
      src/Avalonia.Base/Platform/Storage/IStorageItem.cs
  66. 16
      src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
  67. 8
      src/Avalonia.Base/Point.cs
  68. 2
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  69. 34
      src/Avalonia.Base/Rect.cs
  70. 12
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  71. 55
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  72. 2
      src/Avalonia.Base/Rendering/DirtyRects.cs
  73. 2
      src/Avalonia.Base/Rendering/DisplayDirtyRect.cs
  74. 4
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  75. 1
      src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs
  76. 1
      src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs
  77. 1
      src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs
  78. 1
      src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs
  79. 11
      src/Avalonia.Base/Size.cs
  80. 21
      src/Avalonia.Base/StyledProperty.cs
  81. 2
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  82. 2
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  83. 10
      src/Avalonia.Base/Thickness.cs
  84. 8
      src/Avalonia.Base/Vector.cs
  85. 63
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  86. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  87. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  88. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  89. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  90. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  91. 8
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  92. 5
      src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
  93. 4
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
  94. 12
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  95. 3
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  96. 29
      src/Avalonia.Controls/Automation/Peers/ScrollBarAutomationPeer.cs
  97. 2
      src/Avalonia.Controls/BorderVisual.cs
  98. 20
      src/Avalonia.Controls/ContainerClearingEventArgs.cs
  99. 32
      src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs
  100. 26
      src/Avalonia.Controls/ContainerPreparedEventArgs.cs

1
.gitignore

@ -102,6 +102,7 @@ csx
AppPackages/
# NCrunch
.NCrunch_*/
_NCrunch_*/
*.ncrunchsolution.user
nCrunchTemp_*

5
.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Generators.Sandbox.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

1
Avalonia.Desktop.slnf

@ -39,6 +39,7 @@
"src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
"src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",

2
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -394,7 +394,7 @@
- (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{
return true;
return _parent->CanZoom();
}
-(void)windowDidResignKey:(NSNotification *)notification

2
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -107,6 +107,8 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT GetInputMethod(IAvnTextInputMethod **retOut) override;
virtual bool CanZoom() { return false; }
protected:
virtual NSWindowStyleMask CalculateStyleMask() = 0;
virtual void UpdateStyle();

2
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -97,6 +97,8 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
bool CanZoom() override { return _isEnabled && _canResize; }
protected:
virtual NSWindowStyleMask CalculateStyleMask() override;
void UpdateStyle () override;

12
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -281,10 +281,13 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
case SystemDecorationsFull:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
if (!_isClientAreaExtended) {
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
}
if (currentWindowState == Maximized) {
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
@ -611,7 +614,8 @@ void WindowImpl::UpdateStyle() {
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
bool hasTrafficLights = (_decorations == SystemDecorationsFull) &&
(_isClientAreaExtended ? wantsChrome : true);
NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
@ -622,5 +626,5 @@ void WindowImpl::UpdateStyle() {
[miniaturizeButton setHidden:!hasTrafficLights];
[miniaturizeButton setEnabled:_isEnabled];
[zoomButton setHidden:!hasTrafficLights];
[zoomButton setEnabled:_isEnabled && _canResize];
[zoomButton setEnabled:CanZoom()];
}

4
packages/Avalonia/Avalonia.csproj

@ -43,6 +43,10 @@
<Pack>true</Pack>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="..\..\src\tools\Avalonia.Generators\Avalonia.Generators.props">
<Pack>true</Pack>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="*.targets">
<Pack>true</Pack>
<PackagePath>build\;buildTransitive\</PackagePath>

1
packages/Avalonia/Avalonia.props

@ -6,6 +6,7 @@
<AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
<Import Project="$(MSBuildThisFileDirectory)\Avalonia.Generators.props"/>
<!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
<ItemGroup>

4
readme.md

@ -5,6 +5,10 @@
<br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)
# ⚠️ **v11 Update - Pausing community contributions**
for more information see [this](https://github.com/AvaloniaUI/Avalonia/discussions/10599) discussion.
## 📖 About
Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM.

2
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@ -2,4 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE " />
</manifest>

2
samples/ControlCatalog/MainView.xaml

@ -241,7 +241,7 @@
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
ItemsSource="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" />
</StackPanel>
</Flyout>

60
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace ControlCatalog.Pages
{
public partial class ClipboardPage : UserControl
{
private INotificationManager? _notificationManager;
private INotificationManager NotificationManager => _notificationManager
??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
public ClipboardPage()
{
InitializeComponent();
@ -31,7 +38,7 @@ namespace ControlCatalog.Pages
private async void PasteText(object? sender, RoutedEventArgs args)
{
if(Application.Current!.Clipboard is { } clipboard)
if (Application.Current!.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetTextAsync();
}
@ -59,15 +66,45 @@ namespace ControlCatalog.Pages
{
if (Application.Current!.Clipboard is { } clipboard)
{
var files = (ClipboardContent.Text ?? String.Empty)
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (files.Length == 0)
var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider;
var filesPath = (ClipboardContent.Text ?? string.Empty)
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (filesPath.Length == 0)
{
return;
}
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, files);
await clipboard.SetDataObjectAsync(dataObject);
List<string> invalidFile = new(filesPath.Length);
List<IStorageFile> files = new(filesPath.Length);
for (int i = 0; i < filesPath.Length; i++)
{
var file = await storageProvider.TryGetFileFromPathAsync(filesPath[i]);
if (file is null)
{
invalidFile.Add(filesPath[i]);
}
else
{
files.Add(file);
}
}
if (invalidFile.Count > 0)
{
NotificationManager.Show(new Notification("Warning", "There is one o more invalid path.", NotificationType.Warning));
}
if (files.Count > 0)
{
var dataObject = new DataObject();
dataObject.Set(DataFormats.Files, files);
await clipboard.SetDataObjectAsync(dataObject);
NotificationManager.Show(new Notification("Success", "Copy completated.", NotificationType.Success));
}
else
{
NotificationManager.Show(new Notification("Warning", "Any files to copy in Clipboard.", NotificationType.Warning));
}
}
}
@ -75,8 +112,9 @@ namespace ControlCatalog.Pages
{
if (Application.Current!.Clipboard is { } clipboard)
{
var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
ClipboardContent.Text = files != null ? string.Join(Environment.NewLine, files.Select(f => f.TryGetLocalPath() ?? f.Name)) : string.Empty;
}
}
@ -95,7 +133,7 @@ namespace ControlCatalog.Pages
{
await clipboard.ClearAsync();
}
}
}
}

2
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.SystemFonts;
fontComboBox.ItemsSource = FontManager.Current.SystemFonts;
fontComboBox.SelectedIndex = 0;
}
}

2
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -32,7 +32,7 @@ public partial class CompositionPage : UserControl
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.Get<ItemsControl>("Items").Items = CreateColorItems();
this.Get<ItemsControl>("Items").ItemsSource = CreateColorItems();
}

2
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -61,7 +61,7 @@
<Border.Styles>
<Style Selector="MenuFlyoutPresenter MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>

4
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -51,13 +51,13 @@
<Border.Styles>
<Style Selector="ContextMenu MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Border.Styles>
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}" />
<ContextMenu ItemsSource="{Binding MenuItems}" />
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>

2
samples/ControlCatalog/Pages/CursorPage.xaml

@ -8,7 +8,7 @@
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox Grid.Row="1" ItemsSource="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}" x:DataType="viewModels:StandardCursorModel"/>

8
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -133,17 +133,17 @@ namespace ControlCatalog.Pages
// 0,0 refers to the top-left of the control now. It is not prime time to draw gui stuff because it'll be under the world
var translateModifier = context.PushPreTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
var translateModifier = context.PushTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
// now 0,0 refers to the ViewportCenter(X,Y).
var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation);
var rotationModifier = context.PushPreTransform(rotationMatrix);
var rotationModifier = context.PushTransform(rotationMatrix);
// everything is rotated but not scaled
var scaleModifier = context.PushPreTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
var scaleModifier = context.PushTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
var mapPositionModifier = context.PushPreTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
var mapPositionModifier = context.PushTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
// now everything is rotated and scaled, and at the right position, now we're drawing strictly in world coordinates

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

@ -106,7 +106,7 @@ namespace ControlCatalog.Pages
Directory = initialDirectory,
InitialFileName = initialFileName
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("OpenMultipleFiles").Click += async delegate
@ -118,7 +118,7 @@ namespace ControlCatalog.Pages
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("SaveFile").Click += async delegate
@ -132,7 +132,7 @@ namespace ControlCatalog.Pages
DefaultExtension = filters?.Any() == true ? "txt" : null,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = result != null;
};
this.Get<Button>("SelectFolder").Click += async delegate
@ -149,7 +149,7 @@ namespace ControlCatalog.Pages
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
};
@ -164,7 +164,7 @@ namespace ControlCatalog.Pages
{
AllowDirectorySelection = true
});
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("DecoratedWindow").Click += delegate
@ -324,15 +324,15 @@ 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));
}
}
}
}
results.Items = mappedResults;
results.ItemsSource = mappedResults;
resultsVisible.IsVisible = mappedResults.Any();
}
}

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}";
}
}

2
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -30,7 +30,7 @@
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button>
</StackPanel>
<ListBox Items="{Binding Items}"
<ListBox ItemsSource="{Binding Items}"
Selection="{Binding Selection}"
DisplayMemberBinding="{Binding (viewModels:ItemModel).ID, StringFormat='{}Item {0:N0}'}"
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"

6
samples/ControlCatalog/Pages/MenuPage.xaml

@ -45,11 +45,11 @@
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding MenuItems}">
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
@ -68,7 +68,7 @@
<Separator/>
<MenuItem Header="Execu_te Script..." />
<Separator/>
<MenuItem Header="_Recent" Items="{Binding RecentItems}">
<MenuItem Header="_Recent" ItemsSource="{Binding RecentItems}">
<MenuItem.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>

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

@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
Items =
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}

6
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -27,7 +27,7 @@
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="8" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<ComboBox Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -41,11 +41,11 @@
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<ComboBox Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}"
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Cultures}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>

2
samples/ControlCatalog/Pages/RefreshContainerPage.axaml

@ -21,7 +21,7 @@
Margin="5">
<ListBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Items="{Binding Items}"/>
ItemsSource="{Binding Items}"/>
</RefreshContainer>
</DockPanel>
</UserControl>

4
samples/ControlCatalog/Pages/ScrollSnapPage.xaml

@ -16,14 +16,14 @@
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox Items="{Binding AvailableSnapPointsType}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox Items="{Binding AvailableSnapPointsAlignment}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>

4
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -13,12 +13,12 @@
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Horizontal Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Vertical Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
</StackPanel>
</StackPanel>

2
samples/ControlCatalog/Pages/TabStripPage.xaml

@ -18,7 +18,7 @@
<Separator Margin="0 16"/>
<TextBlock Classes="h1">Dynamically generated</TextBlock>
<TabStrip Items="{Binding Tabs}">
<TabStrip ItemsSource="{Binding Tabs}">
<TabStrip.Styles>
<Style Selector="TabStripItem" x:DataType="viewModels:TabControlPageViewModelItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>

2
samples/ControlCatalog/Pages/ThemePage.axaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
Selector.Items = new[]
Selector.ItemsSource = new[]
{
ThemeVariant.Default,
ThemeVariant.Dark,

2
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@ -53,7 +53,7 @@
<StackPanel Margin="5" Spacing="5" Grid.IsSharedSizeScope="True">
<HeaderedContentControl Header="Select a transition">
<ComboBox Items="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
<ComboBox ItemsSource="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
</HeaderedContentControl>
<HeaderedContentControl Header="Duration">
<NumericUpDown Value="{Binding Duration}" Increment="250" Minimum="100" />

2
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -11,7 +11,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>

11
samples/IntegrationTestApp/MainWindow.axaml

@ -109,7 +109,7 @@
<StackPanel DockPanel.Dock="Bottom">
<Button Name="ListBoxSelectionClear">Clear Selection</Button>
</StackPanel>
<ListBox Name="BasicListBox" Items="{Binding ListBoxItems}" SelectionMode="Multiple"/>
<ListBox Name="BasicListBox" ItemsSource="{Binding ListBoxItems}" SelectionMode="Multiple"/>
</DockPanel>
</TabItem>
@ -151,6 +151,12 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowSystemDecorations" SelectedIndex="2">
<ComboBoxItem Name="ShowWindowSystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
@ -167,6 +173,9 @@
<TabItem Header="SliderTab">
<Slider VerticalAlignment="Top" Name="Slider" Value="30"/>
</TabItem>
<TabItem Header="ScrollBarTab">
<ScrollBar Name="MyScrollBar" Orientation="Horizontal" AllowAutoHide="False" Width="200" Height="30" Value="20"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>

6
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -68,6 +68,8 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
var systemDecorations = this.GetControl<ComboBox>("ShowWindowSystemDecorations");
var extendClientArea = this.GetControl<CheckBox>("ShowWindowExtendClientAreaToDecorationsHint");
var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
@ -95,6 +97,8 @@ namespace IntegrationTestApp
}
sizeTextBox.Text = string.Empty;
window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
window.WindowState = (WindowState)stateComboBox.SelectedIndex;
switch (modeComboBox.SelectedIndex)
@ -158,7 +162,7 @@ namespace IntegrationTestApp
var popup = new Popup
{
WindowManagerAddShadowHint = false,
PlacementMode = PlacementMode.AnchorAndGravity,
Placement = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.Top,
PlacementGravity = PopupGravity.Bottom,
Width= 200,

24
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -6,7 +6,7 @@
x:DataType="Window"
Title="Show Window Test">
<integrationTestApp:MeasureBorder Name="MyBorder">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}" />
@ -35,13 +35,25 @@
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" />
<Label Grid.Column="0" Grid.Row="8">SystemDecorations</Label>
<ComboBox Name="CurrentSystemDecorations" Grid.Column="1" Grid.Row="8" SelectedIndex="{Binding SystemDecorations}">
<ComboBoxItem Name="SystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="CurrentExtendClientAreaToDecorationsHint" Grid.ColumnSpan="2" Grid.Row="9"
IsChecked="{Binding ExtendClientAreaToDecorationsHint}">
ExtendClientAreaToDecorationsHint
</CheckBox>
<Label Grid.Column="0" Grid.Row="10">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="10" IsReadOnly="True" />
<Label Grid.Row="9" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Label Grid.Row="11" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="11" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Button Name="HideButton" Grid.Row="10" Command="{Binding $parent[Window].Hide}">Hide</Button>
<Button Name="HideButton" Grid.Row="12" Command="{Binding $parent[Window].Hide}">Hide</Button>
</Grid>
</integrationTestApp:MeasureBorder>

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -29,7 +29,7 @@ namespace RenderDemo.Pages
public override void Render(DrawingContext context)
{
using (var ctx = _bitmap.CreateDrawingContext())
using (ctx.PushPostTransform(Matrix.CreateTranslation(-100, -100)
using (ctx.PushTransform(Matrix.CreateTranslation(-100, -100)
* Matrix.CreateRotation(_st.Elapsed.TotalSeconds)
* Matrix.CreateTranslation(100, 100)))
{

2
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@ -57,7 +57,7 @@ namespace ControlSamples
{
if (_splitView is not null && _splitView.DisplayMode == SplitViewDisplayMode.Overlay)
{
_splitView.SetValue(SplitView.IsPaneOpenProperty, false, Avalonia.Data.BindingPriority.Animation);
_splitView.SetCurrentValue(SplitView.IsPaneOpenProperty, false);
}
}
}

1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.DocumentFile" Version="1.0.1.16" />
<PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
</ItemGroup>
<ItemGroup>

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

@ -8,7 +8,10 @@ using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Webkit;
using AndroidX.DocumentFile.Provider;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using Java.Lang;
@ -22,20 +25,25 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
{
private Activity? _activity;
private readonly bool _needsExternalFilesPermission;
private readonly AndroidStorageFolder? _parent;
private readonly AndroidUri? _permissionRoot;
protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExternalFilesPermission)
protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExternalFilesPermission, AndroidStorageFolder? parent = null, AndroidUri? permissionRoot = null)
{
_activity = activity;
_needsExternalFilesPermission = needsExternalFilesPermission;
_parent = parent;
_permissionRoot = permissionRoot ?? parent?.Uri ?? Uri;
Uri = uri;
}
internal AndroidUri Uri { get; }
internal AndroidUri Uri { get; set; }
protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem));
public virtual string Name => GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName)
?? Uri.PathSegments?.LastOrDefault() ?? string.Empty;
?? Document?.Name
?? Uri.PathSegments?.LastOrDefault()?.Split("/", StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty;
public Uri Path => new(Uri.ToString()!);
@ -92,6 +100,23 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return null;
}
if(_parent != null)
{
return _parent;
}
var document = Document;
if (document == null)
{
return null;
}
if(document.ParentFile != null)
{
return new AndroidStorageFolder(Activity, document.ParentFile.Uri, true);
}
using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused.
@ -118,12 +143,88 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
{
_activity = null;
}
internal DocumentFile? Document
{
get
{
if (this is AndroidStorageFile)
{
return DocumentFile.FromSingleUri(Activity, Uri);
}
else
{
return DocumentFile.FromTreeUri(Activity, Uri);
}
}
}
internal AndroidUri? PermissionRoot => _permissionRoot;
public abstract Task DeleteAsync();
public abstract Task<IStorageItem?> MoveAsync(IStorageFolder destination);
}
internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{
public AndroidStorageFolder(Activity activity, AndroidUri uri, bool needsExternalFilesPermission) : base(activity, uri, needsExternalFilesPermission)
public AndroidStorageFolder(Activity activity, AndroidUri uri, bool needsExternalFilesPermission, AndroidStorageFolder? parent = null, AndroidUri? permissionRoot = null) : base(activity, uri, needsExternalFilesPermission, parent, permissionRoot)
{
}
public async Task<IStorageFile?> CreateFileAsync(string name)
{
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
var newFile = Document.CreateFile(mimeType, name);
if(newFile == null)
{
return null;
}
return new AndroidStorageFile(Activity, newFile.Uri, this);
}
public async Task<IStorageFolder?> CreateFolderAsync(string name)
{
var newFolder = Document?.CreateDirectory(name);
if (newFolder == null)
{
return null;
}
return new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot);
}
public override async Task DeleteAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return;
}
if (Activity != null)
{
await DeleteContents(this);
}
async Task DeleteContents(AndroidStorageFolder storageFolder)
{
await foreach (var file in storageFolder.GetItemsAsync())
{
if(file is AndroidStorageFolder folder)
{
await DeleteContents(folder);
}
else if(file is AndroidStorageFile storageFile)
{
await storageFile.DeleteAsync();
}
}
DocumentFile.FromTreeUri(Activity, storageFolder.Uri)?.Delete();
}
}
public override Task<StorageItemProperties> GetBasicPropertiesAsync()
@ -131,22 +232,22 @@ 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));
var root = PermissionRoot ?? Uri;
var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId);
var projection = new[]
{
@ -162,19 +263,55 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{
var mime = cursor.GetString(1);
var id = cursor.GetString(0);
var uri = DocumentsContract.BuildDocumentUriUsingTree(Uri!, id);
bool isDirectory = mime == DocumentsContract.Document.MimeTypeDir;
var uri = DocumentsContract.BuildDocumentUriUsingTree(root, id);
if (uri == null)
{
continue;
}
files.Add(mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
new AndroidStorageFile(Activity, uri));
yield return isDirectory ? new AndroidStorageFolder(Activity, uri, false, this, root) :
new AndroidStorageFile(Activity, uri, this, root);
}
}
}
public override async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (Activity != null)
{
return await MoveRecursively(this, (AndroidStorageFolder)destination);
}
return files;
}
return null;
async Task<AndroidStorageFolder?> MoveRecursively(AndroidStorageFolder storageFolder, AndroidStorageFolder destination)
{
destination = await destination.CreateFolderAsync(storageFolder.Name) as AndroidStorageFolder;
if (destination == null)
{
return null;
}
await foreach (var file in storageFolder.GetItemsAsync())
{
if (file is AndroidStorageFolder folder)
{
await MoveRecursively(folder, destination);
}
else if (file is AndroidStorageFile)
{
await file.MoveAsync(destination);
}
}
await storageFolder.DeleteAsync();
return destination;
}
}
}
internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
@ -186,14 +323,14 @@ internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
}
public override string Name { get; }
}
}
internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile
{
public AndroidStorageFile(Activity activity, AndroidUri uri) : base(activity, uri, false)
public AndroidStorageFile(Activity activity, AndroidUri uri, AndroidStorageFolder? parent = null, AndroidUri? permissionRoot = null) : base(activity, uri, false, parent, permissionRoot)
{
}
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Activity, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));
@ -317,4 +454,76 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
return Task.FromResult(new StorageItemProperties(size, itemDate, dateModified));
}
public override async Task DeleteAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return;
}
if (Activity != null)
{
DocumentsContract.DeleteDocument(Activity.ContentResolver!, Uri);
}
}
public override async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (!await EnsureExternalFilesPermission(false))
{
return null;
}
if (Activity != null && destination is AndroidStorageFolder storageFolder)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
{
try
{
var uri = DocumentsContract.MoveDocument(Activity.ContentResolver!, Uri, ((await GetParentAsync()) as AndroidStorageFolder)!.Uri, storageFolder.Document!.Uri);
return new AndroidStorageFile(Activity, uri, storageFolder);
}
catch (Exception ex)
{
// There are many reason why DocumentContract will fail to move a file. We fallback to copying.
return await MoveFileByCopy();
}
}
else
{
return await MoveFileByCopy();
}
}
async Task<AndroidStorageFile?> MoveFileByCopy()
{
var newFile = await storageFolder.CreateFileAsync(Name) as AndroidStorageFile;
try
{
if (newFile != null)
{
using var input = await OpenReadAsync();
using var output = await newFile.OpenWriteAsync();
await input.CopyToAsync(output);
await DeleteAsync();
return new AndroidStorageFile(Activity, newFile.Uri, storageFolder);
}
}
catch
{
newFile?.DeleteAsync();
}
return null;
}
return null;
}
}

78
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);
@ -257,7 +262,18 @@ namespace Avalonia
return result;
}
/// <inheritdoc cref="Register{TOwner, TValue}" />
/// <summary>
/// Registers an attached <see cref="AvaloniaProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <param name="enableDataValidation">if is set to true enable data validation.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
@ -442,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.
@ -567,7 +564,7 @@ namespace Avalonia
_metadata.Add(type, metadata);
_metadataCache.Clear();
_hasMetadataOverrides = true;
_singleMetadata = null;
}
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
@ -584,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)
{
@ -598,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/AvaloniaPropertyRegistry.cs

@ -364,7 +364,7 @@ namespace Avalonia
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, Action{AvaloniaObject, bool})"/>
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, bool)"/>
/// method.
/// </remarks>
public void Register(Type type, AvaloniaProperty property)

9
src/Avalonia.Base/CornerRadius.cs

@ -60,15 +60,6 @@ namespace Avalonia
/// </summary>
public double BottomLeft { get; }
/// <summary>
/// Gets a value indicating whether the instance has default values (all corner radii are set to 0).
/// </summary>
public bool IsDefault => TopLeft == 0 && TopRight == 0 && BottomLeft == 0 && BottomRight == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Gets a value indicating whether all corner radii are equal.
/// </summary>

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);
}
}

2
src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs

@ -180,7 +180,7 @@ namespace Avalonia.Input.GestureRecognizers
internal Velocity GetVelocity()
{
var estimate = GetVelocityEstimate();
if (estimate == null || estimate.PixelsPerSecond.IsDefault)
if (estimate == null || estimate.PixelsPerSecond == default(Vector))
{
return new Velocity(Vector.Zero);
}

11
src/Avalonia.Base/Media/BoxShadow.cs

@ -45,15 +45,6 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Gets a value indicating whether the instance has default values.
/// </summary>
public bool IsDefault => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
private readonly static char[] s_Separator = new char[] { ' ', '\t' };
struct ArrayReader
@ -89,7 +80,7 @@ namespace Avalonia.Media
{
var sb = StringBuilderCache.Acquire();
if (IsDefault)
if (this == default)
{
return "none";
}

4
src/Avalonia.Base/Media/BoxShadows.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media
{
_first = shadow;
_list = null;
Count = _first.IsDefault ? 0 : 1;
Count = _first == default ? 0 : 1;
}
public BoxShadows(BoxShadow first, BoxShadow[] rest)
@ -120,7 +120,7 @@ namespace Avalonia.Media
get
{
foreach(var boxShadow in this)
if (!boxShadow.IsDefault && boxShadow.IsInset)
if (boxShadow != default && boxShadow.IsInset)
return true;
return false;
}

6
src/Avalonia.Base/Media/DrawingGroup.cs

@ -73,7 +73,7 @@ namespace Avalonia.Media
{
var bounds = GetBounds();
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity, bounds))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default)
@ -117,10 +117,10 @@ namespace Avalonia.Media
// root DrawingGroup, and be the same value as the root _currentDrawingGroup.
//
// Either way, _rootDrawing always references the root drawing.
protected Drawing? _rootDrawing;
private Drawing? _rootDrawing;
// Current DrawingGroup that new children are added to
protected DrawingGroup? _currentDrawingGroup;
private DrawingGroup? _currentDrawingGroup;
// Previous values of _currentDrawingGroup
private Stack<DrawingGroup?>? _previousDrawingGroupStack;

5
src/Avalonia.Base/Media/FontFamily.cs

@ -79,11 +79,6 @@ namespace Avalonia.Media
/// <remarks>Key is only used for custom fonts.</remarks>
public FontFamilyKey? Key { get; }
/// <summary>
/// Returns <c>True</c> if this instance is the system's default.
/// </summary>
public bool IsDefault => Name.Equals(DefaultFontFamilyName);
/// <summary>
/// Implicit conversion of string to FontFamily
/// </summary>

10
src/Avalonia.Base/Media/FontManager.cs

@ -107,7 +107,7 @@ namespace Avalonia.Media
source = new Uri(key.BaseUri, source);
}
if (!_fontCollections.TryGetValue(source, out var fontCollection))
if (!_fontCollections.TryGetValue(source, out var fontCollection) && (source.IsAbsoluteResm() || source.IsAvares()))
{
var embeddedFonts = new EmbeddedFontCollection(source, source);
@ -119,7 +119,9 @@ namespace Avalonia.Media
}
}
if (fontCollection != null && fontCollection.TryGetGlyphTypeface(fontFamily.FamilyNames.PrimaryFamilyName,
var familyName = fontFamily.FamilyNames.PrimaryFamilyName.ToUpperInvariant();
if (fontCollection != null && fontCollection.TryGetGlyphTypeface(familyName,
typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
{
return true;
@ -133,13 +135,13 @@ namespace Avalonia.Media
foreach (var familyName in fontFamily.FamilyNames)
{
if (SystemFonts.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
if (SystemFonts.TryGetGlyphTypeface(familyName.ToUpperInvariant(), typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
{
return true;
}
}
return SystemFonts.TryGetGlyphTypeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface);
return TryGetGlyphTypeface(new Typeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch), out glyphTypeface);
}
/// <summary>

15
src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs

@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Platform;
namespace Avalonia.Media.Fonts
@ -43,11 +42,13 @@ namespace Avalonia.Media.Fonts
if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface))
{
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
var familyName = glyphTypeface.FamilyName.ToUpperInvariant();
if (!_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
{
glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>();
if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces))
if (_glyphTypefaceCache.TryAdd(familyName, glyphTypefaces))
{
_fontFamilies.Add(new FontFamily(_key, glyphTypeface.FamilyName));
}
@ -86,6 +87,8 @@ namespace Avalonia.Media.Fonts
public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
familyName = familyName.ToUpperInvariant();
var key = new FontCollectionKey(style, weight, stretch);
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
@ -101,9 +104,11 @@ namespace Avalonia.Media.Fonts
{
var fontFamily = _fontFamilies[i];
if (fontFamily.Name.ToLower(CultureInfo.InvariantCulture).StartsWith(familyName.ToLower(CultureInfo.InvariantCulture)))
if (fontFamily.Name.ToUpperInvariant().StartsWith(familyName.ToUpperInvariant()))
{
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) &&
familyName = fontFamily.Name.ToUpperInvariant();
if (_glyphTypefaceCache.TryGetValue(familyName, out glyphTypefaces) &&
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
{
return true;

2
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@ -42,6 +42,8 @@ namespace Avalonia.Media.Fonts
familyName = _fontManager.DefaultFontFamilyName;
}
familyName = familyName.ToUpperInvariant();
var key = new FontCollectionKey(style, weight, stretch);
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))

5
src/Avalonia.Base/Media/FormattedText.cs

@ -1393,10 +1393,11 @@ namespace Avalonia.Media
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsDefault)
if (accumulatedBounds?.PlatformImpl == null ||
(accumulatedBounds.PlatformImpl.Bounds.Width == 0 && accumulatedBounds.PlatformImpl.Bounds.Height == 0))
{
return null;
}
}
return accumulatedBounds;
}

2
src/Avalonia.Base/Media/ImageDrawing.cs

@ -42,7 +42,7 @@ namespace Avalonia.Media
var imageSource = ImageSource;
var rect = Rect;
if (imageSource is object && !rect.IsDefault)
if (imageSource is object && (rect.Width != 0 || rect.Height != 0))
{
context.DrawImage(imageSource, rect);
}

2
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -77,7 +77,7 @@ namespace Avalonia.Media.Imaging
{
if (Source is not IBitmap bmp)
return default;
if (SourceRect.IsDefault)
if (SourceRect.Width == 0 && SourceRect.Height == 0)
return Source.Size;
return SourceRect.Size.ToSizeWithDpi(bmp.Dpi);
}

2
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -658,7 +658,7 @@ namespace Avalonia.Media.TextFormatting
/// Performs text wrapping returns a list of text lines.
/// </summary>
/// <param name="textRuns"></param>
/// <param name="canReuseTextRunList">Whether <see cref="textRuns"/> can be reused to store the split runs.</param>
/// <param name="canReuseTextRunList">Whether <see cref="TextRun"/> can be reused to store the split runs.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="paragraphProperties">The text paragraph properties.</param>

2
src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs

@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
/// A typical usage example is a ListBox control, where <see cref="InheritDataTypeFromItemsAttribute"/> is defined on the ItemTemplate property,
/// allowing the template to inherit the data type from the Items collection binding.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class InheritDataTypeFromItemsAttribute : Attribute
{
/// <summary>

19
src/Avalonia.Base/PixelRect.cs

@ -9,12 +9,6 @@ namespace Avalonia
/// </summary>
public readonly struct PixelRect : IEquatable<PixelRect>
{
/// <summary>
/// An empty rectangle.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly PixelRect Empty = default;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
@ -133,15 +127,6 @@ namespace Avalonia
/// </summary>
public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
/// <summary>
/// Gets a value indicating whether the instance has default values (the rectangle is empty).
/// </summary>
public bool IsDefault => Width == 0 && Height == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Checks for equality between two <see cref="PixelRect"/>s.
/// </summary>
@ -295,11 +280,11 @@ namespace Avalonia
/// <returns>The union.</returns>
public PixelRect Union(PixelRect rect)
{
if (IsDefault)
if (Width == 0 && Height == 0)
{
return rect;
}
else if (rect.IsDefault)
else if (rect.Width == 0 && rect.Height == 0)
{
return this;
}

1
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -128,6 +128,7 @@ namespace Avalonia.Platform
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <param name="bounds">where to apply the opacity.</param>
void PushOpacity(double opacity, Rect bounds);
/// <summary>

18
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -92,4 +92,22 @@ internal class BclStorageFile : IStorageBookmarkFile
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async Task DeleteAsync()
{
FileInfo.Delete();
}
public async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, FileInfo.Name);
FileInfo.MoveTo(newPath);
return new BclStorageFile(new FileInfo(newPath));
}
return null;
}
}

47
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()
@ -92,4 +94,39 @@ internal class BclStorageFolder : IStorageBookmarkFolder
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async Task DeleteAsync()
{
DirectoryInfo.Delete(true);
}
public async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, DirectoryInfo.Name);
DirectoryInfo.MoveTo(newPath);
return new BclStorageFolder(new DirectoryInfo(newPath));
}
return null;
}
public async Task<IStorageFile?> CreateFileAsync(string name)
{
var fileName = System.IO.Path.Combine(DirectoryInfo.FullName, name);
var newFile = new FileInfo(fileName);
using var stream = newFile.Create();
return new BclStorageFile(newFile);
}
public async Task<IStorageFolder?> CreateFolderAsync(string name)
{
var newFolder = DirectoryInfo.CreateSubdirectory(name);
return new BclStorageFolder(newFolder);
}
}

17
src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
@ -23,7 +24,7 @@ internal static class StorageProviderHelpers
return null;
}
public static Uri FilePathToUri(string path)
{
var uriPath = new StringBuilder(path)
@ -35,6 +36,20 @@ internal static class StorageProviderHelpers
return new UriBuilder("file", string.Empty) { Path = uriPath }.Uri;
}
public static bool TryFilePathToUri(string path, [NotNullWhen(true)] out Uri? uri)
{
try
{
uri = FilePathToUri(path);
return true;
}
catch
{
uri = null;
return false;
}
}
public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter)
{
var name = Path.GetFileName(path);

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

@ -16,5 +16,19 @@ 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();
/// <summary>
/// Creates a file with specified name as a child of the current storage folder
/// </summary>
/// <param name="name">The display name</param>
/// <returns>A new <see cref="IStorageFile"/> pointing to the moved file. If not null, the current storage item becomes invalid</returns>
Task<IStorageFile?> CreateFileAsync(string name);
/// <summary>
/// Creates a folder with specified name as a child of the current storage folder
/// </summary>
/// <param name="name">The display name</param>
/// <returns>A new <see cref="IStorageFolder"/> pointing to the moved file. If not null, the current storage item becomes invalid</returns>
Task<IStorageFolder?> CreateFolderAsync(string name);
}

13
src/Avalonia.Base/Platform/Storage/IStorageItem.cs

@ -50,4 +50,17 @@ public interface IStorageItem : IDisposable
/// Gets the parent folder of the current storage item.
/// </summary>
Task<IStorageFolder?> GetParentAsync();
/// <summary>
/// Deletes the current storage item and it's contents
/// </summary>
/// <returns></returns>
Task DeleteAsync();
/// <summary>
/// Moves the current storage item and it's contents to a <see cref="IStorageFolder"/>
/// </summary>
/// <param name="destination">The <see cref="IStorageFolder"/> to move the item into</param>
/// <returns></returns>
Task<IStorageItem?> MoveAsync(IStorageFolder destination);
}

16
src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs

@ -16,8 +16,13 @@ public static class StorageProviderExtensions
{
return Task.FromResult(StorageProviderHelpers.TryCreateBclStorageItem(filePath) as IStorageFile);
}
return provider.TryGetFileFromPathAsync(StorageProviderHelpers.FilePathToUri(filePath));
if (StorageProviderHelpers.TryFilePathToUri(filePath, out var uri))
{
return provider.TryGetFileFromPathAsync(uri);
}
return Task.FromResult<IStorageFile?>(null);
}
/// <inheritdoc cref="IStorageProvider.TryGetFolderFromPathAsync"/>
@ -29,7 +34,12 @@ public static class StorageProviderExtensions
return Task.FromResult(StorageProviderHelpers.TryCreateBclStorageItem(folderPath) as IStorageFolder);
}
return provider.TryGetFolderFromPathAsync(StorageProviderHelpers.FilePathToUri(folderPath));
if (StorageProviderHelpers.TryFilePathToUri(folderPath, out var uri))
{
return provider.TryGetFolderFromPathAsync(uri);
}
return Task.FromResult<IStorageFolder?>(null);
}
/// <summary>

8
src/Avalonia.Base/Point.cs

@ -288,13 +288,5 @@ namespace Avalonia
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y coordinates are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
}
}

2
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -31,7 +31,7 @@ namespace Avalonia.PropertyStore
var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
if (_metadata.CoerceValue is { } coerce)
{
_uncommon = new()
{

34
src/Avalonia.Base/Rect.cs

@ -16,12 +16,6 @@ namespace Avalonia
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// An empty rectangle.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly Rect Empty = default;
/// <summary>
/// The X position.
/// </summary>
@ -170,17 +164,6 @@ namespace Avalonia
/// </summary>
public Point Center => new Point(_x + (_width / 2), _y + (_height / 2));
/// <summary>
/// Gets a value indicating whether the instance has default values (the rectangle is empty).
/// </summary>
// ReSharper disable CompareOfFloatsByEqualityOperator
public bool IsDefault => _width == 0 && _height == 0;
// ReSharper restore CompareOfFloatsByEqualityOperator
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Checks for equality between two <see cref="Rect"/>s.
/// </summary>
@ -517,19 +500,18 @@ namespace Avalonia
return rect;
}
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
public Rect Union(Rect rect)
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
public Rect Union(Rect rect)
{
if (IsDefault)
if (Width == 0 && Height == 0)
{
return rect;
}
else if (rect.IsDefault)
else if (rect.Width == 0 && rect.Height == 0)
{
return this;
}

12
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -130,8 +130,8 @@ namespace Avalonia.Rendering.Composition.Server
}
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
if(_dirtyRect.IsDefault && !_redrawRequested)
if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
return;
Revision++;
@ -163,7 +163,7 @@ namespace Avalonia.Rendering.Composition.Server
_dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height);
}
if (!_dirtyRect.IsDefault)
if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0)
{
using (var context = _layer.CreateDrawingContext())
{
@ -260,7 +260,7 @@ namespace Avalonia.Rendering.Composition.Server
public void AddDirtyRect(Rect rect)
{
if(rect.IsDefault)
if (rect.Width == 0 && rect.Height == 0)
return;
var snapped = SnapToDevicePixels(rect, Scaling);
DebugEvents?.RectInvalidated(rect);
@ -275,7 +275,7 @@ namespace Avalonia.Rendering.Composition.Server
public void Dispose()
{
if(_disposed)
if (_disposed)
return;
_disposed = true;
using (_compositor.RenderInterface.EnsureCurrent())
@ -302,7 +302,7 @@ namespace Avalonia.Rendering.Composition.Server
{
if (_attachedVisuals.Remove(visual) && IsEnabled)
visual.Deactivate();
if(visual.IsVisibleInFrame)
if (visual.IsVisibleInFrame)
AddDirtyRect(visual.TransformedOwnContentBounds);
}

55
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -23,21 +23,20 @@ namespace Avalonia.Rendering.Composition.Server
private bool _isBackface;
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
}
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
if(Visible == false || IsVisibleInFrame == false)
if (Visible == false || IsVisibleInFrame == false)
return;
if(Opacity == 0)
if (Opacity == 0)
return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
if(currentTransformedClip.IsDefault)
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
return;
Root!.RenderedVisuals++;
@ -61,7 +60,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
if(OpacityMaskBrush != null)
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
RenderCore(canvas, currentTransformedClip);
@ -78,12 +77,12 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopClip();
if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
if (Opacity != 1)
canvas.PopOpacity();
}
protected virtual bool HandlesClipToBounds => false;
private ReadbackData _readback0, _readback1, _readback2;
/// <summary>
@ -98,17 +97,17 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback1;
return ref _readback2;
}
public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
public Matrix4x4 GlobalTransformMatrix { get; private set; }
public virtual void Update(ServerCompositionTarget root)
{
if(Parent == null && Root == null)
if (Parent == null && Root == null)
return;
var wasVisible = IsVisibleInFrame;
// Calculate new parent-relative transform
if (_combinedTransformDirty)
{
@ -122,7 +121,7 @@ namespace Avalonia.Rendering.Composition.Server
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
var newTransform = CombinedTransformMatrix * parentTransform;
// Check if visual was moved and recalculate face orientation
var positionChanged = false;
if (GlobalTransformMatrix != newTransform)
@ -134,23 +133,23 @@ namespace Avalonia.Rendering.Composition.Server
var oldTransformedContentBounds = TransformedOwnContentBounds;
var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;
if (_parent?.IsDirtyComposition == true)
{
IsDirtyComposition = true;
_isDirtyForUpdate = true;
}
var invalidateOldBounds = _isDirtyForUpdate;
var invalidateNewBounds = _isDirtyForUpdate;
GlobalTransformMatrix = newTransform;
var ownBounds = OwnContentBounds;
if (ownBounds != _oldOwnContentBounds || positionChanged)
{
_oldOwnContentBounds = ownBounds;
if (ownBounds.IsDefault)
if (ownBounds.Width == 0 && ownBounds.Height == 0)
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
@ -171,16 +170,16 @@ namespace Avalonia.Rendering.Composition.Server
AdornedVisual?._combinedTransformedClipBounds
?? Parent?._combinedTransformedClipBounds
?? new Rect(Root!.Size);
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible
&& !_isBackface
&& !_combinedTransformedClipBounds.IsDefault;
&& (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0);
IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false
@ -213,11 +212,11 @@ namespace Avalonia.Rendering.Composition.Server
void AddDirtyRect(Rect rc)
{
if(rc == default)
if (rc == default)
return;
Root?.AddDirtyRect(rc);
}
/// <summary>
/// Data that can be read from the UI thread
/// </summary>
@ -228,7 +227,7 @@ namespace Avalonia.Rendering.Composition.Server
public long TargetId;
public bool Visible;
}
partial void DeserializeChangesExtra(BatchStreamReader c)
{
ValuesInvalidated();
@ -245,9 +244,8 @@ namespace Avalonia.Rendering.Composition.Server
protected virtual void OnDetachedFromRoot(ServerCompositionTarget target)
{
}
partial void OnRootChanged()
{
if (Root != null)
@ -256,12 +254,11 @@ namespace Avalonia.Rendering.Composition.Server
OnAttachedToRoot(Root);
}
}
protected virtual void OnAttachedToRoot(ServerCompositionTarget target)
{
}
protected override void ValuesInvalidated()
{
_isDirtyForUpdate = true;
@ -274,6 +271,4 @@ namespace Avalonia.Rendering.Composition.Server
public Rect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y);
}
}

2
src/Avalonia.Base/Rendering/DirtyRects.cs

@ -30,7 +30,7 @@ namespace Avalonia.Rendering
/// </remarks>
public void Add(Rect rect)
{
if (!rect.IsDefault)
if (rect.Width != 0 || rect.Height != 0)
{
for (var i = 0; i < _rects.Count; ++i)
{

2
src/Avalonia.Base/Rendering/DisplayDirtyRect.cs

@ -3,7 +3,7 @@
namespace Avalonia.Rendering
{
/// <summary>
/// Holds the state for a dirty rect rendered when <see cref="IRenderer.DrawDirtyRects"/> is set.
/// Holds the state for a dirty rect rendered when <see cref="IRenderer.SceneInvalidated"/> is set.
/// </summary>
internal class DisplayDirtyRect
{

4
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -83,7 +83,7 @@ namespace Avalonia.Rendering
}
}
using (context.PushPostTransform(m))
using (context.PushTransform(m))
using (context.PushOpacity(opacity, bounds))
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
@ -95,7 +95,7 @@ namespace Avalonia.Rendering
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default)
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransformContainer())
using (context.PushTransform(Matrix.Identity))
{
visual.Render(context);

1
src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public GeometryNode(Matrix transform,
IImmutableBrush? brush,
IPen? pen,

1
src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="pen">The stroke pen.</param>
/// <param name="p1">The start point of the line.</param>
/// <param name="p2">The end point of the line.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public LineNode(
Matrix transform,
IPen pen,

1
src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
/// <param name="mask">The opacity mask to push.</param>
/// <param name="bounds">The bounds of the mask.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public OpacityMaskNode(IImmutableBrush mask, Rect bounds)
: base(default, Matrix.Identity, mask)
{

1
src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs

@ -20,7 +20,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="pen">The stroke pen.</param>
/// <param name="rect">The rectangle to draw.</param>
/// <param name="boxShadows">The box shadow parameters</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public RectangleNode(
Matrix transform,
IImmutableBrush? brush,

11
src/Avalonia.Base/Size.cs

@ -27,12 +27,6 @@ namespace Avalonia
/// </summary>
public static readonly Size Infinity = new Size(double.PositiveInfinity, double.PositiveInfinity);
/// <summary>
/// A size representing zero.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly Size Empty = new Size(0, 0);
/// <summary>
/// The width.
/// </summary>
@ -306,10 +300,5 @@ namespace Avalonia
width = this._width;
height = this._height;
}
/// <summary>
/// Gets a value indicating whether the Width and Height values are zero.
/// </summary>
public bool IsDefault => (_width == 0) && (_height == 0);
}
}

21
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,
@ -31,7 +34,6 @@ namespace Avalonia
{
Inherits = inherits;
ValidateValue = validate;
HasCoercion |= metadata.CoerceValue != null;
if (validate?.Invoke(metadata.DefaultValue) == false)
{
@ -41,16 +43,10 @@ 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; }
/// <summary>
/// Gets a value indicating whether this property has any value coercion callbacks defined
/// in its metadata.
/// </summary>
internal bool HasCoercion { get; private set; }
/// <summary>
/// Registers the property on another type.
/// </summary>
@ -127,10 +123,7 @@ namespace Avalonia
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{
base.OverrideMetadata(typeof(T), metadata);
}
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject => OverrideMetadata(typeof(T), metadata);
/// <summary>
/// Overrides the metadata for the property on the specified type.
@ -148,8 +141,6 @@ namespace Avalonia
}
}
HasCoercion |= metadata.CoerceValue != null;
base.OverrideMetadata(type, metadata);
}

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);
}
}

2
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -52,7 +52,7 @@ namespace Avalonia.Styling
return result;
}
protected TypeNameAndClassSelector(Selector? previous)
TypeNameAndClassSelector(Selector? previous)
{
_previous = previous;
}

10
src/Avalonia.Base/Thickness.cs

@ -97,10 +97,6 @@ namespace Avalonia
/// </summary>
public double Bottom => _bottom;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Gets a value indicating whether all sides are equal.
/// </summary>
@ -293,11 +289,5 @@ namespace Avalonia
right = this._right;
bottom = this._bottom;
}
/// <summary>
/// Gets a value indicating whether the instance has default values
/// (the left, top, right and bottom values are zero).
/// </summary>
public bool IsDefault => (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0);
}
}

8
src/Avalonia.Base/Vector.cs

@ -360,13 +360,5 @@ namespace Avalonia
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y components are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
}
}

63
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -204,6 +204,69 @@ namespace Avalonia.VisualTree
}
}
public static TransformedBounds? GetTransformedBounds(this Visual visual)
{
Rect clip = default;
var transform = Matrix.Identity;
bool Visit(Visual visual)
{
if (!visual.IsVisible)
return false;
// The visual's bounds in local coordinates.
var bounds = new Rect(visual.Bounds.Size);
// If the visual has no parent, we've reached the root. We start the clip
// rectangle with these bounds.
if (visual.GetVisualParent() is not { } parent)
{
clip = bounds;
return true;
}
// Otherwise recurse until the root visual is found, exiting early if one of the
// ancestors is invisible.
if (!Visit(parent))
return false;
// Calculate the transform for this control from its offset and render transform.
var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * offset;
renderTransform *= finalTransform;
}
transform = renderTransform *
Matrix.CreateTranslation(visual.Bounds.Position) *
transform;
// If the visual is clipped, update the clip bounds.
if (visual.ClipToBounds)
{
var globalBounds = bounds.TransformToAABB(transform);
var clipBounds = visual.ClipToBounds ?
globalBounds.Intersect(clip) :
clip;
clip = clip.Intersect(clipBounds);
}
return true;
}
return Visit(visual) ? new(new(visual.Bounds.Size), clip, transform) : null;
}
/// <summary>
/// Gets the first visual in the visual tree whose bounds contain a point.
/// </summary>

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -414,7 +414,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -376,7 +376,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

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');

8
src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs

@ -177,14 +177,14 @@ namespace Avalonia.Controls
}
bool? uneditedValue = editingCheckBox.IsChecked;
if(editingEventArgs is PointerPressedEventArgs args)
if (editingEventArgs is PointerPressedEventArgs args)
{
void ProcessPointerArgs()
{
// Editing was triggered by a mouse click
Point position = args.GetPosition(editingCheckBox);
Rect rect = new Rect(0, 0, editingCheckBox.Bounds.Width, editingCheckBox.Bounds.Height);
if(rect.Contains(position))
if (rect.Contains(position))
{
EditValue();
}
@ -192,14 +192,14 @@ namespace Avalonia.Controls
void OnLayoutUpdated(object sender, EventArgs e)
{
if(!editingCheckBox.Bounds.IsDefault)
if (editingCheckBox.Bounds.Width != 0 || editingCheckBox.Bounds.Height != 0)
{
editingCheckBox.LayoutUpdated -= OnLayoutUpdated;
ProcessPointerArgs();
}
}
if(editingCheckBox.Bounds.IsDefault)
if (editingCheckBox.Bounds.Width == 0 && editingCheckBox.Bounds.Height == 0)
{
editingCheckBox.LayoutUpdated += OnLayoutUpdated;
}

5
src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs

@ -39,7 +39,10 @@ namespace Avalonia.Controls
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsRepeater, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsRepeater>(o => o.Items, (o, v) => o.Items = v);
AvaloniaProperty.RegisterDirect<ItemsRepeater, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="Layout"/> property.

4
src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs

@ -441,7 +441,7 @@ namespace Avalonia.Controls
_pendingViewportShift = default;
_unshiftableShift = default;
if (_visibleWindow.IsDefault)
if (_visibleWindow.Width == 0 && _visibleWindow.Height == 0)
{
// We got cleared.
_layoutExtent = default;
@ -527,7 +527,7 @@ namespace Avalonia.Controls
private void TryInvalidateMeasure()
{
// Don't invalidate measure if we have an invalid window.
if (!_visibleWindow.IsDefault)
if (_visibleWindow.Width != 0 || _visibleWindow.Height != 0)
{
// We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to

12
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -87,12 +87,10 @@ namespace Avalonia.Controls
/// Identifies the <see cref="Text" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Text" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
o => o.Text,
(o, v) => o.Text = v,
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<AutoCompleteBox>(new(string.Empty,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
enableDataValidation: true));
/// <summary>
/// Identifies the <see cref="SearchText" /> property.
@ -317,8 +315,8 @@ namespace Avalonia.Controls
/// <see cref="AutoCompleteBox" /> control.</value>
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>

3
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -198,7 +198,6 @@ namespace Avalonia.Controls
private bool _isDropDownOpen;
private bool _isFocused = false;
private string? _text = string.Empty;
private string? _searchText = string.Empty;
private AutoCompleteFilterPredicate<object?>? _itemFilter;
@ -1275,7 +1274,7 @@ namespace Avalonia.Controls
if ((userInitiated ?? true) && Text != value)
{
_ignoreTextPropertyChange++;
Text = value;
SetCurrentValue(TextProperty, value);
callTextChanged = true;
}

29
src/Avalonia.Controls/Automation/Peers/ScrollBarAutomationPeer.cs

@ -0,0 +1,29 @@
using Avalonia.Controls.Primitives;
namespace Avalonia.Automation.Peers
{
public class ScrollBarAutomationPeer : RangeBaseAutomationPeer
{
public ScrollBarAutomationPeer(ScrollBar owner) : base(owner)
{
}
override protected string GetClassNameCore()
{
return "ScrollBar";
}
override protected AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ScrollBar;
}
// AutomationControlType.ScrollBar must return IsContentElement false.
// See http://msdn.microsoft.com/en-us/library/ms743712.aspx
override protected bool IsContentElementCore()
{
return false;
}
}
}

2
src/Avalonia.Controls/BorderVisual.cs

@ -50,7 +50,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
if (ClipToBounds)
{
var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y)));
if (_cornerRadius.IsDefault)
if (_cornerRadius == default)
canvas.PushClip(clipRect);
else
canvas.PushClip(new RoundedRect(clipRect, _cornerRadius));

20
src/Avalonia.Controls/ContainerClearingEventArgs.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerClearing"/> event.
/// </summary>
public class ContainerClearingEventArgs : EventArgs
{
public ContainerClearingEventArgs(Control container)
{
Container = container;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
}
}

32
src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs

@ -0,0 +1,32 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerIndexChanged"/> event.
/// </summary>
public class ContainerIndexChangedEventArgs : EventArgs
{
public ContainerIndexChangedEventArgs(Control container, int oldIndex, int newIndex)
{
Container = container;
OldIndex = oldIndex;
NewIndex = newIndex;
}
/// <summary>
/// Get the container for which the index changed.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the container after the change.
/// </summary>
public int NewIndex { get; }
/// <summary>
/// Gets the index of the container before the change.
/// </summary>
public int OldIndex { get; }
}
}

26
src/Avalonia.Controls/ContainerPreparedEventArgs.cs

@ -0,0 +1,26 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerPrepared"/> event.
/// </summary>
public class ContainerPreparedEventArgs : EventArgs
{
public ContainerPreparedEventArgs(Control container, int index)
{
Container = container;
Index = index;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the item the container was prepared for.
/// </summary>
public int Index { get; }
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save