Browse Source

Merge remote-tracking branch 'upstream/master' into feature/ImePreeditText

feature/ImePreeditText
Benedikt Stebner 4 years ago
parent
commit
ca9fc3b8e2
  1. 2
      .github/PULL_REQUEST_TEMPLATE.md
  2. 6
      build/HarfBuzzSharp.props
  3. 2
      build/ReactiveUI.props
  4. 2
      build/SharedVersion.props
  5. 6
      build/SkiaSharp.props
  6. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  7. 4
      samples/ControlCatalog.NetCore/Program.cs
  8. 2
      samples/ControlCatalog/DecoratedWindow.xaml
  9. 6
      samples/ControlCatalog/MainView.xaml.cs
  10. 6
      samples/ControlCatalog/MainWindow.xaml
  11. 8
      samples/ControlCatalog/Models/Person.cs
  12. 2
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  13. 24
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  14. 25
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  15. 2
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  16. 2
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  17. 70
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  18. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  19. 20
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  20. 22
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  21. 23
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  22. 4
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  23. 13
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  24. 20
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  25. 2
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  26. 10
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  27. 4
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  28. 6
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  29. 6
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  30. 4
      samples/ControlCatalog/Pages/PointerCanvas.cs
  31. 33
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  32. 8
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  33. 7
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  34. 6
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  35. 37
      samples/IntegrationTestApp/MainWindow.axaml.cs
  36. 3
      samples/IntegrationTestApp/ShowWindowTest.axaml
  37. 6
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  38. 265
      samples/RenderDemo/Pages/DrawingPage.xaml
  39. 4
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  40. 25
      src/Android/Avalonia.Android/AndroidPlatform.cs
  41. 66
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  42. 12
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  43. 26
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  44. 251
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  45. 85
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  46. 17
      src/Avalonia.Base/Animation/Animation.cs
  47. 13
      src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
  48. 35
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  49. 15
      src/Avalonia.Base/AvaloniaProperty`1.cs
  50. 15
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  51. 56
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  52. 5
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  53. 8
      src/Avalonia.Base/Controls/Templates/ITemplateResult.cs
  54. 3
      src/Avalonia.Base/Controls/Templates/TemplateResult.cs
  55. 15
      src/Avalonia.Base/DirectPropertyBase.cs
  56. 4
      src/Avalonia.Base/EnumExtensions.cs
  57. 3
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  58. 30
      src/Avalonia.Base/Input/PointerPoint.cs
  59. 4
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
  60. 18
      src/Avalonia.Base/Interactivity/RoutedEvent.cs
  61. 11
      src/Avalonia.Base/Layout/ILayoutManager.cs
  62. 11
      src/Avalonia.Base/Layout/LayoutManager.cs
  63. 28
      src/Avalonia.Base/Layout/Layoutable.cs
  64. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  65. 16
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  66. 14
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  67. 15
      src/Avalonia.Base/Media/KnownColors.cs
  68. 14
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  69. 12
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  70. 9
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  71. 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  72. 13
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  73. 2
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  74. 3
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  75. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  76. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  77. 2
      src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  78. 35
      src/Avalonia.Base/Utilities/MathUtilities.cs
  79. 25
      src/Avalonia.Base/Utilities/WeakObservable.cs
  80. 193
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  81. 16
      src/Avalonia.Base/Visual.cs
  82. 19
      src/Avalonia.Base/VisualTree/IVisualTreeHost.cs
  83. 15
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  84. 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  85. 23
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  86. 23
      src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml
  87. 10
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  88. 30
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  89. 11
      src/Avalonia.Controls/AutoCompleteBox.cs
  90. 77
      src/Avalonia.Controls/Calendar/Calendar.cs
  91. 71
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  92. 2
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  93. 6
      src/Avalonia.Controls/Control.cs
  94. 17
      src/Avalonia.Controls/Converters/StringFormatConverter.cs
  95. 2
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  96. 61
      src/Avalonia.Controls/DrawingPresenter.cs
  97. 2
      src/Avalonia.Controls/GridLength.cs
  98. 3
      src/Avalonia.Controls/ListBox.cs
  99. 13
      src/Avalonia.Controls/LoggingExtensions.cs
  100. 7
      src/Avalonia.Controls/NativeMenuItem.cs

2
.github/PULL_REQUEST_TEMPLATE.md

@ -21,7 +21,7 @@
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation - [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation
## Breaking changes ## Breaking changes
<!--- List any breaking changes here. When the PR is merged please add an entry to https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes --> <!--- List any breaking changes here. -->
## Obsoletions / Deprecations ## Obsoletions / Deprecations
<!--- Obsolete and Deprecated attributes on APIs MUST only be included when discussed with Core team. @grokys, @kekekeks & @danwalmsley --> <!--- Obsolete and Deprecated attributes on APIs MUST only be included when discussed with Core team. @grokys, @kekekeks & @danwalmsley -->

6
build/HarfBuzzSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.8.2" /> <PackageReference Include="HarfBuzzSharp" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.1-preview.108" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="ReactiveUI" Version="13.2.10" /> <PackageReference Include="ReactiveUI" Version="18.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>0.10.999</Version> <Version>11.0.999</Version>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright> <Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl> <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" /> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.108" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.1" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.108" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -121,7 +121,7 @@ void WindowImpl::BringToFront()
{ {
if(Window != nullptr) if(Window != nullptr)
{ {
if (![Window isMiniaturized]) if ([Window isVisible] && ![Window isMiniaturized])
{ {
if(IsDialog()) if(IsDialog())
{ {

4
samples/ControlCatalog.NetCore/Program.cs

@ -115,10 +115,6 @@ namespace ControlCatalog.NetCore
UseDBusMenu = true, UseDBusMenu = true,
EnableIme = true EnableIme = true
}) })
.With(new Win32PlatformOptions
{
EnableMultitouch = true
})
.UseSkia() .UseSkia()
.AfterSetup(builder => .AfterSetup(builder =>
{ {

2
samples/ControlCatalog/DecoratedWindow.xaml

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.DecoratedWindow" x:Class="ControlCatalog.DecoratedWindow"
Title="Avalonia Control Gallery" Title="Avalonia Control Gallery"
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> xmlns:local="clr-namespace:ControlCatalog" SystemDecorations="None" Name="Window">
<NativeMenu.Menu> <NativeMenu.Menu>
<NativeMenu> <NativeMenu>
<NativeMenuItem Header="Decorated"> <NativeMenuItem Header="Decorated">

6
samples/ControlCatalog/MainView.xaml.cs

@ -22,8 +22,8 @@ namespace ControlCatalog
if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true) if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
{ {
IList tabItems = ((IList)sideBar.Items); var tabItems = (sideBar.Items as IList);
tabItems.Add(new TabItem() tabItems?.Add(new TabItem()
{ {
Header = "Screens", Header = "Screens",
Content = new ScreenPage() Content = new ScreenPage()
@ -36,7 +36,7 @@ namespace ControlCatalog
{ {
if (themes.SelectedItem is CatalogTheme theme) if (themes.SelectedItem is CatalogTheme theme)
{ {
var themeStyle = Application.Current.Styles[0]; var themeStyle = Application.Current!.Styles[0];
if (theme == CatalogTheme.FluentLight) if (theme == CatalogTheme.FluentLight)
{ {
if (App.Fluent.Mode != FluentThemeMode.Light) if (App.Fluent.Mode != FluentThemeMode.Light)

6
samples/ControlCatalog/MainWindow.xaml

@ -18,15 +18,15 @@
<NativeMenu> <NativeMenu>
<NativeMenuItem Header="File"> <NativeMenuItem Header="File">
<NativeMenu> <NativeMenu>
<NativeMenuItem Icon="/Assets/test_icon.ico" Header="Open" Clicked="OnOpenClicked" Gesture="Ctrl+O"/> <NativeMenuItem Icon="/Assets/test_icon.ico" Header="Open" Click="OnOpenClicked" Gesture="Ctrl+O"/>
<NativeMenuItemSeperator/><!-- Uses incorrect spelling to demonstrate backwards compatibility --> <NativeMenuItemSeparator/>
<NativeMenuItem Icon="/Assets/github_icon.png" Header="Recent"> <NativeMenuItem Icon="/Assets/github_icon.png" Header="Recent">
<NativeMenu/> <NativeMenu/>
</NativeMenuItem> </NativeMenuItem>
<NativeMenuItemSeparator/> <NativeMenuItemSeparator/>
<NativeMenuItem Header="{x:Static local:MainWindow.MenuQuitHeader}" <NativeMenuItem Header="{x:Static local:MainWindow.MenuQuitHeader}"
Gesture="{x:Static local:MainWindow.MenuQuitGesture}" Gesture="{x:Static local:MainWindow.MenuQuitGesture}"
Clicked="OnCloseClicked" /> Click="OnCloseClicked" />
</NativeMenu> </NativeMenu>
</NativeMenuItem> </NativeMenuItem>
<NativeMenuItem Header="Edit"> <NativeMenuItem Header="Edit">

8
samples/ControlCatalog/Models/Person.cs

@ -85,7 +85,7 @@ namespace ControlCatalog.Models
} }
else else
{ {
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (_errorLookup.TryGetValue(propertyName, out var errorList))
{ {
errorList.Clear(); errorList.Clear();
errorList.Add(error!); errorList.Add(error!);
@ -114,12 +114,12 @@ namespace ControlCatalog.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
public IEnumerable? GetErrors(string propertyName) public IEnumerable GetErrors(string? propertyName)
{ {
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (propertyName is { } && _errorLookup.TryGetValue(propertyName, out var errorList))
return errorList; return errorList;
else else
return null; return Array.Empty<object>();
} }
} }
} }

2
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -28,7 +28,7 @@
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="MinimumPopulateDelay: 1s" /> <TextBlock Text="MinimumPopulateDelay: 1s" />
<AutoCompleteBox MinimumPopulateDelay="1" /> <AutoCompleteBox MinimumPopulateDelay="00:00:01" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="MaxDropDownHeight: 60" /> <TextBlock Text="MaxDropDownHeight: 60" />

24
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -1,8 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -161,23 +159,23 @@ namespace ControlCatalog.Pages
private bool LastWordContains(string? searchText, string? item) private bool LastWordContains(string? searchText, string? item)
{ {
var words = searchText?.Split(' ') ?? Array.Empty<string>(); var words = searchText?.Split(' ') ?? Array.Empty<string>();
var options = Sentences.Select(x => x.First).ToArray(); var options = Sentences.Select(x => x.First)
.ToArray<LinkedListNode<string>?>();
for (var i = 0; i < words.Length; ++i) for (var i = 0; i < words.Length; ++i)
{ {
var word = words[i]; var word = words[i];
for (var j = 0; word is { } && j < options.Length; ++j) for (var j = 0; word is { } && j < options.Length; ++j)
{ {
var option = options[j]; if (options[i] is { } option)
if (option == null)
continue;
if (i == words.Length - 1)
{
options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
}
else
{ {
options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; if (i == words.Length - 1)
{
options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
}
else
{
options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null;
}
} }
} }
} }

25
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@ -21,20 +21,23 @@ namespace ControlCatalog.Pages
public void OnSpin(object sender, SpinEventArgs e) public void OnSpin(object sender, SpinEventArgs e)
{ {
var spinner = (ButtonSpinner)sender; var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content;
int value = Array.IndexOf(_mountains, txtBox?.Text); if (spinner.Content is TextBlock txtBox)
if (e.Direction == SpinDirection.Increase) {
value++; int value = Array.IndexOf(_mountains, txtBox.Text);
else if (e.Direction == SpinDirection.Increase)
value--; value++;
else
value--;
if (value < 0) if (value < 0)
value = _mountains.Length - 1; value = _mountains.Length - 1;
else if (value >= _mountains.Length) else if (value >= _mountains.Length)
value = 0; value = 0;
txtBox.Text = _mountains[value];
}
txtBox.Text = _mountains[value];
} }
private readonly string[] _mountains = new[] private readonly string[] _mountains = new[]

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

@ -19,7 +19,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
public void OnRepeatButtonClick(object sender, object args) public void OnRepeatButtonClick(object? sender, object args)
{ {
repeatButtonClickCount++; repeatButtonClickCount++;
var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock"); var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");

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

@ -33,7 +33,7 @@ namespace ControlCatalog.Pages
} }
private void TransitionChanged(object sender, SelectionChangedEventArgs e) private void TransitionChanged(object? sender, SelectionChangedEventArgs e)
{ {
switch (_transition.SelectedIndex) switch (_transition.SelectedIndex)
{ {

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

@ -23,55 +23,79 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private async void CopyText(object sender, RoutedEventArgs args) private async void CopyText(object? sender, RoutedEventArgs args)
{ {
await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text); if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty);
} }
private async void PasteText(object sender, RoutedEventArgs args) private async void PasteText(object? sender, RoutedEventArgs args)
{ {
ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync(); if(Application.Current!.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetTextAsync();
}
} }
private async void CopyTextDataObject(object sender, RoutedEventArgs args) private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
{ {
var dataObject = new DataObject(); if (Application.Current!.Clipboard is { } clipboard)
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); {
await Application.Current.Clipboard.SetDataObjectAsync(dataObject); var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
await clipboard.SetDataObjectAsync(dataObject);
}
} }
private async void PasteTextDataObject(object sender, RoutedEventArgs args) private async void PasteTextDataObject(object? sender, RoutedEventArgs args)
{ {
ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; if (Application.Current!.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
}
} }
private async void CopyFilesDataObject(object sender, RoutedEventArgs args) private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
{ {
var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (Application.Current!.Clipboard is { } clipboard)
if (files.Length == 0)
{ {
return; var files = (ClipboardContent.Text ?? String.Empty)
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (files.Length == 0)
{
return;
}
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, files);
await clipboard.SetDataObjectAsync(dataObject);
} }
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, files);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
} }
private async void PasteFilesDataObject(object sender, RoutedEventArgs args) private async void PasteFilesDataObject(object? sender, RoutedEventArgs args)
{ {
var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>; if (Application.Current!.Clipboard is { } clipboard)
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; {
var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
}
} }
private async void GetFormats(object sender, RoutedEventArgs args) private async void GetFormats(object sender, RoutedEventArgs args)
{ {
var formats = await Application.Current.Clipboard.GetFormatsAsync(); if (Application.Current!.Clipboard is { } clipboard)
ClipboardContent.Text = string.Join(Environment.NewLine, formats); {
var formats = await clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
}
} }
private async void Clear(object sender, RoutedEventArgs args) private async void Clear(object sender, RoutedEventArgs args)
{ {
await Application.Current.Clipboard.ClearAsync(); if (Application.Current!.Clipboard is { } clipboard)
{
await clipboard.ClearAsync();
}
} }
} }
} }

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

@ -17,7 +17,7 @@ namespace ControlCatalog.Pages
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Find<ComboBox>("fontComboBox"); var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x)); fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
fontComboBox.SelectedIndex = 0; fontComboBox.SelectedIndex = 0;
} }

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

@ -1,14 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Animations;
@ -18,7 +12,7 @@ namespace ControlCatalog.Pages;
public partial class CompositionPage : UserControl public partial class CompositionPage : UserControl
{ {
private ImplicitAnimationCollection _implicitAnimations; private ImplicitAnimationCollection? _implicitAnimations;
public CompositionPage() public CompositionPage()
{ {
@ -28,7 +22,7 @@ public partial class CompositionPage : UserControl
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
this.FindControl<ItemsControl>("Items").Items = CreateColorItems(); this.Get<ItemsControl>("Items").Items = CreateColorItems();
} }
private List<CompositionPageColorItem> CreateColorItems() private List<CompositionPageColorItem> CreateColorItems()
@ -115,7 +109,6 @@ public partial class CompositionPage : UserControl
public static void SetEnableAnimations(Border border, bool value) public static void SetEnableAnimations(Border border, bool value)
{ {
var page = border.FindAncestorOfType<CompositionPage>(); var page = border.FindAncestorOfType<CompositionPage>();
if (page == null) if (page == null)
{ {
@ -127,8 +120,11 @@ public partial class CompositionPage : UserControl
return; return;
page.EnsureImplicitAnimations(); page.EnsureImplicitAnimations();
ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = if (border.GetVisualParent() is Visual visualParent
page._implicitAnimations; && ElementComposition.GetElementVisual(visualParent) is CompositionVisual compositionVisual)
{
compositionVisual.ImplicitAnimations = page._implicitAnimations;
}
} }
} }
@ -150,4 +146,4 @@ public class CompositionPageColorItem
{ {
Color = color; Color = color;
} }
} }

22
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs

@ -52,13 +52,13 @@ namespace ControlCatalog.Pages
base.OnDataContextChanged(e); base.OnDataContextChanged(e);
} }
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
private void ContextFlyoutPage_Opening(object sender, EventArgs e) private void ContextFlyoutPage_Opening(object? sender, EventArgs e)
{ {
if (e is CancelEventArgs cancelArgs) if (e is CancelEventArgs cancelArgs)
{ {
@ -67,20 +67,20 @@ namespace ControlCatalog.Pages
} }
} }
private void CloseFlyout(object sender, RoutedEventArgs e) private void CloseFlyout(object? sender, RoutedEventArgs e)
{ {
_textBox.ContextFlyout?.Hide(); _textBox.ContextFlyout?.Hide();
} }
public void CustomContextRequested(object sender, ContextRequestedEventArgs e) public void CustomContextRequested(object? sender, ContextRequestedEventArgs e)
{ {
var border = (Border)sender; if (sender is Border border && border.Child is TextBlock textBlock)
var textBlock = (TextBlock)border.Child; {
textBlock.Text = e.TryGetPosition(border, out var point)
textBlock.Text = e.TryGetPosition(border, out var point) ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" : "Context was requested without pointer";
: "Context was requested without pointer"; e.Handled = true;
e.Handled = true; }
} }
private void InitializeComponent() private void InitializeComponent()

23
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@ -35,30 +35,31 @@ namespace ControlCatalog.Pages
base.OnDataContextChanged(e); base.OnDataContextChanged(e);
} }
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox.IsChecked ?? false; e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
private void ContextFlyoutPage_Opening(object sender, EventArgs e) private void ContextFlyoutPage_Opening(object? sender, EventArgs e)
{ {
if (e is CancelEventArgs cancelArgs) if (e is CancelEventArgs cancelArgs)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
} }
public void CustomContextRequested(object sender, ContextRequestedEventArgs e) public void CustomContextRequested(object? sender, ContextRequestedEventArgs e)
{ {
var border = (Border)sender; if (sender is Border border && border.Child is TextBlock textBlock)
var textBlock = (TextBlock)border.Child; {
textBlock.Text = e.TryGetPosition(border, out var point)
? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
: "Context was requested without pointer";
e.Handled = true;
}
textBlock.Text = e.TryGetPosition(border, out var point)
? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
: "Context was requested without pointer";
e.Handled = true;
} }
private void InitializeComponent() private void InitializeComponent()

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

@ -62,7 +62,7 @@ namespace ControlCatalog.Pages
addButton.Click += (a, b) => collectionView3.AddNew(); addButton.Click += (a, b) => collectionView3.AddNew();
} }
private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e) private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e)
{ {
e.Row.Header = e.Row.GetIndex() + 1; e.Row.Header = e.Row.GetIndex() + 1;
} }
@ -74,7 +74,7 @@ namespace ControlCatalog.Pages
private class ReversedStringComparer : IComparer<object>, IComparer private class ReversedStringComparer : IComparer<object>, IComparer
{ {
public int Compare(object x, object y) public int Compare(object? x, object? y)
{ {
if (x is string left && y is string right) if (x is string left && y is string right)
{ {

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

@ -111,9 +111,16 @@ namespace ControlCatalog.Pages
Title = "Select folder", Title = "Select folder",
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null
}.ShowAsync(GetWindow()); }.ShowAsync(GetWindow());
lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); if (string.IsNullOrEmpty(result))
results.Items = new [] { result }; {
resultsVisible.IsVisible = result != null; resultsVisible.IsVisible = false;
}
else
{
lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
results.Items = new[] { result };
resultsVisible.IsVisible = true;
}
}; };
this.Get<Button>("OpenBoth").Click += async delegate this.Get<Button>("OpenBoth").Click += async delegate
{ {

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

@ -1,9 +1,9 @@
using Avalonia.Controls; using System;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -27,9 +27,9 @@ namespace ControlCatalog.Pages
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
{ {
var dragMe = this.Get<Border>("DragMe" + suffix); var dragMe = this.Get<Border>("DragMe" + suffix);
var dragState = this.Get<TextBlock>("DragState"+suffix); var dragState = this.Get<TextBlock>("DragState" + suffix);
async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) async void DoDrag(object? sender, Avalonia.Input.PointerPressedEventArgs e)
{ {
var dragData = new DataObject(); var dragData = new DataObject();
factory(dragData); factory(dragData);
@ -55,7 +55,7 @@ namespace ControlCatalog.Pages
} }
} }
void DragOver(object sender, DragEventArgs e) void DragOver(object? sender, DragEventArgs e)
{ {
if (e.Source is Control c && c.Name == "MoveTarget") if (e.Source is Control c && c.Name == "MoveTarget")
{ {
@ -73,7 +73,7 @@ namespace ControlCatalog.Pages
e.DragEffects = DragDropEffects.None; e.DragEffects = DragDropEffects.None;
} }
void Drop(object sender, DragEventArgs e) void Drop(object? sender, DragEventArgs e)
{ {
if (e.Source is Control c && c.Name == "MoveTarget") if (e.Source is Control c && c.Name == "MoveTarget")
{ {
@ -83,11 +83,11 @@ namespace ControlCatalog.Pages
{ {
e.DragEffects = e.DragEffects & (DragDropEffects.Copy); e.DragEffects = e.DragEffects & (DragDropEffects.Copy);
} }
if (e.Data.Contains(DataFormats.Text)) if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText(); _DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames)) else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames()); _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames() ?? Array.Empty<string>());
else if (e.Data.Contains(CustomFormat)) else if (e.Data.Contains(CustomFormat))
_DropState.Text = "Custom: " + e.Data.Get(CustomFormat); _DropState.Text = "Custom: " + e.Data.Get(CustomFormat);
} }

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

@ -20,7 +20,7 @@ namespace ControlCatalog.Pages
SetXamlTexts(); SetXamlTexts();
} }
private void Afp_DoubleTapped(object sender, RoutedEventArgs e) private void Afp_DoubleTapped(object? sender, RoutedEventArgs e)
{ {
if (sender is Panel p) if (sender is Panel p)
{ {

10
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -123,7 +123,7 @@ namespace ControlCatalog.Pages
element.BringIntoView(); element.BringIntoView();
} }
private void RepeaterClick(object sender, PointerPressedEventArgs e) private void RepeaterClick(object? sender, PointerPressedEventArgs e)
{ {
if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item) if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
{ {
@ -132,7 +132,7 @@ namespace ControlCatalog.Pages
} }
} }
private void RepeaterOnKeyDown(object sender, KeyEventArgs e) private void RepeaterOnKeyDown(object? sender, KeyEventArgs e)
{ {
if (e.Key == Key.F5) if (e.Key == Key.F5)
{ {
@ -140,17 +140,17 @@ namespace ControlCatalog.Pages
} }
} }
private void scrollToLast_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) private void scrollToLast_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
ScrollTo(_viewModel.Items.Count - 1); ScrollTo(_viewModel.Items.Count - 1);
} }
private void scrollToRandom_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) private void scrollToRandom_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
ScrollTo(_random.Next(_viewModel.Items.Count - 1)); ScrollTo(_random.Next(_viewModel.Items.Count - 1));
} }
private void scrollToSelected_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) private void scrollToSelected_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
ScrollTo(_selectedIndex); ScrollTo(_selectedIndex);
} }

4
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -54,11 +54,11 @@
<Grid Grid.Row="0" Grid.Column="2" Margin="8" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, Auto"> <Grid Grid.Row="0" Grid.Column="2" Margin="8" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock> <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
<NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}" <NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/> NumberFormat="{Binding #upDown.NumberFormat}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock> <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}" <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/> NumberFormat="{Binding #upDown.NumberFormat}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock> <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center" <NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"

6
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -2,9 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MiniMvvm; using MiniMvvm;
@ -29,7 +27,7 @@ namespace ControlCatalog.Pages
public class NumbersPageViewModel : ViewModelBase public class NumbersPageViewModel : ViewModelBase
{ {
private IList<FormatObject>? _formats; private IList<FormatObject>? _formats;
private FormatObject _selectedFormat; private FormatObject? _selectedFormat;
private IList<Location>? _spinnerLocations; private IList<Location>? _spinnerLocations;
private double _doubleValue; private double _doubleValue;
@ -89,7 +87,7 @@ namespace ControlCatalog.Pages
.Where(c => new[] { "en-US", "en-GB", "fr-FR", "ar-DZ", "zh-CH", "cs-CZ" }.Contains(c.Name)) .Where(c => new[] { "en-US", "en-GB", "fr-FR", "ar-DZ", "zh-CH", "cs-CZ" }.Contains(c.Name))
.ToArray(); .ToArray();
public FormatObject SelectedFormat public FormatObject? SelectedFormat
{ {
get { return _selectedFormat; } get { return _selectedFormat; }
set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); } set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }

6
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -68,7 +68,7 @@ namespace ControlCatalog.Pages
set => SetAndRaise(DiscoProperty, ref _disco, value); set => SetAndRaise(DiscoProperty, ref _disco, value);
} }
private string _info; private string _info = string.Empty;
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty = public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v); AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v);
@ -205,7 +205,7 @@ namespace ControlCatalog.Pages
public OpenGlPageControl() public OpenGlPageControl()
{ {
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin")); var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name))) using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)!))
{ {
var buf = new byte[sr.ReadInt32()]; var buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length); sr.Read(buf, 0, buf.Length);
@ -345,7 +345,7 @@ namespace ControlCatalog.Pages
0.01f, 1000); 0.01f, 1000);
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0)); var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0));
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll);
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel");
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView");

4
samples/ControlCatalog/Pages/PointerCanvas.cs

@ -174,9 +174,9 @@ Twist: {_lastProperties?.Twist}";
var lastPointer = e.GetCurrentPoint(this); var lastPointer = e.GetCurrentPoint(this);
_lastProperties = lastPointer.Properties; _lastProperties = lastPointer.Properties;
if (_lastProperties.PointerUpdateKind != PointerUpdateKind.Other) if (_lastProperties?.PointerUpdateKind != PointerUpdateKind.Other)
{ {
_lastNonOtherUpdateKind = _lastProperties.PointerUpdateKind; _lastNonOtherUpdateKind = _lastProperties?.PointerUpdateKind;
} }
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)

33
samples/ControlCatalog/Pages/PointersPage.xaml.cs

@ -1,8 +1,6 @@
using System; using System;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages; namespace ControlCatalog.Pages;
@ -31,43 +29,48 @@ public class PointersPage : UserControl
border2.PointerExited += Border_PointerUpdated; border2.PointerExited += Border_PointerUpdated;
} }
private void Border_PointerUpdated(object sender, PointerEventArgs e) private void Border_PointerUpdated(object? sender, PointerEventArgs e)
{ {
var textBlock = (TextBlock)((Border)sender).Child; if (sender is Border border && border.Child is TextBlock textBlock)
var position = e.GetPosition((Border)sender); {
textBlock.Text = @$"Type: {e.Pointer.Type} var position = e.GetPosition(border);
textBlock.Text = @$"Type: {e.Pointer.Type}
Captured: {e.Pointer.Captured == sender} Captured: {e.Pointer.Captured == sender}
PointerId: {e.Pointer.Id} PointerId: {e.Pointer.Id}
Position: {(int)position.X} {(int)position.Y}"; Position: {(int)position.X} {(int)position.Y}";
e.Handled = true; e.Handled = true;
}
} }
private void Border_PointerCaptureLost(object sender, PointerCaptureLostEventArgs e) private void Border_PointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
{ {
var textBlock = (TextBlock)((Border)sender).Child; if (sender is Border border && border.Child is TextBlock textBlock)
textBlock.Text = @$"Type: {e.Pointer.Type} {
textBlock.Text = @$"Type: {e.Pointer.Type}
Captured: {e.Pointer.Captured == sender} Captured: {e.Pointer.Captured == sender}
PointerId: {e.Pointer.Id} PointerId: {e.Pointer.Id}
Position: ??? ???"; Position: ??? ???";
e.Handled = true; e.Handled = true;
}
} }
private void Border_PointerReleased(object sender, PointerReleasedEventArgs e) private void Border_PointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
if (e.Pointer.Captured == sender) if (e.Pointer.Captured == sender)
{ {
e.Pointer.Capture(null); e.Pointer.Capture(null);
e.Handled = true; e.Handled = true;
} }
else else if (e.Pointer.Captured is not null)
{ {
throw new InvalidOperationException("How?"); throw new InvalidOperationException("How?");
} }
} }
private void Border_PointerPressed(object sender, PointerPressedEventArgs e) private void Border_PointerPressed(object? sender, PointerPressedEventArgs e)
{ {
e.Pointer.Capture((Border)sender); e.Pointer.Capture(sender as Border);
e.Handled = true; e.Handled = true;
} }

8
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -53,14 +53,14 @@ namespace ControlCatalog.ViewModels
var window = View?.GetVisualRoot() as Window; var window = View?.GetVisualRoot() as Window;
if (window == null) if (window == null)
return; return;
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(window); var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
if (result != null) if (result != null)
{ {
foreach (var path in result) foreach (var file in result)
{ {
System.Diagnostics.Debug.WriteLine($"Opened: {path}"); System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
} }
} }
} }

7
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -74,14 +74,13 @@ namespace ControlCatalog.ViewModels
var window = View?.GetVisualRoot() as Window; var window = View?.GetVisualRoot() as Window;
if (window == null) if (window == null)
return; return;
var dialog = new OpenFileDialog(); var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
var result = await dialog.ShowAsync(window);
if (result != null) if (result != null)
{ {
foreach (var path in result) foreach (var file in result)
{ {
System.Diagnostics.Debug.WriteLine($"Opened: {path}"); System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
} }
} }
} }

6
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@ -117,9 +117,9 @@ namespace ControlCatalog.ViewModels
PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical); PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical);
var compositeTransition = new CompositePageTransition(); var compositeTransition = new CompositePageTransition();
compositeTransition.PageTransitions.Add(PageTransitions[1].Transition); compositeTransition.PageTransitions.Add(PageTransitions[1].Transition!);
compositeTransition.PageTransitions.Add(PageTransitions[2].Transition); compositeTransition.PageTransitions.Add(PageTransitions[2].Transition!);
compositeTransition.PageTransitions.Add(PageTransitions[3].Transition); compositeTransition.PageTransitions.Add(PageTransitions[3].Transition!);
PageTransitions[4].Transition = compositeTransition; PageTransitions[4].Transition = compositeTransition;
PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration)); PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration));

37
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia; using Avalonia;
@ -31,20 +30,23 @@ namespace IntegrationTestApp
private void InitializeViewMenu() private void InitializeViewMenu()
{ {
var mainTabs = this.FindControl<TabControl>("MainTabs"); var mainTabs = this.Get<TabControl>("MainTabs");
var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1]; var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1];
foreach (TabItem tabItem in mainTabs.Items) if (mainTabs.Items is not null)
{ {
var menuItem = new NativeMenuItem foreach (TabItem tabItem in mainTabs.Items)
{ {
Header = (string)tabItem.Header!, var menuItem = new NativeMenuItem
IsChecked = tabItem.IsSelected, {
ToggleType = NativeMenuItemToggleType.Radio, Header = (string)tabItem.Header!,
}; IsChecked = tabItem.IsSelected,
ToggleType = NativeMenuItemToggleType.Radio,
menuItem.Click += (s, e) => tabItem.IsSelected = true; };
viewMenu.Menu.Items.Add(menuItem);
menuItem.Click += (s, e) => tabItem.IsSelected = true;
viewMenu?.Menu?.Items.Add(menuItem);
}
} }
} }
@ -99,6 +101,7 @@ namespace IntegrationTestApp
foreach (var window in lifetime.Windows) foreach (var window in lifetime.Windows)
{ {
window.Show();
if (window.WindowState == WindowState.Minimized) if (window.WindowState == WindowState.Minimized)
window.WindowState = WindowState.Normal; window.WindowState = WindowState.Normal;
} }
@ -106,8 +109,8 @@ namespace IntegrationTestApp
private void MenuClicked(object? sender, RoutedEventArgs e) private void MenuClicked(object? sender, RoutedEventArgs e)
{ {
var clickedMenuItemTextBlock = this.FindControl<TextBlock>("ClickedMenuItem"); var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");
clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
} }
private void OnButtonClick(object? sender, RoutedEventArgs e) private void OnButtonClick(object? sender, RoutedEventArgs e)
@ -115,13 +118,13 @@ namespace IntegrationTestApp
var source = e.Source as Button; var source = e.Source as Button;
if (source?.Name == "ComboBoxSelectionClear") if (source?.Name == "ComboBoxSelectionClear")
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = -1; this.Get<ComboBox>("BasicComboBox").SelectedIndex = -1;
if (source?.Name == "ComboBoxSelectFirst") if (source?.Name == "ComboBoxSelectFirst")
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0; this.Get<ComboBox>("BasicComboBox").SelectedIndex = 0;
if (source?.Name == "ListBoxSelectionClear") if (source?.Name == "ListBoxSelectionClear")
this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1; this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset") if (source?.Name == "MenuClickedMenuItemReset")
this.FindControl<TextBlock>("ClickedMenuItem").Text = "None"; this.Get<TextBlock>("ClickedMenuItem").Text = "None";
if (source?.Name == "ShowWindow") if (source?.Name == "ShowWindow")
ShowWindow(); ShowWindow();
if (source?.Name == "SendToBack") if (source?.Name == "SendToBack")

3
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -3,7 +3,7 @@
x:Class="IntegrationTestApp.ShowWindowTest" x:Class="IntegrationTestApp.ShowWindowTest"
Name="SecondaryWindow" Name="SecondaryWindow"
Title="Show Window Test"> Title="Show Window Test">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Label Grid.Column="0" Grid.Row="1">Client Size</Label> <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True" <TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}"/> Text="{Binding ClientSize, Mode=OneWay}"/>
@ -31,5 +31,6 @@
<ComboBoxItem>Maximized</ComboBoxItem> <ComboBoxItem>Maximized</ComboBoxItem>
<ComboBoxItem>Fullscreen</ComboBoxItem> <ComboBoxItem>Fullscreen</ComboBoxItem>
</ComboBox> </ComboBox>
<Button Name="HideButton" Grid.Row="8" Command="{Binding $parent[Window].Hide}">Hide</Button>
</Grid> </Grid>
</Window> </Window>

6
samples/RenderDemo/Pages/CustomSkiaPage.cs

@ -40,14 +40,16 @@ namespace RenderDemo.Pages
static Stopwatch St = Stopwatch.StartNew(); static Stopwatch St = Stopwatch.StartNew();
public void Render(IDrawingContextImpl context) public void Render(IDrawingContextImpl context)
{ {
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; var leaseFeature = context.GetFeature<ISkiaSharpApiLeaseFeature>();
if (canvas == null) if (leaseFeature == null)
using (var c = new DrawingContext(context, false)) using (var c = new DrawingContext(context, false))
{ {
c.DrawText(_noSkia, new Point()); c.DrawText(_noSkia, new Point());
} }
else else
{ {
using var lease = leaseFeature.Lease();
var canvas = lease.SkCanvas;
canvas.Save(); canvas.Save();
// create the first shader // create the first shader
var colors = new SKColor[] { var colors = new SKColor[] {

265
samples/RenderDemo/Pages/DrawingPage.xaml

@ -1,134 +1,141 @@
<UserControl <UserControl
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.DrawingPage"> x:Class="RenderDemo.Pages.DrawingPage">
<UserControl.Styles> <UserControl.Styles>
<Style> <Style>
<Style.Resources> <Style.Resources>
<DrawingGroup x:Key="Bulb"> <DrawingGroup x:Key="Bulb">
<DrawingGroup.Transform> <DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,0,-1028.4" /> <MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
</DrawingGroup.Transform> </DrawingGroup.Transform>
<DrawingGroup> <DrawingGroup>
<DrawingGroup.Transform> <DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" /> <MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
</DrawingGroup.Transform> </DrawingGroup.Transform>
<GeometryDrawing Brush="#FF7F8C8D" <GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" /> Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
</DrawingGroup> </DrawingGroup>
<GeometryDrawing Brush="#FFF39C12" <GeometryDrawing Brush="#FFF39C12"
Geometry="F1 M12,1030.4 C8.134,1030.4 5,1033.6 5,1037.6 5,1040.7 8.125,1043.5 9,1045.4 9.875,1047.2 9,1050.4 9,1050.4 L12,1049.9 15,1050.4 C15,1050.4 14.125,1047.2 15,1045.4 15.875,1043.5 19,1040.7 19,1037.6 19,1033.6 15.866,1030.4 12,1030.4 z" /> Geometry="F1 M12,1030.4 C8.134,1030.4 5,1033.6 5,1037.6 5,1040.7 8.125,1043.5 9,1045.4 9.875,1047.2 9,1050.4 9,1050.4 L12,1049.9 15,1050.4 C15,1050.4 14.125,1047.2 15,1045.4 15.875,1043.5 19,1040.7 19,1037.6 19,1033.6 15.866,1030.4 12,1030.4 z" />
<GeometryDrawing Brush="#FFF1C40F" <GeometryDrawing Brush="#FFF1C40F"
Geometry="F1 M12,1030.4 C15.866,1030.4 19,1033.6 19,1037.6 19,1040.7 15.875,1043.5 15,1045.4 14.125,1047.2 15,1050.4 15,1050.4 L12,1049.9 12,1030.4 z" /> Geometry="F1 M12,1030.4 C15.866,1030.4 19,1033.6 19,1037.6 19,1040.7 15.875,1043.5 15,1045.4 14.125,1047.2 15,1050.4 15,1050.4 L12,1049.9 12,1030.4 z" />
<GeometryDrawing Brush="#FFE67E22" <GeometryDrawing Brush="#FFE67E22"
Geometry="F1 M9,1036.4 L8,1037.4 12,1049.4 16,1037.4 15,1036.4 14,1037.4 13,1036.4 12,1037.4 11,1036.4 10,1037.4 9,1036.4 z M9,1037.4 L10,1038.4 10.5,1037.9 11,1037.4 11.5,1037.9 12,1038.4 12.5,1037.9 13,1037.4 13.5,1037.9 14,1038.4 15,1037.4 15.438,1037.8 12,1048.1 8.5625,1037.8 9,1037.4 z" /> Geometry="F1 M9,1036.4 L8,1037.4 12,1049.4 16,1037.4 15,1036.4 14,1037.4 13,1036.4 12,1037.4 11,1036.4 10,1037.4 9,1036.4 z M9,1037.4 L10,1038.4 10.5,1037.9 11,1037.4 11.5,1037.9 12,1038.4 12.5,1037.9 13,1037.4 13.5,1037.9 14,1038.4 15,1037.4 15.438,1037.8 12,1048.1 8.5625,1037.8 9,1037.4 z" />
<DrawingGroup> <DrawingGroup>
<DrawingGroup.Transform> <DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,9,1045.4" /> <MatrixTransform Matrix="1,0,0,1,9,1045.4" />
</DrawingGroup.Transform> </DrawingGroup.Transform>
<GeometryDrawing Brush="#FFBDC3C7"> <GeometryDrawing Brush="#FFBDC3C7">
<GeometryDrawing.Geometry> <GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,6,5" /> <RectangleGeometry Rect="0,0,6,5" />
</GeometryDrawing.Geometry> </GeometryDrawing.Geometry>
</GeometryDrawing> </GeometryDrawing>
</DrawingGroup> </DrawingGroup>
<GeometryDrawing Brush="#FF95A5A6" <GeometryDrawing Brush="#FF95A5A6"
Geometry="F1 M9,1045.4 L9,1050.4 12,1050.4 12,1049.4 15,1049.4 15,1048.4 12,1048.4 12,1047.4 15,1047.4 15,1046.4 12,1046.4 12,1045.4 9,1045.4 z" /> Geometry="F1 M9,1045.4 L9,1050.4 12,1050.4 12,1049.4 15,1049.4 15,1048.4 12,1048.4 12,1047.4 15,1047.4 15,1046.4 12,1046.4 12,1045.4 9,1045.4 z" />
<GeometryDrawing Brush="#FF7F8C8D" <GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M9,1046.4 L9,1047.4 12,1047.4 12,1046.4 9,1046.4 z M9,1048.4 L9,1049.4 12,1049.4 12,1048.4 9,1048.4 z" /> Geometry="F1 M9,1046.4 L9,1047.4 12,1047.4 12,1046.4 9,1046.4 z M9,1048.4 L9,1049.4 12,1049.4 12,1048.4 9,1048.4 z" />
</DrawingGroup> </DrawingGroup>
</Style.Resources> </Style.Resources>
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,Auto" <Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto,Auto,Auto"> ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Text="None" <TextBlock Text="None"
Margin="3" /> Margin="3" />
<Border Grid.Column="0" <Border Grid.Column="0"
Grid.Row="1" Grid.Row="1"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Left" HorizontalAlignment="Left"
BorderThickness="1" BorderThickness="1"
BorderBrush="Gray" BorderBrush="Gray"
Margin="5"> Margin="5">
<DrawingPresenter Drawing="{DynamicResource Bulb}" /> <Image>
</Border> <Image.Source>
<TextBlock Text="Fill" <DrawingImage Drawing="{DynamicResource Bulb}" />
Margin="3" </Image.Source>
Grid.Column="1" /> </Image>
<Border Grid.Column="1" </Border>
Grid.Row="1" <TextBlock Text="Fill"
VerticalAlignment="Top" Margin="3"
HorizontalAlignment="Left" Grid.Column="1" />
BorderThickness="1" <Border Grid.Column="1"
BorderBrush="Gray" Grid.Row="1"
Margin="5"> VerticalAlignment="Top"
<DrawingPresenter Drawing="{DynamicResource Bulb}" HorizontalAlignment="Left"
Width="100" BorderThickness="1"
Height="50" BorderBrush="Gray"
Stretch="Fill" /> Margin="5">
</Border> <Image Width="100" Height="50" Stretch="Fill">
<TextBlock Text="Uniform" <Image.Source>
Margin="3" <DrawingImage Drawing="{DynamicResource Bulb}" />
Grid.Column="2" /> </Image.Source>
<Border Grid.Column="2" </Image>
Grid.Row="1" </Border>
VerticalAlignment="Top" <TextBlock Text="Uniform"
HorizontalAlignment="Left" Margin="3"
BorderThickness="1" Grid.Column="2" />
BorderBrush="Gray" <Border Grid.Column="2"
Margin="5"> Grid.Row="1"
<DrawingPresenter Drawing="{DynamicResource Bulb}" VerticalAlignment="Top"
Width="100" HorizontalAlignment="Left"
Height="50" BorderThickness="1"
Stretch="Uniform" /> BorderBrush="Gray"
</Border> Margin="5">
<TextBlock Text="UniformToFill" <Image Width="100" Height="50" Stretch="Uniform">
Margin="3" <Image.Source>
Grid.Column="3" /> <DrawingImage Drawing="{DynamicResource Bulb}" />
<Border Grid.Column="3" </Image.Source>
Grid.Row="1" </Image>
VerticalAlignment="Top" </Border>
HorizontalAlignment="Left" <TextBlock Text="UniformToFill"
BorderThickness="1" Margin="3"
BorderBrush="Gray" Grid.Column="3" />
Margin="5"> <Border Grid.Column="3"
<DrawingPresenter Drawing="{DynamicResource Bulb}" Grid.Row="1"
Width="100" VerticalAlignment="Top"
Height="50" HorizontalAlignment="Left"
Stretch="UniformToFill" /> BorderThickness="1"
</Border> BorderBrush="Gray"
Margin="5">
<Image Width="100" Height="50" Stretch="UniformToFill">
<Image.Source>
<DrawingImage Drawing="{DynamicResource Bulb}" />
</Image.Source>
</Image>
</Border>
<!-- For comparison --> <!-- For comparison -->
<Ellipse Grid.Row="2" <Ellipse Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Width="100" Width="100"
Height="50" Height="50"
Stretch="None" Stretch="None"
Fill="Blue" Fill="Blue"
Margin="5"/> Margin="5"/>
<Ellipse Grid.Row="2" <Ellipse Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="100" Width="100"
Height="50" Height="50"
Stretch="Fill" Stretch="Fill"
Fill="Blue" Fill="Blue"
Margin="5" /> Margin="5" />
<Ellipse Grid.Row="2" <Ellipse Grid.Row="2"
Grid.Column="2" Grid.Column="2"
Width="100" Width="100"
Height="50" Height="50"
Stretch="Uniform" Stretch="Uniform"
Fill="Blue" Fill="Blue"
Margin="5" /> Margin="5" />
<Ellipse Grid.Row="2" <Ellipse Grid.Row="2"
Grid.Column="3" Grid.Column="3"
Width="100" Width="100"
Height="50" Height="50"
Stretch="UniformToFill" Stretch="UniformToFill"
Fill="Blue" Fill="Blue"
Margin="5" /> Margin="5" />
</Grid> </Grid>
</UserControl> </UserControl>

4
samples/RenderDemo/Pages/TextFormatterPage.axaml.cs

@ -78,7 +78,7 @@ namespace RenderDemo.Pages
_defaultProperties = defaultProperties; _defaultProperties = defaultProperties;
} }
public TextRun? GetTextRun(int textSourceIndex) public TextRun GetTextRun(int textSourceIndex)
{ {
if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength) if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength)
{ {
@ -107,7 +107,7 @@ namespace RenderDemo.Pages
public Control Control => _control; public Control Control => _control;
public override Size Size => _control.DesiredSize; public override Size Size => _control.DesiredSize;
public override double Baseline => 0; public override double Baseline => 0;
public override TextRunProperties? Properties { get; } public override TextRunProperties Properties { get; }
public override void Draw(DrawingContext drawingContext, Point origin) public override void Draw(DrawingContext drawingContext, Point origin)
{ {

25
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -8,7 +8,8 @@ using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Skia; using Avalonia.Rendering.Composition;
using Avalonia.OpenGL;
namespace Avalonia namespace Avalonia
{ {
@ -16,10 +17,8 @@ namespace Avalonia
{ {
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new() public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
{ {
var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
return builder return builder
.UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android") .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
.UseSkia(); .UseSkia();
} }
} }
@ -42,9 +41,11 @@ namespace Avalonia.Android
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
public static void Initialize(AndroidPlatformOptions options) internal static Compositor Compositor { get; private set; }
public static void Initialize()
{ {
Options = options; Options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>() .Bind<IClipboard>().ToTransient<ClipboardImpl>()
@ -58,16 +59,24 @@ namespace Avalonia.Android
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
if (options.UseGpu) if (Options.UseGpu)
{ {
EglPlatformOpenGlInterface.TryInitialize(); EglPlatformOpenGlInterface.TryInitialize();
} }
if (Options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
}
} }
} }
public sealed class AndroidPlatformOptions public sealed class AndroidPlatformOptions
{ {
public bool UseDeferredRendering { get; set; } = true; public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true; public bool UseGpu { get; set; } = true;
public bool UseCompositor { get; set; } = true;
} }
} }

66
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -14,6 +14,7 @@ namespace Avalonia.Android
internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface
{ {
private Handler _handler; private Handler _handler;
private static Thread s_uiThread;
public AndroidThreadingInterface() public AndroidThreadingInterface()
{ {
@ -26,46 +27,33 @@ namespace Avalonia.Android
{ {
if (interval.TotalMilliseconds < 10) if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10); interval = TimeSpan.FromMilliseconds(10);
object l = new object();
var stopped = false; var stopped = false;
Timer timer = null; Timer timer = null;
var scheduled = false;
timer = new Timer(_ => timer = new Timer(_ =>
{ {
lock (l) if (stopped)
return;
EnsureInvokeOnMainThread(() =>
{ {
if (stopped) try
{ {
timer.Dispose(); tick();
return;
} }
if (scheduled) finally
return;
scheduled = true;
EnsureInvokeOnMainThread(() =>
{ {
try if (!stopped)
{ timer.Change(interval, Timeout.InfiniteTimeSpan);
tick(); }
} });
finally },
{ null, interval, Timeout.InfiniteTimeSpan);
lock (l)
{
scheduled = false;
}
}
});
}
}, null, TimeSpan.Zero, interval);
return Disposable.Create(() => return Disposable.Create(() =>
{ {
lock (l) stopped = true;
{ timer.Dispose();
stopped = true;
timer.Dispose();
}
}); });
} }
@ -76,7 +64,25 @@ namespace Avalonia.Android
EnsureInvokeOnMainThread(() => Signaled?.Invoke(null)); EnsureInvokeOnMainThread(() => Signaled?.Invoke(null));
} }
public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); public bool CurrentThreadIsLoopThread
{
get
{
if (s_uiThread != null)
return s_uiThread == Thread.CurrentThread;
var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23)
? Looper.MainLooper.IsCurrentThread
: Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
if (isOnMainThread)
{
s_uiThread = Thread.CurrentThread;
return true;
}
return false;
}
}
public event Action<DispatcherPriority?> Signaled; public event Action<DispatcherPriority?> Signaled;
} }
} }

12
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -47,18 +47,6 @@ namespace Avalonia.Android
} }
} }
[Obsolete("deprecated")]
public override void Invalidate(global::Android.Graphics.Rect dirty)
{
Invalidate();
}
[Obsolete("deprecated")]
public override void Invalidate(int l, int t, int r, int b)
{
Invalidate();
}
public void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) public void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
{ {
Log.Info("AVALONIA", "Surface Changed"); Log.Info("AVALONIA", "Surface Changed");

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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Views; using Android.Views;
@ -19,6 +20,7 @@ using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
@ -29,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IFramebufferPlatformSurface _framebuffer; private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper; private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper; private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly ITextInputMethodImpl _textInputMethod; private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view; private ViewImpl _view;
@ -38,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view = new ViewImpl(avaloniaView.Context, this, placeOnTop); _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view); _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _pointerHelper = new AndroidMotionEventsHelper(this);
GetAvaloniaPointFromEvent);
_gl = GlPlatformSurface.TryCreate(this); _gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this); _framebuffer = new FramebufferManager(this);
@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle }; public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) => public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseDeferredRendering AndroidPlatform.Options.UseCompositor
? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()) { RenderOnlyOnRenderThread = true } ? new CompositingRenderer(root, AndroidPlatform.Compositor)
: new ImmediateRenderer(root); : AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root);
public virtual void Hide() public virtual void Hide()
{ {
@ -157,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_tl.Draw(); _tl.Draw();
} }
protected override bool DispatchGenericPointerEvent(MotionEvent e)
{
bool callBase;
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
return result != null ? result.Value : baseResult;
}
public override bool DispatchTouchEvent(MotionEvent e) public override bool DispatchTouchEvent(MotionEvent e)
{ {
bool callBase; bool callBase;
bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase); bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false; bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult; return result != null ? result.Value : baseResult;

251
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using Android.Views;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Collections.Pooled;
using Avalonia.Input;
using Avalonia.Input.Raw;
#nullable enable
namespace Avalonia.Android.Platform.Specific.Helpers
{
internal class AndroidMotionEventsHelper : IDisposable
{
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private static readonly float s_radiansToDegree = (float)(180f * Math.PI);
private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice;
private readonly PenDevice _penDevice;
private readonly TopLevelImpl _view;
private bool _disposed;
public AndroidMotionEventsHelper(TopLevelImpl view)
{
_touchDevice = new TouchDevice();
_penDevice = new PenDevice();
_mouseDevice = new MouseDevice();
_view = view;
}
public bool? DispatchMotionEvent(MotionEvent e, out bool callBase)
{
callBase = true;
if (_disposed)
{
return null;
}
var eventTime = (ulong)DateTime.Now.Millisecond;
var inputRoot = _view.InputRoot;
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);
if (actionMasked == MotionEventActions.Move)
{
for (int index = 0; index < e.PointerCount; index++)
{
var toolType = e.GetToolType(index);
var device = GetDevice(toolType);
var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
var point = CreatePoint(e, index);
modifiers |= GetToolModifiers(toolType);
// ButtonState reports only mouse buttons, but not touch or stylus pointer.
if (toolType != MotionEventToolType.Mouse)
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index))
{
IntermediatePoints = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
var site = e.HistorySize;
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = site;
for (int pos = 0; pos < site; pos++)
{
s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos));
}
return s_intermediatePointsPooledList;
})
};
_view.Input(args);
}
}
else
{
var index = e.ActionIndex;
var toolType = e.GetToolType(index);
var device = GetDevice(toolType);
modifiers |= GetToolModifiers(toolType);
var point = CreatePoint(e, index);
if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse)
{
var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
_view.Input(args);
}
else
{
var eventType = GetActionType(e, actionMasked, toolType);
if (eventType >= 0)
{
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index));
_view.Input(args);
}
}
}
return true;
}
private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState)
{
var modifiers = RawInputModifiers.None;
if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn))
{
modifiers |= RawInputModifiers.Shift;
}
if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn))
{
modifiers |= RawInputModifiers.Control;
}
if (metaState.HasAnyFlag(MetaKeyStates.AltOn))
{
modifiers |= RawInputModifiers.Alt;
}
if (metaState.HasAnyFlag(MetaKeyStates.MetaOn))
{
modifiers |= RawInputModifiers.Meta;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Primary))
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary))
{
modifiers |= RawInputModifiers.RightMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary))
{
modifiers |= RawInputModifiers.MiddleMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Back))
{
modifiers |= RawInputModifiers.XButton1MouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Forward))
{
modifiers |= RawInputModifiers.XButton2MouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
{
modifiers |= RawInputModifiers.PenBarrelButton;
}
return modifiers;
}
#pragma warning disable CA1416 // Validate platform compatibility
private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType)
{
var isTouch = toolType == MotionEventToolType.Finger;
var isMouse = toolType == MotionEventToolType.Mouse;
switch (actionMasked)
{
// DOWN
case MotionEventActions.Down when !isMouse:
case MotionEventActions.PointerDown when !isMouse:
return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown;
case MotionEventActions.ButtonPress:
return e.ActionButton switch
{
MotionEventButtonState.Back => RawPointerEventType.XButton1Down,
MotionEventButtonState.Forward => RawPointerEventType.XButton2Down,
MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown,
MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown,
MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown,
MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown,
MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown,
_ => RawPointerEventType.LeftButtonDown
};
// UP
case MotionEventActions.Up when !isMouse:
case MotionEventActions.PointerUp when !isMouse:
return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp;
case MotionEventActions.ButtonRelease:
return e.ActionButton switch
{
MotionEventButtonState.Back => RawPointerEventType.XButton1Up,
MotionEventButtonState.Forward => RawPointerEventType.XButton2Up,
MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp,
MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp,
MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp,
MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp,
MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp,
_ => RawPointerEventType.LeftButtonUp
};
// MOVE
case MotionEventActions.Outside:
case MotionEventActions.HoverMove:
case MotionEventActions.Move:
return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
// CANCEL
case MotionEventActions.Cancel:
return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow;
default:
return (RawPointerEventType)(-1);
}
}
#pragma warning restore CA1416 // Validate platform compatibility
private IPointerDevice GetDevice(MotionEventToolType type)
{
return type switch
{
MotionEventToolType.Mouse => _mouseDevice,
MotionEventToolType.Stylus => _penDevice,
MotionEventToolType.Eraser => _penDevice,
MotionEventToolType.Finger => _touchDevice,
_ => _touchDevice
};
}
private RawPointerPoint CreatePoint(MotionEvent e, int index)
{
return new RawPointerPoint
{
Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
Twist = e.GetOrientation(index) * s_radiansToDegree
};
}
private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos)
{
return new RawPointerPoint
{
Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree
};
}
private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType)
{
// Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here.
return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None;
}
public void Dispose()
{
_disposed = true;
}
}
}

85
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -1,85 +0,0 @@
using System;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.Specific.Helpers
{
public class AndroidTouchEventsHelper<TView> : IDisposable where TView : ITopLevelImpl, IAndroidView
{
private TView _view;
public bool HandleEvents { get; set; }
public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, int, Point> getPointfunc)
{
this._view = view;
HandleEvents = true;
_getPointFunc = getPointfunc;
_getInputRoot = getInputRoot;
}
private TouchDevice _touchDevice = new TouchDevice();
private Func<MotionEvent, int, Point> _getPointFunc;
private Func<IInputRoot> _getInputRoot;
public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
{
if (!HandleEvents)
{
callBase = true;
return null;
}
var eventTime = DateTime.Now;
//Basic touch support
var pointerEventType = e.Action switch
{
MotionEventActions.Down => RawPointerEventType.TouchBegin,
MotionEventActions.Up => RawPointerEventType.TouchEnd,
MotionEventActions.Cancel => RawPointerEventType.TouchCancel,
_ => RawPointerEventType.TouchUpdate
};
if (e.Action.HasFlag(MotionEventActions.PointerDown))
{
pointerEventType = RawPointerEventType.TouchBegin;
}
if (e.Action.HasFlag(MotionEventActions.PointerUp))
{
pointerEventType = RawPointerEventType.TouchEnd;
}
for (int i = 0; i < e.PointerCount; i++)
{
//if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event
var point = _getPointFunc(e, i);
double x = _view.View.GetX();
double y = _view.View.GetY();
double r = x + _view.View.Width;
double b = y + _view.View.Height;
if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y)
{
var inputRoot = _getInputRoot();
var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot,
i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i));
_view.Input(mouseEvent);
}
}
callBase = true;
//if return false events for move and up are not received!!!
return e.Action != MotionEventActions.Up;
}
public void Dispose()
{
HandleEvents = false;
}
}
}

17
src/Avalonia.Base/Animation/Animation.cs

@ -172,23 +172,6 @@ namespace Avalonia.Animation
set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); } set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
} }
/// <summary>
/// Obsolete: Do not use this property, use <see cref="IterationCount"/> instead.
/// </summary>
/// <value></value>
[Obsolete("This property has been superceded by IterationCount.")]
public string RepeatCount
{
get { return IterationCount.ToString(); }
set
{
var val = value.ToUpper();
val = val.Replace("LOOP", "INFINITE");
val = val.Replace("NONE", "1");
IterationCount = IterationCount.Parse(val);
}
}
/// <summary> /// <summary>
/// Gets the children of the <see cref="Animation"/>. /// Gets the children of the <see cref="Animation"/>.
/// </summary> /// </summary>

13
src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs

@ -37,17 +37,4 @@ namespace Avalonia.Animation.Animators
} }
} }
[Obsolete("Use ISolidColorBrushAnimator instead")]
public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
{
public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return progress >= 0.5 ? newValue : oldValue;
}
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
}
} }

35
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -468,41 +468,6 @@ namespace Avalonia
}); });
} }
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.
/// </summary>
/// <typeparam name="TTarget">The type of the property change sender.</typeparam>
/// <param name="observable">The property changed observable.</param>
/// <param name="handler">Given a TTarget, returns the handler.</param>
/// <returns>A disposable that can be used to terminate the subscription.</returns>
[Obsolete("Use overload taking Action<TTarget, AvaloniaPropertyChangedEventArgs>.")]
public static IDisposable AddClassHandler<TTarget>(
this IObservable<AvaloniaPropertyChangedEventArgs> observable,
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
where TTarget : class
{
return observable.Subscribe(e => SubscribeAdapter(e, handler));
}
/// <summary>
/// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
/// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.
/// </summary>
/// <typeparam name="TTarget">The sender type to accept.</typeparam>
/// <param name="e">The event args.</param>
/// <param name="handler">Given a TTarget, returns the handler.</param>
private static void SubscribeAdapter<TTarget>(
AvaloniaPropertyChangedEventArgs e,
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
where TTarget : class
{
if (e.Sender is TTarget target)
{
handler(target)(e);
}
}
private class BindingAdaptor : IBinding private class BindingAdaptor : IBinding
{ {
private IObservable<object?> _source; private IObservable<object?> _source;

15
src/Avalonia.Base/AvaloniaProperty`1.cs

