Browse Source

Merge branch 'master' into fixes/dont-allow-focus-non-visible-elements

pull/6214/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
dc5b23d8c9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      Avalonia.sln.DotSettings
  2. 1
      Documentation/build.md
  3. 57
      azure-pipelines.yml
  4. 48
      build.sh
  5. 4
      build/HarfBuzzSharp.props
  6. 3
      build/MicroCom.targets
  7. 1
      build/SharedVersion.props
  8. 4
      build/SkiaSharp.props
  9. 2
      build/SourceLink.props
  10. 6
      global.json
  11. 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  12. 1
      native/Avalonia.Native/src/OSX/KeyTransform.mm
  13. 1
      native/Avalonia.Native/src/OSX/common.h
  14. 11
      native/Avalonia.Native/src/OSX/main.mm
  15. 33
      native/Avalonia.Native/src/OSX/trayicon.h
  16. 85
      native/Avalonia.Native/src/OSX/trayicon.mm
  17. 1
      native/Avalonia.Native/src/OSX/window.h
  18. 86
      native/Avalonia.Native/src/OSX/window.mm
  19. 9
      nukebuild/_build.csproj
  20. 19
      packages/Avalonia/AvaloniaBuildTasks.targets
  21. 11
      readme.md
  22. 2
      samples/BindingDemo/BindingDemo.csproj
  23. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  24. 25
      samples/ControlCatalog/App.xaml
  25. 9
      samples/ControlCatalog/App.xaml.cs
  26. 1
      samples/ControlCatalog/MainView.xaml
  27. 9
      samples/ControlCatalog/MainWindow.xaml
  28. 2
      samples/ControlCatalog/MainWindow.xaml.cs
  29. 2
      samples/ControlCatalog/Pages/ButtonPage.xaml
  30. 6
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  31. 32
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  32. 2
      samples/ControlCatalog/Pages/DataGridPage.xaml
  33. 20
      samples/ControlCatalog/Pages/DialogsPage.xaml
  34. 24
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  35. 11
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  36. 6
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  37. 11
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  38. 6
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  39. 1
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  40. 1
      samples/ControlCatalog/SideBar.xaml
  41. 26
      samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
  42. 2
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  43. 2
      samples/Previewer/Previewer.csproj
  44. 2
      samples/RemoteDemo/RemoteDemo.csproj
  45. 2
      samples/RenderDemo/RenderDemo.csproj
  46. 1
      samples/RenderDemo/SideBar.xaml
  47. 2
      samples/Sandbox/Sandbox.csproj
  48. 2
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  49. 6
      src/Avalonia.Animation/Animation.cs
  50. 12
      src/Avalonia.Animation/Animators/Animator`1.cs
  51. 3
      src/Avalonia.Animation/ApiCompatBaseline.txt
  52. 6
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  53. 2
      src/Avalonia.Base/AvaloniaObject.cs
  54. 4
      src/Avalonia.Base/AvaloniaProperty.cs
  55. 4
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  56. 6
      src/Avalonia.Base/Collections/AvaloniaList.cs
  57. 2
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  58. 6
      src/Avalonia.Base/Data/Converters/BoolConverters.cs
  59. 2
      src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
  60. 15
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  61. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  62. 6
      src/Avalonia.Base/DirectPropertyBase.cs
  63. 2
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  64. 5
      src/Avalonia.Base/Logging/LogArea.cs
  65. 1
      src/Avalonia.Base/Metadata/TemplateContent.cs
  66. 10
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  67. 2
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  68. 46
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  69. 7
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  70. 52
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  71. 8
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  72. 7
      src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs
  73. 121
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  74. 2
      src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
  75. 45
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  76. 2
      src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
  77. 2
      src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
  78. 31
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  79. 56
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  80. 11
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  81. 8
      src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
  82. 42
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  83. 15
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  84. 17
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  85. 18
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  86. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
  87. 13
      src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
  88. 8
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  89. 6
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  90. 6
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  91. 13
      src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs
  92. 4
      src/Avalonia.Controls/ApiCompatBaseline.txt
  93. 2
      src/Avalonia.Controls/AppBuilderBase.cs
  94. 20
      src/Avalonia.Controls/AutoCompleteBox.cs
  95. 2
      src/Avalonia.Controls/Border.cs
  96. 10
      src/Avalonia.Controls/Button.cs
  97. 30
      src/Avalonia.Controls/ComboBox.cs
  98. 32
      src/Avalonia.Controls/ContextMenu.cs
  99. 10
      src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
  100. 4
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

3
Avalonia.sln.DotSettings

@ -1,5 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Examl/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
@ -39,4 +38,4 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

1
Documentation/build.md

@ -6,6 +6,7 @@ Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on
```
git clone https://github.com/AvaloniaUI/Avalonia.git
cd Avalonia
git submodule update --init
```

57
azure-pipelines.yml

@ -1,21 +1,28 @@
variables:
MSBuildEnableWorkloadResolver: 'false'
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-20.04'
steps:
- task: CmdLine@2
displayName: 'Install Nuke'
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
inputs:
version: 6.0.100
- task: CmdLine@2
displayName: 'Run Nuke'
displayName: 'Run Build'
inputs:
script: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
nuke --target CiAzureLinux --configuration=Release
./build.sh --target CiAzureLinux --configuration=Release
- task: PublishTestResults@2
inputs:
@ -23,6 +30,7 @@ jobs:
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled())
- job: macOS
variables:
SolutionDir: '$(Build.SourcesDirectory)'
@ -30,10 +38,15 @@ jobs:
vmImage: 'macOS-10.15'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
displayName: 'Use .NET Core SDK 3.1.414'
inputs:
version: 3.1.401
version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
inputs:
version: 6.0.100
- task: CmdLine@2
displayName: 'Install Mono 5.18'
inputs:
@ -45,7 +58,8 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
cd src/tools/MicroComGenerator; dotnet run -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
export PATH="`pwd`/sdk:$PATH"
cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
- task: Xcode@5
inputs:
@ -58,13 +72,7 @@ jobs:
args: '-derivedDataPath ./'
- task: CmdLine@2
displayName: 'Install Nuke'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
displayName: 'Run Build'
inputs:
script: |
export COREHOST_TRACE=0
@ -72,10 +80,8 @@ jobs:
export DOTNET_CLI_TELEMETRY_OPTOUT=1
which dotnet
dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
nuke --target CiAzureOSX --configuration Release --skip-previewer
./build.sh --target CiAzureOSX --configuration Release --skip-previewer
- task: PublishTestResults@2
inputs:
@ -102,9 +108,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
displayName: 'Use .NET Core SDK 3.1.414'
inputs:
version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
inputs:
version: 3.1.401
version: 6.0.100
- task: CmdLine@2
displayName: 'Install Nuke'

48
build.sh

@ -20,55 +20,11 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
DOTNET_CHANNEL="Current"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export NUGET_XMLDOC_MODE="skip"
###########################################################################
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
}
# If global.json exists, load expected version
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
if [ "$DOTNET_VERSION" == "" ]; then
unset DOTNET_VERSION
fi
fi
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") || "$SKIP_DOTNET_DOWNLOAD" == "1" ]]; then
export DOTNET_EXE="$(command -v dotnet)"
else
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# Install by channel or version
if [ -z ${DOTNET_VERSION+x} ]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
fi
export PATH=$DOTNET_DIRECTORY:$PATH
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
dotnet --info
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}
dotnet run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}

4
build/HarfBuzzSharp.props

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

3
build/MicroCom.targets

@ -15,7 +15,8 @@
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs"
Outputs="%(AvnComIdl.OutputFile)">
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" />
<Exec Command="dotnet $(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/netcoreapp3.1/MicroComGenerator.dll -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)" LogStandardErrorAsError="true" />
<Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll&quot; -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)"
LogStandardErrorAsError="true" />
<ItemGroup>
<!-- Remove and re-add generated file, this is needed for the clean build -->
<Compile Remove="%(AvnComIdl.OutputFile)"/>

1
build/SharedVersion.props

@ -17,7 +17,6 @@
<RepositoryType>git</RepositoryType>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\avalonia.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<DefineConstants Condition="$(SignAssembly) == true">$(DefineConstants);SIGNED_BUILD</DefineConstants>
</PropertyGroup>
<ItemGroup Label="PackageIcon">

4
build/SkiaSharp.props

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

2
build/SourceLink.props

@ -3,7 +3,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>false</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
<DebugType>full</DebugType>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>

6
global.json

@ -1,7 +1,7 @@
{
"sdk": {
"version": "3.1.401"
},
"sdk": {
"version": "6.0.100"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "2.0.54",

6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -22,6 +22,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 522D5958258159C1006F7F7A /* Carbon.framework */; };
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -51,6 +52,8 @@
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
522D5958258159C1006F7F7A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
523484C926EA688F00EA0C2C /* trayicon.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trayicon.mm; sourceTree = "<group>"; };
523484CB26EA68AA00EA0C2C /* trayicon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trayicon.h; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -114,6 +117,8 @@
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
523484C926EA688F00EA0C2C /* trayicon.mm */,
523484CB26EA68AA00EA0C2C /* trayicon.h */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
@ -204,6 +209,7 @@
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,

1
native/Avalonia.Native/src/OSX/KeyTransform.mm

@ -222,6 +222,7 @@ std::map<int, const char*> s_QwertyKeyMap =
{ 45, "n" },
{ 46, "m" },
{ 47, "." },
{ 48, "\t" },
{ 49, " " },
{ 50, "`" },
{ 51, "" },

