Browse Source

Merge branch 'master' into refactor/new-value-store

pull/8600/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
22576b07a2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 6
      build/HarfBuzzSharp.props
  3. 2
      build/ReactiveUI.props
  4. 2
      build/SharedVersion.props
  5. 6
      build/SkiaSharp.props
  6. 90
      nukebuild/Build.cs
  7. 4
      nukebuild/BuildParameters.cs
  8. 57
      nukebuild/DotNetConfigHelper.cs
  9. 4
      samples/ControlCatalog.NetCore/Program.cs
  10. 34
      samples/ControlCatalog/Converter/HexConverter.cs
  11. 2
      samples/ControlCatalog/DecoratedWindow.xaml
  12. 6
      samples/ControlCatalog/MainView.xaml.cs
  13. 6
      samples/ControlCatalog/MainWindow.xaml
  14. 8
      samples/ControlCatalog/Models/Person.cs
  15. 2
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  16. 24
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  17. 25
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  18. 2
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  19. 2
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  20. 70
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  21. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  22. 20
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  23. 22
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  24. 23
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  25. 4
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  26. 13
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  27. 20
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  28. 2
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  29. 10
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  30. 16
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  31. 6
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  32. 6
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  33. 4
      samples/ControlCatalog/Pages/PointerCanvas.cs
  34. 33
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  35. 8
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  36. 7
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  37. 6
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  38. 36
      samples/IntegrationTestApp/MainWindow.axaml.cs
  39. 265
      samples/RenderDemo/Pages/DrawingPage.xaml
  40. 4
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  41. 45
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  42. 12
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  43. 17
      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. 80
      src/Avalonia.Base/Animation/Animation.cs
  47. 7
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  48. 4
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  49. 17
      src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs
  50. 13
      src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
  51. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  52. 35
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  53. 15
      src/Avalonia.Base/AvaloniaProperty`1.cs
  54. 15
      src/Avalonia.Base/Collections/AvaloniaListExtensions.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. 1
      src/Avalonia.Base/Media/DashStyle.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. 12
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  69. 9
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  70. 33
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  71. 4
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  72. 10
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  73. 2
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  74. 33
      src/Avalonia.Base/Rendering/DirtyVisuals.cs
  75. 35
      src/Avalonia.Base/Utilities/MathUtilities.cs
  76. 25
      src/Avalonia.Base/Utilities/WeakObservable.cs
  77. 193
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  78. 16
      src/Avalonia.Base/Visual.cs
  79. 19
      src/Avalonia.Base/VisualTree/IVisualTreeHost.cs
  80. 15
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  81. 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  82. 23
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  83. 23
      src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml
  84. 10
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  85. 30
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  86. 1
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  87. 11
      src/Avalonia.Controls/AutoCompleteBox.cs
  88. 77
      src/Avalonia.Controls/Calendar/Calendar.cs
  89. 71
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  90. 2
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  91. 6
      src/Avalonia.Controls/Control.cs
  92. 2
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  93. 15
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  94. 61
      src/Avalonia.Controls/DrawingPresenter.cs
  95. 2
      src/Avalonia.Controls/GridLength.cs
  96. 13
      src/Avalonia.Controls/LoggingExtensions.cs
  97. 7
      src/Avalonia.Controls/NativeMenuItem.cs
  98. 13
      src/Avalonia.Controls/NativeMenuItemSeparator.cs
  99. 11
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  100. 114
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

3
.gitignore

@ -212,3 +212,6 @@ coc-settings.json
*.map
src/Web/Avalonia.Web.Blazor/wwwroot/*.js
src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
node_modules
src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
src/Web/Avalonia.Web.Blazor/wwwroot

6
build/HarfBuzzSharp.props

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

2
build/ReactiveUI.props

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

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.10.999</Version>
<Version>11.0.999</Version>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<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">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.1" />
<PackageReference Include="SkiaSharp" Version="2.88.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1" />
</ItemGroup>
</Project>

90
nukebuild/Build.cs

@ -36,25 +36,6 @@ partial class Build : NukeBuild
{
[Solution("Avalonia.sln")] readonly Solution Solution;
static Lazy<string> MsBuildExe = new Lazy<string>(() =>
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return null;
var msBuildDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text;
if (!string.IsNullOrWhiteSpace(msBuildDirectory))
{
string msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\Current\Bin\MSBuild.exe");
if (!System.IO.File.Exists(msBuildExe))
msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\15.0\Bin\MSBuild.exe");
return msBuildExe;
}
return null;
}, false);
BuildParameters Parameters { get; set; }
protected override void OnBuildInitialized()
{
@ -89,25 +70,28 @@ partial class Build : NukeBuild
}
ExecWait("dotnet version:", "dotnet", "--info");
ExecWait("dotnet workloads:", "dotnet", "workload list");
Information("Processor count: " + Environment.ProcessorCount);
Information("Available RAM: " + GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 0x100000 + "MB");
}
IReadOnlyCollection<Output> MsBuildCommon(
string projectFile,
Configure<MSBuildSettings> configurator = null)
DotNetConfigHelper ApplySettingCore(DotNetConfigHelper c)
{
return MSBuild(c => c
.SetProjectFile(projectFile)
// This is required for VS2019 image on Azure Pipelines
.When(Parameters.IsRunningOnWindows &&
Parameters.IsRunningOnAzure, _ => _
.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64")))
.AddProperty("PackageVersion", Parameters.Version)
if (Parameters.IsRunningOnAzure)
c.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64"));
c.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", true)
.SetProcessToolPath(MsBuildExe.Value)
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.Apply(configurator));
.SetVerbosity(DotNetVerbosity.Minimal);
return c;
}
DotNetBuildSettings ApplySetting(DotNetBuildSettings c, Configure<DotNetBuildSettings> configurator = null) =>
ApplySettingCore(c).Build.Apply(configurator);
DotNetPackSettings ApplySetting(DotNetPackSettings c, Configure<DotNetPackSettings> configurator = null) =>
ApplySettingCore(c).Pack.Apply(configurator);
DotNetTestSettings ApplySetting(DotNetTestSettings c, Configure<DotNetTestSettings> configurator = null) =>
ApplySettingCore(c).Test.Apply(configurator);
Target Clean => _ => _.Executes(() =>
{
@ -149,20 +133,11 @@ partial class Build : NukeBuild
Target Compile => _ => _
.DependsOn(Clean, CompileNative)
.DependsOn(CompileHtmlPreviewer)
.Executes(async () =>
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)
MsBuildCommon(Parameters.MSBuildSolution, c => c
.SetProcessArgumentConfigurator(a => a.Add("/r"))
.AddTargets("Build")
);
else
DotNetBuild(c => c
.SetProjectFile(Parameters.MSBuildSolution)
.AddProperty("PackageVersion", Parameters.Version)
.SetConfiguration(Parameters.Configuration)
);
DotNetBuild(c => ApplySetting(c)
.SetProjectFile(Parameters.MSBuildSolution)
);
});
void RunCoreTest(string projectName)
@ -182,9 +157,8 @@ partial class Build : NukeBuild
Information($"Running for {projectName} ({fw}) ...");
DotNetTest(c => c
DotNetTest(c => ApplySetting(c)
.SetProjectFile(project)
.SetConfiguration(Parameters.Configuration)
.SetFramework(fw)
.EnableNoBuild()
.EnableNoRestore()
@ -263,19 +237,7 @@ partial class Build : NukeBuild
.Executes(() =>
{
var data = Parameters;
var pathToProjectSource = RootDirectory / "samples" / "ControlCatalog.NetCore";
var pathToPublish = pathToProjectSource / "bin" / data.Configuration / "publish";
DotNetPublish(c => c
.SetProject(pathToProjectSource / "ControlCatalog.NetCore.csproj")
.EnableNoBuild()
.SetConfiguration(data.Configuration)
.AddProperty("PackageVersion", data.Version)
.AddProperty("PublishDir", pathToPublish));
Zip(data.ZipCoreArtifacts, data.BinRoot);
Zip(data.ZipNuGetArtifacts, data.NugetRoot);
Zip(data.ZipTargetControlCatalogNetCoreDir, pathToPublish);
});
Target CreateIntermediateNugetPackages => _ => _
@ -283,15 +245,7 @@ partial class Build : NukeBuild
.After(RunTests)
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)
MsBuildCommon(Parameters.MSBuildSolution, c => c
.AddTargets("Pack"));
else
DotNetPack(c => c
.SetProject(Parameters.MSBuildSolution)
.SetConfiguration(Parameters.Configuration)
.AddProperty("PackageVersion", Parameters.Version));
DotNetPack(c => ApplySetting(c).SetProject(Parameters.MSBuildSolution));
});
Target CreateNugetPackages => _ => _

4
nukebuild/BuildParameters.cs

@ -51,14 +51,12 @@ public partial class Build
public AbsolutePath NugetIntermediateRoot { get; }
public AbsolutePath NugetRoot { get; }
public AbsolutePath ZipRoot { get; }
public AbsolutePath BinRoot { get; }
public AbsolutePath TestResultsRoot { get; }
public string DirSuffix { get; }
public List<string> BuildDirs { get; }
public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; }
public AbsolutePath ZipTargetControlCatalogNetCoreDir { get; }
public BuildParameters(Build b)
@ -121,14 +119,12 @@ public partial class Build
NugetRoot = ArtifactsDir / "nuget";
NugetIntermediateRoot = RootDirectory / "build-intermediate" / "nuget";
ZipRoot = ArtifactsDir / "zip";
BinRoot = ArtifactsDir / "bin";
TestResultsRoot = ArtifactsDir / "test-results";
BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList();
DirSuffix = Configuration;
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
ZipTargetControlCatalogNetCoreDir = ZipRoot / ("ControlCatalog.NetCore-" + FileZipSuffix);
}
string GetVersion()

57
nukebuild/DotNetConfigHelper.cs

@ -0,0 +1,57 @@
using System.Globalization;
using JetBrains.Annotations;
using Nuke.Common.Tools.DotNet;
// ReSharper disable ReturnValueOfPureMethodIsNotUsed
public class DotNetConfigHelper
{
public DotNetBuildSettings Build;
public DotNetPackSettings Pack;
public DotNetTestSettings Test;
public DotNetConfigHelper(DotNetBuildSettings s)
{
Build = s;
}
public DotNetConfigHelper(DotNetPackSettings s)
{
Pack = s;
}
public DotNetConfigHelper(DotNetTestSettings s)
{
Test = s;
}
public DotNetConfigHelper AddProperty(string key, bool value) =>
AddProperty(key, value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
public DotNetConfigHelper AddProperty(string key, string value)
{
Build = Build?.AddProperty(key, value);
Pack = Pack?.AddProperty(key, value);
Test = Test?.AddProperty(key, value);
return this;
}
public DotNetConfigHelper SetConfiguration(string configuration)
{
Build = Build?.SetConfiguration(configuration);
Pack = Pack?.SetConfiguration(configuration);
Test = Test?.SetConfiguration(configuration);
return this;
}
public DotNetConfigHelper SetVerbosity(DotNetVerbosity verbosity)
{
Build = Build?.SetVerbosity(verbosity);
Pack = Pack?.SetVerbostiy(verbosity);
Test = Test?.SetVerbosity(verbosity);
return this;
}
public static implicit operator DotNetConfigHelper(DotNetBuildSettings s) => new DotNetConfigHelper(s);
public static implicit operator DotNetConfigHelper(DotNetPackSettings s) => new DotNetConfigHelper(s);
public static implicit operator DotNetConfigHelper(DotNetTestSettings s) => new DotNetConfigHelper(s);
}

4
samples/ControlCatalog.NetCore/Program.cs

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

34
samples/ControlCatalog/Converter/HexConverter.cs

@ -0,0 +1,34 @@
using System;
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
namespace ControlCatalog.Converter;
public class HexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = value?.ToString();
if (str == null)
return AvaloniaProperty.UnsetValue;
if (int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int x))
return (decimal)x;
return AvaloniaProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if (value is decimal d)
return ((int)d).ToString("X8");
return AvaloniaProperty.UnsetValue;
}
catch
{
return AvaloniaProperty.UnsetValue;
}
}
}

2
samples/ControlCatalog/DecoratedWindow.xaml

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.DecoratedWindow"
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>
<NativeMenuItem Header="Decorated">

6
samples/ControlCatalog/MainView.xaml.cs

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

6
samples/ControlCatalog/MainWindow.xaml

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

8
samples/ControlCatalog/Models/Person.cs

@ -85,7 +85,7 @@ namespace ControlCatalog.Models
}
else
{
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
if (_errorLookup.TryGetValue(propertyName, out var errorList))
{
errorList.Clear();
errorList.Add(error!);
@ -114,12 +114,12 @@ namespace ControlCatalog.Models
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;
else
return null;
return Array.Empty<object>();
}
}
}

2
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

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

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

@ -1,8 +1,6 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@ -161,23 +159,23 @@ namespace ControlCatalog.Pages
private bool LastWordContains(string? searchText, string? item)
{
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)
{
var word = words[i];
for (var j = 0; word is { } && j < options.Length; ++j)
{
var option = options[j];
if (option == null)
continue;
if (i == words.Length - 1)
{
options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
}
else
if (options[i] is { } option)
{
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)
{
var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content;
int value = Array.IndexOf(_mountains, txtBox?.Text);
if (e.Direction == SpinDirection.Increase)
value++;
else
value--;
if (spinner.Content is TextBlock txtBox)
{
int value = Array.IndexOf(_mountains, txtBox.Text);
if (e.Direction == SpinDirection.Increase)
value++;
else
value--;
if (value < 0)
value = _mountains.Length - 1;
else if (value >= _mountains.Length)
value = 0;
if (value < 0)
value = _mountains.Length - 1;
else if (value >= _mountains.Length)
value = 0;
txtBox.Text = _mountains[value];
}
txtBox.Text = _mountains[value];
}
private readonly string[] _mountains = new[]

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

@ -19,7 +19,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
public void OnRepeatButtonClick(object sender, object args)
public void OnRepeatButtonClick(object? sender, object args)
{
repeatButtonClickCount++;
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)
{

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

@ -23,55 +23,79 @@ namespace ControlCatalog.Pages
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();
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
if (Application.Current!.Clipboard is { } clipboard)
{
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 (files.Length == 0)
if (Application.Current!.Clipboard is { } clipboard)
{
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>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
if (Application.Current!.Clipboard is { } clipboard)
{
var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
}
}
private async void GetFormats(object sender, RoutedEventArgs args)
{
var formats = await Application.Current.Clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
if (Application.Current!.Clipboard is { } clipboard)
{
var formats = await clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
}
}
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()
{
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.SelectedIndex = 0;
}

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

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

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

@ -52,13 +52,13 @@ namespace ControlCatalog.Pages
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");
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)
{
@ -67,20 +67,20 @@ namespace ControlCatalog.Pages
}
}
private void CloseFlyout(object sender, RoutedEventArgs e)
private void CloseFlyout(object? sender, RoutedEventArgs e)
{
_textBox.ContextFlyout?.Hide();
}
public void CustomContextRequested(object sender, ContextRequestedEventArgs e)
public void CustomContextRequested(object? sender, ContextRequestedEventArgs e)
{
var border = (Border)sender;
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;
if (sender is Border border && border.Child is TextBlock textBlock)
{
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()

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

@ -35,30 +35,31 @@ namespace ControlCatalog.Pages
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");
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)
{
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;
var textBlock = (TextBlock)border.Child;
if (sender is Border border && border.Child is TextBlock textBlock)
{
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()

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

@ -62,7 +62,7 @@ namespace ControlCatalog.Pages
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;
}
@ -74,7 +74,7 @@ namespace ControlCatalog.Pages
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)
{

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

@ -111,9 +111,16 @@ namespace ControlCatalog.Pages
Title = "Select folder",
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null
}.ShowAsync(GetWindow());
lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
results.Items = new [] { result };
resultsVisible.IsVisible = result != null;
if (string.IsNullOrEmpty(result))
{
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
{

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

@ -1,9 +1,9 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
@ -27,9 +27,9 @@ namespace ControlCatalog.Pages
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
{
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();
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")
{
@ -73,7 +73,7 @@ namespace ControlCatalog.Pages
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")
{
@ -83,11 +83,11 @@ namespace ControlCatalog.Pages
{
e.DragEffects = e.DragEffects & (DragDropEffects.Copy);
}
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
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))
_DropState.Text = "Custom: " + e.Data.Get(CustomFormat);
}

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

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

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

@ -123,7 +123,7 @@ namespace ControlCatalog.Pages
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)
{
@ -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)
{
@ -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);
}
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));
}
private void scrollToSelected_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private void scrollToSelected_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
ScrollTo(_selectedIndex);
}

16
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -1,6 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:converter="clr-namespace:ControlCatalog.Converter"
x:Class="ControlCatalog.Pages.NumericUpDownPage">
<StackPanel Orientation="Vertical" Spacing="4"
MaxWidth="800">
@ -54,11 +55,11 @@
<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>
<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>
<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>
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
@ -97,6 +98,17 @@
</DataValidationErrors.Error>
</NumericUpDown>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="HexUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown in HEX mode:</Label>
<NumericUpDown x:Name="HexUpDown" Value="0"
VerticalAlignment="Center">
<NumericUpDown.TextConverter>
<converter:HexConverter></converter:HexConverter>
</NumericUpDown.TextConverter>
</NumericUpDown>
</StackPanel>
</WrapPanel>
</StackPanel>

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

@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using MiniMvvm;
@ -29,7 +27,7 @@ namespace ControlCatalog.Pages
public class NumbersPageViewModel : ViewModelBase
{
private IList<FormatObject>? _formats;
private FormatObject _selectedFormat;
private FormatObject? _selectedFormat;
private IList<Location>? _spinnerLocations;
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))
.ToArray();
public FormatObject SelectedFormat
public FormatObject? SelectedFormat
{
get { return _selectedFormat; }
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);
}
private string _info;
private string _info = string.Empty;
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v);
@ -205,7 +205,7 @@ namespace ControlCatalog.Pages
public OpenGlPageControl()
{
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()];
sr.Read(buf, 0, buf.Length);
@ -345,7 +345,7 @@ namespace ControlCatalog.Pages
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 modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel");
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView");

4
samples/ControlCatalog/Pages/PointerCanvas.cs

@ -174,9 +174,9 @@ Twist: {_lastProperties?.Twist}";
var lastPointer = e.GetCurrentPoint(this);
_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)

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

@ -1,8 +1,6 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages;
@ -31,43 +29,48 @@ public class PointersPage : UserControl
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;
var position = e.GetPosition((Border)sender);
textBlock.Text = @$"Type: {e.Pointer.Type}
if (sender is Border border && border.Child is TextBlock textBlock)
{
var position = e.GetPosition(border);
textBlock.Text = @$"Type: {e.Pointer.Type}
Captured: {e.Pointer.Captured == sender}
PointerId: {e.Pointer.Id}
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;
textBlock.Text = @$"Type: {e.Pointer.Type}
if (sender is Border border && border.Child is TextBlock textBlock)
{
textBlock.Text = @$"Type: {e.Pointer.Type}
Captured: {e.Pointer.Captured == sender}
PointerId: {e.Pointer.Id}
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)
{
e.Pointer.Capture(null);
e.Handled = true;
}
else
else if (e.Pointer.Captured is not null)
{
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;
}

8
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -53,14 +53,14 @@ namespace ControlCatalog.ViewModels
var window = View?.GetVisualRoot() as Window;
if (window == null)
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)
{
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;
if (window == null)
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)
{
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);
var compositeTransition = new CompositePageTransition();
compositeTransition.PageTransitions.Add(PageTransitions[1].Transition);
compositeTransition.PageTransitions.Add(PageTransitions[2].Transition);
compositeTransition.PageTransitions.Add(PageTransitions[3].Transition);
compositeTransition.PageTransitions.Add(PageTransitions[1].Transition!);
compositeTransition.PageTransitions.Add(PageTransitions[2].Transition!);
compositeTransition.PageTransitions.Add(PageTransitions[3].Transition!);
PageTransitions[4].Transition = compositeTransition;
PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration));

36
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
@ -31,20 +30,23 @@ namespace IntegrationTestApp
private void InitializeViewMenu()
{
var mainTabs = this.FindControl<TabControl>("MainTabs");
var mainTabs = this.Get<TabControl>("MainTabs");
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!,
IsChecked = tabItem.IsSelected,
ToggleType = NativeMenuItemToggleType.Radio,
};
menuItem.Click += (s, e) => tabItem.IsSelected = true;
viewMenu.Menu.Items.Add(menuItem);
var menuItem = new NativeMenuItem
{
Header = (string)tabItem.Header!,
IsChecked = tabItem.IsSelected,
ToggleType = NativeMenuItemToggleType.Radio,
};
menuItem.Click += (s, e) => tabItem.IsSelected = true;
viewMenu?.Menu?.Items.Add(menuItem);
}
}
}
@ -107,8 +109,8 @@ namespace IntegrationTestApp
private void MenuClicked(object? sender, RoutedEventArgs e)
{
var clickedMenuItemTextBlock = this.FindControl<TextBlock>("ClickedMenuItem");
clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString();
var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");
clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
}
private void OnButtonClick(object? sender, RoutedEventArgs e)
@ -116,13 +118,13 @@ namespace IntegrationTestApp
var source = e.Source as Button;
if (source?.Name == "ComboBoxSelectionClear")
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = -1;
this.Get<ComboBox>("BasicComboBox").SelectedIndex = -1;
if (source?.Name == "ComboBoxSelectFirst")
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0;
this.Get<ComboBox>("BasicComboBox").SelectedIndex = 0;
if (source?.Name == "ListBoxSelectionClear")
this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1;
this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.FindControl<TextBlock>("ClickedMenuItem").Text = "None";
this.Get<TextBlock>("ClickedMenuItem").Text = "None";
if (source?.Name == "ShowWindow")
ShowWindow();
if (source?.Name == "SendToBack")

265
samples/RenderDemo/Pages/DrawingPage.xaml

@ -1,134 +1,141 @@
<UserControl
xmlns="https://github.com/avaloniaui"
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.DrawingPage">
<UserControl.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="Bulb">
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
</DrawingGroup.Transform>
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
</DrawingGroup>
<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" />
<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" />
<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" />
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,9,1045.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FFBDC3C7">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,6,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<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" />
<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" />
</DrawingGroup>
</Style.Resources>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Text="None"
Margin="3" />
<Border Grid.Column="0"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{DynamicResource Bulb}" />
</Border>
<TextBlock Text="Fill"
Margin="3"
Grid.Column="1" />
<Border Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{DynamicResource Bulb}"
Width="100"
Height="50"
Stretch="Fill" />
</Border>
<TextBlock Text="Uniform"
Margin="3"
Grid.Column="2" />
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{DynamicResource Bulb}"
Width="100"
Height="50"
Stretch="Uniform" />
</Border>
<TextBlock Text="UniformToFill"
Margin="3"
Grid.Column="3" />
<Border Grid.Column="3"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<DrawingPresenter Drawing="{DynamicResource Bulb}"
Width="100"
Height="50"
Stretch="UniformToFill" />
</Border>
<UserControl.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="Bulb">
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
</DrawingGroup.Transform>
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FF7F8C8D"
Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
</DrawingGroup>
<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" />
<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" />
<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" />
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,9,1045.4" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FFBDC3C7">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,6,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<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" />
<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" />
</DrawingGroup>
</Style.Resources>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Text="None"
Margin="3" />
<Border Grid.Column="0"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<Image>
<Image.Source>
<DrawingImage Drawing="{DynamicResource Bulb}" />
</Image.Source>
</Image>
</Border>
<TextBlock Text="Fill"
Margin="3"
Grid.Column="1" />
<Border Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<Image Width="100" Height="50" Stretch="Fill">
<Image.Source>
<DrawingImage Drawing="{DynamicResource Bulb}" />
</Image.Source>
</Image>
</Border>
<TextBlock Text="Uniform"
Margin="3"
Grid.Column="2" />
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
BorderBrush="Gray"
Margin="5">
<Image Width="100" Height="50" Stretch="Uniform">
<Image.Source>
<DrawingImage Drawing="{DynamicResource Bulb}" />
</Image.Source>
</Image>
</Border>
<TextBlock Text="UniformToFill"
Margin="3"
Grid.Column="3" />
<Border Grid.Column="3"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
BorderThickness="1"
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"
Grid.Column="0"
Width="100"
Height="50"
Stretch="None"
Fill="Blue"
Margin="5"/>
<Ellipse Grid.Row="2"
Grid.Column="1"
Width="100"
Height="50"
Stretch="Fill"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="2"
Width="100"
Height="50"
Stretch="Uniform"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="3"
Width="100"
Height="50"
Stretch="UniformToFill"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="0"
Width="100"
Height="50"
Stretch="None"
Fill="Blue"
Margin="5"/>
<Ellipse Grid.Row="2"
Grid.Column="1"
Width="100"
Height="50"
Stretch="Fill"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="2"
Width="100"
Height="50"
Stretch="Uniform"
Fill="Blue"
Margin="5" />
<Ellipse Grid.Row="2"
Grid.Column="3"
Width="100"
Height="50"
Stretch="UniformToFill"
Fill="Blue"
Margin="5" />
</Grid>
</UserControl>
</Grid>
</UserControl>

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

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

45
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -27,46 +27,33 @@ namespace Avalonia.Android
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);
object l = new object();
var stopped = false;
Timer timer = null;
var scheduled = false;
timer = new Timer(_ =>
{
lock (l)
if (stopped)
return;
EnsureInvokeOnMainThread(() =>
{
if (stopped)
try
{
timer.Dispose();
return;
tick();
}
if (scheduled)
return;
scheduled = true;
EnsureInvokeOnMainThread(() =>
finally
{
try
{
tick();
}
finally
{
lock (l)
{
scheduled = false;
}
}
});
}
}, null, TimeSpan.Zero, interval);
if (!stopped)
timer.Change(interval, Timeout.InfiniteTimeSpan);
}
});
},
null, interval, Timeout.InfiniteTimeSpan);
return Disposable.Create(() =>
{
lock (l)
{
stopped = true;
timer.Dispose();
}
stopped = true;
timer.Dispose();
});
}

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)
{
Log.Info("AVALONIA", "Surface Changed");

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
using Android.Views;
@ -30,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view;
@ -39,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
GetAvaloniaPointFromEvent);
_pointerHelper = new AndroidMotionEventsHelper(this);
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
@ -160,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_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)
{
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;
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;
}
}
}

80
src/Avalonia.Base/Animation/Animation.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@ -172,23 +173,6 @@ namespace Avalonia.Animation
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>
/// Gets the children of the <see cref="Animation"/>.
/// </summary>
@ -196,14 +180,14 @@ namespace Avalonia.Animation
public KeyFrames Children { get; } = new KeyFrames();
// Store values for the Animator attached properties for IAnimationSetter objects.
private static readonly Dictionary<IAnimationSetter, Type> s_animators = new Dictionary<IAnimationSetter, Type>();
private static readonly Dictionary<IAnimationSetter, (Type Type, Func<IAnimator> Factory)> s_animators = new();
/// <summary>
/// Gets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <returns>The property animator type.</returns>
public static Type? GetAnimator(IAnimationSetter setter)
public static (Type Type, Func<IAnimator> Factory)? GetAnimator(IAnimationSetter setter)
{
if (s_animators.TryGetValue(setter, out var type))
{
@ -217,24 +201,28 @@ namespace Avalonia.Animation
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, Type value)
public static void SetAnimator(IAnimationSetter setter,
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
#endif
Type value)
{
s_animators[setter] = value;
s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!);
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> Animators = new()
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
};
/// <summary>
@ -249,18 +237,18 @@ namespace Avalonia.Animation
/// The type of the animator to instantiate.
/// </typeparam>
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator : IAnimator
where TAnimator : IAnimator, new()
{
Animators.Insert(0, (condition, typeof(TAnimator)));
Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
private static Type? GetAnimatorType(AvaloniaProperty property)
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type) in Animators)
foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
return type;
return (type, factory);
}
}
return null;
@ -268,7 +256,7 @@ namespace Avalonia.Animation
private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new List<(Type type, AvaloniaProperty property)>();
var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>();
var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>();
@ -288,8 +276,10 @@ namespace Avalonia.Animation
throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
}
if (!handlerList.Contains((handler, setter.Property)))
handlerList.Add((handler, setter.Property));
var (type, factory) = handler.Value;
if (!handlerList.ContainsKey((type, setter.Property)))
handlerList[(type, setter.Property)] = factory;
var cue = keyframe.Cue;
@ -298,7 +288,7 @@ namespace Avalonia.Animation
cue = new Cue(keyframe.KeyTime.TotalSeconds / Duration.TotalSeconds);
}
var newKF = new AnimatorKeyFrame(handler, cue, keyframe.KeySpline);
var newKF = new AnimatorKeyFrame(type, factory, cue, keyframe.KeySpline);
subscriptions.Add(newKF.BindSetter(setter, control));
@ -308,10 +298,10 @@ namespace Avalonia.Animation
var newAnimatorInstances = new List<IAnimator>();
foreach (var (handlerType, property) in handlerList)
foreach (var handler in handlerList)
{
var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!;
newInstance.Property = property;
var newInstance = handler.Value();
newInstance.Property = handler.Key.Property;
newAnimatorInstances.Add(newInstance);
}

7
src/Avalonia.Base/Animation/AnimatorKeyFrame.cs

@ -20,22 +20,25 @@ namespace Avalonia.Animation
}
public AnimatorKeyFrame(Type? animatorType, Cue cue)
public AnimatorKeyFrame(Type? animatorType, Func<IAnimator>? animatorFactory, Cue cue)
{
AnimatorType = animatorType;
AnimatorFactory = animatorFactory;
Cue = cue;
KeySpline = null;
}
public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline)
public AnimatorKeyFrame(Type? animatorType, Func<IAnimator>? animatorFactory, Cue cue, KeySpline? keySpline)
{
AnimatorType = animatorType;
AnimatorFactory = animatorFactory;
Cue = cue;
KeySpline = keySpline;
}
internal bool isNeutral;
public Type? AnimatorType { get; }
public Func<IAnimator>? AnimatorFactory { get; }
public Cue Cue { get; }
public KeySpline? KeySpline { get; }
public AvaloniaProperty? Property { get; private set; }

4
src/Avalonia.Base/Animation/Animators/Animator`1.cs