@ -30,21 +30,6 @@ namespace Avalonia
_changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>(); _changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
} }
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param>
[Obsolete("Use constructor with AvaloniaProperty<TValue> instead.", true)]
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
AvaloniaPropertyMetadata? metadata)
: this(source as AvaloniaProperty<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class. /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary> /// </summary>

15
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -140,21 +140,6 @@ namespace Avalonia.Collections
} }
} }
[Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
this IAvaloniaReadOnlyList<TSource> collection,
Func<TSource, TDerived> select)
{
var result = new AvaloniaList<TDerived>();
collection.ForEachItem(
(i, item) => result.Insert(i, select(item)),
(i, item) => result.RemoveAt(i),
() => result.Clear());
return result;
}
/// <summary> /// <summary>
/// Listens for property changed events from all items in a collection. /// Listens for property changed events from all items in a collection.
/// </summary> /// </summary>

56
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -29,7 +30,11 @@ namespace Avalonia.Controls
public object? this[object key] public object? this[object key]
{ {
get => _inner?[key]; get
{
TryGetValue(key, out var value);
return value;
}
set set
{ {
Inner[key] = value; Inner[key] = value;
@ -119,6 +124,12 @@ namespace Avalonia.Controls
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
} }
public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
{
Inner.Add(key, new DeferredItem(factory));
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
public void Clear() public void Clear()
{ {
if (_inner?.Count > 0) if (_inner?.Count > 0)
@ -143,10 +154,8 @@ namespace Avalonia.Controls
public bool TryGetResource(object key, out object? value) public bool TryGetResource(object key, out object? value)
{ {
if (_inner is not null && _inner.TryGetValue(key, out value)) if (TryGetValue(key, out value))
{
return true; return true;
}
if (_mergedDictionaries != null) if (_mergedDictionaries != null)
{ {
@ -165,12 +174,28 @@ namespace Avalonia.Controls
public bool TryGetValue(object key, out object? value) public bool TryGetValue(object key, out object? value)
{ {
if (_inner is not null) if (_inner is not null && _inner.TryGetValue(key, out value))
return _inner.TryGetValue(key, out value); {
if (value is DeferredItem deffered)
{
_inner[key] = value = deffered.Factory(null) switch
{
ITemplateResult t => t.Result,
object v => v,
_ => null,
};
}
return true;
}
value = null; value = null;
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();
}
void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item) void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
{ {
@ -198,12 +223,17 @@ namespace Avalonia.Controls
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool ContainsDeferredKey(object key)
{ {
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator(); if (_inner is not null && _inner.TryGetValue(key, out var result))
} {
return result is DeferredItem;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return false;
}
void IResourceProvider.AddOwner(IResourceHost owner) void IResourceProvider.AddOwner(IResourceHost owner)
{ {
@ -258,5 +288,11 @@ namespace Avalonia.Controls
} }
} }
} }
private class DeferredItem
{
public DeferredItem(Func<IServiceProvider?, object?> factory) => Factory = factory;
public Func<IServiceProvider?, object?> Factory { get; }
}
} }
} }

5
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -132,6 +132,11 @@ namespace Avalonia.Controls
{ {
_target.OwnerChanged += OwnerChanged; _target.OwnerChanged += OwnerChanged;
_owner = _target.Owner; _owner = _target.Owner;
if (_owner is object)
{
_owner.ResourcesChanged += ResourcesChanged;
}
} }
protected override void Deinitialize() protected override void Deinitialize()

8
src/Avalonia.Base/Controls/Templates/ITemplateResult.cs

@ -0,0 +1,8 @@
namespace Avalonia.Controls.Templates
{
public interface ITemplateResult
{
public object? Result { get; }
public INameScope NameScope { get; }
}
}

3
src/Avalonia.Controls/Templates/TemplateResult.cs → src/Avalonia.Base/Controls/Templates/TemplateResult.cs

@ -1,9 +1,10 @@
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
public class TemplateResult<T> public class TemplateResult<T> : ITemplateResult
{ {
public T Result { get; } public T Result { get; }
public INameScope NameScope { get; } public INameScope NameScope { get; }
object? ITemplateResult.Result => Result;
public TemplateResult(T result, INameScope nameScope) public TemplateResult(T result, INameScope nameScope)
{ {

15
src/Avalonia.Base/DirectPropertyBase.cs

@ -29,21 +29,6 @@ namespace Avalonia
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param>
[Obsolete("Use constructor with DirectPropertyBase<TValue> instead.", true)]
protected DirectPropertyBase(
AvaloniaProperty source,
Type ownerType,
AvaloniaPropertyMetadata metadata)
: this(source as DirectPropertyBase<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class. /// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
/// </summary> /// </summary>

4
src/Avalonia.Base/EnumExtensions.cs

@ -8,10 +8,6 @@ namespace Avalonia
/// </summary> /// </summary>
public static class EnumExtensions public static class EnumExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("This method is obsolete. Use HasAllFlags instead.")]
public static bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
=> value.HasAllFlags(flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasAllFlags<T>(this T value, T flags) where T : unmanaged, Enum public static unsafe bool HasAllFlags<T>(this T value, T flags) where T : unmanaged, Enum

3
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@ -158,6 +158,8 @@ namespace Avalonia.Input
ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers); ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers);
} }
} }
_lastPointer = (pointer, root.PointToScreen(position));
} }
private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element, private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
@ -195,7 +197,6 @@ namespace Avalonia.Input
} }
el = root.PointerOverElement = element; el = root.PointerOverElement = element;
_lastPointer = (pointer, root.PointToScreen(position));
e.RoutedEvent = InputElement.PointerEnteredEvent; e.RoutedEvent = InputElement.PointerEnteredEvent;