1
native/Avalonia.Native/src/OSX/common.h

@ -22,6 +22,7 @@ extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);

11
native/Avalonia.Native/src/OSX/main.mm

@ -303,6 +303,17 @@ public:
}
}
virtual HRESULT CreateTrayIcon (IAvnTrayIcon** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreateTrayIcon();
return S_OK;
}
}
virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{
START_COM_CALL;

33
native/Avalonia.Native/src/OSX/trayicon.h

@ -0,0 +1,33 @@
//
// trayicon.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 09/09/2021.
// Copyright © 2021 Avalonia. All rights reserved.
//
#ifndef trayicon_h
#define trayicon_h
#include "common.h"
class AvnTrayIcon : public ComSingleObject<IAvnTrayIcon, &IID_IAvnTrayIcon>
{
private:
NSStatusItem* _native;
public:
FORWARD_IUNKNOWN()
AvnTrayIcon();
~AvnTrayIcon ();
virtual HRESULT SetIcon (void* data, size_t length) override;
virtual HRESULT SetMenu (IAvnMenu* menu) override;
virtual HRESULT SetIsVisible (bool isVisible) override;
};
#endif /* trayicon_h */

85
native/Avalonia.Native/src/OSX/trayicon.mm

@ -0,0 +1,85 @@
#include "common.h"
#include "trayicon.h"
#include "menu.h"
extern IAvnTrayIcon* CreateTrayIcon()
{
@autoreleasepool
{
return new AvnTrayIcon();
}
}
AvnTrayIcon::AvnTrayIcon()
{
_native = [[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength];
}
AvnTrayIcon::~AvnTrayIcon()
{
if(_native != nullptr)
{
[[_native statusBar] removeStatusItem:_native];
_native = nullptr;
}
}
HRESULT AvnTrayIcon::SetIcon (void* data, size_t length)
{
START_COM_CALL;
@autoreleasepool
{
if(data != nullptr)
{
NSData *imageData = [NSData dataWithBytes:data length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];
NSSize originalSize = [image size];
NSSize size;
size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;
auto scaleFactor = size.height / originalSize.height;
size.width = originalSize.width * scaleFactor;
[image setSize: size];
[_native setImage:image];
}
else
{
[_native setImage:nullptr];
}
return S_OK;
}
}
HRESULT AvnTrayIcon::SetMenu (IAvnMenu* menu)
{
START_COM_CALL;
@autoreleasepool
{
auto appMenu = dynamic_cast<AvnAppMenu*>(menu);
if(appMenu != nullptr)
{
[_native setMenu:appMenu->GetNative()];
}
}
return S_OK;
}
HRESULT AvnTrayIcon::SetIsVisible(bool isVisible)
{
START_COM_CALL;
@autoreleasepool
{
[_native setVisible:isVisible];
}
return S_OK;
}

1
native/Avalonia.Native/src/OSX/window.h

@ -12,6 +12,7 @@ class WindowBaseImpl;
-(AvnPixelSize) getPixelSize;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
@interface AutoFitContentView : NSView

86
native/Avalonia.Native/src/OSX/window.mm

@ -52,7 +52,6 @@ public:
[Window setBackingType:NSBackingStoreBuffered];
[Window setOpaque:false];
[Window setContentView: StandardContainer];
}
virtual HRESULT ObtainNSWindowHandle(void** ret) override
@ -125,6 +124,8 @@ public:
SetPosition(lastPositionSet);
UpdateStyle();
[Window setContentView: StandardContainer];
[Window setTitle:_lastTitle];
if(ShouldTakeFocusOnShow() && activate)
@ -205,7 +206,11 @@ public:
auto window = Window;
Window = nullptr;
[window close];
try{
// Seems to throw sometimes on application exit.
[window close];
}
catch(NSException*){}
}
return S_OK;
@ -231,6 +236,8 @@ public:
virtual HRESULT GetFrameSize(AvnSize* ret) override
{
START_COM_CALL;
@autoreleasepool
{
if(ret == nullptr)
@ -321,6 +328,7 @@ public:
BaseEvents->Resized(AvnSize{x,y}, reason);
}
[StandardContainer setFrameSize:NSSize{x,y}];
[Window setContentSize:NSSize{x, y}];
}
@finally
@ -641,6 +649,7 @@ private:
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
}
void HideOrShowTrafficLights ()
@ -713,6 +722,13 @@ private:
if(cparent == nullptr)
return E_INVALIDARG;
// If one tries to show a child window with a minimized parent window, then the parent window will be
// restored but MacOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
UpdateStyle();
@ -1085,14 +1101,7 @@ private:
{
_fullScreenActive = true;
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
[Window toggleFullScreen:nullptr];
}
@ -1204,6 +1213,7 @@ private:
}
_actualWindowState = _lastWindowState;
WindowEvents->WindowStateChanged(_actualWindowState);
}
@ -1540,7 +1550,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return pt;
}
- (AvnPoint)toAvnPoint:(CGPoint)p
+ (AvnPoint)toAvnPoint:(CGPoint)p
{
AvnPoint result;
@ -1597,7 +1607,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta;
@ -1665,6 +1675,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
switch(event.buttonNumber)
{
case 2:
case 3:
_isMiddlePressed = true;
[self mouseEvent:event withType:MiddleButtonDown];
@ -1697,6 +1708,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
switch(event.buttonNumber)
{
case 2:
case 3:
_isMiddlePressed = false;
[self mouseEvent:event withType:MiddleButtonUp];
@ -1940,7 +1952,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
{
auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
@ -2373,6 +2385,56 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->BaseEvents->PositionChanged(position);
}
}
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self frame].size.height - pt.Y;
return pt;
}
- (void)sendEvent:(NSEvent *)event
{
[super sendEvent:event];
/// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
if(_parent != nullptr && dynamic_cast<WindowImpl*>(_parent.getRaw()) != nullptr)
{
switch(event.type)
{
case NSEventTypeLeftMouseDown:
{
AvnView* view = _parent->View;
NSPoint windowPoint = [event locationInWindow];
NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
if (!NSPointInRect(viewPoint, view.bounds))
{
auto avnPoint = [AvnView toAvnPoint:windowPoint];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta;
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, [event timestamp] * 1000, AvnInputModifiersNone, point, delta);
}
}
break;
case NSEventTypeMouseEntered:
{
_parent->UpdateCursor();
}
break;
case NSEventTypeMouseExited:
{
[[NSCursor arrowCursor] set];
}
break;
default:
break;
}
}
}
@end
class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup

9
nukebuild/_build.csproj

@ -15,6 +15,7 @@
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
</ItemGroup>
@ -36,10 +37,10 @@
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\tools\MicroComGenerator\MicroComGenerator.csproj" />
<Compile Include="..\src\tools\MicroComGenerator\**\*.cs" Exclude="..\src\tools\MicroComGenerator\obj\**">
<Link>MicroComGenerator\%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\src\tools\MicroComGenerator\Program.cs" />
</ItemGroup>
</Project>

19
packages/Avalonia/AvaloniaBuildTasks.targets

@ -42,12 +42,25 @@
</Target>
<PropertyGroup>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences</BuildAvaloniaResourcesDependsOn>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache</BuildAvaloniaResourcesDependsOn>
</PropertyGroup>
<Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
<ItemGroup>
<CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" />
</ItemGroup>
<Hash ItemsToHash="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)">
<Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" />
</Hash>
<MakeDir Directories="$(IntermediateOutputPath)/Avalonia" />
<WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
</Target>
<Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)"
Inputs="@(AvaloniaResource);@(AvaloniaXaml);@(CustomAdditionalGenerateAvaloniaResourcesInputs);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<ItemGroup>
@ -75,6 +88,7 @@
<AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath>
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
</PropertyGroup>
<WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@ -94,6 +108,7 @@
DelaySign="$(DelaySign)"
EnableComInteropPatching="$(_AvaloniaPatchComInterop)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

11
readme.md