@ -171,12 +171,12 @@ namespace Avalonia.Animation.Animators
{
if (!hasStartKey)
{
_convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
_convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
}
if (!hasEndKey)
{
_convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
_convertedKeyframes.Add(new AnimatorKeyFrame(null, null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
}
}
}

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

@ -17,8 +17,7 @@ namespace Avalonia.Animation.Animators
/// </summary>
public class BaseBrushAnimator : Animator<IBrush?>
{
private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
new List<(Func<Type, bool> Match, Type AnimatorType)>();
private static readonly List<(Func<Type, bool> Match, Type AnimatorType, Func<IAnimator> AnimatorFactory)> _brushAnimators = new();
/// <summary>
/// Register an <see cref="Animator{T}"/> that handles a specific
@ -34,7 +33,7 @@ namespace Avalonia.Animation.Animators
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
where TAnimator : IAnimator, new()
{
_brushAnimators.Insert(0, (condition, typeof(TAnimator)));
_brushAnimators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
/// <inheritdoc/>
@ -85,14 +84,14 @@ namespace Avalonia.Animation.Animators
{
if (keyframe.Value is ISolidColorBrush solidColorBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
});
}
else if (keyframe.Value is IGradientBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
@ -117,7 +116,7 @@ namespace Avalonia.Animation.Animators
{
if (keyframe.Value is ISolidColorBrush)
{
solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), () => new ISolidColorBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
@ -137,18 +136,18 @@ namespace Avalonia.Animation.Animators
{
if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType)
{
foreach (var (match, animatorType) in _brushAnimators)
foreach (var (match, animatorType, animatorFactory) in _brushAnimators)
{
if (!match(firstKeyType))
continue;
animator = (IAnimator?)Activator.CreateInstance(animatorType);
animator = animatorFactory();
if (animator != null)
{
animator.Property = Property;
foreach (var keyframe in this)
{
animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
animator.Add(new AnimatorKeyFrame(animatorType, animatorFactory, keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});

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

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -6,6 +6,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\*.trie" />

35
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -464,41 +464,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 IObservable<object?> _source;

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

@ -30,21 +30,6 @@ namespace Avalonia
_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>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </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>
/// Listens for property changed events from all items in a collection.
/// </summary>

15
src/Avalonia.Base/DirectPropertyBase.cs

@ -30,21 +30,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>
/// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
/// </summary>

4
src/Avalonia.Base/EnumExtensions.cs

@ -8,10 +8,6 @@ namespace Avalonia
/// </summary>
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)]
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);
}
}
_lastPointer = (pointer, root.PointToScreen(position));
}
private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
@ -195,7 +197,6 @@ namespace Avalonia.Input
}
el = root.PointerOverElement = element;
_lastPointer = (pointer, root.PointToScreen(position));
e.RoutedEvent = InputElement.PointerEnteredEvent;

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

@ -5,7 +5,7 @@ namespace Avalonia.Input
/// <summary>
/// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact.
/// </summary>
public sealed class PointerPoint
public struct PointerPoint
{
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{
@ -33,47 +33,47 @@ namespace Avalonia.Input
/// <summary>
/// Provides extended properties for a PointerPoint object.
/// </summary>
public sealed class PointerPointProperties
public struct PointerPointProperties
{
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.
/// </summary>
public bool IsLeftButtonPressed { get; }
public bool IsLeftButtonPressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device.
/// </summary>
public bool IsMiddleButtonPressed { get; }
public bool IsMiddleButtonPressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device.
/// </summary>
public bool IsRightButtonPressed { get; }
public bool IsRightButtonPressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1).
/// </summary>
public bool IsXButton1Pressed { get; }
public bool IsXButton1Pressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2).
/// </summary>
public bool IsXButton2Pressed { get; }
public bool IsXButton2Pressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed.
/// </summary>
public bool IsBarrelButtonPressed { get; }
public bool IsBarrelButtonPressed { get; } = false;
/// <summary>
/// Gets a value that indicates whether the input is from a pen eraser.
/// </summary>
public bool IsEraser { get; }
public bool IsEraser { get; } = false;
/// <summary>
/// Gets a value that indicates whether the digitizer pen is inverted.
/// </summary>
public bool IsInverted { get; }
public bool IsInverted { get; } = false;
/// <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).
@ -81,7 +81,7 @@ namespace Avalonia.Input
/// <returns>
/// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0.
/// </returns>
public float Twist { get; }
public float Twist { get; } = 0.0F;
/// <summary>
/// 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>
/// 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>
public float XTilt { get; }
public float XTilt { get; } = 0.0F;
/// <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).
@ -105,14 +105,14 @@ namespace Avalonia.Input
/// <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.
/// </returns>
public float YTilt { get; }
public float YTilt { get; } = 0.0F;
/// <summary>
/// Gets the kind of pointer state change.
/// </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>
public Point Position { get; set; }
/// <inheritdoc cref="PointerPointProperties.Twist" />
public float Twist { get; set; }
/// <inheritdoc cref="PointerPointProperties.Pressure" />
public float Pressure { get; set; }
/// <inheritdoc cref="PointerPointProperties.XTilt" />
public float XTilt { get; set; }
/// <inheritdoc cref="PointerPointProperties.YTilt" />
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>(
Action<TTarget, TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,

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

@ -44,17 +44,6 @@ namespace Avalonia.Layout
/// </remarks>
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>
/// Registers a control as wanting to receive effective viewport notifications.
/// </summary>

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

@ -196,17 +196,6 @@ namespace Avalonia.Layout
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()
{
_disposed = true;

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

@ -460,20 +460,6 @@ namespace Avalonia.Layout
_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>
/// Marks a property as affecting the control's measurement.
/// </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>
/// Marks a property as affecting the control's arrangement.
/// </summary>

1
src/Avalonia.Base/Media/DashStyle.cs

@ -35,7 +35,6 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="DashStyle"/> class.
/// </summary>
public DashStyle()
: this(null, 0)
{
}

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

@ -89,22 +89,6 @@ namespace Avalonia.Media.Imaging
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>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>

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

@ -9,18 +9,6 @@ namespace Avalonia.Media.Imaging
/// </summary>
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>
/// 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="alphaFormat">The alpha format (optional).</param>
/// <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))
{
}

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

@ -1,10 +1,11 @@
using System;
using System.Reflection;
using System.Collections.Generic;
using Avalonia.SourceGenerator;
namespace Avalonia.Media
{
internal static class KnownColors
internal static partial class KnownColors
{
private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames;
private static readonly IReadOnlyDictionary<uint, string> _knownColors;
@ -12,23 +13,25 @@ namespace Avalonia.Media
private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes;
#endif
[GenerateEnumValueDictionary()]
private static partial Dictionary<string, KnownColor> GetKnownColors();
static KnownColors()
{
var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase);
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 = (KnownColor)field.GetValue(null)!;
var knownColor = field.Value;
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
if (!knownColors.ContainsKey((uint)knownColor))
{
knownColors.Add((uint)knownColor, field.Name);
knownColors.Add((uint)knownColor, field.Key);
}
}

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

@ -12,6 +12,11 @@ public class BclStorageFile : IStorageBookmarkFile
{
private readonly FileInfo _fileInfo;
public BclStorageFile(string fileName)
{
_fileInfo = new FileInfo(fileName);
}
public BclStorageFile(FileInfo fileInfo)
{
_fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
@ -27,15 +32,14 @@ public class BclStorageFile : IStorageBookmarkFile
public Task<StorageItemProperties> GetBasicPropertiesAsync()
{
var props = new StorageItemProperties();
if (_fileInfo.Exists)
{
props = new StorageItemProperties(
return Task.FromResult(new StorageItemProperties(
(ulong)_fileInfo.Length,
_fileInfo.CreationTimeUtc,
_fileInfo.LastAccessTimeUtc);
_fileInfo.LastAccessTimeUtc));
}
return Task.FromResult(props);
return Task.FromResult(new StorageItemProperties());
}
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;
public BclStorageFolder(string path)
{
_directoryInfo = new DirectoryInfo(path);
if (!_directoryInfo.Exists)
{
throw new ArgumentException("Directory must exist");
}
}
public BclStorageFolder(DirectoryInfo directoryInfo)
{
_directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));

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

@ -29,6 +29,7 @@ public class CompositingRenderer : IRendererWithCompositor
private bool _queuedUpdate;
private Action _update;
private Action _invalidateScene;
private bool _updating;
internal CompositionTarget CompositionTarget;
@ -77,6 +78,8 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public void AddDirty(IVisual visual)
{
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
_dirty.Add((Visual)visual);
QueueUpdate();
}
@ -84,7 +87,16 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter)
{
var res = CompositionTarget.TryHitTest(p, filter);
Func<CompositionVisual, bool>? f = null;
if (filter != null)
f = v =>
{
if (v is CompositionDrawListVisual dlv)
return filter(dlv.Visual);
return true;
};
var res = CompositionTarget.TryHitTest(p, f);
if(res == null)
yield break;
foreach(var v in res)
@ -107,6 +119,8 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/>
public void RecalculateChildren(IVisual visual)
{
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
_recalculateChildren.Add((Visual)visual);
QueueUpdate();
}
@ -191,7 +205,7 @@ public class CompositingRenderer : IRendererWithCompositor
private void InvalidateScene() =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
private void Update()
private void UpdateCore()
{
_queuedUpdate = false;
foreach (var visual in _dirty)
@ -240,6 +254,21 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.Scaling = _root.RenderScaling;
Compositor.InvokeOnNextCommit(_invalidateScene);
}
private void Update()
{
if(_updating)
return;
_updating = true;
try
{
UpdateCore();
}
finally
{
_updating = false;
}
}
public void Resized(Size size)
{

4
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -54,13 +54,11 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
Visual = visual;
}
internal override bool HitTest(Point pt, Func<IVisual, bool>? filter)
internal override bool HitTest(Point pt)
{
var custom = Visual as ICustomHitTest;
if (DrawList == null && custom == null)
return false;
if (filter != null && !filter(Visual))
return false;
if (custom != null)
{
// Simulate the old behavior

10
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -31,7 +31,7 @@ namespace Avalonia.Rendering.Composition
/// <param name="point"></param>
/// <param name="filter"></param>
/// <returns></returns>
public PooledList<CompositionVisual>? TryHitTest(Point point, Func<IVisual, bool>? filter)
public PooledList<CompositionVisual>? TryHitTest(Point point, Func<CompositionVisual, bool>? filter)
{
Server.Readback.NextRead();
if (Root == null)
@ -88,10 +88,14 @@ namespace Avalonia.Rendering.Composition
}
void HitTestCore(CompositionVisual visual, Point globalPoint, PooledList<CompositionVisual> result,
Func<IVisual, bool>? filter)
Func<CompositionVisual, bool>? filter)
{
if (visual.Visible == false)
return;
if (filter != null && !filter(visual))
return;
if (!TryTransformTo(visual, globalPoint, out var point))
return;
@ -111,7 +115,7 @@ namespace Avalonia.Rendering.Composition
}
// Hit-test the current node
if (visual.HitTest(point, filter))
if (visual.HitTest(point))
result.Add(visual);
}

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

@ -53,6 +53,6 @@ namespace Avalonia.Rendering.Composition
internal object? Tag { get; set; }
internal virtual bool HitTest(Point point, Func<IVisual, bool>? filter) => true;
internal virtual bool HitTest(Point point) => true;
}
}

33
src/Avalonia.Base/Rendering/DirtyVisuals.cs

@ -17,8 +17,7 @@ namespace Avalonia.Rendering
{
private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>();
private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>();
private List<IVisual> _deferredChanges = new List<IVisual>();
private int _deferring;
private int _enumerating;
/// <summary>
/// Gets the number of dirty visuals.
@ -31,10 +30,9 @@ namespace Avalonia.Rendering
/// <param name="visual">The dirty visual.</param>
public void Add(IVisual visual)
{
if (_deferring > 0)
if (_enumerating > 0)
{
_deferredChanges.Add(visual);
return;
throw new InvalidOperationException("Visual was invalidated during a render pass");
}
var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot);
@ -65,7 +63,7 @@ namespace Avalonia.Rendering
/// </summary>
public void Clear()
{
if (_deferring > 0)
if (_enumerating > 0)
{
throw new InvalidOperationException("Cannot clear while enumerating");
}
@ -80,7 +78,7 @@ namespace Avalonia.Rendering
/// <returns>A collection of visuals.</returns>
public IEnumerator<IVisual> GetEnumerator()
{
BeginDefer();
_enumerating++;
try
{
foreach (var i in _inner)
@ -93,27 +91,10 @@ namespace Avalonia.Rendering
}
finally
{
EndDefer();
_enumerating--;
}
}
private void BeginDefer()
{
++_deferring;
}
private void EndDefer()
{
if (--_deferring > 0) return;
foreach (var visual in _deferredChanges)
{
Add(visual);
}
_deferredChanges.Clear();
}
/// <summary>
/// Gets the dirty visuals, in ascending order of distance to their root.
/// </summary>

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

@ -324,6 +324,41 @@ namespace Avalonia.Utilities
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)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");

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

@ -9,31 +9,6 @@ namespace Avalonia.Utilities
/// </summary>
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>
: 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);
}
/// <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>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// 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,
ZIndex = element.ZIndex,
})
.OrderBy(x => x, null)
.OrderBy(x => x, ZOrderElement.Comparer)
.Select(x => x.Element!);
}
@ -448,6 +448,19 @@ namespace Avalonia.VisualTree
public int Index { 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)
{
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="System.Numerics.Vectors" Version="4.5.0" />
</ItemGroup>
<Import Project="..\..\build\SourceGenerators.props" />
</Project>

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

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

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

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

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

@ -12,9 +12,6 @@ namespace Avalonia.Collections
{
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 bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath);
public abstract IComparer<object> Comparer { get; }
@ -254,13 +251,6 @@ namespace Avalonia.Collections
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)
{
return new DataGridPathSortDescription(propertyPath, direction, comparer, null);

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

@ -2167,7 +2167,23 @@ namespace Avalonia.Controls
return desiredSize;
}
/// <inheritdoc/>
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
NotifyDataContextPropertyForAllRowCells(GetAllRows(), true);
}
/// <inheritdoc/>
protected override void OnDataContextEndUpdate()
{
base.OnDataContextEndUpdate();
NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
}
/// <summary>
/// Raises the BeginningEdit event.
/// </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)
{
int itemCount = DataConnection.Count;

1
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -53,7 +53,6 @@
<Setter Property="VerticalAlignment" Value="Center" />
</ControlTheme>
<ControlTheme x:Key="DataGridCellTextBoxTheme" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Margin" Value="12,0,12,0" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Style Selector="^ /template/ DataValidationErrors">

11
src/Avalonia.Controls/AutoCompleteBox.cs

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

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

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

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

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

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

@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
}
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]);
base.SetItem(index, item);