30
src/Avalonia.Base/Input/PointerPoint.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact. /// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact.
/// </summary> /// </summary>
public sealed class PointerPoint public struct PointerPoint
{ {
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties) public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{ {
@ -33,47 +33,47 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Provides extended properties for a PointerPoint object. /// Provides extended properties for a PointerPoint object.
/// </summary> /// </summary>
public sealed class PointerPointProperties public struct PointerPointProperties
{ {
/// <summary> /// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device. /// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.
/// </summary> /// </summary>
public bool IsLeftButtonPressed { get; } public bool IsLeftButtonPressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device. /// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device.
/// </summary> /// </summary>
public bool IsMiddleButtonPressed { get; } public bool IsMiddleButtonPressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device. /// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device.
/// </summary> /// </summary>
public bool IsRightButtonPressed { get; } public bool IsRightButtonPressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1). /// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1).
/// </summary> /// </summary>
public bool IsXButton1Pressed { get; } public bool IsXButton1Pressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2). /// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2).
/// </summary> /// </summary>
public bool IsXButton2Pressed { get; } public bool IsXButton2Pressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed. /// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed.
/// </summary> /// </summary>
public bool IsBarrelButtonPressed { get; } public bool IsBarrelButtonPressed { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the input is from a pen eraser. /// Gets a value that indicates whether the input is from a pen eraser.
/// </summary> /// </summary>
public bool IsEraser { get; } public bool IsEraser { get; } = false;
/// <summary> /// <summary>
/// Gets a value that indicates whether the digitizer pen is inverted. /// Gets a value that indicates whether the digitizer pen is inverted.
/// </summary> /// </summary>
public bool IsInverted { get; } public bool IsInverted { get; } = false;
/// <summary> /// <summary>
/// Gets the clockwise rotation in degrees of a pen device around its own major axis (such as when the user spins the pen in their fingers). /// Gets the clockwise rotation in degrees of a pen device around its own major axis (such as when the user spins the pen in their fingers).
@ -81,7 +81,7 @@ namespace Avalonia.Input
/// <returns> /// <returns>
/// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0. /// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0.
/// </returns> /// </returns>
public float Twist { get; } public float Twist { get; } = 0.0F;
/// <summary> /// <summary>
/// Gets a value that indicates the force that the pointer device (typically a pen/stylus) exerts on the surface of the digitizer. /// Gets a value that indicates the force that the pointer device (typically a pen/stylus) exerts on the surface of the digitizer.
@ -97,7 +97,7 @@ namespace Avalonia.Input
/// <returns> /// <returns>
/// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted to the right of perpendicular, and between 0.0 and -90.0 when tilted to the left of perpendicular. The default value is 0.0. /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted to the right of perpendicular, and between 0.0 and -90.0 when tilted to the left of perpendicular. The default value is 0.0.
/// </returns> /// </returns>
public float XTilt { get; } public float XTilt { get; } = 0.0F;
/// <summary> /// <summary>
/// Gets the plane angle between the X-Z plane and the plane that contains the X axis and the axis of the input device (typically a pen/stylus). /// Gets the plane angle between the X-Z plane and the plane that contains the X axis and the axis of the input device (typically a pen/stylus).
@ -105,14 +105,14 @@ namespace Avalonia.Input
/// <returns> /// <returns>
/// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted towards the user, and between 0.0 and -90.0 when tilted away from the user. The default value is 0.0. /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted towards the user, and between 0.0 and -90.0 when tilted away from the user. The default value is 0.0.
/// </returns> /// </returns>
public float YTilt { get; } public float YTilt { get; } = 0.0F;
/// <summary> /// <summary>
/// Gets the kind of pointer state change. /// Gets the kind of pointer state change.
/// </summary> /// </summary>
public PointerUpdateKind PointerUpdateKind { get; } public PointerUpdateKind PointerUpdateKind { get; } = PointerUpdateKind.LeftButtonPressed;
private PointerPointProperties() public PointerPointProperties()
{ {
} }

4
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw
/// </summary> /// </summary>
public Point Position { get; set; } public Point Position { get; set; }
/// <inheritdoc cref="PointerPointProperties.Twist" />
public float Twist { get; set; } public float Twist { get; set; }
/// <inheritdoc cref="PointerPointProperties.Pressure" />
public float Pressure { get; set; } public float Pressure { get; set; }
/// <inheritdoc cref="PointerPointProperties.XTilt" />
public float XTilt { get; set; } public float XTilt { get; set; }
/// <inheritdoc cref="PointerPointProperties.YTilt" />
public float YTilt { get; set; } public float YTilt { get; set; }

18
src/Avalonia.Base/Interactivity/RoutedEvent.cs

@ -113,24 +113,6 @@ namespace Avalonia.Interactivity
{ {
} }
[Obsolete("Use overload taking Action<TTarget, TEventArgs>.")]
public IDisposable AddClassHandler<TTarget>(
Func<TTarget, Action<TEventArgs>> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false)
where TTarget : class, IInteractive
{
void Adapter(object? sender, RoutedEventArgs e)
{
if (sender is TTarget target && e is TEventArgs args)
{
handler(target)(args);
}
}
return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo);
}
public IDisposable AddClassHandler<TTarget>( public IDisposable AddClassHandler<TTarget>(
Action<TTarget, TEventArgs> handler, Action<TTarget, TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,

11
src/Avalonia.Base/Layout/ILayoutManager.cs

@ -44,17 +44,6 @@ namespace Avalonia.Layout
/// </remarks> /// </remarks>
void ExecuteInitialLayoutPass(); void ExecuteInitialLayoutPass();
/// <summary>
/// Executes the initial layout pass on a layout root.
/// </summary>
/// <param name="root">The control to lay out.</param>
/// <remarks>
/// You should not usually need to call this method explictly, the layout root will call
/// it to carry out the initial layout of the control.
/// </remarks>
[Obsolete("Call ExecuteInitialLayoutPass without parameter")]
void ExecuteInitialLayoutPass(ILayoutRoot root);
/// <summary> /// <summary>
/// Registers a control as wanting to receive effective viewport notifications. /// Registers a control as wanting to receive effective viewport notifications.
/// </summary> /// </summary>

11
src/Avalonia.Base/Layout/LayoutManager.cs

@ -196,17 +196,6 @@ namespace Avalonia.Layout
ExecuteLayoutPass(); ExecuteLayoutPass();
} }
[Obsolete("Call ExecuteInitialLayoutPass without parameter")]
public void ExecuteInitialLayoutPass(ILayoutRoot root)
{
if (root != _owner)
{
throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
}
ExecuteInitialLayoutPass();
}
public void Dispose() public void Dispose()
{ {
_disposed = true; _disposed = true;

28
src/Avalonia.Base/Layout/Layoutable.cs

@ -460,20 +460,6 @@ namespace Avalonia.Layout
_effectiveViewportChanged?.Invoke(this, e); _effectiveViewportChanged?.Invoke(this, e);
} }
/// <summary>
/// Marks a property as affecting the control's measurement.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateMeasure"/> to be called on the element.
/// </remarks>
[Obsolete("Use AffectsMeasure<T> and specify the control type.")]
protected static void AffectsMeasure(params AvaloniaProperty[] properties)
{
AffectsMeasure<Layoutable>(properties);
}
/// <summary> /// <summary>
/// Marks a property as affecting the control's measurement. /// Marks a property as affecting the control's measurement.
/// </summary> /// </summary>
@ -497,20 +483,6 @@ namespace Avalonia.Layout
} }
} }
/// <summary>
/// Marks a property as affecting the control's arrangement.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateArrange"/> to be called on the element.
/// </remarks>
[Obsolete("Use AffectsArrange<T> and specify the control type.")]
protected static void AffectsArrange(params AvaloniaProperty[] properties)
{
AffectsArrange<Layoutable>(properties);
}
/// <summary> /// <summary>
/// Marks a property as affecting the control's arrangement. /// Marks a property as affecting the control's arrangement.
/// </summary> /// </summary>

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

