Browse Source

Merge branch 'master' into feature/ui-automation

ui-automation-test
Steven Kirk 5 years ago
parent
commit
bfbe425804
  1. 25
      Documentation/build.md
  2. 7
      native/Avalonia.Native/src/OSX/window.mm
  3. 2
      packages/Avalonia/AvaloniaBuildTasks.targets
  4. 6
      src/Avalonia.Animation/Animation.cs
  5. 12
      src/Avalonia.Animation/Animators/Animator`1.cs
  6. 3
      src/Avalonia.Animation/ApiCompatBaseline.txt
  7. 2
      src/Avalonia.Base/Data/BindingValue.cs
  8. 17
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  9. 7
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  10. 52
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  11. 6
      src/Avalonia.Controls/Application.cs
  12. 14
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  13. 6
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  14. 2
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  15. 8
      src/Avalonia.Controls/Primitives/Popup.cs
  16. 3
      src/Avalonia.Controls/Remote/RemoteServer.cs
  17. 10
      src/Avalonia.Controls/SplitView.cs
  18. 2
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  19. 2
      src/Avalonia.Controls/TrayIcon.cs
  20. 6
      src/Avalonia.Controls/WindowBase.cs
  21. 2
      src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
  22. 25
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  23. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  24. 6
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  25. 3
      src/Avalonia.Input/ApiCompatBaseline.txt
  26. 16
      src/Avalonia.Input/FocusManager.cs
  27. 6
      src/Avalonia.Input/ICommandSource.cs
  28. 8
      src/Avalonia.Input/IFocusManager.cs
  29. 2
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  30. 6
      src/Avalonia.Styling/Styling/PropertySetterInstance.cs
  31. 4
      src/Avalonia.Styling/Styling/Setter.cs
  32. 23
      src/Avalonia.Themes.Default/Expander.xaml
  33. 22
      src/Avalonia.Visuals/Matrix.cs
  34. 10
      src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
  35. 5
      src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs
  36. 60
      src/Avalonia.X11/ICELib.cs
  37. 133
      src/Avalonia.X11/SMLib.cs
  38. 17
      src/Avalonia.X11/X11Platform.cs
  39. 259
      src/Avalonia.X11/X11PlatformLifetimeEvents.cs
  40. 4
      src/Avalonia.X11/X11Window.Xim.cs
  41. 1
      src/Avalonia.X11/X11Window.cs
  42. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  43. 1
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  44. 2
      src/tools/MicroComGenerator/CSharpGen.Utils.cs
  45. 67
      tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
  46. 2
      tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
  47. 27
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  48. 72
      tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs

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

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

@ -219,7 +219,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;
@ -737,6 +741,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();

2
packages/Avalonia/AvaloniaBuildTasks.targets

@ -88,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'"
@ -107,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'"

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

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

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

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)

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--)
{ {

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

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

@ -94,8 +94,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);
@ -139,7 +139,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>
@ -311,7 +311,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);

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)

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;

2
src/Avalonia.Controls/TrayIcon.cs

@ -140,7 +140,7 @@ namespace Avalonia.Controls
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a /// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="TrayIcon"/>. /// <see cref="TrayIcon"/>.
/// </summary> /// </summary>
public object CommandParameter public object? CommandParameter
{ {
get { return GetValue(CommandParameterProperty); } get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); } set { SetValue(CommandParameterProperty, value); }

6
src/Avalonia.Controls/WindowBase.cs

@ -194,6 +194,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();

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

@ -17,16 +17,16 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlDetailsViewModel : ViewModelBase, IDisposable internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{ {
private readonly IVisual _control; private readonly IVisual _control;
private IDictionary<object, List<PropertyViewModel>> _propertyIndex; private IDictionary<object, List<PropertyViewModel>>? _propertyIndex;
private PropertyViewModel? _selectedProperty; private PropertyViewModel? _selectedProperty;
private DataGridCollectionView _propertiesView; 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 object? _selectedEntity;
private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new(); private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new();
private string _selectedEntityName; private string? _selectedEntityName;
private string _selectedEntityType; private string? _selectedEntityType;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{ {
@ -117,7 +117,7 @@ namespace Avalonia.Diagnostics.ViewModels
public TreePageViewModel TreePage { get; } public TreePageViewModel TreePage { get; }
public DataGridCollectionView PropertiesView public DataGridCollectionView? PropertiesView
{ {
get => _propertiesView; get => _propertiesView;
private set => RaiseAndSetIfChanged(ref _propertiesView, value); private set => RaiseAndSetIfChanged(ref _propertiesView, value);
@ -127,7 +127,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; } public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public object SelectedEntity public object? SelectedEntity
{ {
get => _selectedEntity; get => _selectedEntity;
set set
@ -137,7 +137,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public string SelectedEntityName public string? SelectedEntityName
{ {
get => _selectedEntityName; get => _selectedEntityName;
set set
@ -147,7 +147,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public string SelectedEntityType public string? SelectedEntityType
{ {
get => _selectedEntityType; get => _selectedEntityType;
set set
@ -270,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)
{ {
@ -284,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)
@ -402,7 +403,7 @@ namespace Avalonia.Diagnostics.ViewModels
var selectedProperty = SelectedProperty; var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity; var selectedEntity = SelectedEntity;
var selectedEntityName = SelectedEntityName; var selectedEntityName = SelectedEntityName;
if (selectedProperty == null) if (selectedEntity == null || selectedProperty == null)
return; return;
object? property; object? property;
@ -419,7 +420,7 @@ namespace Avalonia.Diagnostics.ViewModels
?.GetValue(selectedEntity); ?.GetValue(selectedEntity);
} }
if (property == null) return; if (property == null) return;
_selectedEntitiesStack.Push((Name:selectedEntityName,Entry:selectedEntity)); _selectedEntitiesStack.Push((Name:selectedEntityName!,Entry:selectedEntity));
NavigateToProperty(property, selectedProperty.Name); NavigateToProperty(property, selectedProperty.Name);
} }

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

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)
{ {

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>

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

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)
{ {

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
{ {

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

23
src/Avalonia.Themes.Default/Expander.xaml

@ -15,7 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Row="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> <ToggleButton Name="PART_toggle" Grid.Row="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1" Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
@ -32,9 +32,12 @@
<Style Selector="Expander[ExpandDirection=Up]"> <Style Selector="Expander[ExpandDirection=Up]">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> <ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0" Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
@ -51,9 +54,12 @@
<Style Selector="Expander[ExpandDirection=Right]"> <Style Selector="Expander[ExpandDirection=Right]">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*"> <Grid ColumnDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Column="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> <ToggleButton Name="PART_toggle" Grid.Column="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1" Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
@ -70,9 +76,12 @@
<Style Selector="Expander[ExpandDirection=Left]"> <Style Selector="Expander[ExpandDirection=Left]">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> <ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0" Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"

22
src/Avalonia.Visuals/Matrix.cs

@ -215,6 +215,28 @@ namespace Avalonia
return angle * 0.0174532925; return angle * 0.0174532925;
} }
/// <summary>
/// Appends another matrix as post-multiplication operation.
/// Equivalent to this * value;
/// </summary>
/// <param name="value">A matrix.</param>
/// <returns>Post-multiplied matrix.</returns>
public Matrix Append(Matrix value)
{
return this * value;
}
/// <summary>
/// Prpends another matrix as pre-multiplication operation.
/// Equivalent to value * this;
/// </summary>
/// <param name="value">A matrix.</param>
/// <returns>Pre-multiplied matrix.</returns>
public Matrix Prepend(Matrix value)
{
return value * this;
}
/// <summary> /// <summary>
/// Calculates the determinant for this matrix. /// Calculates the determinant for this matrix.
/// </summary> /// </summary>

10
src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs

@ -18,11 +18,11 @@ namespace Avalonia.Media.Transformation
public static Matrix ComposeTransform(Matrix.Decomposed decomposed) public static Matrix ComposeTransform(Matrix.Decomposed decomposed)
{ {
// According to https://www.w3.org/TR/css-transforms-1/#recomposing-to-a-2d-matrix // According to https://www.w3.org/TR/css-transforms-1/#recomposing-to-a-2d-matrix
return Matrix.Identity
return Matrix.CreateTranslation(decomposed.Translate) * .Prepend(Matrix.CreateTranslation(decomposed.Translate))
Matrix.CreateRotation(decomposed.Angle) * .Prepend(Matrix.CreateRotation(decomposed.Angle))
Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y) * .Prepend(Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y))
Matrix.CreateScale(decomposed.Scale); .Prepend(Matrix.CreateScale(decomposed.Scale));
} }
public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress) public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress)

5
src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs

@ -86,6 +86,8 @@ namespace Avalonia.Media.Transformation
if (fromIdentity && toIdentity) if (fromIdentity && toIdentity)
{ {
result.Matrix = Matrix.Identity;
return true; return true;
} }
@ -179,7 +181,8 @@ namespace Avalonia.Media.Transformation
} }
case OperationType.Identity: case OperationType.Identity:
{ {
// Do nothing. result.Matrix = Matrix.Identity;
break; break;
} }
} }

60
src/Avalonia.X11/ICELib.cs

@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.X11
{
internal static class ICELib
{
private const string LibIce = "libICE.so.6";
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern int IceAddConnectionWatch(
IntPtr watchProc,
IntPtr clientData
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern void IceRemoveConnectionWatch(
IntPtr watchProc,
IntPtr clientData
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IceProcessMessagesStatus IceProcessMessages(
IntPtr iceConn,
out IntPtr replyWait,
out bool replyReadyRet
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr IceSetErrorHandler(
IntPtr handler
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr IceSetIOErrorHandler(
IntPtr handler
);
public enum IceProcessMessagesStatus
{
IceProcessMessagesIoError = 1
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceErrorHandler(
IntPtr iceConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
int errorClass,
int severity,
IntPtr values
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceIOErrorHandler(
IntPtr iceConn
);
}
}

133
src/Avalonia.X11/SMLib.cs

@ -0,0 +1,133 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.X11
{
internal static unsafe class SMLib
{
private const string LibSm = "libSM.so.6";
[DllImport(LibSm, CharSet = CharSet.Ansi)]
public static extern IntPtr SmcOpenConnection(
[MarshalAs(UnmanagedType.LPWStr)] string networkId,
IntPtr content,
int xsmpMajorRev,
int xsmpMinorRev,
ulong mask,
ref SmcCallbacks callbacks,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string previousId,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string clientIdRet,
int errorLength,
[Out] char[] errorStringRet);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern int SmcCloseConnection(
IntPtr smcConn,
int count,
string[] reasonMsgs
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern void SmcSaveYourselfDone(
IntPtr smcConn,
bool success
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern int SmcInteractRequest(
IntPtr smcConn,
SmDialogValue dialogType,
IntPtr interactProc,
IntPtr clientData
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern void SmcInteractDone(
IntPtr smcConn,
bool success
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SmcGetIceConnection(
IntPtr smcConn
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SmcSetErrorHandler(
IntPtr handler
);
public enum SmDialogValue
{
SmDialogError = 0
}
[StructLayout(LayoutKind.Sequential)]
public struct SmcCallbacks
{
public IntPtr SaveYourself;
private readonly IntPtr Unused0;
public IntPtr Die;
private readonly IntPtr Unused1;
public IntPtr SaveComplete;
private readonly IntPtr Unused2;
public IntPtr ShutdownCancelled;
private readonly IntPtr Unused3;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceWatchProc(
IntPtr iceConn,
IntPtr clientData,
bool opening,
IntPtr* watchData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcDieProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcInteractProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcSaveCompleteProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcSaveYourselfProc(
IntPtr smcConn,
IntPtr clientData,
int saveType,
bool shutdown,
int interactStyle,
bool fast
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcShutdownCancelledProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcErrorHandler(
IntPtr smcConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
int errorClass,
int severity,
IntPtr values
);
}
}

17
src/Avalonia.X11/X11Platform.cs

@ -80,7 +80,8 @@ namespace Avalonia.X11
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub()) .Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info)) .Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog()) .Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider()); .Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents(this));
X11Screens = Avalonia.X11.X11Screens.Init(this); X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens); Screens = new X11Screens(X11Screens);
@ -230,7 +231,19 @@ namespace Avalonia
/// on their input devices by using sequences of characters or mouse operations that are natively available on their input devices. /// on their input devices by using sequences of characters or mouse operations that are natively available on their input devices.
/// </remarks> /// </remarks>
public bool? EnableIme { get; set; } public bool? EnableIme { get; set; }
/// <summary>
/// Determines whether to enable support for the
/// X Session Management Protocol.
/// </summary>
/// <remarks>
/// X Session Management Protocol is a standard implemented on most
/// Linux systems that uses Xorg. This enables apps to control how they
/// can control and/or cancel the pending shutdown requested by the user.
/// </remarks>
public bool EnableSessionManagement { get; set; } =
Environment.GetEnvironmentVariable("AVALONIA_X11_USE_SESSION_MANAGEMENT") != "0";
public IList<GlVersion> GlProfiles { get; set; } = new List<GlVersion> public IList<GlVersion> GlProfiles { get; set; } = new List<GlVersion>
{ {
new GlVersion(GlProfileType.OpenGL, 4, 0), new GlVersion(GlProfileType.OpenGL, 4, 0),

259
src/Avalonia.X11/X11PlatformLifetimeEvents.cs

@ -0,0 +1,259 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Logging;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.X11
{
internal unsafe class X11PlatformLifetimeEvents : IDisposable, IPlatformLifetimeEventsImpl
{
private readonly AvaloniaX11Platform _platform;
private const ulong SmcSaveYourselfProcMask = 1L;
private const ulong SmcDieProcMask = 2L;
private const ulong SmcSaveCompleteProcMask = 4L;
private const ulong SmcShutdownCancelledProcMask = 8L;
private static readonly ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents> s_nativeToManagedMapper =
new ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents>();
private static readonly SMLib.SmcSaveYourselfProc s_saveYourselfProcDelegate = SmcSaveYourselfHandler;
private static readonly SMLib.SmcDieProc s_dieDelegate = SmcDieHandler;
private static readonly SMLib.SmcShutdownCancelledProc
s_shutdownCancelledDelegate = SmcShutdownCancelledHandler;
private static readonly SMLib.SmcSaveCompleteProc s_saveCompleteDelegate = SmcSaveCompleteHandler;
private static readonly SMLib.SmcInteractProc s_smcInteractDelegate = StaticInteractHandler;
private static readonly SMLib.SmcErrorHandler s_smcErrorHandlerDelegate = StaticErrorHandler;
private static readonly ICELib.IceErrorHandler s_iceErrorHandlerDelegate = StaticErrorHandler;
private static readonly ICELib.IceIOErrorHandler s_iceIoErrorHandlerDelegate = StaticIceIOErrorHandler;
private static readonly SMLib.IceWatchProc s_iceWatchProcDelegate = IceWatchHandler;
private static SMLib.SmcCallbacks s_callbacks = new SMLib.SmcCallbacks()
{
ShutdownCancelled = Marshal.GetFunctionPointerForDelegate(s_shutdownCancelledDelegate),
Die = Marshal.GetFunctionPointerForDelegate(s_dieDelegate),
SaveYourself = Marshal.GetFunctionPointerForDelegate(s_saveYourselfProcDelegate),
SaveComplete = Marshal.GetFunctionPointerForDelegate(s_saveCompleteDelegate)
};
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly IntPtr _currentIceConn;
private readonly IntPtr _currentSmcConn;
private bool _saveYourselfPhase;
internal X11PlatformLifetimeEvents(AvaloniaX11Platform platform)
{
_platform = platform;
if (ICELib.IceAddConnectionWatch(
Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
IntPtr.Zero) == 0)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib was unable to add an ICE connection watcher.");
return;
}
var errorBuf = new char[255];
var smcConn = SMLib.SmcOpenConnection(null!,
IntPtr.Zero, 1, 0,
SmcSaveYourselfProcMask |
SmcSaveCompleteProcMask |
SmcShutdownCancelledProcMask |
SmcDieProcMask,
ref s_callbacks,
out _,
out _,
errorBuf.Length,
errorBuf);
if (smcConn == IntPtr.Zero)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
$"SMLib/ICELib reported a new error: {new string(errorBuf)}");
return;
}
if (!s_nativeToManagedMapper.TryAdd(smcConn, this))
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib was unable to add this instance to the native to managed map.");
return;
}
_ = SMLib.SmcSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_smcErrorHandlerDelegate));
_ = ICELib.IceSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceErrorHandlerDelegate));
_ = ICELib.IceSetIOErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceIoErrorHandlerDelegate));
_currentSmcConn = smcConn;
_currentIceConn = SMLib.SmcGetIceConnection(smcConn);
Task.Run(() =>
{
var token = _cancellationTokenSource.Token;
while (!token.IsCancellationRequested) HandleRequests();
}, _cancellationTokenSource.Token);
}
public void Dispose()
{
if (_currentSmcConn == IntPtr.Zero) return;
s_nativeToManagedMapper.TryRemove(_currentSmcConn, out _);
_ = SMLib.SmcCloseConnection(_currentSmcConn, 1,
new[] { $"{nameof(X11PlatformLifetimeEvents)} was disposed in managed code." });
}
private static void SmcSaveCompleteHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.SaveCompleteHandler();
}
private static X11PlatformLifetimeEvents? GetInstance(IntPtr smcConn)
{
return s_nativeToManagedMapper.TryGetValue(smcConn, out var instance) ? instance : null;
}
private static void SmcShutdownCancelledHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.ShutdownCancelledHandler();
}
private static void SmcDieHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.DieHandler();
}
private static void SmcSaveYourselfHandler(IntPtr smcConn, IntPtr clientData, int saveType,
bool shutdown, int interactStyle, bool fast)
{
GetInstance(smcConn)?.SaveYourselfHandler(smcConn, clientData, shutdown, fast);
}
private static void StaticInteractHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.InteractHandler(smcConn);
}
private static void StaticIceIOErrorHandler(IntPtr iceConn)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(null,
"ICELib reported an unknown IO Error.");
}
private static void StaticErrorHandler(IntPtr smcConn, bool swap, int offendingMinorOpcode,
ulong offendingSequence, int errorClass, int severity, IntPtr values)
{
GetInstance(smcConn)
?.ErrorHandler(swap, offendingMinorOpcode, offendingSequence, errorClass, severity, values);
}
// ReSharper disable UnusedParameter.Local
private void ErrorHandler(bool swap, int offendingMinorOpcode, ulong offendingSequence, int errorClass,
int severity, IntPtr values)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib reported an error:" +
$" severity {severity:X}" +
$" mOpcode {offendingMinorOpcode:X}" +
$" mSeq {offendingSequence:X}" +
$" errClass {errorClass:X}.");
}
private void HandleRequests()
{
if (ICELib.IceProcessMessages(_currentIceConn, out _, out _) ==
ICELib.IceProcessMessagesStatus.IceProcessMessagesIoError)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib lost its underlying ICE connection.");
Dispose();
}
}
private void SaveCompleteHandler()
{
_saveYourselfPhase = false;
}
private void ShutdownCancelledHandler()
{
if (_saveYourselfPhase)
SMLib.SmcSaveYourselfDone(_currentSmcConn, true);
_saveYourselfPhase = false;
}
private void DieHandler()
{
Dispose();
}
private void SaveYourselfHandler(IntPtr smcConn, IntPtr clientData, bool shutdown, bool fast)
{
if (_saveYourselfPhase)
{
SMLib.SmcSaveYourselfDone(smcConn, true);
}
_saveYourselfPhase = true;
if (shutdown && !fast)
{
var _ = SMLib.SmcInteractRequest(smcConn, SMLib.SmDialogValue.SmDialogError,
Marshal.GetFunctionPointerForDelegate(s_smcInteractDelegate),
clientData);
}
else
{
SMLib.SmcSaveYourselfDone(smcConn, true);
_saveYourselfPhase = false;
}
}
private void InteractHandler(IntPtr smcConn)
{
Dispatcher.UIThread.Post(() => ActualInteractHandler(smcConn));
}
private void ActualInteractHandler(IntPtr smcConn)
{
var e = new ShutdownRequestedEventArgs();
if (_platform.Options?.EnableSessionManagement ?? false)
{
ShutdownRequested?.Invoke(this, e);
}
SMLib.SmcInteractDone(smcConn, e.Cancel);
if (e.Cancel)
{
return;
}
_saveYourselfPhase = false;
SMLib.SmcSaveYourselfDone(smcConn, true);
}
private static void IceWatchHandler(IntPtr iceConn, IntPtr clientData, bool opening, IntPtr* watchData)
{
if (!opening) return;
ICELib.IceRemoveConnectionWatch(Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
IntPtr.Zero);
}
public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
}
}

4
src/Avalonia.X11/X11Window.Xim.cs

@ -112,8 +112,8 @@ namespace Avalonia.X11
public ValueTask<bool> HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode) => public ValueTask<bool> HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode) =>
new ValueTask<bool>(false); new ValueTask<bool>(false);
public event Action<string> Commit; public event Action<string> Commit { add { } remove { } }
public event Action<X11InputMethodForwardedKey> ForwardKey; public event Action<X11InputMethodForwardedKey> ForwardKey { add { } remove { } }
} }

1
src/Avalonia.X11/X11Window.cs

@ -1027,6 +1027,7 @@ namespace Avalonia.X11
if (string.IsNullOrEmpty(title)) if (string.IsNullOrEmpty(title))
{ {
XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME); XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME);
XDeleteProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_NAME);
} }
else else
{ {

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Converters; using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
@ -33,6 +34,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
_ => null, _ => null,
}; };
if (provideTarget.TargetObject is Setter setter)
{
targetType = setter.Property.PropertyType;
}
// Look upwards though the ambient context for IResourceHosts and IResourceProviders // Look upwards though the ambient context for IResourceHosts and IResourceProviders
// which might be able to give us the resource. // which might be able to give us the resource.
foreach (var e in stack.Parents) foreach (var e in stack.Parents)

1
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -199,7 +199,6 @@ namespace Avalonia.Win32
case WindowsMessage.WM_MBUTTONUP: case WindowsMessage.WM_MBUTTONUP:
case WindowsMessage.WM_XBUTTONUP: case WindowsMessage.WM_XBUTTONUP:
{ {
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage()) if (ShouldIgnoreTouchEmulatedMessage())
{ {
break; break;

2
src/tools/MicroComGenerator/CSharpGen.Utils.cs

@ -40,7 +40,7 @@ namespace MicroComGenerator
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken); SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
static VariableDeclarationSyntax DeclareVar(string type, string name, static VariableDeclarationSyntax DeclareVar(string type, string name,
ExpressionSyntax? initializer = null) ExpressionSyntax initializer = null)
=> VariableDeclaration(ParseTypeName(type), => VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(VariableDeclarator(name) SingletonSeparatedList(VariableDeclarator(name)
.WithInitializer(initializer == null ? null : EqualsValueClause(initializer)))); .WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));

67
tests/Avalonia.Animation.UnitTests/AnimatableTests.cs

@ -1,5 +1,7 @@
using System; using System;
using Avalonia.Animation.Animators;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
@ -100,6 +102,71 @@ namespace Avalonia.Animation.UnitTests
Times.Never); Times.Never);
} }
[Theory]
[InlineData(null)] //null value
[InlineData("stringValue")] //string value
public void Invalid_Values_In_Animation_Should_Not_Crash_Animations(object invalidValue)
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, 1d),
},
KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, 2d),
},
KeyTime = TimeSpan.FromSeconds(2),
};
var keyframe3 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, invalidValue),
},
KeyTime = TimeSpan.FromSeconds(3),
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(3),
Children =
{
keyframe1,
keyframe2,
keyframe3
},
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate,
};
var rect = new Rectangle()
{
Width = 11,
};
var originalValue = rect.Width;
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(rect.Width, 1);
clock.Step(TimeSpan.FromSeconds(2));
Assert.Equal(rect.Width, 2);
clock.Step(TimeSpan.FromSeconds(3));
//here we have invalid value so value should be expected and set to initial original value
Assert.Equal(rect.Width, originalValue);
}
[Fact] [Fact]
public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present() public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present()
{ {

2
tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string> private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string>
{ {
public event NotifyCollectionChangedEventHandler CollectionChanged; public event NotifyCollectionChangedEventHandler CollectionChanged { add { } remove { } }
public IEnumerator<string> GetEnumerator() public IEnumerator<string> GetEnumerator()
{ {

27
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -512,6 +512,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var brush = (ISolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
[Fact]
public void Automatically_Converts_Color_To_SolidColorBrush_From_Setter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<Color x:Key='color'>#ff506070</Color>
</Window.Resources>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='Background' Value='{StaticResource color}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
private IDisposable StyledWindow(params (string, string)[] assets) private IDisposable StyledWindow(params (string, string)[] assets)
{ {

72
tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs

@ -129,7 +129,7 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Single(operations); Assert.Single(operations);
Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type); Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type);
var expectedMatrix = new Matrix(1, 2, 3, 4, 5, 6); var expectedMatrix = new Matrix(1, 2, 3, 4, 5, 6);
Assert.Equal(expectedMatrix, operations[0].Matrix); Assert.Equal(expectedMatrix, operations[0].Matrix);
@ -195,7 +195,7 @@ namespace Avalonia.Visuals.UnitTests.Media
[Theory] [Theory]
[InlineData(0d, 10d)] [InlineData(0d, 10d)]
[InlineData(0.5d, 15d)] [InlineData(0.5d, 15d)]
[InlineData(1d,20d)] [InlineData(1d, 20d)]
public void Can_Interpolate_Rotation(double progress, double angle) public void Can_Interpolate_Rotation(double progress, double angle)
{ {
var from = TransformOperations.Parse("rotate(10deg)"); var from = TransformOperations.Parse("rotate(10deg)");
@ -225,5 +225,73 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Single(operations); Assert.Single(operations);
Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type); Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type);
} }
[Fact]
public void Order_Of_Operations_Is_Preserved_No_Prefix()
{
var from = TransformOperations.Parse("scale(1)");
var to = TransformOperations.Parse("translate(50px,50px) scale(0.5,0.5)");
var interpolated_0 = TransformOperations.Interpolate(from, to, 0);
Assert.True(interpolated_0.IsIdentity);
var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5);
AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 12.5, translateY: 12.5);
var interpolated_100 = TransformOperations.Interpolate(from, to, 1);
AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 25, translateY: 25);
}
[Fact]
public void Order_Of_Operations_Is_Preserved_One_Prefix()
{
var from = TransformOperations.Parse("scale(1)");
var to = TransformOperations.Parse("scale(0.5,0.5) translate(50px,50px)");
var interpolated_0 = TransformOperations.Interpolate(from, to, 0);
Assert.True(interpolated_0.IsIdentity);
var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5);
AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 25.0, translateY: 25);
var interpolated_100 = TransformOperations.Interpolate(from, to, 1);
AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 50, translateY: 50);
}
private static void AssertMatrix(Matrix matrix, double? angle = null, double? scaleX = null, double? scaleY = null, double? translateX = null, double? translateY = null)
{
Assert.True(Matrix.TryDecomposeTransform(matrix, out var composed));
if (angle.HasValue)
{
Assert.Equal(angle.Value, composed.Angle);
}
if (scaleX.HasValue)
{
Assert.Equal(scaleX.Value, composed.Scale.X);
}
if (scaleY.HasValue)
{
Assert.Equal(scaleY.Value, composed.Scale.Y);
}
if (translateX.HasValue)
{
Assert.Equal(translateX.Value, composed.Translate.X);
}
if (translateY.HasValue)
{
Assert.Equal(translateY.Value, composed.Translate.Y);
}
}
} }
} }

Loading…
Cancel
Save