Browse Source

Merge branch 'master' into datagrid_layoutrounding

pull/6860/head
Max Katz 5 years ago
committed by GitHub
parent
commit
937c5b392d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      Documentation/build.md
  2. 55
      azure-pipelines.yml
  3. 48
      build.sh
  4. 4
      build/HarfBuzzSharp.props
  5. 2
      build/MicroCom.targets
  6. 1
      build/SharedVersion.props
  7. 4
      build/SkiaSharp.props
  8. 6
      global.json
  9. 35
      native/Avalonia.Native/src/OSX/window.mm
  10. 9
      nukebuild/_build.csproj
  11. 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  12. 11
      readme.md
  13. 2
      samples/BindingDemo/BindingDemo.csproj
  14. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  15. 8
      samples/ControlCatalog/MainWindow.xaml
  16. 2
      samples/ControlCatalog/Pages/ButtonPage.xaml
  17. 6
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  18. 20
      samples/ControlCatalog/Pages/DialogsPage.xaml
  19. 24
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  20. 11
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  21. 6
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  22. 6
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  23. 2
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  24. 2
      samples/Previewer/Previewer.csproj
  25. 2
      samples/RemoteDemo/RemoteDemo.csproj
  26. 2
      samples/RenderDemo/RenderDemo.csproj
  27. 2
      samples/Sandbox/Sandbox.csproj
  28. 2
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  29. 6
      src/Avalonia.Animation/Animation.cs
  30. 12
      src/Avalonia.Animation/Animators/Animator`1.cs
  31. 3
      src/Avalonia.Animation/ApiCompatBaseline.txt
  32. 6
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  33. 2
      src/Avalonia.Base/Data/BindingValue.cs
  34. 17
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  35. 10
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  36. 7
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  37. 52
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  38. 30
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  39. 8
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  40. 6
      src/Avalonia.Controls/Application.cs
  41. 14
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  42. 3
      src/Avalonia.Controls/Button.cs
  43. 15
      src/Avalonia.Controls/ContextMenu.cs
  44. 6
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  45. 43
      src/Avalonia.Controls/ItemsControl.cs
  46. 2
      src/Avalonia.Controls/MenuItem.cs
  47. 3
      src/Avalonia.Controls/NativeMenuItem.cs
  48. 24
      src/Avalonia.Controls/Panel.cs
  49. 6
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  50. 2
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  51. 4
      src/Avalonia.Controls/Platform/PlatformManager.cs
  52. 41
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  53. 2
      src/Avalonia.Controls/Primitives/AccessText.cs
  54. 8
      src/Avalonia.Controls/Primitives/Popup.cs
  55. 8
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  56. 3
      src/Avalonia.Controls/Remote/RemoteServer.cs
  57. 31
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  58. 10
      src/Avalonia.Controls/SplitView.cs
  59. 2
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  60. 49
      src/Avalonia.Controls/TrayIcon.cs
  61. 27
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  62. 6
      src/Avalonia.Controls/WindowBase.cs
  63. 2
      src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
  64. 2
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs
  65. 2
      src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
  66. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  67. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  68. 144
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  69. 31
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  70. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
  71. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  72. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  73. 17
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  74. 11
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
  75. 6
      src/Avalonia.FreeDesktop/DBusHelper.cs
  76. 6
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  77. 178
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  78. 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  79. 3
      src/Avalonia.Input/ApiCompatBaseline.txt
  80. 16
      src/Avalonia.Input/FocusManager.cs
  81. 7
      src/Avalonia.Input/Gestures.cs
  82. 6
      src/Avalonia.Input/ICommandSource.cs
  83. 8
      src/Avalonia.Input/IFocusManager.cs
  84. 4
      src/Avalonia.Input/InputElement.cs
  85. 9
      src/Avalonia.Layout/ElementManager.cs
  86. 6
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  87. 2
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  88. 26
      src/Avalonia.Styling/LogicalTree/ChildIndexChangedEventArgs.cs
  89. 32
      src/Avalonia.Styling/LogicalTree/IChildIndexProvider.cs
  90. 7
      src/Avalonia.Styling/Properties/AssemblyInfo.cs
  91. 56
      src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs
  92. 145
      src/Avalonia.Styling/Styling/NthChildSelector.cs
  93. 23
      src/Avalonia.Styling/Styling/NthLastChildSelector.cs
  94. 6
      src/Avalonia.Styling/Styling/PropertySetterInstance.cs
  95. 16
      src/Avalonia.Styling/Styling/Selectors.cs
  96. 4
      src/Avalonia.Styling/Styling/Setter.cs
  97. 3
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  98. 1
      src/Avalonia.Themes.Default/Button.xaml
  99. 1
      src/Avalonia.Themes.Default/CheckBox.xaml
  100. 3
      src/Avalonia.Themes.Default/ComboBox.xaml

25
Documentation/build.md

@ -1,6 +1,6 @@
# Windows # Windows
Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on Windows. Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms.
### Clone the Avalonia repository ### Clone the Avalonia repository
@ -16,7 +16,7 @@ Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the l
### Open in Visual Studio ### Open in Visual Studio
Open the `Avalonia.sln` solution in Visual Studio 2019 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application. Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
### Troubleshooting ### Troubleshooting
@ -43,27 +43,6 @@ Go to https://www.microsoft.com/net/core and follow the instructions for your OS
The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install. The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install.
Linux operating systems ship with their own respective package managers however we will use [Homebrew](https://brew.sh/) to manage packages on macOS. To install follow the instructions [here](https://docs.brew.sh/Installation).
### Install CastXML (pre Nov 2020)
Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processing during the build process. The easiest way to install this is via the operating system's package managers, such as below.
On macOS:
```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
```
On Debian based Linux (Debian, Ubuntu, Mint, etc):
```
sudo apt install castxml
```
On Red Hat based Linux (Fedora, CentOS, RHEL, etc) using `yum` (`dnf` takes same arguments though):
```
sudo yum install castxml
```
### Clone the Avalonia repository ### Clone the Avalonia repository

55
azure-pipelines.yml

@ -1,21 +1,28 @@
variables:
MSBuildEnableWorkloadResolver: 'false'
jobs: jobs:
- job: Linux - job: Linux
pool: pool:
vmImage: 'ubuntu-20.04' vmImage: 'ubuntu-20.04'
steps: steps:
- task: CmdLine@2 - task: UseDotNet@2
displayName: 'Install Nuke' displayName: 'Use .NET Core SDK 3.1.414'
inputs: inputs:
script: | version: 3.1.414
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
inputs:
version: 6.0.100
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Nuke' displayName: 'Run Build'
inputs: inputs:
script: | script: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info dotnet --info
printenv printenv
nuke --target CiAzureLinux --configuration=Release ./build.sh --target CiAzureLinux --configuration=Release
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -23,6 +30,7 @@ jobs:
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled()) condition: not(canceled())
- job: macOS - job: macOS
variables: variables:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
@ -30,10 +38,15 @@ jobs:
vmImage: 'macOS-10.15' vmImage: 'macOS-10.15'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401' displayName: 'Use .NET Core SDK 3.1.414'
inputs: 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 - task: CmdLine@2
displayName: 'Install Mono 5.18' displayName: 'Install Mono 5.18'
inputs: inputs:
@ -45,7 +58,8 @@ jobs:
displayName: 'Generate avalonia-native' displayName: 'Generate avalonia-native'
inputs: inputs:
script: | 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 - task: Xcode@5
inputs: inputs:
@ -58,13 +72,7 @@ jobs:
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Run Build'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
inputs: inputs:
script: | script: |
export COREHOST_TRACE=0 export COREHOST_TRACE=0
@ -72,10 +80,8 @@ jobs:
export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1
which dotnet which dotnet
dotnet --info dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv printenv
nuke --target CiAzureOSX --configuration Release --skip-previewer ./build.sh --target CiAzureOSX --configuration Release --skip-previewer
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -102,9 +108,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
steps: steps:
- task: UseDotNet@2 - 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: inputs:
version: 3.1.401 version: 6.0.100
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' 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" 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_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export NUGET_XMLDOC_MODE="skip" export NUGET_XMLDOC_MODE="skip"
########################################################################### dotnet --info
# 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_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"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.7" /> <PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.155" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.7" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.155" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/MicroCom.targets

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

1
build/SharedVersion.props

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

4
build/SkiaSharp.props

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

6
global.json

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

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

@ -52,7 +52,6 @@ public:
[Window setBackingType:NSBackingStoreBuffered]; [Window setBackingType:NSBackingStoreBuffered];
[Window setOpaque:false]; [Window setOpaque:false];
[Window setContentView: StandardContainer];
} }
virtual HRESULT ObtainNSWindowHandle(void** ret) override virtual HRESULT ObtainNSWindowHandle(void** ret) override
@ -125,6 +124,8 @@ public:
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
UpdateStyle(); UpdateStyle();
[Window setContentView: StandardContainer];
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
if(ShouldTakeFocusOnShow() && activate) if(ShouldTakeFocusOnShow() && activate)
@ -205,7 +206,11 @@ public:
auto window = Window; auto window = Window;
Window = nullptr; Window = nullptr;
[window close]; try{
// Seems to throw sometimes on application exit.
[window close];
}
catch(NSException*){}
} }
return S_OK; return S_OK;
@ -323,6 +328,7 @@ public:
BaseEvents->Resized(AvnSize{x,y}, reason); BaseEvents->Resized(AvnSize{x,y}, reason);
} }
[StandardContainer setFrameSize:NSSize{x,y}];
[Window setContentSize:NSSize{x, y}]; [Window setContentSize:NSSize{x, y}];
} }
@finally @finally
@ -722,6 +728,7 @@ private:
if (cparent->WindowState() == Minimized) if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal); cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
[cparent->Window addChildWindow:Window ordered:NSWindowAbove]; [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
UpdateStyle(); UpdateStyle();
@ -1487,7 +1494,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
NSRect rect = NSZeroRect; NSRect rect = NSZeroRect;
rect.size = newSize; rect.size = newSize;
NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingEnabledDuringMouseDrag; NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag;
_area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr]; _area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr];
[self addTrackingArea:_area]; [self addTrackingArea:_area];
@ -2387,17 +2394,27 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)sendEvent:(NSEvent *)event - (void)sendEvent:(NSEvent *)event
{ {
if(_parent != nullptr) [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) switch(event.type)
{ {
case NSEventTypeLeftMouseDown: case NSEventTypeLeftMouseDown:
{ {
auto avnPoint = [AvnView toAvnPoint:[event locationInWindow]]; AvnView* view = _parent->View;
auto point = [self translateLocalPoint:avnPoint]; NSPoint windowPoint = [event locationInWindow];
AvnVector delta; NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, [event timestamp] * 1000, AvnInputModifiersNone, point, delta); 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; break;
@ -2417,8 +2434,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
break; break;
} }
} }
[super sendEvent:event];
} }
@end @end

9
nukebuild/_build.csproj

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

3
packages/Avalonia/AvaloniaBuildTasks.targets

@ -54,6 +54,7 @@
<Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" /> <Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" />
</Hash> </Hash>
<MakeDir Directories="$(IntermediateOutputPath)/Avalonia" />
<WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" /> <WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
</Target> </Target>
@ -87,6 +88,7 @@
<AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath> <AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath>
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath> <AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl> <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
</PropertyGroup> </PropertyGroup>
<WriteLinesToFile <WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'" Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@ -106,6 +108,7 @@
DelaySign="$(DelaySign)" DelaySign="$(DelaySign)"
EnableComInteropPatching="$(_AvaloniaPatchComInterop)" EnableComInteropPatching="$(_AvaloniaPatchComInterop)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)" SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
/> />
<Exec <Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'" Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

11
readme.md

@ -60,6 +60,9 @@ See the [build instructions here](Documentation/build.md).
## Contributing ## 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. Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request.
## Code of Conduct ## 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). 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 ### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)] 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/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/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/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 ## .NET Foundation

2
samples/BindingDemo/BindingDemo.csproj

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

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

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

8
samples/ControlCatalog/MainWindow.xaml

@ -63,11 +63,11 @@
<Panel Margin="{Binding #MainWindow.OffScreenMargin}"> <Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}"> <DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
<Menu Name="MainMenu" DockPanel.Dock="Top"> <Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File"> <MenuItem Header="_File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" /> <MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem> </MenuItem>
<MenuItem Header="Help"> <MenuItem Header="_Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" /> <MenuItem Header="_About" Command="{Binding AboutCommand}" />
</MenuItem> </MenuItem>
</Menu> </Menu>
<local:MainView /> <local:MainView />

2
samples/ControlCatalog/Pages/ButtonPage.xaml

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

6
samples/ControlCatalog/Pages/CheckBoxPage.xaml

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

20
samples/ControlCatalog/Pages/DialogsPage.xaml

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

24
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -1,17 +1,33 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ItemsRepeaterPage"> 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> <UserControl.Resources>
<RecyclePool x:Key="RecyclePool" /> <RecyclePool x:Key="RecyclePool" />
<DataTemplate x:Key="odd"> <DataTemplate x:Key="odd">
<TextBlock Background="Yellow" <TextBlock Classes="oddTemplate"
Foreground="Black"
Height="{Binding Height}" Height="{Binding Height}"
Text="{Binding Text}"/> Text="{Binding Text}"/>
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="even"> <DataTemplate x:Key="even">
<TextBlock Background="Wheat" <TextBlock Classes="evenTemplate"
Foreground="Black"
Height="{Binding Height}" Height="{Binding Height}"
Text="{Binding Text}"/> Text="{Binding Text}"/>
</DataTemplate> </DataTemplate>

11
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -2,9 +2,20 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ListBoxPage"> x:Class="ControlCatalog.Pages.ListBoxPage">
<DockPanel> <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"> <StackPanel DockPanel.Dock="Top" Margin="4">
<TextBlock Classes="h1">ListBox</TextBlock> <TextBlock Classes="h1">ListBox</TextBlock>
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</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>
<StackPanel DockPanel.Dock="Right" Margin="4"> <StackPanel DockPanel.Dock="Right" Margin="4">
<CheckBox IsChecked="{Binding Multiple}">Multiple</CheckBox> <CheckBox IsChecked="{Binding Multiple}">Multiple</CheckBox>

6
samples/ControlCatalog/Pages/RadioButtonPage.xaml

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

6
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

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

2
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

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

2
samples/Previewer/Previewer.csproj

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

2
samples/RemoteDemo/RemoteDemo.csproj

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

2
samples/RenderDemo/RenderDemo.csproj

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

2
samples/Sandbox/Sandbox.csproj

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

2
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <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); return new CompositeDisposable(subscriptions);
} }
/// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null)
{
return RunAsync(control, clock, default);
}
/// <inheritdoc/> /// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) 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; T oldValue, newValue;
if (firstKeyframe.isNeutral) if (!firstKeyframe.isNeutral && firstKeyframe.Value is T firstKeyframeValue)
oldValue = neutralValue; oldValue = firstKeyframeValue;
else else
oldValue = (T)firstKeyframe.Value; oldValue = neutralValue;
if (lastKeyframe.isNeutral) if (!lastKeyframe.isNeutral && lastKeyframe.Value is T lastKeyframeValue)
newValue = neutralValue; newValue = lastKeyframeValue;
else else
newValue = (T)lastKeyframe.Value; newValue = neutralValue;
if (lastKeyframe.KeySpline != null) if (lastKeyframe.KeySpline != null)
progress = lastKeyframe.KeySpline.GetSplineProgress(progress); progress = lastKeyframe.KeySpline.GetSplineProgress(progress);

3
src/Avalonia.Animation/ApiCompatBaseline.txt

@ -1,6 +1,5 @@
Compat issues with assembly Avalonia.Animation: 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. 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. 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. 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 Avalonia.Metadata;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [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.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.LeakTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.LeakTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.LeakTests")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests")]
#endif

2
src/Avalonia.Base/Data/BindingValue.cs

@ -247,7 +247,7 @@ namespace Avalonia.Data
UnsetValueType _ => Unset, UnsetValueType _ => Unset,
DoNothingType _ => DoNothing, DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast<T>(), BindingNotification n => n.ToBindingValue().Cast<T>(),
_ => new BindingValue<T>((T)value) _ => new BindingValue<T>((T?)value)
}; };
} }

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

@ -140,18 +140,9 @@ namespace Avalonia.Data.Converters
); );
} }
Action<object> action = null; return Expression
try .Lambda<Action<object>>(body, parameter)
{ .Compile();
action = Expression
.Lambda<Action<object>>(body, parameter)
.Compile();
}
catch (Exception ex)
{
throw ex;
}
return action;
} }
static Func<object, bool> CreateCanExecute(object target static Func<object, bool> CreateCanExecute(object target
@ -170,7 +161,7 @@ namespace Avalonia.Data.Converters
.Compile(); .Compile();
} }
private static Expression? ConvertTarget(object? target, MethodInfo method) => private static Expression ConvertTarget(object target, MethodInfo method) =>
target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType); target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType);
internal class WeakPropertyChangedProxy internal class WeakPropertyChangedProxy

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

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

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

@ -1,9 +1,6 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks namespace Avalonia.Build.Tasks
@ -41,7 +38,7 @@ namespace Avalonia.Build.Tasks
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl, outputImportance, ProjectDirectory, OutputPath, VerifyIl, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
EnableComInteropPatching, SkipXamlCompilation); EnableComInteropPatching, SkipXamlCompilation, DebuggerLaunch);
if (!res.Success) if (!res.Success)
return false; return false;
if (!res.WrittenFile) if (!res.WrittenFile)
@ -87,5 +84,7 @@ namespace Avalonia.Build.Tasks
public IBuildEngine BuildEngine { get; set; } public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { 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;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
using Mono.Cecil; using Mono.Cecil;
using Avalonia.Utilities;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
using XamlX; using XamlX;
@ -44,16 +41,23 @@ namespace Avalonia.Build.Tasks
string projectDirectory, string projectDirectory,
string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom,
bool skipXamlCompilation) 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 var typeSystem = new CecilTypeSystem(references
.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")) .Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll"))
.Concat(new[] { input }), input); .Concat(new[] { input }), input);
var asm = typeSystem.TargetAssemblyDefinition; var asm = typeSystem.TargetAssemblyDefinition;
if (!skipXamlCompilation) if (!skipXamlCompilation)
{ {
var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance); var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch);
if (compileRes == null && !patchCom) if (compileRes == null && !patchCom)
return new CompileResult(true); return new CompileResult(true);
if (compileRes == false) if (compileRes == false)
@ -62,7 +66,7 @@ namespace Avalonia.Build.Tasks
if (patchCom) if (patchCom)
ComInteropHelper.PatchAssembly(asm, typeSystem); ComInteropHelper.PatchAssembly(asm, typeSystem);
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey)) if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
@ -70,13 +74,43 @@ namespace Avalonia.Build.Tasks
asm.Write(output, writerParameters); asm.Write(output, writerParameters);
return new CompileResult(true, true); return new CompileResult(true, true);
} }
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem, static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
string projectDirectory, bool verifyIl, 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 asm = typeSystem.TargetAssemblyDefinition;
var emres = new EmbeddedResources(asm); var emres = new EmbeddedResources(asm);
var avares = new AvaloniaResources(asm, projectDirectory); var avares = new AvaloniaResources(asm, projectDirectory);

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

@ -35,6 +35,7 @@ namespace Avalonia.Controls
private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5; private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5;
private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1; private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1;
private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5;
private bool _areHandlersSuspended; private bool _areHandlersSuspended;
private static DragMode _dragMode; private static DragMode _dragMode;
@ -448,19 +449,6 @@ namespace Avalonia.Controls
OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders, distanceFromLeft, distanceFromRight); 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); SetDragCursor(mousePosition);
} }
@ -732,15 +720,19 @@ namespace Avalonia.Controls
{ {
return; return;
} }
//handle entry into reorder mode //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); var distanceFromInitial = (Vector)(mousePositionHeaders - _lastMousePositionHeaders);
if (distanceFromInitial.Length > DATAGRIDCOLUMNHEADER_columnsDragTreshold)
if (handled)
{ {
OnMouseMove_BeginReorder(mousePosition); handled = CanReorderColumn(OwningColumn);
if (handled)
{
OnMouseMove_BeginReorder(mousePosition);
}
} }
} }

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

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

6
src/Avalonia.Controls/Application.cs

@ -104,7 +104,7 @@ namespace Avalonia
/// <value> /// <value>
/// The application's focus manager. /// The application's focus manager.
/// </value> /// </value>
public IFocusManager FocusManager public IFocusManager? FocusManager
{ {
get; get;
private set; private set;
@ -116,7 +116,7 @@ namespace Avalonia
/// <value> /// <value>
/// The application's input manager. /// The application's input manager.
/// </value> /// </value>
public InputManager InputManager public InputManager? InputManager
{ {
get; get;
private set; private set;
@ -175,7 +175,7 @@ namespace Avalonia
/// - <see cref="ISingleViewApplicationLifetime"/> /// - <see cref="ISingleViewApplicationLifetime"/>
/// - <see cref="IControlledApplicationLifetime"/> /// - <see cref="IControlledApplicationLifetime"/>
/// </summary> /// </summary>
public IApplicationLifetime ApplicationLifetime { get; set; } public IApplicationLifetime? ApplicationLifetime { get; set; }
event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesAdded event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesAdded
{ {

14
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
@ -122,12 +123,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
lifetimeEvents.ShutdownRequested += OnShutdownRequested; lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
MainWindow?.Show();
// Note due to a bug in the JIT we wrap this in a method, otherwise MainWindow
// gets stuffed into a local var and can not be GCed until after the program stops.
// this method never exits until program end.
ShowMainWindow();
Dispatcher.UIThread.MainLoop(_cts.Token); Dispatcher.UIThread.MainLoop(_cts.Token);
Environment.ExitCode = _exitCode; Environment.ExitCode = _exitCode;
return _exitCode; return _exitCode;
} }
[MethodImpl(MethodImplOptions.NoInlining)]
private void ShowMainWindow()
{
MainWindow?.Show();
}
public void Dispose() public void Dispose()
{ {
if (_activeLifetime == this) if (_activeLifetime == this)

3
src/Avalonia.Controls/Button.cs

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

15
src/Avalonia.Controls/ContextMenu.cs

@ -329,16 +329,8 @@ namespace Avalonia.Controls
{ {
_popup = new Popup _popup = new Popup
{ {
HorizontalOffset = HorizontalOffset,
VerticalOffset = VerticalOffset,
PlacementAnchor = PlacementAnchor,
PlacementConstraintAdjustment = PlacementConstraintAdjustment,
PlacementGravity = PlacementGravity,
PlacementMode = PlacementMode,
PlacementRect = PlacementRect,
IsLightDismissEnabled = true, IsLightDismissEnabled = true,
OverlayDismissEventPassThrough = true, OverlayDismissEventPassThrough = true,
WindowManagerAddShadowHint = WindowManagerAddShadowHint,
}; };
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;
@ -358,6 +350,13 @@ namespace Avalonia.Controls
: PlacementMode; : PlacementMode;
_popup.PlacementTarget = placementTarget; _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; _popup.Child = this;
IsOpen = true; IsOpen = true;
_popup.IsOpen = true; _popup.IsOpen = true;

6
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -562,8 +562,12 @@ namespace Avalonia.Controls.Primitives
return eventArgs.Cancel; return eventArgs.Cancel;
} }
internal static void SetPresenterClasses(IControl presenter, Classes classes) internal static void SetPresenterClasses(IControl? presenter, Classes classes)
{ {
if(presenter is null)
{
return;
}
//Remove any classes no longer in use, ignoring pseudo classes //Remove any classes no longer in use, ignoring pseudo classes
for (int i = presenter.Classes.Count - 1; i >= 0; i--) for (int i = presenter.Classes.Count - 1; i >= 0; i--)
{ {

43
src/Avalonia.Controls/ItemsControl.cs

@ -21,7 +21,7 @@ namespace Avalonia.Controls
/// Displays a collection of items. /// Displays a collection of items.
/// </summary> /// </summary>
[PseudoClasses(":empty", ":singleitem")] [PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener, IChildIndexProvider
{ {
/// <summary> /// <summary>
/// The default value for the <see cref="ItemsPanel"/> property. /// The default value for the <see cref="ItemsPanel"/> property.
@ -56,6 +56,7 @@ namespace Avalonia.Controls
private IEnumerable _items = new AvaloniaList<object>(); private IEnumerable _items = new AvaloniaList<object>();
private int _itemCount; private int _itemCount;
private IItemContainerGenerator _itemContainerGenerator; private IItemContainerGenerator _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class. /// Initializes static members of the <see cref="ItemsControl"/> class.
@ -145,11 +146,28 @@ namespace Avalonia.Controls
protected set; protected set;
} }
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <inheritdoc/> /// <inheritdoc/>
void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter) void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
{ {
if (Presenter is IChildIndexProvider oldInnerProvider)
{
oldInnerProvider.ChildIndexChanged -= PresenterChildIndexChanged;
}
Presenter = presenter; Presenter = presenter;
ItemContainerGenerator.Clear(); ItemContainerGenerator.Clear();
if (Presenter is IChildIndexProvider innerProvider)
{
innerProvider.ChildIndexChanged += PresenterChildIndexChanged;
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
}
} }
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
@ -506,5 +524,28 @@ namespace Avalonia.Controls
return null; return null;
} }
private void PresenterChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
_childIndexChanged?.Invoke(this, e);
}
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return Presenter is IChildIndexProvider innerProvider
? innerProvider.GetChildIndex(child) : -1;
}
bool IChildIndexProvider.TryGetTotalCount(out int count)
{
if (Presenter is IChildIndexProvider presenter
&& presenter.TryGetTotalCount(out count))
{
return true;
}
count = ItemCount;
return true;
}
} }
} }

2
src/Avalonia.Controls/MenuItem.cs

@ -387,7 +387,7 @@ namespace Avalonia.Controls
parent = parent.Parent; parent = parent.Parent;
} }
_isEmbeddedInMenu = parent is IMenu; _isEmbeddedInMenu = parent.FindLogicalAncestorOfType<IMenu>(true) != null;
} }
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)

3
src/Avalonia.Controls/NativeMenuItem.cs

@ -16,6 +16,7 @@ namespace Avalonia.Controls
private bool _isChecked = false; private bool _isChecked = false;
private NativeMenuItemToggleType _toggleType; private NativeMenuItemToggleType _toggleType;
private IBitmap _icon; private IBitmap _icon;
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
private NativeMenu _menu; private NativeMenu _menu;
@ -47,8 +48,6 @@ namespace Avalonia.Controls
} }
} }
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
public NativeMenuItem() public NativeMenuItem()
{ {

24
src/Avalonia.Controls/Panel.cs

@ -2,8 +2,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -14,7 +16,7 @@ namespace Avalonia.Controls
/// Controls can be added to a <see cref="Panel"/> by adding them to its <see cref="Children"/> /// Controls can be added to a <see cref="Panel"/> by adding them to its <see cref="Children"/>
/// collection. All children are layed out to fill the panel. /// collection. All children are layed out to fill the panel.
/// </remarks> /// </remarks>
public class Panel : Control, IPanel public class Panel : Control, IPanel, IChildIndexProvider
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
@ -30,6 +32,8 @@ namespace Avalonia.Controls
AffectsRender<Panel>(BackgroundProperty); AffectsRender<Panel>(BackgroundProperty);
} }
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class. /// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary> /// </summary>
@ -53,6 +57,12 @@ namespace Avalonia.Controls
set { SetValue(BackgroundProperty, value); } set { SetValue(BackgroundProperty, value); }
} }
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <summary> /// <summary>
/// Renders the visual to a <see cref="DrawingContext"/>. /// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary> /// </summary>
@ -137,6 +147,7 @@ namespace Avalonia.Controls
throw new NotSupportedException(); throw new NotSupportedException();
} }
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
InvalidateMeasureOnChildrenChanged(); InvalidateMeasureOnChildrenChanged();
} }
@ -160,5 +171,16 @@ namespace Avalonia.Controls
var panel = control?.VisualParent as TPanel; var panel = control?.VisualParent as TPanel;
panel?.InvalidateMeasure(); panel?.InvalidateMeasure();
} }
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return child is IControl control ? Children.IndexOf(control) : -1;
}
public bool TryGetTotalCount(out int count)
{
count = Children.Count;
return true;
}
} }
} }

6
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -376,7 +377,10 @@ namespace Avalonia.Controls.Platform
{ {
if (item.IsSubMenuOpen) if (item.IsSubMenuOpen)
{ {
if (item.IsTopLevel) // PointerPressed events may bubble from disabled items in sub-menus. In this case,
// keep the sub-menu open.
var popup = (e.Source as ILogical)?.FindLogicalAncestorOfType<Popup>();
if (item.IsTopLevel && popup == null)
{ {
CloseMenu(item); CloseMenu(item);
} }

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

@ -85,7 +85,9 @@ namespace Avalonia.Controls.Platform
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action<DispatcherPriority?> Signaled; public event Action<DispatcherPriority?> Signaled;
#pragma warning disable CS0067
public event Action<TimeSpan> Tick; public event Action<TimeSpan> Tick;
#pragma warning restore CS0067
} }
} }

4
src/Avalonia.Controls/Platform/PlatformManager.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls.Platform
if (platform == null) if (platform == null)
{ {
throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); throw new Exception("Could not CreateTrayIcon(): IWindowingPlatform is not registered.");
} }
return s_designerMode ? null : platform.CreateTrayIcon(); return s_designerMode ? null : platform.CreateTrayIcon();
@ -45,7 +45,7 @@ namespace Avalonia.Controls.Platform
throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered.");
} }
return s_designerMode ? (IWindowImpl)platform.CreateEmbeddableWindow() : platform.CreateWindow(); return s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow();
} }
public static IWindowImpl CreateEmbeddableWindow() public static IWindowImpl CreateEmbeddableWindow()

41
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -5,6 +5,7 @@ using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
@ -12,7 +13,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Base class for controls that present items inside an <see cref="ItemsControl"/>. /// Base class for controls that present items inside an <see cref="ItemsControl"/>.
/// </summary> /// </summary>
public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl, IChildIndexProvider
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Items"/> property. /// Defines the <see cref="Items"/> property.
@ -36,6 +37,7 @@ namespace Avalonia.Controls.Presenters
private IDisposable _itemsSubscription; private IDisposable _itemsSubscription;
private bool _createdPanel; private bool _createdPanel;
private IItemContainerGenerator _generator; private IItemContainerGenerator _generator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class. /// Initializes static members of the <see cref="ItemsPresenter"/> class.
@ -129,6 +131,12 @@ namespace Avalonia.Controls.Presenters
protected bool IsHosted => TemplatedParent is IItemsPresenterHost; protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
/// <inheritdoc/> /// <inheritdoc/>
public override sealed void ApplyTemplate() public override sealed void ApplyTemplate()
{ {
@ -149,6 +157,8 @@ namespace Avalonia.Controls.Presenters
if (Panel != null) if (Panel != null)
{ {
ItemsChanged(e); ItemsChanged(e);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
} }
} }
@ -169,9 +179,21 @@ namespace Avalonia.Controls.Presenters
result.ItemTemplate = ItemTemplate; result.ItemTemplate = ItemTemplate;
} }
result.Materialized += ContainerActionHandler;
result.Dematerialized += ContainerActionHandler;
result.Recycled += ContainerActionHandler;
return result; return result;
} }
private void ContainerActionHandler(object sender, ItemContainerEventArgs e)
{
for (var i = 0; i < e.Containers.Count; i++)
{
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl));
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
@ -248,5 +270,22 @@ namespace Avalonia.Controls.Presenters
{ {
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this); (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
} }
int IChildIndexProvider.GetChildIndex(ILogical child)
{
if (child is IControl control && ItemContainerGenerator is { } generator)
{
var index = ItemContainerGenerator.IndexFromContainer(control);
return index;
}
return -1;
}
bool IChildIndexProvider.TryGetTotalCount(out int count)
{
return Items.TryGetCountFast(out count);
}
} }
} }

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

@ -68,7 +68,7 @@ namespace Avalonia.Controls.Primitives
if (underscore != -1 && ShowAccessKey) if (underscore != -1 && ShowAccessKey)
{ {
var rect = TextLayout.HitTestTextPosition(underscore); var rect = TextLayout.HitTestTextPosition(underscore);
var offset = new Vector(0, -0.5); var offset = new Vector(0, -1.5);
context.DrawLine( context.DrawLine(
new Pen(Foreground, 1), new Pen(Foreground, 1),
rect.BottomLeft + offset, rect.BottomLeft + offset,

8
src/Avalonia.Controls/Primitives/Popup.cs

@ -93,8 +93,8 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty = public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough)); AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough));
public static readonly DirectProperty<Popup, IInputElement> OverlayInputPassThroughElementProperty = public static readonly DirectProperty<Popup, IInputElement?> OverlayInputPassThroughElementProperty =
AvaloniaProperty.RegisterDirect<Popup, IInputElement>( AvaloniaProperty.RegisterDirect<Popup, IInputElement?>(
nameof(OverlayInputPassThroughElement), nameof(OverlayInputPassThroughElement),
o => o.OverlayInputPassThroughElement, o => o.OverlayInputPassThroughElement,
(o, v) => o.OverlayInputPassThroughElement = v); (o, v) => o.OverlayInputPassThroughElement = v);
@ -138,7 +138,7 @@ namespace Avalonia.Controls.Primitives
private bool _isOpen; private bool _isOpen;
private bool _ignoreIsOpenChanged; private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState; private PopupOpenState? _openState;
private IInputElement _overlayInputPassThroughElement; private IInputElement? _overlayInputPassThroughElement;
private Action<IPopupHost?>? _popupHostChangedHandler; private Action<IPopupHost?>? _popupHostChangedHandler;
/// <summary> /// <summary>
@ -310,7 +310,7 @@ namespace Avalonia.Controls.Primitives
/// Gets or sets an element that should receive pointer input events even when underneath /// Gets or sets an element that should receive pointer input events even when underneath
/// the popup's overlay. /// the popup's overlay.
/// </summary> /// </summary>
public IInputElement OverlayInputPassThroughElement public IInputElement? OverlayInputPassThroughElement
{ {
get => _overlayInputPassThroughElement; get => _overlayInputPassThroughElement;
set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);

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

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

3
src/Avalonia.Controls/Remote/RemoteServer.cs

@ -15,9 +15,6 @@ namespace Avalonia.Controls.Remote
public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport)
{ {
} }
#pragma warning disable 67
public Action LostFocus { get; set; }
} }
public RemoteServer(IAvaloniaRemoteTransportConnection transport) public RemoteServer(IAvaloniaRemoteTransportConnection transport)

31
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -19,7 +20,7 @@ namespace Avalonia.Controls
/// Represents a data-driven collection control that incorporates a flexible layout system, /// Represents a data-driven collection control that incorporates a flexible layout system,
/// custom views, and virtualization. /// custom views, and virtualization.
/// </summary> /// </summary>
public class ItemsRepeater : Panel public class ItemsRepeater : Panel, IChildIndexProvider
{ {
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property. /// Defines the <see cref="HorizontalCacheLength"/> property.
@ -61,8 +62,9 @@ namespace Avalonia.Controls
private readonly ViewportManager _viewportManager; private readonly ViewportManager _viewportManager;
private IEnumerable _items; private IEnumerable _items;
private VirtualizingLayoutContext _layoutContext; private VirtualizingLayoutContext _layoutContext;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange; private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
private bool _isLayoutInProgress; private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private ItemsRepeaterElementPreparedEventArgs _elementPreparedArgs; private ItemsRepeaterElementPreparedEventArgs _elementPreparedArgs;
private ItemsRepeaterElementClearingEventArgs _elementClearingArgs; private ItemsRepeaterElementClearingEventArgs _elementClearingArgs;
private ItemsRepeaterElementIndexChangedEventArgs _elementIndexChangedArgs; private ItemsRepeaterElementIndexChangedEventArgs _elementIndexChangedArgs;
@ -163,6 +165,25 @@ namespace Avalonia.Controls
} }
} }
event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return child is IControl control
? GetElementIndex(control)
: -1;
}
bool IChildIndexProvider.TryGetTotalCount(out int count)
{
count = ItemsSourceView.Count;
return true;
}
/// <summary> /// <summary>
/// Occurs each time an element is cleared and made available to be re-used. /// Occurs each time an element is cleared and made available to be re-used.
/// </summary> /// </summary>
@ -545,6 +566,8 @@ namespace Avalonia.Controls
ElementPrepared(this, _elementPreparedArgs); ElementPrepared(this, _elementPreparedArgs);
} }
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
} }
internal void OnElementClearing(IControl element) internal void OnElementClearing(IControl element)
@ -562,6 +585,8 @@ namespace Avalonia.Controls
ElementClearing(this, _elementClearingArgs); ElementClearing(this, _elementClearingArgs);
} }
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
} }
internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex) internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex)
@ -579,6 +604,8 @@ namespace Avalonia.Controls
ElementIndexChanged(this, _elementIndexChangedArgs); ElementIndexChanged(this, _elementIndexChangedArgs);
} }
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
} }
private void OnDataSourcePropertyChanged(ItemsSourceView oldValue, ItemsSourceView newValue) private void OnDataSourcePropertyChanged(ItemsSourceView oldValue, ItemsSourceView newValue)

10
src/Avalonia.Controls/SplitView.cs

@ -129,14 +129,14 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Pane"/> property /// Defines the <see cref="Pane"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<object?> PaneProperty = public static readonly StyledProperty<object> PaneProperty =
AvaloniaProperty.Register<SplitView, object?>(nameof(Pane)); AvaloniaProperty.Register<SplitView, object>(nameof(Pane));
/// <summary> /// <summary>
/// Defines the <see cref="PaneTemplate"/> property. /// Defines the <see cref="PaneTemplate"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IDataTemplate?> PaneTemplateProperty = public static readonly StyledProperty<IDataTemplate> PaneTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate?>(nameof(PaneTemplate)); AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(PaneTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="UseLightDismissOverlayMode"/> property /// Defines the <see cref="UseLightDismissOverlayMode"/> property
@ -267,7 +267,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the data template used to display the header content of the control. /// Gets or sets the data template used to display the header content of the control.
/// </summary> /// </summary>
public IDataTemplate? PaneTemplate public IDataTemplate PaneTemplate
{ {
get => GetValue(PaneTemplateProperty); get => GetValue(PaneTemplateProperty);
set => SetValue(PaneTemplateProperty, value); set => SetValue(PaneTemplateProperty, value);

2
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
public bool SupportsSurroundingText => false; public bool SupportsSurroundingText => false;
public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException(); public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException();
public event EventHandler SurroundingTextChanged; public event EventHandler SurroundingTextChanged { add { } remove { } }
public string TextBeforeCursor => null; public string TextBeforeCursor => null;
public string TextAfterCursor => null; public string TextAfterCursor => null;

49
src/Avalonia.Controls/TrayIcon.cs

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Input;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -13,10 +15,13 @@ namespace Avalonia.Controls
public sealed class TrayIcons : AvaloniaList<TrayIcon> public sealed class TrayIcons : AvaloniaList<TrayIcon>
{ {
} }
public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
{ {
private readonly ITrayIconImpl? _impl; private readonly ITrayIconImpl? _impl;
private ICommand? _command;
private TrayIcon(ITrayIconImpl? impl) private TrayIcon(ITrayIconImpl? impl)
{ {
@ -26,7 +31,15 @@ namespace Avalonia.Controls
_impl.SetIsVisible(IsVisible); _impl.SetIsVisible(IsVisible);
_impl.OnClicked = () => Clicked?.Invoke(this, EventArgs.Empty); _impl.OnClicked = () =>
{
Clicked?.Invoke(this, EventArgs.Empty);
if (Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
}
};
} }
} }
@ -64,6 +77,21 @@ namespace Avalonia.Controls
/// on OSX this event is not raised. /// on OSX this event is not raised.
/// </summary> /// </summary>
public event EventHandler? Clicked; public event EventHandler? Clicked;
/// <summary>
/// Defines the <see cref="Command"/> property.
/// </summary>
public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<TrayIcon>(
trayIcon => trayIcon.Command,
(trayIcon, command) => trayIcon.Command = command,
enableDataValidation: true);
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object?> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
/// <summary> /// <summary>
/// Defines the <see cref="TrayIcons"/> attached property. /// Defines the <see cref="TrayIcons"/> attached property.
@ -98,6 +126,25 @@ namespace Avalonia.Controls
public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons); public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons);
public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty); public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty);
/// <summary>
/// Gets or sets the <see cref="Command"/> property of a TrayIcon.
/// </summary>
public ICommand? Command
{
get => _command;
set => SetAndRaise(CommandProperty, ref _command, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="TrayIcon"/>.
/// </summary>
public object? CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the Menu of the TrayIcon. /// Gets or sets the Menu of the TrayIcon.

27
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@ -12,23 +12,36 @@ namespace Avalonia.Controls.Utils
return items.IndexOf(item) != -1; return items.IndexOf(item) != -1;
} }
public static int Count(this IEnumerable items) public static bool TryGetCountFast(this IEnumerable items, out int count)
{ {
if (items != null) if (items != null)
{ {
if (items is ICollection collection) if (items is ICollection collection)
{ {
return collection.Count; count = collection.Count;
return true;
} }
else if (items is IReadOnlyCollection<object> readOnly) else if (items is IReadOnlyCollection<object> readOnly)
{ {
return readOnly.Count; count = readOnly.Count;
} return true;
else
{
return Enumerable.Count(items.Cast<object>());
} }
} }
count = 0;
return false;
}
public static int Count(this IEnumerable items)
{
if (TryGetCountFast(items, out var count))
{
return count;
}
else if (items != null)
{
return Enumerable.Count(items.Cast<object>());
}
else else
{ {
return 0; return 0;

6
src/Avalonia.Controls/WindowBase.cs

@ -193,6 +193,12 @@ namespace Avalonia.Controls
try try
{ {
IsVisible = false; IsVisible = false;
if (this is IFocusScope scope)
{
FocusManager.Instance?.RemoveFocusScope(scope);
}
base.HandleClosed(); base.HandleClosed();
} }
finally finally

2
src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs

@ -59,7 +59,7 @@ namespace Avalonia.DesignerSupport.Remote
remove { _onMessage -= value; } remove { _onMessage -= value; }
} }
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException; public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException { add { } remove { } }
public void Start() public void Start()
{ {
UpdaterThread(); UpdaterThread();

2
src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs

@ -25,7 +25,6 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
private AutoResetEvent _wakeup = new AutoResetEvent(false); private AutoResetEvent _wakeup = new AutoResetEvent(false);
private FrameMessage _lastFrameMessage = null; private FrameMessage _lastFrameMessage = null;
private FrameMessage _lastSentFrameMessage = null; private FrameMessage _lastSentFrameMessage = null;
private RequestViewportResizeMessage _lastViewportRequest;
private Action<IAvaloniaRemoteTransportConnection, object> _onMessage; private Action<IAvaloniaRemoteTransportConnection, object> _onMessage;
private Action<IAvaloniaRemoteTransportConnection, Exception> _onException; private Action<IAvaloniaRemoteTransportConnection, Exception> _onException;
@ -177,6 +176,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
public void Dispose() public void Dispose()
{ {
_disposed = true;
_pendingSocket?.Dispose(); _pendingSocket?.Dispose();
_simpleServer.Dispose(); _simpleServer.Dispose();
} }

2
src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs

@ -29,7 +29,7 @@ namespace Avalonia.Diagnostics.Models
} }
} }
public bool Handled { get; } public bool Handled { get; set; }
public RoutingStrategies Route { get; } public RoutingStrategies Route { get; }
} }

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -19,7 +19,7 @@ namespace Avalonia.Diagnostics.ViewModels
Name = property.IsAttached ? Name = property.IsAttached ?
$"[{property.OwnerType.Name}.{property.Name}]" : $"[{property.OwnerType.Name}.{property.Name}]" :
property.Name; property.Name;
DeclaringType = property.OwnerType;
Update(); Update();
} }
@ -50,6 +50,8 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Group => _group; public override string Group => _group;
public override System.Type? DeclaringType { get; }
// [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] // [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))]
public override void Update() public override void Update()
{ {

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -24,7 +24,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
Name = property.DeclaringType.Name + '.' + property.Name; Name = property.DeclaringType.Name + '.' + property.Name;
} }
DeclaringType = property.DeclaringType;
Update(); Update();
} }
@ -55,6 +55,8 @@ namespace Avalonia.Diagnostics.ViewModels
public override bool? IsAttached => public override bool? IsAttached =>
default; default;
public override System.Type? DeclaringType { get; }
// [MemberNotNull(nameof(_type))] // [MemberNotNull(nameof(_type))]
public override void Update() public override void Update()
{ {

144
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -17,42 +17,26 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlDetailsViewModel : ViewModelBase, IDisposable internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{ {
private readonly IVisual _control; private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex; private IDictionary<object, List<PropertyViewModel>>? _propertyIndex;
private PropertyViewModel? _selectedProperty; private PropertyViewModel? _selectedProperty;
private DataGridCollectionView? _propertiesView;
private bool _snapshotStyles; private bool _snapshotStyles;
private bool _showInactiveStyles; private bool _showInactiveStyles;
private string? _styleStatus; private string? _styleStatus;
private object? _selectedEntity;
private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new();
private string? _selectedEntityName;
private string? _selectedEntityType;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{ {
_control = control; _control = control;
TreePage = treePage; TreePage = treePage;
var properties = GetAvaloniaProperties(control)
.Concat(GetClrProperties(control))
.OrderBy(x => x, PropertyComparer.Instance)
.ThenBy(x => x.Name)
.ToList();
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList());
var view = new DataGridCollectionView(properties);
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));
view.Filter = FilterProperty;
PropertiesView = view;
Layout = new ControlLayoutViewModel(control); Layout = new ControlLayoutViewModel(control);
if (control is INotifyPropertyChanged inpc) NavigateToProperty(control, (control as IControl)?.Name ?? control.ToString());
{
inpc.PropertyChanged += ControlPropertyChanged;
}
if (control is AvaloniaObject ao)
{
ao.PropertyChanged += ControlPropertyChanged;
}
AppliedStyles = new ObservableCollection<StyleViewModel>(); AppliedStyles = new ObservableCollection<StyleViewModel>();
PseudoClasses = new ObservableCollection<PseudoClassViewModel>(); PseudoClasses = new ObservableCollection<PseudoClassViewModel>();
@ -133,12 +117,46 @@ namespace Avalonia.Diagnostics.ViewModels
public TreePageViewModel TreePage { get; } public TreePageViewModel TreePage { get; }
public DataGridCollectionView PropertiesView { get; } public DataGridCollectionView? PropertiesView
{
get => _propertiesView;
private set => RaiseAndSetIfChanged(ref _propertiesView, value);
}
public ObservableCollection<StyleViewModel> AppliedStyles { get; } public ObservableCollection<StyleViewModel> AppliedStyles { get; }
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; } public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public object? SelectedEntity
{
get => _selectedEntity;
set
{
RaiseAndSetIfChanged(ref _selectedEntity, value);
}
}
public string? SelectedEntityName
{
get => _selectedEntityName;
set
{
RaiseAndSetIfChanged(ref _selectedEntityName, value);
}
}
public string? SelectedEntityType
{
get => _selectedEntityType;
set
{
RaiseAndSetIfChanged(ref _selectedEntityType, value);
}
}
public PropertyViewModel? SelectedProperty public PropertyViewModel? SelectedProperty
{ {
get => _selectedProperty; get => _selectedProperty;
@ -252,7 +270,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (_propertyIndex.TryGetValue(e.Property, out var properties)) if (_propertyIndex is { } && _propertyIndex.TryGetValue(e.Property, out var properties))
{ {
foreach (var property in properties) foreach (var property in properties)
{ {
@ -266,6 +284,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e) private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName != null if (e.PropertyName != null
&& _propertyIndex is { }
&& _propertyIndex.TryGetValue(e.PropertyName, out var properties)) && _propertyIndex.TryGetValue(e.PropertyName, out var properties))
{ {
foreach (var property in properties) foreach (var property in properties)
@ -378,5 +397,78 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
} }
public void ApplySelectedProperty()
{
var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity;
var selectedEntityName = SelectedEntityName;
if (selectedEntity == null || selectedProperty == null)
return;
object? property;
if (selectedProperty.Key is AvaloniaProperty avaloniaProperty)
{
property = (_selectedEntity as IControl)?.GetValue(avaloniaProperty);
}
else
{
property = selectedEntity.GetType().GetProperties()
.FirstOrDefault(pi => pi.Name == selectedProperty.Name
&& pi.DeclaringType == selectedProperty.DeclaringType
&& pi.PropertyType.Name == selectedProperty.Type)
?.GetValue(selectedEntity);
}
if (property == null) return;
_selectedEntitiesStack.Push((Name:selectedEntityName!,Entry:selectedEntity));
NavigateToProperty(property, selectedProperty.Name);
}
public void ApplyParentProperty()
{
if (_selectedEntitiesStack.Any())
{
var property = _selectedEntitiesStack.Pop();
NavigateToProperty(property.Entry, property.Name);
}
}
protected void NavigateToProperty(object o, string entityName)
{
var oldSelectedEntity = SelectedEntity;
if (oldSelectedEntity is IAvaloniaObject ao1)
{
ao1.PropertyChanged -= ControlPropertyChanged;
}
else if (oldSelectedEntity is INotifyPropertyChanged inpc1)
{
inpc1.PropertyChanged -= ControlPropertyChanged;
}
SelectedEntity = o;
SelectedEntityName = entityName;
SelectedEntityType = o.ToString();
var properties = GetAvaloniaProperties(o)
.Concat(GetClrProperties(o))
.OrderBy(x => x, PropertyComparer.Instance)
.ThenBy(x => x.Name)
.ToList();
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList());
var view = new DataGridCollectionView(properties);
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));
view.Filter = FilterProperty;
PropertiesView = view;
if (o is IAvaloniaObject ao2)
{
ao2.PropertyChanged += ControlPropertyChanged;
}
else if (o is INotifyPropertyChanged inpc2)
{
inpc2.PropertyChanged += ControlPropertyChanged;
}
}
} }
} }

31
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs

@ -55,6 +55,8 @@ namespace Avalonia.Diagnostics.ViewModels
// FIXME: This leaks event handlers. // FIXME: This leaks event handlers.
Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true); Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true);
Event.RouteFinished.Subscribe(HandleRouteFinished);
_isRegistered = true; _isRegistered = true;
} }
} }
@ -92,6 +94,35 @@ namespace Avalonia.Diagnostics.ViewModels
else else
handler(); handler();
} }
private void HandleRouteFinished(RoutedEventArgs e)
{
if (!_isRegistered || IsEnabled == false)
return;
if (e.Source is IVisual v && BelongsToDevTool(v))
return;
var s = e.Source;
var handled = e.Handled;
var route = e.Route;
void handler()
{
if (_currentEvent != null && handled)
{
var linkIndex = _currentEvent.EventChain.Count - 1;
var link = _currentEvent.EventChain[linkIndex];
link.Handled = true;
_currentEvent.HandledBy = link;
}
}
if (!Dispatcher.UIThread.CheckAccess())
Dispatcher.UIThread.Post(handler);
else
handler();
}
private static bool BelongsToDevTool(IVisual v) private static bool BelongsToDevTool(IVisual v)
{ {

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs

@ -63,7 +63,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
if (EventChain.Count > 0) if (EventChain.Count > 0)
{ {
var prevLink = EventChain[EventChain.Count-1]; var prevLink = EventChain[EventChain.Count - 1];
if (prevLink.Route != link.Route) if (prevLink.Route != link.Route)
{ {
@ -72,6 +72,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
EventChain.Add(link); EventChain.Add(link);
if (HandledBy == null && link.Handled) if (HandledBy == null && link.Handled)
HandledBy = link; HandledBy = link;
} }

1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -15,6 +15,7 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract string Name { get; } public abstract string Name { get; }
public abstract string Group { get; } public abstract string Group { get; }
public abstract string Type { get; } public abstract string Type { get; }
public abstract Type? DeclaringType { get; }
public abstract string Value { get; set; } public abstract string Value { get; set; }
public abstract string Priority { get; } public abstract string Priority { get; }
public abstract bool? IsAttached { get; } public abstract bool? IsAttached { get; }

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.ViewModels
Nodes = nodes; Nodes = nodes;
PropertiesFilter = new FilterViewModel(); PropertiesFilter = new FilterViewModel();
PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView.Refresh(); PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView?.Refresh();
SettersFilter = new FilterViewModel(); SettersFilter = new FilterViewModel();
SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters(); SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters();

17
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -13,23 +13,30 @@
<Grid ColumnDefinitions="*,Auto,320"> <Grid ColumnDefinitions="*,Auto,320">
<Grid Grid.Column="0" RowDefinitions="Auto,*"> <Grid Grid.Column="0" RowDefinitions="Auto,Auto,*">
<controls:FilterTextBox Grid.Row="0" <Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto">
<Button Grid.Column="0" Grid.RowSpan="2" Content="^" Command="{Binding ApplyParentProperty}" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding SelectedEntityName}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding SelectedEntityType}" FontStyle="Italic" />
</Grid>
<controls:FilterTextBox Grid.Row="1"
BorderThickness="0" BorderThickness="0"
DataContext="{Binding TreePage.PropertiesFilter}" DataContext="{Binding TreePage.PropertiesFilter}"
Text="{Binding FilterString}" Text="{Binding FilterString}"
Watermark="Filter properties" Watermark="Filter properties"
UseCaseSensitiveFilter="{Binding UseCaseSensitiveFilter}" UseCaseSensitiveFilter="{Binding UseCaseSensitiveFilter}"
UseWholeWordFilter="{Binding UseWholeWordFilter}" UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" /> UseRegexFilter="{Binding UseRegexFilter}"/>
<DataGrid Items="{Binding PropertiesView}" <DataGrid Items="{Binding PropertiesView}"
Grid.Row="1" Grid.Row="2"
BorderThickness="0" BorderThickness="0"
RowBackground="Transparent" RowBackground="Transparent"
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}" SelectedItem="{Binding SelectedProperty, Mode=TwoWay}"
CanUserResizeColumns="true"> CanUserResizeColumns="true"
DoubleTapped="PropertiesGrid_OnDoubleTapped">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" /> <DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" /> <DataGridTextColumn Header="Value" Binding="{Binding Value}" />

11
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs

@ -1,4 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace Avalonia.Diagnostics.Views namespace Avalonia.Diagnostics.Views
@ -14,5 +16,14 @@ namespace Avalonia.Diagnostics.Views
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void PropertiesGrid_OnDoubleTapped(object sender, TappedEventArgs e)
{
if (sender is DataGrid grid && grid.DataContext is ControlDetailsViewModel controlDetails)
{
controlDetails.ApplySelectedProperty();
}
}
} }
} }

6
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -12,7 +12,7 @@ namespace Avalonia.FreeDesktop
/// This class uses synchronous execution at DBus connection establishment stage /// This class uses synchronous execution at DBus connection establishment stage
/// then switches to using AvaloniaSynchronizationContext /// then switches to using AvaloniaSynchronizationContext
/// </summary> /// </summary>
class DBusSyncContext : SynchronizationContext private class DBusSyncContext : SynchronizationContext
{ {
private SynchronizationContext _ctx; private SynchronizationContext _ctx;
private object _lock = new object(); private object _lock = new object();
@ -51,10 +51,10 @@ namespace Avalonia.FreeDesktop
public static Connection TryInitialize(string dbusAddress = null) public static Connection TryInitialize(string dbusAddress = null)
{ {
return Connection ?? TryGetConnection(dbusAddress); return Connection ?? TryCreateNewConnection(dbusAddress);
} }
public static Connection TryGetConnection(string dbusAddress = null) public static Connection TryCreateNewConnection(string dbusAddress = null)
{ {
var oldContext = SynchronizationContext.Current; var oldContext = SynchronizationContext.Current;
try try

6
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -413,10 +413,10 @@ namespace Avalonia.FreeDesktop
#region Events #region Events
private event Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> private event Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)>
ItemsPropertiesUpdated; ItemsPropertiesUpdated { add { } remove { } }
private event Action<(uint revision, int parent)> LayoutUpdated; private event Action<(uint revision, int parent)> LayoutUpdated;
private event Action<(int id, uint timestamp)> ItemActivationRequested; private event Action<(int id, uint timestamp)> ItemActivationRequested { add { } remove { } }
private event Action<PropertyChanges> PropertiesChanged; private event Action<PropertyChanges> PropertiesChanged { add { } remove { } }
async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError) async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError)
{ {

178
src/Avalonia.X11/X11TrayIconImpl.cs → src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -6,55 +6,65 @@ using System.Reactive.Disposables;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Tmds.DBus; using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] [assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.X11 [assembly:
InternalsVisibleTo(
"Avalonia.X11, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
namespace Avalonia.FreeDesktop
{ {
internal class X11TrayIconImpl : ITrayIconImpl internal class DBusTrayIconImpl : ITrayIconImpl
{ {
private static int s_trayIconInstanceId; private static int s_trayIconInstanceId;
private readonly ObjectPath _dbusMenuPath; private readonly ObjectPath _dbusMenuPath;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private readonly Connection? _connection; private readonly Connection? _connection;
private DbusPixmap _icon; private IDisposable? _serviceWatchDisposable;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private IStatusNotifierWatcher? _statusNotifierWatcher; private IStatusNotifierWatcher? _statusNotifierWatcher;
private DbusPixmap _icon;
private string? _sysTrayServiceName; private string? _sysTrayServiceName;
private string? _tooltipText; private string? _tooltipText;
private bool _isActive;
private bool _isDisposed; private bool _isDisposed;
private readonly bool _ctorFinished; private bool _serviceConnected;
private bool _isVisible = true;
public bool IsActive { get; private set; }
public INativeMenuExporter? MenuExporter { get; } public INativeMenuExporter? MenuExporter { get; }
public Action? OnClicked { get; set; } public Action? OnClicked { get; set; }
public Func<IWindowIconImpl?, uint[]>? IconConverterDelegate { get; set; }
public X11TrayIconImpl() public DBusTrayIconImpl()
{ {
_connection = DBusHelper.TryGetConnection(); _connection = DBusHelper.TryCreateNewConnection();
if (_connection is null) if (_connection is null)
{ {
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, "Unable to get a dbus connection for system tray icons."); ?.Log(this, "Unable to get a dbus connection for system tray icons.");
return; return;
} }
IsActive = true;
_dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath;
MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection);
CreateTrayIcon();
_ctorFinished = true; WatchAsync();
} }
public async void CreateTrayIcon() private void InitializeSNWService()
{ {
if (_connection is null) if (_connection is null || _isDisposed) return;
return;
try try
{ {
@ -64,12 +74,58 @@ namespace Avalonia.X11
} }
catch catch
{ {
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this,
"org.kde.StatusNotifierWatcher service is not available on this system. Tray Icons will not work without it.");
return;
}
_serviceConnected = true;
}
private async void WatchAsync()
{
try
{
_serviceWatchDisposable =
await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, ?.Log(this,
"DBUS: org.kde.StatusNotifierWatcher service is not available on this system. System Tray Icons will not work without it."); $"Unable to hook watcher method on org.kde.StatusNotifierWatcher: {e}");
} }
}
if (_statusNotifierWatcher is null) private void OnNameChange(ServiceOwnerChangedEventArgs obj)
{
if (_isDisposed)
return;
if (!_serviceConnected & obj.NewOwner != null)
{
_serviceConnected = true;
InitializeSNWService();
DestroyTrayIcon();
if (_isVisible)
{
CreateTrayIcon();
}
}
else if (_serviceConnected & obj.NewOwner is null)
{
DestroyTrayIcon();
_serviceConnected = false;
}
}
private void CreateTrayIcon()
{
if (_connection is null || !_serviceConnected || _isDisposed)
return; return;
var pid = Process.GetCurrentProcess().Id; var pid = Process.GetCurrentProcess().Id;
@ -78,45 +134,61 @@ namespace Avalonia.X11
_sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}";
_statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath);
await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); try
{
await _connection.RegisterServiceAsync(_sysTrayServiceName); _connection.RegisterObjectAsync(_statusNotifierItemDbusObj);
_connection.RegisterServiceAsync(_sysTrayServiceName);
_statusNotifierWatcher?.RegisterStatusNotifierItemAsync(_sysTrayServiceName);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, $"Error creating a DBus tray icon: {e}.");
await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); _serviceConnected = false;
}
_statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText);
_statusNotifierItemDbusObj.SetIcon(_icon); _statusNotifierItemDbusObj.SetIcon(_icon);
_statusNotifierItemDbusObj.ActivationDelegate += OnClicked; _statusNotifierItemDbusObj.ActivationDelegate += OnClicked;
_isActive = true;
} }
public async void DestroyTrayIcon() private void DestroyTrayIcon()
{ {
if (_connection is null) if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null)
return; return;
_connection.UnregisterObject(_statusNotifierItemDbusObj); _connection.UnregisterObject(_statusNotifierItemDbusObj);
await _connection.UnregisterServiceAsync(_sysTrayServiceName); _connection.UnregisterServiceAsync(_sysTrayServiceName);
_isActive = false;
} }
public void Dispose() public void Dispose()
{ {
IsActive = false;
_isDisposed = true; _isDisposed = true;
DestroyTrayIcon(); DestroyTrayIcon();
_connection?.Dispose(); _connection?.Dispose();
_serviceWatchDisposable?.Dispose();
} }
public void SetIcon(IWindowIconImpl? icon) public void SetIcon(IWindowIconImpl? icon)
{ {
if (_isDisposed) if (_isDisposed || IconConverterDelegate is null)
return; return;
if (!(icon is X11IconData x11icon))
if (icon is null)
{
_statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap);
return; return;
}
var x11iconData = IconConverterDelegate(icon);
if (x11iconData.Length == 0) return;
var w = (int)x11icon.Data[0]; var w = (int)x11iconData[0];
var h = (int)x11icon.Data[1]; var h = (int)x11iconData[1];
var pixLength = w * h; var pixLength = w * h;
var pixByteArrayCounter = 0; var pixByteArrayCounter = 0;
@ -124,7 +196,7 @@ namespace Avalonia.X11
for (var i = 0; i < pixLength; i++) for (var i = 0; i < pixLength; i++)
{ {
var rawPixel = x11icon.Data[i + 2].ToUInt32(); var rawPixel = x11iconData[i + 2];
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8);
@ -137,18 +209,21 @@ namespace Avalonia.X11
public void SetIsVisible(bool visible) public void SetIsVisible(bool visible)
{ {
if (_isDisposed || !_ctorFinished) if (_isDisposed)
return; return;
if (visible & !_isActive) switch (visible)
{
DestroyTrayIcon();
CreateTrayIcon();
}
else if (!visible & _isActive)
{ {
DestroyTrayIcon(); case true when !_isVisible:
DestroyTrayIcon();
CreateTrayIcon();
break;
case false when _isVisible:
DestroyTrayIcon();
break;
} }
_isVisible = visible;
} }
public void SetToolTipText(string? text) public void SetToolTipText(string? text)
@ -248,7 +323,20 @@ namespace Avalonia.X11
return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler)); return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler));
} }
public Task<object> GetAsync(string prop) => Task.FromResult(new object()); public Task<object?> GetAsync(string prop)
{
return Task.FromResult<object?>(prop switch
{
nameof(_backingProperties.Category) => _backingProperties.Category,
nameof(_backingProperties.Id) => _backingProperties.Id,
nameof(_backingProperties.Menu) => _backingProperties.Menu,
nameof(_backingProperties.IconPixmap) => _backingProperties.IconPixmap,
nameof(_backingProperties.Status) => _backingProperties.Status,
nameof(_backingProperties.Title) => _backingProperties.Title,
nameof(_backingProperties.ToolTip) => _backingProperties.ToolTip,
_ => null
});
}
public Task<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties); public Task<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties);
@ -298,17 +386,17 @@ namespace Avalonia.X11
Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError); Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError); Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError); Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError);
Task<object> GetAsync(string prop); Task<object?> GetAsync(string prop);
Task<StatusNotifierItemProperties> GetAllAsync(); Task<StatusNotifierItemProperties> GetAllAsync();
Task SetAsync(string prop, object val); Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler); Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
} }
[Dictionary]
// This class is used by Tmds.Dbus to ferry properties // This class is used by Tmds.Dbus to ferry properties
// from the SNI spec. // from the SNI spec.
// Don't change this to actual C# properties since // Don't change this to actual C# properties since
// Tmds.Dbus will get confused. // Tmds.Dbus will get confused.
[Dictionary]
internal class StatusNotifierItemProperties internal class StatusNotifierItemProperties
{ {
public string? Category; public string? Category;
@ -363,5 +451,7 @@ namespace Avalonia.X11
Height = height; Height = height;
Data = data; Data = data;
} }
public static DbusPixmap EmptyPixmap = new DbusPixmap(1, 1, new byte[] { 255, 0, 0, 0 });
} }
} }

2
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -199,7 +199,7 @@ namespace Avalonia.Headless
public ILockedFramebuffer Lock() public ILockedFramebuffer Lock()
{ {
var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling); var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling, PixelFormat.Rgba8888, AlphaFormat.Premul);
var fb = bmp.Lock(); var fb = bmp.Lock();
return new FramebufferProxy(fb, () => return new FramebufferProxy(fb, () =>
{ {

3
src/Avalonia.Input/ApiCompatBaseline.txt

@ -3,6 +3,7 @@ MembersMustExist : Member 'public Avalonia.Platform.IPlatformHandle Avalonia.Inp
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.RightTappedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.RightTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.TappedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.TappedEvent' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.IFocusManager.RemoveFocusScope(Avalonia.Input.IFocusScope)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.TappedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.TappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
@ -10,4 +11,4 @@ MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_Tapped(Sy
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_Tapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_Tapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract.
Total Issues: 11 Total Issues: 12

16
src/Avalonia.Input/FocusManager.cs

@ -162,6 +162,22 @@ namespace Avalonia.Input
Focus(e); Focus(e);
} }
public void RemoveFocusScope(IFocusScope scope)
{
scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (_focusScopes.TryGetValue(scope, out _))
{
SetFocusedElement(scope, null);
_focusScopes.Remove(scope);
}
if (Scope == scope)
{
Scope = null;
}
}
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope; public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
/// <summary> /// <summary>

7
src/Avalonia.Input/Gestures.cs

@ -30,7 +30,7 @@ namespace Avalonia.Input
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null); private static readonly WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
static Gestures() static Gestures()
@ -86,16 +86,15 @@ namespace Avalonia.Input
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
if (clickCount <= 1) if (clickCount <= 1)
{ {
s_lastPress = new WeakReference<IInteractive>(ev.Source); s_lastPress.SetTarget(ev.Source);
} }
else if (s_lastPress != null && clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) else if (clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
if (s_lastPress.TryGetTarget(out var target) && target == e.Source) if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{ {
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
} }
} }
} }
} }

6
src/Avalonia.Input/ICommandSource.cs

@ -1,5 +1,5 @@
using System.Windows.Input; using System.Windows.Input;
#nullable enable
namespace Avalonia.Input namespace Avalonia.Input
{ {
///<summary> ///<summary>
@ -12,13 +12,13 @@ namespace Avalonia.Input
/// Classes that implement this interface should enable or disable based on the command's CanExecute return value. /// Classes that implement this interface should enable or disable based on the command's CanExecute return value.
/// The property may be implemented as read-write if desired. /// The property may be implemented as read-write if desired.
/// </summary> /// </summary>
ICommand Command { get; } ICommand? Command { get; }
/// <summary> /// <summary>
/// The parameter that will be passed to the command when executing the command. /// The parameter that will be passed to the command when executing the command.
/// The property may be implemented as read-write if desired. /// The property may be implemented as read-write if desired.
/// </summary> /// </summary>
object CommandParameter { get; } object? CommandParameter { get; }
/// <summary> /// <summary>

8
src/Avalonia.Input/IFocusManager.cs

@ -35,5 +35,13 @@ namespace Avalonia.Input
/// when it activates, e.g. when a Window is activated. /// when it activates, e.g. when a Window is activated.
/// </remarks> /// </remarks>
void SetFocusScope(IFocusScope scope); void SetFocusScope(IFocusScope scope);
/// <summary>
/// Notifies the focus manager that a focus scope has been removed.
/// </summary>
/// <param name="scope">The focus scope to be removed.</param>
/// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
/// when it deactivates or closes, e.g. when a Window is closed.
void RemoveFocusScope(IFocusScope scope);
} }
} }

4
src/Avalonia.Input/InputElement.cs

@ -16,7 +16,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Implements input-related functionality for a control. /// Implements input-related functionality for a control.
/// </summary> /// </summary>
[PseudoClasses(":disabled", ":focus", ":focus-visible", ":pointerover")] [PseudoClasses(":disabled", ":focus", ":focus-visible", ":focus-within", ":pointerover")]
public class InputElement : Interactive, IInputElement public class InputElement : Interactive, IInputElement
{ {
/// <summary> /// <summary>
@ -632,7 +632,7 @@ namespace Avalonia.Input
} }
else if (change.Property == IsKeyboardFocusWithinProperty) else if (change.Property == IsKeyboardFocusWithinProperty)
{ {
PseudoClasses.Set(":focus-within", _isKeyboardFocusWithin); PseudoClasses.Set(":focus-within", change.NewValue.GetValueOrDefault<bool>());
} }
} }

9
src/Avalonia.Layout/ElementManager.cs

@ -314,12 +314,11 @@ namespace Avalonia.Layout
} }
break; break;
// Remove clear all realized elements just to align the begavior
// with ViewManager which resets realized item indices to defaults.
// Freeing only removed items causes wrong indices to be stored
// in virtualized info of items under some circumstances.
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
{
OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count);
}
break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
ClearRealizedRange(); ClearRealizedRange();
break; break;

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

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

2
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -44,7 +44,7 @@ namespace Avalonia.Native
public bool IsNativeMenuExported => _exported; public bool IsNativeMenuExported => _exported;
public event EventHandler OnIsNativeMenuExportedChanged; public event EventHandler OnIsNativeMenuExportedChanged { add { } remove { } }
public void SetNativeMenu(NativeMenu menu) public void SetNativeMenu(NativeMenu menu)
{ {

26
src/Avalonia.Styling/LogicalTree/ChildIndexChangedEventArgs.cs

@ -0,0 +1,26 @@
#nullable enable
using System;
namespace Avalonia.LogicalTree
{
/// <summary>
/// Event args for <see cref="IChildIndexProvider.ChildIndexChanged"/> event.
/// </summary>
public class ChildIndexChangedEventArgs : EventArgs
{
public ChildIndexChangedEventArgs()
{
}
public ChildIndexChangedEventArgs(ILogical child)
{
Child = child;
}
/// <summary>
/// Logical child which index was changed.
/// If null, all children should be reset.
/// </summary>
public ILogical? Child { get; }
}
}

32
src/Avalonia.Styling/LogicalTree/IChildIndexProvider.cs

@ -0,0 +1,32 @@
#nullable enable
using System;
namespace Avalonia.LogicalTree
{
/// <summary>
/// Child's index and total count information provider used by list-controls (ListBox, StackPanel, etc.)
/// </summary>
/// <remarks>
/// Used by nth-child and nth-last-child selectors.
/// </remarks>
public interface IChildIndexProvider
{
/// <summary>
/// Gets child's actual index in order of the original source.
/// </summary>
/// <param name="child">Logical child.</param>
/// <returns>Index or -1 if child was not found.</returns>
int GetChildIndex(ILogical child);
/// <summary>
/// Total children count or null if source is infinite.
/// Some Avalonia features might not work if <see cref="TryGetTotalCount"/> returns false, for instance: nth-last-child selector.
/// </summary>
bool TryGetTotalCount(out int count);
/// <summary>
/// Notifies subscriber when child's index or total count was changed.
/// </summary>
event EventHandler<ChildIndexChangedEventArgs>? ChildIndexChanged;
}
}

7
src/Avalonia.Styling/Properties/AssemblyInfo.cs

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

56
src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs

@ -0,0 +1,56 @@
#nullable enable
using Avalonia.LogicalTree;
namespace Avalonia.Styling.Activators
{
/// <summary>
/// An <see cref="IStyleActivator"/> which is active when control's index was changed.
/// </summary>
internal sealed class NthChildActivator : StyleActivatorBase
{
private readonly ILogical _control;
private readonly IChildIndexProvider _provider;
private readonly int _step;
private readonly int _offset;
private readonly bool _reversed;
public NthChildActivator(
ILogical control,
IChildIndexProvider provider,
int step, int offset, bool reversed)
{
_control = control;
_provider = provider;
_step = step;
_offset = offset;
_reversed = reversed;
}
protected override void Initialize()
{
PublishNext(IsMatching());
_provider.ChildIndexChanged += ChildIndexChanged;
}
protected override void Deinitialize()
{
_provider.ChildIndexChanged -= ChildIndexChanged;
}
private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
// Run matching again if:
// 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index.
// 2. e.Child is null, when all children indeces were changed.
// 3. Subscribed child index was changed.
if (_reversed
|| e.Child is null
|| e.Child == _control)
{
PublishNext(IsMatching());
}
}
private bool IsMatching() => NthChildSelector.Evaluate(_control, _provider, _step, _offset, _reversed).IsMatch;
}
}

145
src/Avalonia.Styling/Styling/NthChildSelector.cs

@ -0,0 +1,145 @@
#nullable enable
using System;
using System.Text;
using Avalonia.LogicalTree;
using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
/// <summary>
/// The :nth-child() pseudo-class matches elements based on their position in a group of siblings.
/// </summary>
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthChildSelector : Selector
{
private const string NthChildSelectorName = "nth-child";
private const string NthLastChildSelectorName = "nth-last-child";
private readonly Selector? _previous;
private readonly bool _reversed;
internal protected NthChildSelector(Selector? previous, int step, int offset, bool reversed)
{
_previous = previous;
Step = step;
Offset = offset;
_reversed = reversed;
}
/// <summary>
/// Creates an instance of <see cref="NthChildSelector"/>
/// </summary>
/// <param name="previous">Previous selector.</param>
/// <param name="step">Position step.</param>
/// <param name="offset">Initial index offset.</param>
public NthChildSelector(Selector? previous, int step, int offset)
: this(previous, step, offset, false)
{
}
public override bool InTemplate => _previous?.InTemplate ?? false;
public override bool IsCombinator => false;
public override Type? TargetType => _previous?.TargetType;
public int Step { get; }
public int Offset { get; }
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
if (!(control is ILogical logical))
{
return SelectorMatch.NeverThisType;
}
var controlParent = logical.LogicalParent;
if (controlParent is IChildIndexProvider childIndexProvider)
{
return subscribe
? new SelectorMatch(new NthChildActivator(logical, childIndexProvider, Step, Offset, _reversed))
: Evaluate(logical, childIndexProvider, Step, Offset, _reversed);
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
internal static SelectorMatch Evaluate(
ILogical logical, IChildIndexProvider childIndexProvider,
int step, int offset, bool reversed)
{
var index = childIndexProvider.GetChildIndex(logical);
if (index < 0)
{
return SelectorMatch.NeverThisInstance;
}
if (reversed)
{
if (childIndexProvider.TryGetTotalCount(out var totalCountValue))
{
index = totalCountValue - index;
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
else
{
// nth child index is 1-based
index += 1;
}
var n = Math.Sign(step);
var diff = index - offset;
var match = diff == 0 || (Math.Sign(diff) == n && diff % step == 0);
return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
}
protected override Selector? MovePrevious() => _previous;
public override string ToString()
{
var expectedCapacity = NthLastChildSelectorName.Length + 8;
var stringBuilder = new StringBuilder(_previous?.ToString(), expectedCapacity);
stringBuilder.Append(':');
stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName);
stringBuilder.Append('(');
var hasStep = false;
if (Step != 0)
{
hasStep = true;
stringBuilder.Append(Step);
stringBuilder.Append('n');
}
if (Offset > 0)
{
if (hasStep)
{
stringBuilder.Append('+');
}
stringBuilder.Append(Offset);
}
else if (Offset < 0)
{
stringBuilder.Append('-');
stringBuilder.Append(-Offset);
}
stringBuilder.Append(')');
return stringBuilder.ToString();
}
}
}

23
src/Avalonia.Styling/Styling/NthLastChildSelector.cs

@ -0,0 +1,23 @@
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
/// The :nth-child() pseudo-class matches elements based on their position among a group of siblings, counting from the end.
/// </summary>
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthLastChildSelector : NthChildSelector
{
/// <summary>
/// Creates an instance of <see cref="NthLastChildSelector"/>
/// </summary>
/// <param name="previous">Previous selector.</param>
/// <param name="step">Position step.</param>
/// <param name="offset">Initial index offset, counting from the end.</param>
public NthLastChildSelector(Selector? previous, int step, int offset) : base(previous, step, offset, true)
{
}
}
}

6
src/Avalonia.Styling/Styling/PropertySetterInstance.cs

@ -16,14 +16,14 @@ namespace Avalonia.Styling
private readonly IStyleable _target; private readonly IStyleable _target;
private readonly StyledPropertyBase<T>? _styledProperty; private readonly StyledPropertyBase<T>? _styledProperty;
private readonly DirectPropertyBase<T>? _directProperty; private readonly DirectPropertyBase<T>? _directProperty;
private readonly T _value; private readonly T? _value;
private IDisposable? _subscription; private IDisposable? _subscription;
private bool _isActive; private bool _isActive;
public PropertySetterInstance( public PropertySetterInstance(
IStyleable target, IStyleable target,
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
T value) T? value)
{ {
_target = target; _target = target;
_styledProperty = property; _styledProperty = property;
@ -57,7 +57,7 @@ namespace Avalonia.Styling
{ {
if (_styledProperty is object) if (_styledProperty is object)
{ {
_subscription = _target.SetValue(_styledProperty, _value, BindingPriority.Style); _subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style);
} }
else else
{ {

16
src/Avalonia.Styling/Styling/Selectors.cs

@ -123,6 +123,22 @@ namespace Avalonia.Styling
return new NotSelector(previous, argument); return new NotSelector(previous, argument);
} }
/// <inheritdoc cref="NthChildSelector"/>
/// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns>
public static Selector NthChild(this Selector previous, int step, int offset)
{
return new NthChildSelector(previous, step, offset);
}
/// <inheritdoc cref="NthLastChildSelector"/>
/// <inheritdoc cref="NthLastChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns>
public static Selector NthLastChild(this Selector previous, int step, int offset)
{
return new NthLastChildSelector(previous, step, offset);
}
/// <summary> /// <summary>
/// Returns a selector which matches a type. /// Returns a selector which matches a type.
/// </summary> /// </summary>

4
src/Avalonia.Styling/Styling/Setter.cs

@ -101,7 +101,7 @@ namespace Avalonia.Styling
data.result = new PropertySetterInstance<T>( data.result = new PropertySetterInstance<T>(
data.target, data.target,
property, property,
(T)data.value); (T?)data.value);
} }
} }
@ -128,7 +128,7 @@ namespace Avalonia.Styling
data.result = new PropertySetterInstance<T>( data.result = new PropertySetterInstance<T>(
data.target, data.target,
property, property,
(T)data.value); (T)data.value!);
} }
} }

3
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@ -21,7 +21,8 @@
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
IsLightDismissEnabled="True"> IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ListBox Name="PART_SelectingItemsControl" <ListBox Name="PART_SelectingItemsControl"
BorderThickness="0" BorderThickness="0"

1
src/Avalonia.Themes.Default/Button.xaml

@ -17,6 +17,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>

1
src/Avalonia.Themes.Default/CheckBox.xaml

@ -41,6 +41,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}"

3
src/Avalonia.Themes.Default/ComboBox.xaml

@ -69,7 +69,8 @@
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
IsLightDismissEnabled="True"> IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"> VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">

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

Loading…
Cancel
Save