@ -228,6 +228,8 @@ namespace Avalonia.Media
throw new NotImplementedException(); throw new NotImplementedException();
} }
public object? GetFeature(Type t) => null;
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

16
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -89,22 +89,6 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Dispose(); PlatformImpl.Dispose();
} }
/// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>
/// <param name="format">The pixel format.</param>
/// <param name="data">The pointer to the source bytes.</param>
/// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="stride">The number of bytes per row.</param>
[Obsolete("Use overload taking an AlphaFormat.")]
public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{
var ri = GetFactory();
PlatformImpl = RefCountable.Create(ri
.LoadBitmap(format, ri.DefaultAlphaFormat, data, size, dpi, stride));
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </summary>

14
src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs

@ -9,18 +9,6 @@ namespace Avalonia.Media.Imaging
/// </summary> /// </summary>
public class WriteableBitmap : Bitmap public class WriteableBitmap : Bitmap
{ {
/// <summary>
/// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
/// </summary>
/// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="format">The pixel format (optional).</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
[Obsolete("Use overload taking an AlphaFormat.")]
public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
: base(CreatePlatformImpl(size, dpi, format, null))
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WriteableBitmap"/> class. /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
@ -30,7 +18,7 @@ namespace Avalonia.Media.Imaging
/// <param name="format">The pixel format (optional).</param> /// <param name="format">The pixel format (optional).</param>
/// <param name="alphaFormat">The alpha format (optional).</param> /// <param name="alphaFormat">The alpha format (optional).</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns> /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat) public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
: base(CreatePlatformImpl(size, dpi, format, alphaFormat)) : base(CreatePlatformImpl(size, dpi, format, alphaFormat))
{ {
} }

15
src/Avalonia.Base/Media/KnownColors.cs

@ -1,10 +1,11 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.SourceGenerator;
namespace Avalonia.Media namespace Avalonia.Media
{ {
internal static class KnownColors internal static partial class KnownColors
{ {
private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames; private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames;
private static readonly IReadOnlyDictionary<uint, string> _knownColors; private static readonly IReadOnlyDictionary<uint, string> _knownColors;
@ -12,23 +13,25 @@ namespace Avalonia.Media
private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes; private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes;
#endif #endif
[GenerateEnumValueDictionary()]
private static partial Dictionary<string, KnownColor> GetKnownColors();
static KnownColors() static KnownColors()
{ {
var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase); var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase);
var knownColors = new Dictionary<uint, string>(); var knownColors = new Dictionary<uint, string>();
foreach (var field in typeof(KnownColor).GetRuntimeFields()) foreach (var field in GetKnownColors())
{ {
if (field.FieldType != typeof(KnownColor)) continue; var knownColor = field.Value;
var knownColor = (KnownColor)field.GetValue(null)!;
if (knownColor == KnownColor.None) continue; if (knownColor == KnownColor.None) continue;
knownColorNames.Add(field.Name, knownColor); knownColorNames.Add(field.Key, knownColor);
// some known colors have the same value, so use the first // some known colors have the same value, so use the first
if (!knownColors.ContainsKey((uint)knownColor)) if (!knownColors.ContainsKey((uint)knownColor))
{ {
knownColors.Add((uint)knownColor, field.Name); knownColors.Add((uint)knownColor, field.Key);
} }
} }

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

@ -172,6 +172,20 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="custom">Custom draw operation</param> /// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom); void Custom(ICustomDrawOperation custom);
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>
object? GetFeature(Type t);
}
public static class DrawingContextImplExtensions
{
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>
public static T? GetFeature<T>(this IDrawingContextImpl context) where T : class =>
(T?)context.GetFeature(typeof(T));
} }
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl

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

@ -12,6 +12,11 @@ public class BclStorageFile : IStorageBookmarkFile
{ {
private readonly FileInfo _fileInfo; private readonly FileInfo _fileInfo;
public BclStorageFile(string fileName)
{
_fileInfo = new FileInfo(fileName);
}
public BclStorageFile(FileInfo fileInfo) public BclStorageFile(FileInfo fileInfo)
{ {
_fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo)); _fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
@ -27,15 +32,14 @@ public class BclStorageFile : IStorageBookmarkFile
public Task<StorageItemProperties> GetBasicPropertiesAsync() public Task<StorageItemProperties> GetBasicPropertiesAsync()
{ {
var props = new StorageItemProperties();
if (_fileInfo.Exists) if (_fileInfo.Exists)
{ {
props = new StorageItemProperties( return Task.FromResult(new StorageItemProperties(
(ulong)_fileInfo.Length, (ulong)_fileInfo.Length,
_fileInfo.CreationTimeUtc, _fileInfo.CreationTimeUtc,
_fileInfo.LastAccessTimeUtc); _fileInfo.LastAccessTimeUtc));
} }
return Task.FromResult(props); return Task.FromResult(new StorageItemProperties());
} }
public Task<IStorageFolder?> GetParentAsync() public Task<IStorageFolder?> GetParentAsync()

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

@ -14,6 +14,15 @@ public class BclStorageFolder : IStorageBookmarkFolder
{ {
private readonly DirectoryInfo _directoryInfo; private readonly DirectoryInfo _directoryInfo;
public BclStorageFolder(string path)
{
_directoryInfo = new DirectoryInfo(path);
if (!_directoryInfo.Exists)
{
throw new ArgumentException("Directory must exist");
}
}
public BclStorageFolder(DirectoryInfo directoryInfo) public BclStorageFolder(DirectoryInfo directoryInfo)
{ {
_directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo)); _directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));

2
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -71,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); _compositor.InvokeWhenReadyForNextCommit(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>

13
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition
internal IEasing DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit; private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new(); private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary> /// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU /// Creates a new compositor on a specified render loop that would use a particular GPU
@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition
if (_invokeOnNextCommit != null) if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed); ScheduleCommitCallbacks(batch.Completed);
return batch.Completed; return _lastBatchCompleted = batch.Completed;
} }
async void ScheduleCommitCallbacks(Task task) async void ScheduleCommitCallbacks(Task task)
@ -139,5 +140,15 @@ namespace Avalonia.Rendering.Composition
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action); _invokeOnNextCommit.Add(action);
} }
public void InvokeWhenReadyForNextCommit(Action action)
{
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted)
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition);
else
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
}
} }
} }