@ -60,6 +60,9 @@ See the [build instructions here](Documentation/build.md).
## Contributing
This project exists thanks to all the people who contribute.
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request.
## Code of Conduct
@ -71,11 +74,6 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
Avalonia is licenced under the [MIT licence](licence.md).
## Contributors
This project exists thanks to all the people who contribute. [[Contribute](https://avaloniaui.net/contributing)].
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]
@ -95,7 +93,8 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/Avalonia/sponsor/6/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/7/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/8/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
<a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a>
## .NET Foundation

2
samples/BindingDemo/BindingDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

2
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>

25
samples/ControlCatalog/App.xaml

@ -1,5 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ControlCatalog.ViewModels"
x:DataType="vm:ApplicationViewModel"
x:CompileBindings="True"
x:Class="ControlCatalog.App">
<Application.Styles>
<Style Selector="TextBlock.h1">
@ -22,6 +25,26 @@
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<StyleInclude Source="/SideBar.xaml"/>
<StyleInclude Source="/SideBar.xaml" />
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="/Assets/test_icon.ico" ToolTipText="Avalonia Tray Icon ToolTip">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Settings">
<NativeMenu>
<NativeMenuItem Header="Option 1" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Header="Option 2" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItemSeparator />
<NativeMenuItem Header="Option 3" ToggleType="CheckBox" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Icon="/Assets/test_icon.ico" Header="Restore Defaults" Command="{Binding ToggleCommand}" />
</NativeMenu>
</NativeMenuItem>
<NativeMenuItem Header="Exit" Command="{Binding ExitCommand}" />
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.Icons>
</Application>

9
samples/ControlCatalog/App.xaml.cs

@ -1,14 +1,21 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using ControlCatalog.ViewModels;
namespace ControlCatalog
{
public class App : Application
{
public App()
{
DataContext = new ApplicationViewModel();
}
private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
@ -97,7 +104,9 @@ namespace ControlCatalog
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow = new MainWindow();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();

1
samples/ControlCatalog/MainView.xaml

@ -98,6 +98,7 @@
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
<ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
</StackPanel>

9
samples/ControlCatalog/MainWindow.xaml

@ -12,6 +12,7 @@
ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
TransparencyLevelHint="{Binding TransparencyLevel}"
x:Name="MainWindow"
Background="Transparent"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
<NativeMenu.Menu>
<NativeMenu>
@ -62,11 +63,11 @@
<Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
<MenuItem Header="_File">
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
<MenuItem Header="_Help">
<MenuItem Header="_About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />

2
samples/ControlCatalog/MainWindow.xaml.cs

@ -35,6 +35,8 @@ namespace ControlCatalog
var mainMenu = this.FindControl<Menu>("MainMenu");
mainMenu.AttachedToVisualTree += MenuAttached;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
}
public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";

2
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8" Width="200">
<Button>Standard XAML Button</Button>
<Button>Standard _XAML Button</Button>
<Button Foreground="White">Foreground</Button>
<Button Background="{DynamicResource SystemAccentColor}">Background</Button>
<Button IsEnabled="False">Disabled</Button>

6
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@ -11,9 +11,9 @@
Spacing="16">
<StackPanel Orientation="Vertical"
Spacing="16">
<CheckBox>Unchecked</CheckBox>
<CheckBox IsChecked="True">Checked</CheckBox>
<CheckBox IsChecked="{x:Null}">Indeterminate</CheckBox>
<CheckBox>_Unchecked</CheckBox>
<CheckBox IsChecked="True">_Checked</CheckBox>
<CheckBox IsChecked="{x:Null}">_Indeterminate</CheckBox>
<CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
</StackPanel>
<StackPanel Orientation="Vertical"

32
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -1,12 +1,20 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ComboBoxPage"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="using:System"
xmlns:col="using:System.Collections">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ComboBox</TextBlock>
<TextBlock Classes="h2">A drop-down list.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="8">
<WrapPanel HorizontalAlignment="Center" Margin="0 16 0 0"
MaxWidth="750">
<WrapPanel.Styles>
<Style Selector="ComboBox">
<Setter Property="Width" Value="250" />
<Setter Property="Margin" Value="10" />
</Style>
</WrapPanel.Styles>
<ComboBox PlaceholderText="Pick an Item">
<ComboBoxItem>Inline Items</ComboBoxItem>
<ComboBoxItem>Inline Item 2</ComboBoxItem>
@ -14,6 +22,24 @@
<ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox>
<ComboBox>
<ComboBox.Items>
<col:ArrayList>
<x:Null />
<sys:String>Hello</sys:String>
<sys:String>World</sys:String>
</col:ArrayList>
</ComboBox.Items>
<ComboBox.ItemTemplate>
<DataTemplate>
<Panel>
<TextBlock Text="{Binding}" />
<TextBlock Text="Null object" IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" />
</Panel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox SelectedIndex="0">
<ComboBoxItem>
<Panel>
@ -46,7 +72,7 @@
<sys:Exception />
</DataValidationErrors.Error>
</ComboBox>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

2
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -28,7 +28,7 @@
DockPanel.Dock="Top"/>
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" MinWidth="400" />
<!-- CompiledBinding example of usage. -->
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />

20
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -3,15 +3,15 @@
x:Class="ControlCatalog.Pages.DialogsPage">
<StackPanel Orientation="Vertical" Spacing="4" Margin="4">
<CheckBox Name="UseFilters">Use filters</CheckBox>
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<Button Name="OpenBoth">Select Both</Button>
<Button Name="DecoratedWindow">Decorated window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (No taskbar icon)</Button>
<Button Name="OwnedWindow">Owned window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No taskbar icon)</Button>
<Button Name="OpenFile">_Open File</Button>
<Button Name="SaveFile">_Save File</Button>
<Button Name="SelectFolder">Select Fo_lder</Button>
<Button Name="OpenBoth">Select _Both</Button>
<Button Name="DecoratedWindow">Decorated _window</Button>
<Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
<Button Name="Dialog">_Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
<Button Name="OwnedWindow">Own_ed window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>
</StackPanel>
</UserControl>

24
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -1,17 +1,33 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ItemsRepeaterPage">
<UserControl.Styles>
<Style Selector="ItemsRepeater TextBlock.oddTemplate">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock.evenTemplate">
<Setter Property="Background" Value="Wheat" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Styles>
<UserControl.Resources>
<RecyclePool x:Key="RecyclePool" />
<DataTemplate x:Key="odd">
<TextBlock Background="Yellow"
Foreground="Black"
<TextBlock Classes="oddTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate x:Key="even">
<TextBlock Background="Wheat"
Foreground="Black"
<TextBlock Classes="evenTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>

11
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -2,9 +2,20 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ListBoxPage">
<DockPanel>
<DockPanel.Styles>
<Style Selector="ListBox ListBoxItem:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ListBox ListBoxItem:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</DockPanel.Styles>
<StackPanel DockPanel.Dock="Top" Margin="4">
<TextBlock Classes="h1">ListBox</TextBlock>
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock>
<TextBlock Classes="h2">Each 5th item is highlighted with nth-child(5n+3) and nth-last-child(5n+4) rules.</TextBlock>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Margin="4">
<CheckBox IsChecked="{Binding Multiple}">Multiple</CheckBox>

6
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -11,9 +11,9 @@
Spacing="16">
<StackPanel Orientation="Vertical"
Spacing="16">
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton IsChecked="{x:Null}">Option 3</RadioButton>
<RadioButton IsChecked="True">_Option 1</RadioButton>
<RadioButton>O_ption 2</RadioButton>
<RadioButton IsChecked="{x:Null}">Op_tion 3</RadioButton>
<RadioButton IsEnabled="False">Disabled</RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical"

11
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -11,13 +11,22 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" />
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200"
FontFamily="Comic Sans MS"
Foreground="Blue">
<TextBox.ContextFlyout>
<Flyout>
<TextBlock>Custom context flyout</TextBlock>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric Watermark" x:Name="numericWatermark"/>
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<MaskedTextBox Width="200" ResetOnSpace="False" Mask="(LLL) 999-0000"/>
<TextBox Width="200" Text="Validation Error">
<DataValidationErrors.Error>

6
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

@ -14,7 +14,7 @@
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="headered" IsChecked="true" Margin="10"/>
<ToggleSwitch Content="h_eadered" IsChecked="true" Margin="10"/>
<TextBox Classes="CodeBox"
Text="&lt;ToggleSwitch&gt;headered&lt;/ToggleSwitch&gt;"/>
</StackPanel>
@ -24,7 +24,7 @@
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="Custom"
<ToggleSwitch Content="_Custom"
OnContent="On"
OffContent="Off"
Margin="10"/>
@ -40,7 +40,7 @@ ContentOff=&quot;Off&quot; /&gt;"
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="Just Click!" Margin="10">
<ToggleSwitch Content="_Just Click!" Margin="10">
<ToggleSwitch.OnContent>
<Image Source="/Assets/hirsch-899118_640.jpg" Height="32"/>
</ToggleSwitch.OnContent>

1
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -14,6 +14,7 @@
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
</StackPanel>
</UserControl>

1
samples/ControlCatalog/SideBar.xaml

@ -16,7 +16,6 @@
<Setter Property="Template">
<ControlTemplate>
<Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>

26
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

@ -0,0 +1,26 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class ApplicationViewModel : ViewModelBase
{
public ApplicationViewModel()
{
ExitCommand = MiniCommand.Create(() =>
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.Shutdown();
}
});
ToggleCommand = MiniCommand.Create(() => { });
}
public MiniCommand ExitCommand { get; }
public MiniCommand ToggleCommand { get; }
}
}