6
src/Avalonia.Controls/Control.cs

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

2
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@ -8,8 +8,6 @@ namespace Avalonia.Controls
{
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>
/// On desktop-style platforms runs the application's main loop until closable is closed

15
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@ -87,18 +87,7 @@ namespace Avalonia.Controls.Documents
public override TextRunProperties? Properties { get; }
public override Size Size
{
get
{
if (!Control.IsMeasureValid)
{
Control.Measure(Size.Infinity);
}
return Control.DesiredSize;
}
}
public override Size Size => Control.DesiredSize;
public override double Baseline
{
@ -118,7 +107,7 @@ namespace Avalonia.Controls.Documents
public override void Draw(DrawingContext drawingContext, Point origin)
{
Control.Arrange(new Rect(origin, Size));
//noop
}
}
}

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";
}
string s = _value.ToString();
string s = _value.ToString(CultureInfo.InvariantCulture);
return IsStar ? s + "*" : s;
}

13
src/Avalonia.Controls/LoggingExtensions.cs

@ -1,21 +1,10 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Logging;
namespace Avalonia
{
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>
/// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink.
/// </summary>

7
src/Avalonia.Controls/NativeMenuItem.cs

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

13
src/Avalonia.Controls/NativeMenuItemSeparator.cs

@ -1,16 +1,7 @@
using System;
namespace Avalonia.Controls
namespace Avalonia.Controls
{
[Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")]
public class NativeMenuItemSeperator : NativeMenuItemSeparator
{
}
public class NativeMenuItemSeparator : NativeMenuItemBase
{
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
public string Header => "-";
}
}

11
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -102,15 +102,12 @@ namespace Avalonia.Controls.Notifications
Content = content
};
if (notification != null)
notificationControl.NotificationClosed += (sender, args) =>
{
notificationControl.NotificationClosed += (sender, args) =>
{
notification.OnClose?.Invoke();
notification?.OnClose?.Invoke();
_items?.Remove(sender);
};
}
_items?.Remove(sender);
};
notificationControl.PointerPressed += (sender, args) =>
{

114
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -5,6 +5,7 @@ using System.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
@ -45,14 +46,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
/// <summary>
/// Defines the <see cref="CultureInfo"/> property.
/// </summary>
[Obsolete]
public static readonly DirectProperty<NumericUpDown, CultureInfo?> CultureInfoProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo?>(nameof(CultureInfo), o => o.CultureInfo,
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
/// <summary>
/// Defines the <see cref="NumberFormat"/> property.
/// </summary>
@ -104,6 +97,13 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<NumericUpDown, string?>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
/// Defines the <see cref="TextConverter"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, IValueConverter?> TextConverterProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, IValueConverter?>(nameof(TextConverter),
updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
@ -133,6 +133,7 @@ namespace Avalonia.Controls
private decimal? _value;
private string? _text;
private IValueConverter? _textConverter;
private bool _internalValueSet;
private bool _clipValueToMinMax;
private bool _isSyncingTextAndValueProperties;
@ -187,21 +188,6 @@ namespace Avalonia.Controls
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
}
/// <summary>
/// Gets or sets the current CultureInfo.
/// </summary>
[Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")]
public CultureInfo? CultureInfo
{
get { return _cultureInfo; }
set
{
SetAndRaise(CultureInfoProperty, ref _cultureInfo, value);
//Set and Raise the NumberFormatProperty when CultureInfo is changed.
SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat);
}
}
/// <summary>
/// Gets or sets the current NumberFormatInfo
/// </summary>
@ -258,6 +244,8 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
/// Note that Hex style does not work with decimal.
/// For hexadecimal display, use <see cref="TextConverter"/>.
/// </summary>
public NumberStyles ParsingNumberStyle
{
@ -274,6 +262,17 @@ namespace Avalonia.Controls
set { SetAndRaise(TextProperty, ref _text, value); }
}
/// <summary>
/// Gets or sets the custom bidirectional Text-Value converter.
/// Non-null converter overrides <see cref="ParsingNumberStyle"/>, providing finer control over
/// string representation of the underlying value.
/// </summary>
public IValueConverter? TextConverter
{
get { return _textConverter; }
set { SetAndRaise(TextConverterProperty, ref _textConverter, value); }
}
/// <summary>
/// Gets or sets the value.
/// </summary>
@ -335,9 +334,6 @@ namespace Avalonia.Controls
/// </summary>
static NumericUpDown()
{
#pragma warning disable CS0612 // Type or member is obsolete
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
#pragma warning restore CS0612 // Type or member is obsolete
NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);
@ -345,6 +341,7 @@ namespace Avalonia.Controls
MaximumProperty.Changed.Subscribe(OnMaximumChanged);
MinimumProperty.Changed.Subscribe(OnMinimumChanged);
TextProperty.Changed.Subscribe(OnTextChanged);
TextConverterProperty.Changed.Subscribe(OnTextConverterChanged);
ValueProperty.Changed.Subscribe(OnValueChanged);
}
@ -416,19 +413,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnCultureInfoChanged(CultureInfo? oldValue, CultureInfo? newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
@ -524,6 +508,19 @@ namespace Avalonia.Controls
SyncTextAndValueProperties(true, Text);
}
}
/// <summary>
/// Called when the <see cref="Text"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnTextConverterChanged(IValueConverter? oldValue, IValueConverter? newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
@ -651,6 +648,10 @@ namespace Avalonia.Controls
/// <returns></returns>
private string? ConvertValueToText()
{
if (TextConverter != null)
{
return TextConverter.ConvertBack(Value, typeof(string), null, CultureInfo.CurrentCulture)?.ToString();
}
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0"))
{
@ -729,20 +730,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (CultureInfo?)e.OldValue;
var newValue = (CultureInfo?)e.NewValue;
upDown.OnCultureInfoChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
@ -841,6 +828,21 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="TextConverter"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnTextConverterChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (IValueConverter?)e.OldValue;
var newValue = (IValueConverter?)e.NewValue;
upDown.OnTextConverterChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
/// </summary>
@ -1065,6 +1067,12 @@ namespace Avalonia.Controls
return null;
}
if (TextConverter != null)
{
var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);
return (decimal?)valueFromText;
}
if (IsPercent(FormatString))
{
result = ParsePercent(text, NumberFormat);

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

Loading…
Cancel
Save