2
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
++_drawOperationIndex; ++_drawOperationIndex;
} }
public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {

3
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -1,3 +1,4 @@
using System;
using System.Numerics; using System.Numerics;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.Custom(custom); _impl.Custom(custom);
} }
public object? GetFeature(Type t) => _impl.GetFeature(t);
public class VisualBrushRenderer : IVisualBrushRenderer public class VisualBrushRenderer : IVisualBrushRenderer
{ {
public CompositionDrawList? VisualBrushDrawList { get; set; } public CompositionDrawList? VisualBrushDrawList { get; set; }

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

@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server
if (Root == null) if (Root == null)
return; return;
if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
_renderTarget!.Dispose();
_renderTarget = null;
}
_renderTarget ??= _renderTargetFactory(); _renderTarget ??= _renderTargetFactory();
Compositor.UpdateServerTime(); Compositor.UpdateServerTime();

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server
private void RenderCore() private void RenderCore()
{ {
ApplyPendingBatches(); ApplyPendingBatches();
CompletePendingBatches();
foreach(var animation in _activeAnimations) foreach(var animation in _activeAnimations)
_animationsToUpdate.Add(animation); _animationsToUpdate.Add(animation);
@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server
foreach (var t in _activeTargets) foreach (var t in _activeTargets)
t.Render(); t.Render();
CompletePendingBatches();
} }
public void AddCompositionTarget(ServerCompositionTarget target) public void AddCompositionTarget(ServerCompositionTarget target)

2
src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex; ++_drawOperationindex;
} }
public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {

35
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -324,6 +324,41 @@ namespace Avalonia.Utilities
return angle * 2 * Math.PI; return angle * 2 * Math.PI;
} }
/// <summary>
/// Calculates the point of an angle on an ellipse.
/// </summary>
/// <param name="centre">The centre point of the ellipse.</param>
/// <param name="radiusX">The x radius of the ellipse.</param>
/// <param name="radiusY">The y radius of the ellipse.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>A point on the ellipse.</returns>
public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle)
{
return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y);
}
/// <summary>
/// Gets the minimum and maximum from the specified numbers.
/// </summary>
/// <param name="a">The first number.</param>
/// <param name="b">The second number.</param>
/// <returns>A tuple containing the minimum and maximum of the two specified numbers.</returns>
public static (double min, double max) GetMinMax(double a, double b)
{
return a < b ? (a, b) : (b, a);
}
/// <summary>
/// Gets the minimum and maximum from the specified number and the difference with that number.
/// </summary>
/// <param name="initialValue">The initial value to use.</param>
/// <param name="delta">The difference for <paramref name="initialValue"/>.</param>
/// <returns>A tuple containing the minimum and maximum of the specified number and the difference with that number.</returns>
public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta)
{
return GetMinMax(initialValue, initialValue + delta);
}
private static void ThrowCannotBeGreaterThanException<T>(T min, T max) private static void ThrowCannotBeGreaterThanException<T>(T min, T max)
{ {
throw new ArgumentException($"{min} cannot be greater than {max}."); throw new ArgumentException($"{min} cannot be greater than {max}.");

25
src/Avalonia.Base/Utilities/WeakObservable.cs

@ -9,31 +9,6 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
public static class WeakObservable public static class WeakObservable
{ {
/// <summary>
/// Converts a .NET event conforming to the standard .NET event pattern into an observable
/// sequence, subscribing weakly.
/// </summary>
/// <typeparam name="TTarget">The type of target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event args.</typeparam>
/// <param name="target">Object instance that exposes the event to convert.</param>
/// <param name="eventName">Name of the event to convert.</param>
/// <returns></returns>
[Obsolete("Use WeakEvent-based overload")]
public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
TTarget target,
string eventName)
where TEventArgs : EventArgs
{
_ = target ?? throw new ArgumentNullException(nameof(target));
_ = eventName ?? throw new ArgumentNullException(nameof(eventName));
return Observable.Create<EventPattern<object, TEventArgs>>(observer =>
{
var handler = new Handler<TEventArgs>(observer);
WeakSubscriptionManager.Subscribe(target, eventName, handler);
return () => WeakSubscriptionManager.Unsubscribe(target, eventName, handler);
}).Publish().RefCount();
}
private class Handler<TEventArgs> private class Handler<TEventArgs>
: IWeakSubscriber<TEventArgs>, : IWeakSubscriber<TEventArgs>,

193
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@ -1,193 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
/// <summary>
/// Manages subscriptions to events using weak listeners.
/// </summary>
public static class WeakSubscriptionManager
{
/// <summary>
/// Subscribes to an event on an object using a weak subscription.
/// </summary>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
[Obsolete("Use WeakEvent")]
public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
where TEventArgs : EventArgs
{
_ = target ?? throw new ArgumentNullException(nameof(target));
var dic = SubscriptionTypeStorage<TEventArgs>.Subscribers.GetOrCreateValue(target);
if (!dic.TryGetValue(eventName, out var sub))
{
dic[eventName] = sub = new Subscription<TEventArgs>(dic, typeof(TTarget), target, eventName);
}
sub.Add(new WeakReference<IWeakSubscriber<TEventArgs>>(subscriber));
}
/// <summary>
/// Unsubscribes from an event.
/// </summary>
/// <typeparam name="T">The type of the event arguments.</typeparam>
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
public static void Unsubscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber)
where T : EventArgs
{
if (SubscriptionTypeStorage<T>.Subscribers.TryGetValue(target, out var dic))
{
if (dic.TryGetValue(eventName, out var sub))
{
sub.Remove(subscriber);
}
}
}
private static class SubscriptionTypeStorage<T>
where T : EventArgs
{
public static readonly ConditionalWeakTable<object, SubscriptionDic<T>> Subscribers
= new ConditionalWeakTable<object, SubscriptionDic<T>>();
}
private class SubscriptionDic<T> : Dictionary<string, Subscription<T>>
where T : EventArgs
{
}
private static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors
= new Dictionary<Type, Dictionary<string, EventInfo>>();
private class Subscription<T> where T : EventArgs
{
private readonly EventInfo _info;
private readonly SubscriptionDic<T> _sdic;
private readonly object _target;
private readonly string _eventName;
private readonly Delegate _delegate;
private WeakReference<IWeakSubscriber<T>>?[] _data = new WeakReference<IWeakSubscriber<T>>?[16];
private int _count = 0;
public Subscription(SubscriptionDic<T> sdic, Type targetType, object target, string eventName)
{
_sdic = sdic;
_target = target;
_eventName = eventName;
if (!Accessors.TryGetValue(targetType, out var evDic))
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
if (evDic.TryGetValue(eventName, out var info))
{
_info = info;
}
else
{
var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
if (ev == null)
{
throw new ArgumentException(
$"The event {eventName} was not found on {target.GetType()}.");
}
evDic[eventName] = _info = ev;
}
var del = new Action<object, T>(OnEvent);
_delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target);
_info.AddMethod!.Invoke(target, new[] { _delegate });
}
void Destroy()
{
_info.RemoveMethod!.Invoke(_target, new[] { _delegate });
_sdic.Remove(_eventName);
}
public void Add(WeakReference<IWeakSubscriber<T>> s)
{
if (_count == _data.Length)
{
//Extend capacity
var ndata = new WeakReference<IWeakSubscriber<T>>?[_data.Length*2];
Array.Copy(_data, ndata, _data.Length);
_data = ndata;
}
_data[_count] = s!;
_count++;
}
public void Remove(IWeakSubscriber<T> s)
{
var removed = false;
for (int c = 0; c < _count; ++c)
{
var reference = _data[c];
IWeakSubscriber<T>? instance;
if (reference != null && reference.TryGetTarget(out instance) && instance == s)
{
_data[c] = null;
removed = true;
}
}
if (removed)
{
Compact();
}
}
void Compact()
{
int empty = -1;
for (int c = 0; c < _count; c++)
{
var r = _data[c];
//Mark current index as first empty
if (r == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r != null && empty != -1)
{
_data[c] = null;
_data[empty] = r;
empty++;
}
}
if (empty != -1)
_count = empty;
if (_count == 0)
Destroy();
}
void OnEvent(object sender, T eventArgs)
{
var needCompact = false;
for(var c=0; c<_count; c++)
{
var r = _data[c];
if (r?.TryGetTarget(out var sub) == true)
sub!.OnEvent(sender, eventArgs);
else
needCompact = true;
}
if (needCompact)
Compact();
}
}
}
}