2
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

2
samples/Previewer/Previewer.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">

2
samples/RemoteDemo/RemoteDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

2
samples/RenderDemo/RenderDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" />

1
samples/RenderDemo/SideBar.xaml

@ -7,7 +7,6 @@
<Setter Property="Template">
<ControlTemplate>
<Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>

2
samples/Sandbox/Sandbox.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>

2
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

6
src/Avalonia.Animation/Animation.cs

@ -353,6 +353,12 @@ namespace Avalonia.Animation
return new CompositeDisposable(subscriptions);
}
/// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null)
{
return RunAsync(control, clock, default);
}
/// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default)
{

12
src/Avalonia.Animation/Animators/Animator`1.cs

@ -79,15 +79,15 @@ namespace Avalonia.Animation.Animators
T oldValue, newValue;
if (firstKeyframe.isNeutral)
oldValue = neutralValue;
if (!firstKeyframe.isNeutral && firstKeyframe.Value is T firstKeyframeValue)
oldValue = firstKeyframeValue;
else
oldValue = (T)firstKeyframe.Value;
oldValue = neutralValue;
if (lastKeyframe.isNeutral)
newValue = neutralValue;
if (!lastKeyframe.isNeutral && lastKeyframe.Value is T lastKeyframeValue)
newValue = lastKeyframeValue;
else
newValue = (T)lastKeyframe.Value;
newValue = neutralValue;
if (lastKeyframe.KeySpline != null)
progress = lastKeyframe.KeySpline.GetSplineProgress(progress);

3
src/Avalonia.Animation/ApiCompatBaseline.txt

@ -1,6 +1,5 @@
Compat issues with assembly Avalonia.Animation:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
Total Issues: 4
Total Issues: 3

6
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -1,15 +1,9 @@
using Avalonia.Metadata;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.LeakTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.LeakTests")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests")]
#endif

2
src/Avalonia.Base/AvaloniaObject.cs

@ -861,7 +861,7 @@ namespace Avalonia
}
/// <summary>
/// Logs a mesage if the notification represents a binding error.
/// Logs a message if the notification represents a binding error.
/// </summary>
/// <param name="property">The property being bound.</param>
/// <param name="value">The binding notification.</param>

4
src/Avalonia.Base/AvaloniaProperty.cs

@ -465,9 +465,9 @@ namespace Avalonia
/// Uses the visitor pattern to resolve an untyped property to a typed property.
/// </summary>
/// <typeparam name="TData">The type of user data passed.</typeparam>
/// <param name="vistor">The visitor which will accept the typed property.</param>
/// <param name="visitor">The visitor which will accept the typed property.</param>
/// <param name="data">The user data to pass.</param>
public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
where TData : struct;
/// <summary>

4
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@ -58,8 +58,8 @@ namespace Avalonia
/// <remarks>
/// This will usually be true, except in
/// <see cref="AvaloniaObject.OnPropertyChangedCore{T}(AvaloniaPropertyChangedEventArgs{T})"/>
/// which recieves notifications for all changes to property values, whether a value with a higher
/// priority is present or not. When this property is false, the change that is being signalled
/// which receives notifications for all changes to property values, whether a value with a higher
/// priority is present or not. When this property is false, the change that is being signaled
/// has not resulted in a change to the property value on the object.
/// </remarks>
public bool IsEffectiveValueChange { get; private set; }

6
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -280,8 +280,8 @@ namespace Avalonia.Collections
/// <summary>
/// Gets a range of items from the collection.
/// </summary>
/// <param name="index">The first index to remove.</param>
/// <param name="count">The number of items to remove.</param>
/// <param name="index">The zero-based <see cref="AvaloniaList{T}"/> index at which the range starts.</param>
/// <param name="count">The number of elements in the range.</param>
public IEnumerable<T> GetRange(int index, int count)
{
return _inner.GetRange(index, count);
@ -455,7 +455,7 @@ namespace Avalonia.Collections
}
/// <summary>
/// Ensures that the capacity of the list is at least <see cref="capacity"/>.
/// Ensures that the capacity of the list is at least <see cref="Capacity"/>.
/// </summary>
/// <param name="capacity">The capacity.</param>
public void EnsureCapacity(int capacity)

2
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@ -1271,7 +1271,7 @@ namespace Avalonia.Collections.Pooled
/// Reverses the elements in a range of this list. Following a call to this
/// method, an element in the range given by index and count
/// which was previously located at index i will now be located at
/// index index + (index + count - i - 1).
/// index + (index + count - i - 1).
/// </summary>
public void Reverse(int index, int count)
{

6
src/Avalonia.Base/Data/Converters/BoolConverters.cs

@ -18,5 +18,11 @@ namespace Avalonia.Data.Converters
/// </summary>
public static readonly IMultiValueConverter Or =
new FuncMultiValueConverter<bool, bool>(x => x.Any(y => y));
/// <summary>
/// A value converter that returns true when input is false and false when input is true.
/// </summary>
public static readonly IValueConverter Not =
new FuncValueConverter<bool, bool>(x => !x);
}
}

2
src/Avalonia.Base/Data/Converters/FuncValueConverter.cs

@ -26,7 +26,7 @@ namespace Avalonia.Data.Converters
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TIn || (value == null && TypeUtilities.AcceptsNull(typeof(TIn))))
if (TypeUtilities.CanCast<TIn>(value))
{
return _convert((TIn)value);
}

15
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@ -140,18 +140,9 @@ namespace Avalonia.Data.Converters
);
}
Action<object> action = null;
try
{
action = Expression
.Lambda<Action<object>>(body, parameter)
.Compile();
}
catch (Exception ex)
{
throw ex;
}
return action;
return Expression
.Lambda<Action<object>>(body, parameter)
.Compile();
}
static Func<object, bool> CreateCanExecute(object target

2
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="inner">The inner property accessor used to aceess the property.</param>
/// <param name="inner">The inner property accessor used to access the property.</param>
/// <returns>
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.

6
src/Avalonia.Base/DirectPropertyBase.cs

@ -13,7 +13,7 @@ namespace Avalonia
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <remarks>
/// Whereas <see cref="DirectProperty{TOwner, TValue}"/> is typed on the owner type, this base
/// class provides a non-owner-typed interface to a direct poperty.
/// class provides a non-owner-typed interface to a direct property.
/// </remarks>
public abstract class DirectPropertyBase<TValue> : AvaloniaProperty<TValue>
{
@ -123,9 +123,9 @@ namespace Avalonia
}
/// <inheritdoc/>
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
{
vistor.Visit(this, ref data);
visitor.Visit(this, ref data);
}
/// <inheritdoc/>

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

@ -38,7 +38,7 @@ namespace Avalonia
/// <remarks>
/// Data validation is validation performed at the target of a binding, for example in a
/// view model using the INotifyDataErrorInfo interface. Only certain properties on a
/// control (such as a TextBox's Text property) will be interested in recieving data
/// control (such as a TextBox's Text property) will be interested in receiving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
/// </remarks>
public bool? EnableDataValidation { get; private set; }

5
src/Avalonia.Base/Logging/LogArea.cs

@ -39,5 +39,10 @@ namespace Avalonia.Logging
/// The log event comes from Win32Platform.
/// </summary>
public const string Win32Platform = nameof(Win32Platform);
/// <summary>
/// The log event comes from X11Platform.
/// </summary>
public const string X11Platform = nameof(X11Platform);
}
}

1
src/Avalonia.Base/Metadata/TemplateContent.cs

@ -8,5 +8,6 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.Property)]
public class TemplateContentAttribute : Attribute
{
public Type TemplateResultType { get; set; }
}
}

10
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -5,18 +5,10 @@ using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Visuals, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.Visuals")]
#endif

2
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -39,7 +39,7 @@ namespace Avalonia.Threading
if (Dispatcher.UIThread.CheckAccess())
d(state);
else
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).GetAwaiter().GetResult();
}

46
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
@ -93,13 +94,36 @@ namespace Avalonia.Utilities
return !type.IsValueType || IsNullableType(type);
}
/// <summary>
/// Returns a value indicating whether null can be assigned to the specified type.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <returns>True if the type accepts null values; otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AcceptsNull<T>()
{
return default(T) is null;
}
/// <summary>
/// Returns a value indicating whether value can be casted to the specified type.
/// If value is null, checks if instances of that type can be null.
/// </summary>
/// <typeparam name="T">The type to cast to</typeparam>
/// <param name="value">The value to check if cast possible</param>
/// <returns>True if the cast is possible, otherwise false.</returns>
public static bool CanCast<T>(object value)
{
return value is T || (value is null && AcceptsNull<T>());
}
/// <summary>
/// Try to convert a value to a type by any means possible.
/// </summary>
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="to">The type to convert to.</param>
/// <param name="value">The value to convert.</param>
/// <param name="culture">The culture to use.</param>
/// <param name="result">If successful, contains the cast value.</param>
/// <param name="result">If successful, contains the convert value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
public static bool TryConvert(Type to, object value, CultureInfo culture, out object result)
{
@ -216,10 +240,10 @@ namespace Avalonia.Utilities
/// Try to convert a value to a type using the implicit conversions allowed by the C#
/// language.
/// </summary>
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="result">If successful, contains the cast value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
/// <param name="to">The type to convert to.</param>
/// <param name="value">The value to convert.</param>
/// <param name="result">If successful, contains the converted value.</param>
/// <returns>True if the convert was successful, otherwise false.</returns>
public static bool TryConvertImplicit(Type to, object value, out object result)
{
if (value == null)
@ -278,8 +302,8 @@ namespace Avalonia.Utilities
/// Convert a value to a type by any means possible, returning the default for that type
/// if the value could not be converted.
/// </summary>
/// <param name="value">The value to cast.</param>
/// <param name="type">The type to cast to..</param>
/// <param name="value">The value to convert.</param>
/// <param name="type">The type to convert to..</param>
/// <param name="culture">The culture to use.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
public static object ConvertOrDefault(object value, Type type, CultureInfo culture)
@ -291,8 +315,8 @@ namespace Avalonia.Utilities
/// Convert a value to a type using the implicit conversions allowed by the C# language or
/// return the default for the type if the value could not be converted.
/// </summary>
/// <param name="value">The value to cast.</param>
/// <param name="type">The type to cast to..</param>
/// <param name="value">The value to convert.</param>
/// <param name="type">The type to convert to.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
public static object ConvertImplicitOrDefault(object value, Type type)
{

7
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -1,9 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
@ -41,7 +38,7 @@ namespace Avalonia.Build.Tasks
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
EnableComInteropPatching, SkipXamlCompilation);
EnableComInteropPatching, SkipXamlCompilation, DebuggerLaunch);
if (!res.Success)
return false;
if (!res.WrittenFile)
@ -87,5 +84,7 @@ namespace Avalonia.Build.Tasks
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
public bool DebuggerLaunch { get; set; }
}
}

52
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using XamlX;
@ -44,16 +41,23 @@ namespace Avalonia.Build.Tasks
string projectDirectory,
string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom,
bool skipXamlCompilation)
{
return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, patchCom, skipXamlCompilation, debuggerLaunch:false);
}
internal static CompileResult Compile(IBuildEngine engine, string input, string[] references,
string projectDirectory,
string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch)
{
var typeSystem = new CecilTypeSystem(references
.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll"))
.Concat(new[] { input }), input);
var asm = typeSystem.TargetAssemblyDefinition;
if (!skipXamlCompilation)
{
var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance);
var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch);
if (compileRes == null && !patchCom)
return new CompileResult(true);
if (compileRes == false)
@ -62,7 +66,7 @@ namespace Avalonia.Build.Tasks
if (patchCom)
ComInteropHelper.PatchAssembly(asm, typeSystem);
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
@ -70,13 +74,43 @@ namespace Avalonia.Build.Tasks
asm.Write(output, writerParameters);
return new CompileResult(true, true);
}
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
string projectDirectory, bool verifyIl,
MessageImportance logImportance)
MessageImportance logImportance
, bool debuggerLaunch = false)
{
if (debuggerLaunch)
{
// According this https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debugger.launch?view=net-6.0#remarks
// documentation, on not windows platform Debugger.Launch() always return true without running a debugger.
if (System.Diagnostics.Debugger.Launch())
{
// Set timeout at 1 minut.
var time = new System.Diagnostics.Stopwatch();
var timeout = TimeSpan.FromMinutes(1);
time.Start();
// wait for the debugger to be attacked or timeout.
while (!System.Diagnostics.Debugger.IsAttached && time.Elapsed < timeout)
{
engine.LogMessage($"[PID:{System.Diagnostics.Process.GetCurrentProcess().Id}] Wating attach debugger. Elapsed {time.Elapsed}...", MessageImportance.High);
System.Threading.Thread.Sleep(100);
}
time.Stop();
if (time.Elapsed >= timeout)
{
engine.LogMessage("Wating attach debugger timeout.", MessageImportance.Normal);
}
}
else
{
engine.LogMessage("Debugging cancelled.", MessageImportance.Normal);
}
}
var asm = typeSystem.TargetAssemblyDefinition;
var emres = new EmbeddedResources(asm);
var avares = new AvaloniaResources(asm, projectDirectory);

8
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -877,7 +877,7 @@ namespace Avalonia.Collections
if (!CheckFlag(CollectionViewFlags.IsMoveToPageDeferred))
{
// if the temporaryGroup was not created yet and is out of sync
// then create it so that we can use it as a refernce while paging.
// then create it so that we can use it as a reference while paging.
if (IsGrouping && _temporaryGroup.ItemCount != InternalList.Count)
{
PrepareTemporaryGroups();
@ -889,7 +889,7 @@ namespace Avalonia.Collections
else if (IsGrouping)
{
// if the temporaryGroup was not created yet and is out of sync
// then create it so that we can use it as a refernce while paging.
// then create it so that we can use it as a reference while paging.
if (_temporaryGroup.ItemCount != InternalList.Count)
{
// update the groups that get created for the
@ -1951,7 +1951,7 @@ namespace Avalonia.Collections
EnsureCollectionInSync();
VerifyRefreshNotDeferred();
// for indicies larger than the count
// for indices larger than the count
if (index >= Count || index < 0)
{
throw new ArgumentOutOfRangeException("index");
@ -3800,7 +3800,7 @@ namespace Avalonia.Collections
/// </summary>
/// <remarks>
/// This method can be called from a constructor - it does not call
/// any virtuals. The 'count' parameter is substitute for the real Count,
/// any virtuals. The 'count' parameter is substitute for the real Count,
/// used only when newItem is null.
/// In that case, this method sets IsCurrentAfterLast to true if and only
/// if newPosition >= count. This distinguishes between a null belonging

7
src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs

@ -83,8 +83,9 @@ namespace Avalonia.Collections
if (key == null)
key = item;
if (_valueConverter != null)
key = _valueConverter.Convert(key, typeof(object), level, culture);
var valueConverter = ValueConverter;
if (valueConverter != null)
key = valueConverter.Convert(key, typeof(object), level, culture);
return key;
}
@ -99,6 +100,8 @@ namespace Avalonia.Collections
}
public override string PropertyName => _propertyPath;
public IValueConverter ValueConverter { get => _valueConverter; set => _valueConverter = value; }
private Type GetPropertyType(object o)
{
return o.GetType().GetNestedPropertyType(_propertyPath);

121
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -25,6 +25,7 @@ using System.ComponentModel.DataAnnotations;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Controls.Metadata;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Controls
{
@ -67,7 +68,7 @@ namespace Avalonia.Controls
private const double DATAGRID_minimumColumnHeaderHeight = 4;
internal const double DATAGRID_maximumStarColumnWidth = 10000;
internal const double DATAGRID_minimumStarColumnWidth = 0.001;
private const double DATAGRID_mouseWheelDelta = 72.0;
private const double DATAGRID_mouseWheelDelta = 50.0;
private const double DATAGRID_maxHeadersThickness = 32768;
private const double DATAGRID_defaultRowHeight = 22;
@ -2214,32 +2215,75 @@ namespace Avalonia.Controls
/// <param name="e">PointerWheelEventArgs</param>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0)
e.Handled = e.Handled || UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta);
}
internal bool UpdateScroll(Vector delta)
{
if (IsEnabled && DisplayData.NumDisplayedScrollingElements > 0)
{
double scrollHeight = 0;
if (e.Delta.Y > 0)
var handled = false;
var ignoreInvalidate = false;
var scrollHeight = 0d;
// Vertical scroll handling
if (delta.Y > 0)
{
scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta);
scrollHeight = Math.Max(-_verticalOffset, -delta.Y);
}
else if (e.Delta.Y < 0)
else if (delta.Y < 0)
{
if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible)
{
scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), -delta.Y);
}
else
{
double maximum = EdgedRowsHeightCalculated - CellsHeight;
scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), -delta.Y);
}
}
if (scrollHeight != 0)
{
DisplayData.PendingVerticalScrollHeight = scrollHeight;
InvalidateRowsMeasure(invalidateIndividualElements: false);
e.Handled = true;
handled = true;
}
// Horizontal scroll handling
if (delta.X != 0)
{
var horizontalOffset = HorizontalOffset - delta.X;
var widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth);
if (horizontalOffset < 0)
{
horizontalOffset = 0;
}
if (horizontalOffset > widthNotVisible)
{
horizontalOffset = widthNotVisible;
}
if (UpdateHorizontalOffset(horizontalOffset))
{
// We don't need to invalidate once again after UpdateHorizontalOffset.
ignoreInvalidate = true;
handled = true;
}
}
if (handled)
{
if (!ignoreInvalidate)
{
InvalidateRowsMeasure(invalidateIndividualElements: false);
}
return true;
}
}
return false;
}
/// <summary>
@ -2892,7 +2936,7 @@ namespace Avalonia.Controls
return SetCurrentCellCore(columnIndex, slot, commitEdit: true, endRowEdit: true);
}
internal void UpdateHorizontalOffset(double newValue)
internal bool UpdateHorizontalOffset(double newValue)
{
if (HorizontalOffset != newValue)
{
@ -2900,7 +2944,9 @@ namespace Avalonia.Controls
InvalidateColumnHeadersMeasure();
InvalidateRowsMeasure(true);
return true;
}
return false;
}
internal bool UpdateSelectionAndCurrency(int columnIndex, int slot, DataGridSelectionAction action, bool scrollIntoView)
@ -2999,6 +3045,12 @@ namespace Avalonia.Controls
}
}
//TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
@ -4449,17 +4501,27 @@ namespace Avalonia.Controls
element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext);
if (element != null)
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
dataGridCell.Content = element;
if (element.IsInitialized)
{
PreparingCellForEditPrivate(element as Control);
}
else
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
}
}
}
else
{
// Generate Element and apply column style if available
element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext);
dataGridCell.Content = element;
}
dataGridCell.Content = element;
}
private void PreparingCellForEditPrivate(Control editingElement)
@ -5671,6 +5733,35 @@ namespace Avalonia.Controls
VerticalScroll?.Invoke(sender, e);
}
//TODO: Ensure right button is checked for
private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{
Debug.Assert(slot >= 0);
if (shift || ctrl)
{
return true;
}
if (IsSlotOutOfBounds(slot))
{
return true;
}
if (GetRowSelection(slot))
{
return true;
}
// Unselect everything except the row that was clicked on
try
{
UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
finally
{
NoSelectionChangeCount--;
}
return true;
}
//TODO: Ensure left button is checked for
private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{
@ -5731,7 +5822,7 @@ namespace Avalonia.Controls
{
if (SelectionMode == DataGridSelectionMode.Single || !ctrl)
{
// Unselect the currectly selected rows except the new selected row
// Unselect the currently selected rows except the new selected row
action = DataGridSelectionAction.SelectCurrent;
}
else

2
src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs

@ -133,7 +133,7 @@ namespace Avalonia.Controls
protected abstract IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem);
internal AvaloniaProperty BindingTarget { get; set; }
protected AvaloniaProperty BindingTarget { get; set; }
internal void SetHeaderFromBinding()
{

45
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -161,21 +161,42 @@ namespace Avalonia.Controls
private void DataGridCell_PointerPressed(PointerPressedEventArgs e)
{
// OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow
if (OwningGrid != null)
if (OwningGrid == null)
{
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
}
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
OwningGrid.Focus();
}
if (OwningRow != null)
{
var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
// Do not handle PointerPressed with touch,
// so we can start scroll gesture on the same event.
if (e.Pointer.Type != PointerType.Touch)
{
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
e.Handled = handled;
}
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
}
}
}
@ -197,7 +218,7 @@ namespace Avalonia.Controls
}
// Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the
// right gridline will be collapsed if this cell belongs to the lastVisibileColumn and there is no filler column
// right gridline will be collapsed if this cell belongs to the lastVisibleColumn and there is no filler column
internal void EnsureGridLine(DataGridColumn lastVisibleColumn)
{
if (OwningGrid != null && _rightGridLine != null)

2
src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs

@ -40,7 +40,7 @@ namespace Avalonia.Controls
return false;
}
// There is build warning if this is missiing
// There is build warning if this is missing
public override int GetHashCode()
{
return base.GetHashCode();

2
src/Avalonia.Controls.DataGrid/DataGridClipboard.cs

@ -189,7 +189,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// DataGrid row item used for proparing the ClipboardRowContent.
/// DataGrid row item used for preparing the ClipboardRowContent.
/// </summary>
public object Item
{

31
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -9,6 +9,7 @@ using Avalonia.VisualTree;
using Avalonia.Collections;
using Avalonia.Utilities;
using System;
using System.ComponentModel;
using System.Linq;
using System.Diagnostics;
using Avalonia.Controls.Utils;
@ -653,6 +654,34 @@ namespace Avalonia.Controls
return null;
}
/// <summary>
/// Clears the current sort direction
/// </summary>
public void ClearSort()
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.Control);
}
/// <summary>
/// Switches the current state of sort direction
/// </summary>
public void Sort()
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.None);
}
/// <summary>
/// Changes the sort direction of this column
/// </summary>
/// <param name="direction">New sort direction</param>
public void Sort(ListSortDirection direction)
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.None, direction);
}
/// <summary>
/// When overridden in a derived class, causes the column cell being edited to revert to the unedited value.
/// </summary>
@ -795,7 +824,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// If the DataGrid is using using layout rounding, the pixel snapping will force all widths to
/// If the DataGrid is using layout rounding, the pixel snapping will force all widths to
/// whole numbers. Since the column widths aren't visual elements, they don't go through the normal
/// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some
/// pixel gaps and/or overlaps between columns.

56
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -35,6 +35,7 @@ namespace Avalonia.Controls
private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5;
private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1;
private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5;
private bool _areHandlersSuspended;
private static DragMode _dragMode;
@ -201,21 +202,21 @@ namespace Avalonia.Controls
handled = true;
}
internal void InvokeProcessSort(KeyModifiers keyModifiers)
internal void InvokeProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
{
Debug.Assert(OwningGrid != null);
if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers)))
if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers, forcedDirection)))
{
return;
}
if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers));
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers, forcedDirection));
}
}
//TODO GroupSorting
internal void ProcessSort(KeyModifiers keyModifiers)
internal void ProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
{
// if we can sort:
// - AllowUserToSortColumns and CanSort are true, and
@ -259,7 +260,14 @@ namespace Avalonia.Controls
{
if (sort != null)
{
newSort = sort.SwitchSortDirection();
if (forcedDirection == null || sort.Direction != forcedDirection)
{
newSort = sort.SwitchSortDirection();
}
else
{
newSort = sort;
}
// changing direction should not affect sort order, so we replace this column's
// sort description instead of just adding it to the end of the collection
@ -276,7 +284,10 @@ namespace Avalonia.Controls
}
else if (OwningColumn.CustomSortComparer != null)
{
newSort = DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer);
newSort = forcedDirection != null ?
DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer, forcedDirection.Value) :
DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer);
owningGrid.DataConnection.SortDescriptions.Add(newSort);
}
@ -290,6 +301,10 @@ namespace Avalonia.Controls
}
newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
if (forcedDirection != null && newSort.Direction != forcedDirection)
{
newSort = newSort.SwitchSortDirection();
}
owningGrid.DataConnection.SortDescriptions.Add(newSort);
}
@ -434,19 +449,6 @@ namespace Avalonia.Controls
OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders, distanceFromLeft, distanceFromRight);
// if we still haven't done anything about moving the mouse while
// the button is down, we remember that we're dragging, but we don't
// claim to have actually handled the event
if (_dragMode == DragMode.MouseDown)
{
_dragMode = DragMode.Drag;
}
_lastMousePositionHeaders = mousePositionHeaders;
if (args.Pointer.Captured != this && _dragMode == DragMode.Drag)
args.Pointer.Capture(this);
SetDragCursor(mousePosition);
}
@ -718,15 +720,19 @@ namespace Avalonia.Controls
{
return;
}
//handle entry into reorder mode
if (_dragMode == DragMode.MouseDown && _dragColumn == null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth))
if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth))
{
handled = CanReorderColumn(OwningColumn);
if (handled)
var distanceFromInitial = (Vector)(mousePositionHeaders - _lastMousePositionHeaders);
if (distanceFromInitial.Length > DATAGRIDCOLUMNHEADER_columnsDragTreshold)
{
OnMouseMove_BeginReorder(mousePosition);
handled = CanReorderColumn(OwningColumn);
if (handled)
{
OnMouseMove_BeginReorder(mousePosition);
}
}
}

11
src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs

@ -233,7 +233,7 @@ namespace Avalonia.Controls
else
{
editableCollectionView.EditItem(dataItem);
return editableCollectionView.IsEditingItem;
return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew;
}
}
@ -314,7 +314,14 @@ namespace Avalonia.Controls
CommittingEdit = true;
try
{
editableCollectionView.CommitEdit();
if (editableCollectionView.IsAddingNew)
{
editableCollectionView.CommitNew();
}
else
{
editableCollectionView.CommitEdit();
}
}
finally
{

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

@ -79,7 +79,7 @@ namespace Avalonia.Controls
set;
}
internal void AddRecylableRow(DataGridRow row)
internal void AddRecyclableRow(DataGridRow row)
{
Debug.Assert(!_recyclableRows.Contains(row));
row.DetachFromDataGrid(true);
@ -120,7 +120,7 @@ namespace Avalonia.Controls
{
if (row.IsRecyclable)
{
AddRecylableRow(row);
AddRecyclableRow(row);
}
else
{
@ -193,7 +193,7 @@ namespace Avalonia.Controls
internal void FullyRecycleElements()
{
// Fully recycle Recycleable rows and transfer them to Recycled rows
// Fully recycle Recyclable rows and transfer them to Recycled rows
while (_recyclableRows.Count > 0)
{
DataGridRow row = _recyclableRows.Pop();
@ -202,7 +202,7 @@ namespace Avalonia.Controls
Debug.Assert(!_fullyRecycledRows.Contains(row));
_fullyRecycledRows.Push(row);
}
// Fully recycle Recycleable GroupHeaders and transfer them to Recycled GroupHeaders
// Fully recycle Recyclable GroupHeaders and transfer them to Recycled GroupHeaders
while (_recyclableGroupHeaders.Count > 0)
{
DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop();

42
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -378,13 +378,13 @@ namespace Avalonia.Controls
}
}
}
}
}
internal Panel RootElement
{
get;
private set;
}
}
internal int Slot
{
@ -392,7 +392,7 @@ namespace Avalonia.Controls
set;
}
// Height that the row will eventually end up at after a possible detalis animation has completed
// Height that the row will eventually end up at after a possible details animation has completed
internal double TargetHeight
{
get
@ -517,7 +517,7 @@ namespace Avalonia.Controls
return base.MeasureOverride(availableSize);
}
//Allow the DataGrid specific componets to adjust themselves based on new values
//Allow the DataGrid specific components to adjust themselves based on new values
if (_headerElement != null)
{
_headerElement.InvalidateMeasure();
@ -638,7 +638,7 @@ namespace Avalonia.Controls
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
}
}
}
//TODO Animation
@ -722,7 +722,7 @@ namespace Avalonia.Controls
if (_bottomGridLine != null)
{
// It looks like setting Visibility sometimes has side effects so make sure the value is actually
// diffferent before setting it
// different before setting it
bool newVisibility = OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Horizontal || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All;
if (newVisibility != _bottomGridLine.IsVisible)
@ -896,7 +896,7 @@ namespace Avalonia.Controls
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
}
}
// Makes sure the _detailsDesiredHeight is initialized. We need to measure it to know what
// height we want to animate to. Subsequently, we just update that height in response to SizeChanged
@ -919,7 +919,7 @@ namespace Avalonia.Controls
//TODO Cleanup
double? _previousDetailsHeight = null;
//TODO Animation
private void DetailsContent_HeightChanged(double newValue)
{
@ -1022,7 +1022,7 @@ namespace Avalonia.Controls
}
}
}
internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
{
if (_detailsElement != null && AreDetailsVisible)
@ -1066,7 +1066,7 @@ namespace Avalonia.Controls
.Subscribe(DetailsContent_MarginChanged);
}
_detailsElement.Children.Add(_detailsContent);
}
}
@ -1090,6 +1090,28 @@ namespace Avalonia.Controls
}
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
if (change.Property == DataContextProperty)
{
var owner = OwningGrid;
if (owner != null && this.IsRecycled)
{
var columns = owner.ColumnsItemsInternal;
var nc = columns.Count;
for (int ci = 0; ci < nc; ci++)
{
if (columns[ci] is DataGridTemplateColumn column)
{
column.RefreshCellContent((Control)this.Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate));
}
}
}
}
base.OnPropertyChanged(change);
}
}

15
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -283,7 +283,11 @@ namespace Avalonia.Controls
//TODO TabStop
private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
{
if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled)
{
@ -300,6 +304,15 @@ namespace Avalonia.Controls
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
private void EnsureChildClip(Visual child, double frozenLeftEdge)

17
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@ -179,12 +179,12 @@ namespace Avalonia.Controls.Primitives
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (OwningGrid != null)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
@ -199,6 +199,19 @@ namespace Avalonia.Controls.Primitives
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
Debug.Assert(sender is DataGridRowHeader);
Debug.Assert(sender == this);
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, -1, Slot, false);
}
}
}
}

18
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@ -1193,7 +1193,7 @@ namespace Avalonia.Controls
else
{
groupHeader = element as DataGridRowGroupHeader;
Debug.Assert(groupHeader != null); // Nothig other and Rows and RowGroups now
Debug.Assert(groupHeader != null); // Nothing other and Rows and RowGroups now
if (groupHeader != null)
{
groupHeader.TotalIndent = (groupHeader.Level == 0) ? 0 : RowGroupSublevelIndents[groupHeader.Level - 1];
@ -1636,7 +1636,7 @@ namespace Avalonia.Controls
if (slot >= DisplayData.FirstScrollingSlot &&
slot <= DisplayData.LastScrollingSlot)
{
// Additional row takes the spot of a displayed row - it is necessarilly displayed
// Additional row takes the spot of a displayed row - it is necessarily displayed
return true;
}
else if (DisplayData.FirstScrollingSlot == -1 &&
@ -1825,7 +1825,7 @@ namespace Avalonia.Controls
if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
{
// We've scrolled off more of the first row than what's possible. This can happen
// if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
// if the first row got shorter (Ex: Collapsing RowDetails) or if the user has a recycling
// cleanup issue. In this case, simply try to display the next row as the first row instead
if (newFirstScrollingSlot < SlotCount - 1)
{
@ -2014,7 +2014,7 @@ namespace Avalonia.Controls
if (recycleRow)
{
DisplayData.AddRecylableRow(dataGridRow);
DisplayData.AddRecyclableRow(dataGridRow);
}
else
{
@ -2265,7 +2265,7 @@ namespace Avalonia.Controls
if (parentGroupInfo.LastSubItemSlot - parentGroupInfo.Slot == 1)
{
// We just added the first item to a RowGroup so the header should transition from Empty to either Expanded or Collapsed
EnsureAnscestorsExpanderButtonChecked(parentGroupInfo);
EnsureAncestorsExpanderButtonChecked(parentGroupInfo);
}
}
}
@ -2407,7 +2407,7 @@ namespace Avalonia.Controls
return treeCount;
}
private void EnsureAnscestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
private void EnsureAncestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
{
if (IsSlotVisible(parentGroupInfo.Slot))
{
@ -2789,11 +2789,11 @@ namespace Avalonia.Controls
return null;
}
internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisibile, bool setCurrent)
internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisible, bool setCurrent)
{
Debug.Assert(groupHeader.RowGroupInfo.CollectionViewGroup.ItemCount > 0);
if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisibile, setCurrent); }) || !CommitEdit())
if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisible, setCurrent); }) || !CommitEdit())
{
return;
}
@ -2804,7 +2804,7 @@ namespace Avalonia.Controls
UpdateSelectionAndCurrency(CurrentColumnIndex, groupHeader.RowGroupInfo.Slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisibile, isDisplayed: true);
UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisible, isDisplayed: true);
ComputeScrollBarsLayout();
// We need force arrange since our Scrollings Rows could update without automatically triggering layout

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs

@ -140,7 +140,7 @@ namespace Avalonia.Controls.Primitives
if (dataGridColumn.IsFrozen)
{
columnHeader.Arrange(new Rect(frozenLeftEdge, 0, dataGridColumn.LayoutRoundedWidth, finalSize.Height));
columnHeader.Clip = null; // The layout system could have clipped this becaues it's not aware of our render transform
columnHeader.Clip = null; // The layout system could have clipped this because it's not aware of our render transform
if (DragColumn == dataGridColumn && DragIndicator != null)
{
dragIndicatorLeftEdge = frozenLeftEdge + DragIndicatorOffset;

13
src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs

@ -5,6 +5,9 @@
using System;
using System.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Layout;
using Avalonia.Media;
@ -16,6 +19,11 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public sealed class DataGridRowsPresenter : Panel
{
public DataGridRowsPresenter()
{
AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture);
}
internal DataGrid OwningGrid
{
get;
@ -176,6 +184,11 @@ namespace Avalonia.Controls.Primitives
return new Size(totalCellsWidth + headerWidth, totalHeight);
}
private void OnScrollGesture(object sender, ScrollGestureEventArgs e)
{
e.Handled = e.Handled || OwningGrid.UpdateScroll(-e.Delta);
}
#if DEBUG
internal void PrintChildren()
{

8
src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs

@ -1,13 +1,9 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
#endif
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")]

6
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -247,7 +247,11 @@
<DataGridColumnHeader Name="PART_TopRightCornerHeader" Grid.Column="2"/>
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator" Grid.ColumnSpan="3" VerticalAlignment="Bottom" StrokeThickness="1" Height="1" Fill="{DynamicResource ThemeControlMidHighBrush}"/>
<DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" />
<DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Column="2" Grid.Row="2" />
<Rectangle Name="BottomLeftCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Row="2" Grid.ColumnSpan="2" />
<ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}"/>

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

@ -601,7 +601,11 @@
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3" />
Grid.ColumnSpan="3">
<DataGridRowsPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</DataGridRowsPresenter.GestureRecognizers>
</DataGridRowsPresenter>
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"

13
src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs

@ -1,10 +1,8 @@
using Avalonia.Data;
using Avalonia.Reactive;
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Reactive.Subjects;
using System.Text;
namespace Avalonia.Controls.Utils
{
@ -67,11 +65,14 @@ namespace Avalonia.Controls.Utils
private void SetSourceValue(object value)
{
_settingSourceValue = true;
if (!_settingSourceValue)
{
_settingSourceValue = true;
_sourceSubject.OnNext(value);
_sourceSubject.OnNext(value);
_settingSourceValue = false;
_settingSourceValue = false;
}
}
private void SetControlValue(object value)
{
@ -157,4 +158,4 @@ namespace Avalonia.Controls.Utils
}
}
}
}
}

4
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -37,6 +37,7 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
@ -55,4 +56,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
Total Issues: 56
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 58

2
src/Avalonia.Controls/AppBuilderBase.cs

@ -273,7 +273,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Sets up the platform-speciic services for the <see cref="Application"/>.
/// Sets up the platform-specific services for the <see cref="Application"/>.
/// </summary>
private void Setup()
{

20
src/Avalonia.Controls/AutoCompleteBox.cs

@ -2005,7 +2005,7 @@ namespace Avalonia.Controls
// The TextBox.TextChanged event was not firing immediately and
// was causing an immediate update, even with wrapping. If there is
// a selection currently, no update should happen.
if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != (TextBox.Text?.Length ?? 0))
{
return;
}
@ -2094,7 +2094,21 @@ namespace Avalonia.Controls
bool inResults = !(stringFiltering || objectFiltering);
if (!inResults)
{
inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item);
if (stringFiltering)
{
inResults = TextFilter(text, FormatValue(item));
}
else
{
if (ItemFilter is null)
{
throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
else
{
inResults = ItemFilter(text, item);
}
}
}
if (view_count > view_index && inResults && _view[view_index] == item)
@ -2303,7 +2317,7 @@ namespace Avalonia.Controls
{
if (IsTextCompletionEnabled && TextBox != null && userInitiated)
{
int currentLength = TextBox.Text.Length;
int currentLength = TextBox.Text?.Length ?? 0;
int selectionStart = TextBoxSelectionStart;
if (selectionStart == text.Length && selectionStart > _textSelectionStart)
{

2
src/Avalonia.Controls/Border.cs

@ -8,7 +8,9 @@ namespace Avalonia.Controls
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public partial class Border : Decorator, IVisualWithRoundRectClip
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Defines the <see cref="Background"/> property.

10
src/Avalonia.Controls/Button.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged);
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<Button>((lbl, args) => lbl.OnAccessKey(args));
}
public Button()
@ -256,6 +257,8 @@ namespace Avalonia.Controls
}
}
protected virtual void OnAccessKey(RoutedEventArgs e) => OnClick();
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -358,6 +361,13 @@ namespace Avalonia.Controls
IsPressed = false;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
IsPressed = false;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

30
src/Avalonia.Controls/ComboBox.cs

@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
@ -80,7 +82,7 @@ namespace Avalonia.Controls
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
private IDisposable _subscriptionsOnOpen;
private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
/// <summary>
/// Initializes static members of the <see cref="ComboBox"/> class.
@ -291,6 +293,7 @@ namespace Avalonia.Controls
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
}
internal void ItemFocused(ComboBoxItem dropDownItem)
@ -303,8 +306,7 @@ namespace Avalonia.Controls
private void PopupClosed(object sender, EventArgs e)
{
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
_subscriptionsOnOpen.Clear();
if (CanFocus(this))
{
@ -316,20 +318,34 @@ namespace Avalonia.Controls
{
TryFocusSelectedItem();
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
_subscriptionsOnOpen.Clear();
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) =>
toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)
{
ev.Handled = true;
}
}, Interactivity.RoutingStrategies.Tunnel);
}, Interactivity.RoutingStrategies.Tunnel).DisposeWith(_subscriptionsOnOpen);
}
this.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen);
foreach (var parent in this.GetVisualAncestors().OfType<IControl>())
{
parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen);
}
}
private void IsVisibleChanged(bool isVisible)
{
if (!isVisible && IsDropDownOpen)
{
IsDropDownOpen = false;
}
}

32
src/Avalonia.Controls/ContextMenu.cs

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
@ -21,7 +21,7 @@ namespace Avalonia.Controls
/// <summary>
/// A control context menu.
/// </summary>
public class ContextMenu : MenuBase, ISetterValue
public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider
{
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
@ -82,6 +82,7 @@ namespace Avalonia.Controls
private Popup? _popup;
private List<Control>? _attachedControls;
private IInputElement? _previousFocus;
private Action<IPopupHost?>? _popupHostChangedHandler;
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
@ -304,6 +305,14 @@ namespace Avalonia.Controls
}
}
IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host;
event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged
{
add => _popupHostChangedHandler += value;
remove => _popupHostChangedHandler -= value;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
@ -320,16 +329,8 @@ namespace Avalonia.Controls
{
_popup = new Popup
{
HorizontalOffset = HorizontalOffset,
VerticalOffset = VerticalOffset,
PlacementAnchor = PlacementAnchor,
PlacementConstraintAdjustment = PlacementConstraintAdjustment,
PlacementGravity = PlacementGravity,
PlacementMode = PlacementMode,
PlacementRect = PlacementRect,
IsLightDismissEnabled = true,
OverlayDismissEventPassThrough = true,
WindowManagerAddShadowHint = WindowManagerAddShadowHint,
};
_popup.Opened += PopupOpened;
@ -349,6 +350,13 @@ namespace Avalonia.Controls
: PlacementMode;
_popup.PlacementTarget = placementTarget;
_popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset;
_popup.PlacementAnchor = PlacementAnchor;
_popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment;
_popup.PlacementGravity = PlacementGravity;
_popup.PlacementRect = PlacementRect;
_popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint;
_popup.Child = this;
IsOpen = true;
_popup.IsOpen = true;
@ -364,6 +372,8 @@ namespace Avalonia.Controls
{
_previousFocus = FocusManager.Instance?.Current;
Focus();
_popupHostChangedHandler?.Invoke(_popup!.Host);
}
private void PopupClosing(object sender, CancelEventArgs e)
@ -397,6 +407,8 @@ namespace Avalonia.Controls
RoutedEvent = MenuClosedEvent,
Source = this,
});
_popupHostChangedHandler?.Invoke(null);
}
private void PopupKeyUp(object sender, KeyEventArgs e)

10
src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs

@ -26,13 +26,13 @@ namespace Avalonia.Controls.Converters
Right ? Indent * scalarDepth : 0,
Bottom ? Indent * scalarDepth : 0);
}
else if (value is Thickness thinknessDepth)
else if (value is Thickness thicknessDepth)
{
return new Thickness(
Left ? Indent * thinknessDepth.Left : 0,
Top ? Indent * thinknessDepth.Top : 0,
Right ? Indent * thinknessDepth.Right : 0,
Bottom ? Indent * thinknessDepth.Bottom : 0);
Left ? Indent * thicknessDepth.Left : 0,
Top ? Indent * thicknessDepth.Top : 0,
Right ? Indent * thicknessDepth.Right : 0,
Bottom ? Indent * thicknessDepth.Bottom : 0);
}
return new Thickness(0);

4
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Converters
if (parameter == null ||
values == null ||
values.Count != 4 ||
!(values[0] is ScrollBarVisibility visiblity) ||
!(values[0] is ScrollBarVisibility visibility) ||
!(values[1] is double offset) ||
!(values[2] is double extent) ||
!(values[3] is double viewport))
@ -24,7 +24,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
if (visiblity == ScrollBarVisibility.Auto)
if (visibility == ScrollBarVisibility.Auto)
{
if (extent == viewport)
{

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

Loading…
Cancel
Save