16
src/Avalonia.Base/Visual.cs

@ -338,22 +338,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(context != null);
} }
/// <summary>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// called.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// This method should be called in a control's static constructor with each property
/// on the control which when changed should cause a redraw. This is similar to WPF's
/// FrameworkPropertyMetadata.AffectsRender flag.
/// </remarks>
[Obsolete("Use AffectsRender<T> and specify the control type.")]
protected static void AffectsRender(params AvaloniaProperty[] properties)
{
AffectsRender<Visual>(properties);
}
/// <summary> /// <summary>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be /// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// called. /// called.

19
src/Avalonia.Base/VisualTree/IVisualTreeHost.cs

@ -1,19 +0,0 @@
using System;
namespace Avalonia.VisualTree
{
/// <summary>
/// Interface for controls that host their own separate visual tree, such as popups.
/// </summary>
[Obsolete]
public interface IVisualTreeHost
{
/// <summary>
/// Gets the root of the hosted visual tree.
/// </summary>
/// <value>
/// The root of the hosted visual tree.
/// </value>
IVisual? Root { get; }
}
}

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

@ -413,7 +413,7 @@ namespace Avalonia.VisualTree
Index = index, Index = index,
ZIndex = element.ZIndex, ZIndex = element.ZIndex,
}) })
.OrderBy(x => x, null) .OrderBy(x => x, ZOrderElement.Comparer)
.Select(x => x.Element!); .Select(x => x.Element!);
} }
@ -448,6 +448,19 @@ namespace Avalonia.VisualTree
public int Index { get; set; } public int Index { get; set; }
public int ZIndex { get; set; } public int ZIndex { get; set; }
class ZOrderComparer : IComparer<ZOrderElement>
{
public int Compare(ZOrderElement? x, ZOrderElement? y)
{
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
return x.CompareTo(y);
}
}
public static IComparer<ZOrderElement> Comparer { get; } = new ZOrderComparer();
public int CompareTo(ZOrderElement? other) public int CompareTo(ZOrderElement? other)
{ {
if (other is null) if (other is null)

1
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -108,4 +108,5 @@
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" /> <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SourceGenerators.props" />
</Project> </Project>

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

@ -11,15 +11,20 @@
Stretch="Uniform" Stretch="Uniform"
DestinationRect="0,0,8,8"> DestinationRect="0,0,8,8">
<VisualBrush.Visual> <VisualBrush.Visual>
<DrawingPresenter Width="8" <Image Width="8" Height="8">
Height="8"> <Image.Source>
<DrawingGroup> <DrawingImage>
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z" <DrawingImage.Drawing>
Brush="Transparent" /> <DrawingGroup>
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z" <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
Brush="#19808080" /> Brush="Transparent" />
</DrawingGroup> <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
</DrawingPresenter> Brush="#19808080" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual> </VisualBrush.Visual>
</VisualBrush> </VisualBrush>

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

@ -11,15 +11,20 @@
Stretch="Uniform" Stretch="Uniform"
DestinationRect="0,0,8,8"> DestinationRect="0,0,8,8">
<VisualBrush.Visual> <VisualBrush.Visual>
<DrawingPresenter Width="8" <Image Width="8" Height="8">
Height="8"> <Image.Source>
<DrawingGroup> <DrawingImage>
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z" <DrawingImage.Drawing>
Brush="Transparent" /> <DrawingGroup>
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z" <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
Brush="#19808080" /> Brush="Transparent" />
</DrawingGroup> <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
</DrawingPresenter> Brush="#19808080" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual> </VisualBrush.Visual>
</VisualBrush> </VisualBrush>

10
src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs

@ -12,9 +12,6 @@ namespace Avalonia.Collections
{ {
public virtual string PropertyPath => null; public virtual string PropertyPath => null;
[Obsolete("Use Direction property to read or override sorting direction.")]
public virtual bool Descending => Direction == ListSortDirection.Descending;
public virtual ListSortDirection Direction => ListSortDirection.Ascending; public virtual ListSortDirection Direction => ListSortDirection.Ascending;
public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath); public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath);
public abstract IComparer<object> Comparer { get; } public abstract IComparer<object> Comparer { get; }
@ -254,13 +251,6 @@ namespace Avalonia.Collections
return new DataGridPathSortDescription(propertyPath, direction, null, culture); return new DataGridPathSortDescription(propertyPath, direction, null, culture);
} }
[Obsolete("Use overload taking a ListSortDirection.")]
public static DataGridSortDescription FromPath(string propertyPath, bool descending, CultureInfo culture = null)
{
return new DataGridPathSortDescription(propertyPath, descending ? ListSortDirection.Descending : ListSortDirection.Ascending, null, culture);
}
public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer) public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer)
{ {
return new DataGridPathSortDescription(propertyPath, direction, comparer, null); return new DataGridPathSortDescription(propertyPath, direction, comparer, null);

30
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2167,7 +2167,23 @@ namespace Avalonia.Controls
return desiredSize; return desiredSize;
} }
/// <inheritdoc/>
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
NotifyDataContextPropertyForAllRowCells(GetAllRows(), true);
}
/// <inheritdoc/>
protected override void OnDataContextEndUpdate()
{
base.OnDataContextEndUpdate();
NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
}
/// <summary> /// <summary>
/// Raises the BeginningEdit event. /// Raises the BeginningEdit event.
/// </summary> /// </summary>
@ -3165,6 +3181,20 @@ namespace Avalonia.Controls
} }
} }
private static void NotifyDataContextPropertyForAllRowCells(IEnumerable<DataGridRow> rowSource, bool arg2)
{
foreach (DataGridRow row in rowSource)
{
foreach (DataGridCell cell in row.Cells)
{
if (cell.Content is StyledElement cellContent)
{
DataContextProperty.Notifying?.Invoke(cellContent, arg2);
}
}
}
}
private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode) private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode)
{ {
int itemCount = DataConnection.Count; int itemCount = DataConnection.Count;

11
src/Avalonia.Controls/AutoCompleteBox.cs

@ -392,6 +392,8 @@ namespace Avalonia.Controls
private AutoCompleteSelector<object>? _itemSelector; private AutoCompleteSelector<object>? _itemSelector;
private AutoCompleteSelector<string?>? _textSelector; private AutoCompleteSelector<string?>? _textSelector;
private readonly EventHandler _populateDropDownHandler;
public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent = public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
@ -668,6 +670,7 @@ namespace Avalonia.Controls
if (newValue == TimeSpan.Zero) if (newValue == TimeSpan.Zero)
{ {
_delayTimer.Tick -= _populateDropDownHandler;
_delayTimer = null; _delayTimer = null;
} }
} }
@ -678,7 +681,7 @@ namespace Avalonia.Controls
if (_delayTimer == null) if (_delayTimer == null)
{ {
_delayTimer = new DispatcherTimer(); _delayTimer = new DispatcherTimer();
_delayTimer.Tick += PopulateDropDown; _delayTimer.Tick += _populateDropDownHandler;
} }
// Set the new tick interval // Set the new tick interval
@ -864,6 +867,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public AutoCompleteBox() public AutoCompleteBox()
{ {
_populateDropDownHandler = PopulateDropDown;
ClearView(); ClearView();
} }
@ -1771,10 +1775,7 @@ namespace Avalonia.Controls
/// <param name="e">The event arguments.</param> /// <param name="e">The event arguments.</param>
private void PopulateDropDown(object? sender, EventArgs e) private void PopulateDropDown(object? sender, EventArgs e)
{ {
if (_delayTimer != null) _delayTimer?.Stop();
{
_delayTimer.Stop();
}
// Update the prefix/search text. // Update the prefix/search text.
SearchText = Text; SearchText = Text;

77
src/Avalonia.Controls/Calendar/Calendar.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls
/// </para> /// </para>
/// </remarks> /// </remarks>
[TemplatePart(PART_ElementMonth, typeof(CalendarItem))] [TemplatePart(PART_ElementMonth, typeof(CalendarItem))]
[TemplatePart(PART_ElementRoot, typeof(Panel))] [TemplatePart(PART_ElementRoot, typeof(Panel))]
public class Calendar : TemplatedControl public class Calendar : TemplatedControl
{ {
internal const int RowsPerMonth = 7; internal const int RowsPerMonth = 7;
@ -338,14 +338,11 @@ namespace Avalonia.Controls
/// <param name="e">The DependencyPropertyChangedEventArgs.</param> /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e) private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (DisplayDate != null) int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
{
int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
if (i > -2 && i < 2) if (i > -2 && i < 2)
{ {
UpdateMonths(); UpdateMonths();
}
} }
} }
@ -655,7 +652,7 @@ namespace Avalonia.Controls
SelectedDatesChanged?.Invoke(this, e); SelectedDatesChanged?.Invoke(this, e);
} }
} }
internal Collection<DateTime> RemovedItems { get; set; } internal Collection<DateTime> RemovedItems { get; set; }
internal DateTime? LastSelectedDateInternal { get; set; } internal DateTime? LastSelectedDateInternal { get; set; }
internal DateTime? LastSelectedDate internal DateTime? LastSelectedDate
@ -914,7 +911,7 @@ namespace Avalonia.Controls
o => o.DisplayDateEnd, o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v, (o, v) => o.DisplayDateEnd = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the last date to be displayed. /// Gets or sets the last date to be displayed.
/// </summary> /// </summary>
@ -1242,7 +1239,7 @@ namespace Avalonia.Controls
{ {
b.IsSelected = false; b.IsSelected = false;
} }
} }
} }
} }
} }
@ -1278,7 +1275,7 @@ namespace Avalonia.Controls
internal void OnPreviousClick() internal void OnPreviousClick()
{ {
if (DisplayMode == CalendarMode.Month && DisplayDate != null) if (DisplayMode == CalendarMode.Month)
{ {
DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1); DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1);
if (d.HasValue) if (d.HasValue)
@ -1326,7 +1323,7 @@ namespace Avalonia.Controls
} }
internal void OnNextClick() internal void OnNextClick()
{ {
if (DisplayMode == CalendarMode.Month && DisplayDate != null) if (DisplayMode == CalendarMode.Month)
{ {
DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1); DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1);
if (d.HasValue) if (d.HasValue)
@ -1645,7 +1642,7 @@ namespace Avalonia.Controls
{ {
if (DisplayMode == CalendarMode.Month) if (DisplayMode == CalendarMode.Month)
{ {
if (LastSelectedDate.HasValue && DisplayDateInternal != null) if (LastSelectedDate.HasValue)
{ {
// If a blackout day is inactive, when clicked on it, the // If a blackout day is inactive, when clicked on it, the
// previous inactive day which is not a blackout day can get // previous inactive day which is not a blackout day can get
@ -1897,24 +1894,21 @@ namespace Avalonia.Controls
{ {
case CalendarMode.Month: case CalendarMode.Month:
{ {
if (DisplayDate != null) DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
{
DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0) if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
{ {
// since DisplayDate is not equal to // since DisplayDate is not equal to
// DateTime.MaxValue we are sure selectedDate is\ // DateTime.MaxValue we are sure selectedDate is\
// not null // not null
selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value; selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value; selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
}
else
{
selectedDate = DateTime.MaxValue;
}
ProcessSelection(shift, selectedDate, null);
} }
else
{
selectedDate = DateTime.MaxValue;
}
ProcessSelection(shift, selectedDate, null);
break; break;
} }
case CalendarMode.Year: case CalendarMode.Year:
@ -2026,7 +2020,6 @@ namespace Avalonia.Controls
focusDate = DisplayDate; focusDate = DisplayDate;
LastSelectedDate = DisplayDate; LastSelectedDate = DisplayDate;
} }
Debug.Assert(focusDate != null, "focusDate should not be null!");
FocusButton = FindDayButtonFromDay(focusDate); FocusButton = FindDayButtonFromDay(focusDate);
if (FocusButton != null) if (FocusButton != null)
@ -2091,17 +2084,17 @@ namespace Avalonia.Controls
static Calendar() static Calendar()
{ {
IsEnabledProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsEnabledChanged(e)); IsEnabledProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnIsEnabledChanged(e));
FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnFirstDayOfWeekChanged(e)); FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnFirstDayOfWeekChanged(e));
IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsTodayHighlightedChanged(e)); IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnIsTodayHighlightedChanged(e));
DisplayModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayModePropertyChanged(e)); DisplayModeProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayModePropertyChanged(e));
SelectionModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectionModeChanged(e)); SelectionModeProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnSelectionModeChanged(e));
SelectedDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectedDateChanged(e)); SelectedDateProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnSelectedDateChanged(e));
DisplayDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateChanged(e)); DisplayDateProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateStartChanged(e)); DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateEndChanged(e)); DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateEndChanged(e));
KeyDownEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyDown(e)); KeyDownEvent.AddClassHandler<Calendar>((x, e) => x.Calendar_KeyDown(e));
KeyUpEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyUp(e)); KeyUpEvent.AddClassHandler<Calendar>((x, e) => x.Calendar_KeyUp(e));
} }
/// <summary> /// <summary>

71
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -4,7 +4,6 @@
// All other rights reserved. // All other rights reserved.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Avalonia.Collections.Pooled; using Avalonia.Collections.Pooled;
@ -353,7 +352,6 @@ namespace Avalonia.Controls.Primitives
{ {
if (Owner != null) if (Owner != null)
{ {
Debug.Assert(Owner.DisplayDate != null, "The Owner Calendar's DisplayDate should not be null!");
_currentMonth = Owner.DisplayDateInternal; _currentMonth = Owner.DisplayDateInternal;
} }
else else
@ -361,17 +359,14 @@ namespace Avalonia.Controls.Primitives
_currentMonth = DateTime.Today; _currentMonth = DateTime.Today;
} }
if (_currentMonth != null) SetMonthModeHeaderButton();
{ SetMonthModePreviousButton(_currentMonth);
SetMonthModeHeaderButton(); SetMonthModeNextButton(_currentMonth);
SetMonthModePreviousButton(_currentMonth);
SetMonthModeNextButton(_currentMonth);
if (MonthView != null) if (MonthView != null)
{ {
SetDayTitles(); SetDayTitles();
SetCalendarDayButtons(_currentMonth); SetCalendarDayButtons(_currentMonth);
}
} }
} }
private void SetMonthModeHeaderButton() private void SetMonthModeHeaderButton()
@ -592,7 +587,6 @@ namespace Avalonia.Controls.Primitives
{ {
if (Owner != null) if (Owner != null)
{ {
Debug.Assert(Owner.SelectedMonth != null, "The Owner Calendar's SelectedMonth should not be null!");
_currentMonth = (DateTime)Owner.SelectedMonth; _currentMonth = (DateTime)Owner.SelectedMonth;
} }
else else
@ -600,16 +594,13 @@ namespace Avalonia.Controls.Primitives
_currentMonth = DateTime.Today; _currentMonth = DateTime.Today;
} }
if (_currentMonth != null) SetYearModeHeaderButton();
{ SetYearModePreviousButton();
SetYearModeHeaderButton(); SetYearModeNextButton();
SetYearModePreviousButton();
SetYearModeNextButton();
if (YearView != null) if (YearView != null)
{ {
SetMonthButtonsForYearMode(); SetMonthButtonsForYearMode();
}
} }
} }
private void SetYearModeHeaderButton() private void SetYearModeHeaderButton()
@ -660,7 +651,6 @@ namespace Avalonia.Controls.Primitives
childButton.IsCalendarButtonFocused = false; childButton.IsCalendarButtonFocused = false;
} }
Debug.Assert(Owner.DisplayDateInternal != null, "The Owner Calendar's DisplayDateInternal should not be null!");
childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0); childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0);
if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0) if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0)
@ -685,7 +675,6 @@ namespace Avalonia.Controls.Primitives
if (Owner != null) if (Owner != null)
{ {
Debug.Assert(Owner.SelectedYear != null, "The owning Calendar's selected year should not be null!");
selectedYear = Owner.SelectedYear; selectedYear = Owner.SelectedYear;
_currentMonth = (DateTime)Owner.SelectedMonth; _currentMonth = (DateTime)Owner.SelectedMonth;
} }
@ -695,19 +684,16 @@ namespace Avalonia.Controls.Primitives
selectedYear = DateTime.Today; selectedYear = DateTime.Today;
} }
if (_currentMonth != null) int decade = DateTimeHelper.DecadeOfDate(selectedYear);
{ int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
int decade = DateTimeHelper.DecadeOfDate(selectedYear);
int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
SetDecadeModeHeaderButton(decade, decadeEnd); SetDecadeModeHeaderButton(decade, decadeEnd);
SetDecadeModePreviousButton(decade); SetDecadeModePreviousButton(decade);
SetDecadeModeNextButton(decadeEnd); SetDecadeModeNextButton(decadeEnd);
if (YearView != null) if (YearView != null)
{ {
SetYearButtons(decade, decadeEnd); SetYearButtons(decade, decadeEnd);
}
} }
} }
internal void UpdateYearViewSelection(CalendarButton calendarButton) internal void UpdateYearViewSelection(CalendarButton calendarButton)
@ -822,22 +808,15 @@ namespace Avalonia.Controls.Primitives
{ {
if (Owner.DisplayMode == CalendarMode.Month) if (Owner.DisplayMode == CalendarMode.Month)
{ {
if (Owner.DisplayDate != null) d = Owner.DisplayDateInternal;
{ Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
d = Owner.DisplayDateInternal;
Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
}
Owner.DisplayMode = CalendarMode.Year; Owner.DisplayMode = CalendarMode.Year;
} }
else else
{ {
Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!"); Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!");
d = Owner.SelectedMonth;
if (Owner.SelectedMonth != null) Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
{
d = Owner.SelectedMonth;
Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
}
Owner.DisplayMode = CalendarMode.Decade; Owner.DisplayMode = CalendarMode.Decade;
} }
} }

2
src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs

@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item)) if (DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
{ {
removedItems.Add(this[index]); removedItems.Add(this[index]);
base.SetItem(index, item); base.SetItem(index, item);

6
src/Avalonia.Controls/Control.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
@ -104,7 +105,6 @@ namespace Avalonia.Controls
private static readonly HashSet<Control> _loadedQueue = new HashSet<Control>(); private static readonly HashSet<Control> _loadedQueue = new HashSet<Control>();
private static readonly HashSet<Control> _loadedProcessingQueue = new HashSet<Control>(); private static readonly HashSet<Control> _loadedProcessingQueue = new HashSet<Control>();
private bool _isAttachedToVisualTree = false;
private bool _isLoaded = false; private bool _isLoaded = false;
private DataTemplates? _dataTemplates; private DataTemplates? _dataTemplates;
private IControl? _focusAdorner; private IControl? _focusAdorner;
@ -347,7 +347,7 @@ namespace Avalonia.Controls
internal void OnLoadedCore() internal void OnLoadedCore()
{ {
if (_isLoaded == false && if (_isLoaded == false &&
_isAttachedToVisualTree) ((ILogical)this).IsAttachedToLogicalTree)
{ {
_isLoaded = true; _isLoaded = true;
OnLoaded(); OnLoaded();
@ -395,7 +395,6 @@ namespace Avalonia.Controls
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTreeCore(e); base.OnAttachedToVisualTreeCore(e);
_isAttachedToVisualTree = true;
InitializeIfNeeded(); InitializeIfNeeded();
@ -406,7 +405,6 @@ namespace Avalonia.Controls
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTreeCore(e); base.OnDetachedFromVisualTreeCore(e);
_isAttachedToVisualTree = false;
OnUnloadedCore(); OnUnloadedCore();
} }

17
src/Avalonia.Controls/Converters/StringFormatConverter.cs

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters; namespace Avalonia.Controls.Converters;
@ -15,13 +14,17 @@ public class StringFormatConverter : IMultiValueConverter
{ {
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture) public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{ {
try if (values[0] is string format)
{ {
return string.Format((string)values[0]!, values.Skip(1).ToArray()); try
} {
catch (Exception e) return string.Format(format, values.Skip(1).ToArray());
{ }
return new BindingNotification(e, BindingErrorType.Error); catch
{
return AvaloniaProperty.UnsetValue;
}
} }
return AvaloniaProperty.UnsetValue;
} }
} }

2
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@ -8,8 +8,6 @@ namespace Avalonia.Controls
{ {
public static class DesktopApplicationExtensions public static class DesktopApplicationExtensions
{ {
[Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)]
public static void Run(this Application app) => throw new NotSupportedException();
/// <summary> /// <summary>
/// On desktop-style platforms runs the application's main loop until closable is closed /// On desktop-style platforms runs the application's main loop until closable is closed

61
src/Avalonia.Controls/DrawingPresenter.cs

@ -1,61 +0,0 @@
using System;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
[Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control
{
static DrawingPresenter()
{
AffectsMeasure<DrawingPresenter>(DrawingProperty);
AffectsRender<DrawingPresenter>(DrawingProperty);
}
public static readonly StyledProperty<Drawing> DrawingProperty =
AvaloniaProperty.Register<DrawingPresenter, Drawing>(nameof(Drawing));
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<DrawingPresenter, Stretch>(nameof(Stretch), Stretch.Uniform);
[Content]
public Drawing Drawing
{
get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value);
}
public Stretch Stretch
{
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
private Matrix _transform = Matrix.Identity;
protected override Size MeasureOverride(Size availableSize)
{
if (Drawing == null) return new Size();
var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch);
_transform = transform;
return size;
}
public override void Render(DrawingContext context)
{
if (Drawing != null)
{
using (context.PushPreTransform(_transform))
using (context.PushClip(new Rect(Bounds.Size)))
{
Drawing.Draw(context);
}
}
}
}
}

2
src/Avalonia.Controls/GridLength.cs

@ -180,7 +180,7 @@ namespace Avalonia.Controls
return "Auto"; return "Auto";
} }
string s = _value.ToString(); string s = _value.ToString(CultureInfo.InvariantCulture);
return IsStar ? s + "*" : s; return IsStar ? s + "*" : s;
} }

3
src/Avalonia.Controls/ListBox.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -157,7 +158,7 @@ namespace Avalonia.Controls
e.Source, e.Source,
true, true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(KeyModifiers.Control), e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().CommandModifiers),
point.Properties.IsRightButtonPressed); point.Properties.IsRightButtonPressed);
} }
} }

13
src/Avalonia.Controls/LoggingExtensions.cs

@ -1,21 +1,10 @@
using System; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Logging; using Avalonia.Logging;
namespace Avalonia namespace Avalonia
{ {
public static class LoggingExtensions public static class LoggingExtensions
{ {
[Obsolete("Use LogToTrace")]
public static T LogToDebug<T>(
this T builder,
LogEventLevel level = LogEventLevel.Warning,
params string[] areas)
where T : AppBuilderBase<T>, new()
{
return LogToTrace(builder, level, areas);
}
/// <summary> /// <summary>
/// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink. /// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink.
/// </summary> /// </summary>

7
src/Avalonia.Controls/NativeMenuItem.cs

@ -186,13 +186,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public event EventHandler? Click; public event EventHandler? Click;
[Obsolete("Use Click event.")]
public event EventHandler Clicked
{
add => Click += value;
remove => Click -= value;
}
void INativeMenuItemExporterEventsImplBridge.RaiseClicked() void INativeMenuItemExporterEventsImplBridge.RaiseClicked()
{ {
Click?.Invoke(this, new EventArgs()); Click?.Invoke(this, new EventArgs());

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

Loading…
Cancel
Save