diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
new file mode 100644
index 0000000000..6a7d53544e
--- /dev/null
+++ b/api/Avalonia.nupkg.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ CP0006
+ P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
\ No newline at end of file
diff --git a/build/ExternalConsumers.props b/build/ExternalConsumers.props
index d79e951330..12c747f3f0 100644
--- a/build/ExternalConsumers.props
+++ b/build/ExternalConsumers.props
@@ -1,6 +1,7 @@
+
diff --git a/build/SourceLink.props b/build/SourceLink.props
index dd7ecc8d2a..b0f1f2c2cc 100644
--- a/build/SourceLink.props
+++ b/build/SourceLink.props
@@ -1,9 +1,9 @@
true
- false
+ true
+ snupkg
true
- $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
@@ -14,10 +14,6 @@
true
-
- embedded
-
-
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index b954bdd4e3..1a4f979ce7 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -151,6 +151,14 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool {
if (Window != nullptr) {
+ auto frame = [Window frame];
+
+ AvnPoint point;
+ point.X = frame.origin.x;
+ point.Y = frame.origin.y + frame.size.height;
+
+ lastPositionSet = ConvertPointY(point);
+ hasPosition = true;
[Window orderOut:Window];
}
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index 9cc9fc9523..88cdf4d9de 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -2,6 +2,7 @@
#include "AvnString.h"
@interface AvnAppDelegate : NSObject
-(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events;
+-(void) releaseEvents;
@end
NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@@ -15,6 +16,11 @@ ComPtr _events;
return self;
}
+- (void)releaseEvents
+{
+ _events = nil;
+}
+
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy)
@@ -105,6 +111,18 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDeleg
}
}
+extern void ReleaseAvnAppEvents()
+{
+ NSApplication* app = [AvnApplication sharedApplication];
+ id delegate = [app delegate];
+ if ([delegate isMemberOfClass:[AvnAppDelegate class]])
+ {
+ AvnAppDelegate* avnDelegate = delegate;
+ [avnDelegate releaseEvents];
+ [app setDelegate:nil];
+ }
+}
+
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 5cf1b94a2f..672525c64a 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -38,6 +38,7 @@ extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
+extern void ReleaseAvnAppEvents();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r);
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 1c7e2cf25a..3fddb72529 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -197,6 +197,14 @@ class AvaloniaNative : public ComSingleObject();
var right = new List();
- var suppressionFile = Path.Combine(suppressionFilesFolder, GetPackageId(packagePath) + ".nupkg.xml");
+ var packageId = GetPackageId(packagePath);
+ var suppressionFile = Path.Combine(suppressionFilesFolder, packageId + ".nupkg.xml");
// Don't use Path.Combine with these left and right tool parameters.
// Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files.
@@ -57,7 +58,7 @@ public static class ApiDiffValidation
e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name);
if (targetDll.entry is null)
{
- throw new InvalidOperationException($"Some assemblies are missing in the new package: {baselineDll.entry.Name} for {baselineDll.target}");
+ throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}");
}
var targetDllPath = $"target/{targetDll.target}/{targetDll.entry.Name}";
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index b82c446249..bbfc28aa9f 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -288,7 +288,7 @@ partial class Build : NukeBuild
.Executes(async () =>
{
await Task.WhenAll(
- Directory.GetFiles(Parameters.NugetRoot).Select(nugetPackage => ApiDiffValidation.ValidatePackage(
+ Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffValidation.ValidatePackage(
ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
});
diff --git a/nukebuild/Numerge b/nukebuild/Numerge
index aef10ae67d..9738c6121f 160000
--- a/nukebuild/Numerge
+++ b/nukebuild/Numerge
@@ -1 +1 @@
-Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
+Subproject commit 9738c6121fdd143c78d3e25686a7e4e13c00f586
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 43453833d7..64eccbbb3f 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -8,8 +8,8 @@
1
net7.0
-
-
+
+
@@ -32,9 +32,9 @@
-
-
-
+
+
+
@@ -43,7 +43,5 @@
dirs.proj
-
-
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 33f22f4d02..6af35ff0d2 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -144,4 +144,30 @@
+
+
+ Build
+
+
+
+
+ http://127.0.0.1:6001
+ $(OutputPath)/$(AssemblyName).dll
+ MainWindow.axaml
+ $(APreviewExecutable)
+ $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.deps.json'))
+ $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.runtimeconfig.json'))
+ $([System.IO.Path]::GetFullPath('$(APreviewFile)'))
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Models/GDPdLengthConverter.cs b/samples/ControlCatalog/Models/GDPdLengthConverter.cs
new file mode 100644
index 0000000000..034e664305
--- /dev/null
+++ b/samples/ControlCatalog/Models/GDPdLengthConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Globalization;
+using Avalonia.Data.Converters;
+
+namespace ControlCatalog.Models;
+
+internal class GDPdLengthConverter : IValueConverter
+{
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is double d)
+ {
+ return new Avalonia.Controls.DataGridLength(d,Avalonia.Controls.DataGridLengthUnitType.Pixel,d,d);
+ }
+ else if (value is decimal d2)
+ {
+ var dv =System.Convert.ToDouble(d2);
+ return new Avalonia.Controls.DataGridLength(dv, Avalonia.Controls.DataGridLengthUnitType.Pixel, dv, dv);
+ }
+ return value;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is Avalonia.Controls.DataGridLength width)
+ {
+ return System.Convert.ToDecimal(width.DisplayValue);
+ }
+ return value;
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 356834832d..88252091c4 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -1,11 +1,14 @@
+
+
@@ -28,8 +31,18 @@
-
+
+
+
+
+
@@ -38,9 +51,11 @@
-
diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml
index f96abcac96..6929f192c7 100644
--- a/samples/Sandbox/MainWindow.axaml
+++ b/samples/Sandbox/MainWindow.axaml
@@ -1,11 +1,4 @@
-
-
-
-
-
-
-
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index ea1958efa6..7d5130cf5d 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -18,6 +18,8 @@ namespace Avalonia.Android
public bool IsActive { get; }
public InputMethodManager IMM { get; }
+
+ void OnBatchEditedEnded();
}
enum CustomImeFlags
@@ -103,6 +105,13 @@ namespace Avalonia.Android
}
private void _client_SelectionChanged(object sender, EventArgs e)
+ {
+ if (_inputConnection.IsInBatchEdit)
+ return;
+ OnSelectionChanged();
+ }
+
+ private void OnSelectionChanged()
{
var selection = Client.Selection;
@@ -113,17 +122,67 @@ namespace Avalonia.Android
private void _client_SurroundingTextChanged(object sender, EventArgs e)
{
+ if (_inputConnection.IsInBatchEdit)
+ return;
+ OnSurroundingTextChanged();
+ }
+
+ public void OnBatchEditedEnded()
+ {
+ if (_inputConnection.IsInBatchEdit)
+ return;
+
+ OnSurroundingTextChanged();
+ OnSelectionChanged();
+ }
+
+ private void OnSurroundingTextChanged()
+ {
+ if(_client is null)
+ {
+ return;
+ }
+
var surroundingText = _client.SurroundingText ?? "";
+ var editableText = _inputConnection.EditableWrapper.ToString();
- _inputConnection.EditableWrapper.IgnoreChange = true;
+ if (editableText != surroundingText)
+ {
+ _inputConnection.EditableWrapper.IgnoreChange = true;
- _inputConnection.Editable.Replace(0, _inputConnection.Editable.Length(), surroundingText);
+ var diff = GetDiff();
- _inputConnection.EditableWrapper.IgnoreChange = false;
+ _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff);
- var selection = Client.Selection;
+ _inputConnection.EditableWrapper.IgnoreChange = false;
- _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+ if(diff.index == 0)
+ {
+ var selection = _client.Selection;
+ _client.Selection = new TextSelection(selection.Start, 0);
+ _client.Selection = selection;
+ }
+ }
+
+ (int index, string diff) GetDiff()
+ {
+ int index = 0;
+
+ var longerLength = Math.Max(surroundingText.Length, editableText.Length);
+
+ for (int i = 0; i < longerLength; i++)
+ {
+ if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i])
+ {
+ index = i;
+ break;
+ }
+ }
+
+ var diffString = surroundingText.Substring(index, surroundingText.Length - index);
+
+ return (index, diffString);
+ }
}
public void SetCursorRect(Rect rect)
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 5d82d099c9..a6e8d10777 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
+using System.Threading;
using Android.App;
using Android.Content;
using Android.Graphics;
@@ -447,19 +448,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly AvaloniaInputConnection _inputConnection;
+ public event EventHandler SelectionChanged;
+
public EditableWrapper(AvaloniaInputConnection inputConnection)
{
_inputConnection = inputConnection;
}
+ public TextSelection CurrentSelection => new TextSelection(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this));
+ public TextSelection CurrentComposition => new TextSelection(BaseInputConnection.GetComposingSpanStart(this), BaseInputConnection.GetComposingSpanEnd(this));
+
public bool IgnoreChange { get; set; }
public override IEditable Replace(int start, int end, ICharSequence tb)
{
if (!IgnoreChange && start != end)
{
- var text = tb.SubSequence(0, tb.Length());
-
SelectSurroundingTextForDeletion(start, end);
}
@@ -470,8 +474,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
if (!IgnoreChange && start != end)
{
- var text = tb.SubSequence(tbstart, tbend);
-
SelectSurroundingTextForDeletion(start, end);
}
@@ -490,8 +492,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IAndroidInputMethod _inputMethod;
private readonly EditableWrapper _editable;
private bool _commitInProgress;
- private (int Start, int End)? _composingRegion;
- private TextSelection _selection;
+ private int _batchLevel = 0;
public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
@@ -510,63 +511,87 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public TopLevelImpl Toplevel => _toplevel;
+ public bool IsInBatchEdit => _batchLevel > 0;
+
public override bool SetComposingRegion(int start, int end)
{
- _composingRegion = new(start, end);
-
return base.SetComposingRegion(start, end);
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
- if(_composingRegion != null)
+ BeginBatchEdit();
+ _editable.IgnoreChange = true;
+
+ try
{
- // Select the composing region.
- InputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End);
- }
- var compositionText = text.SubSequence(0, text.Length());
+ if (_editable.CurrentComposition.Start > -1)
+ {
+ // Select the composing region.
+ InputMethod.Client.Selection = new TextSelection(_editable.CurrentComposition.Start, _editable.CurrentComposition.End);
+ }
+ var compositionText = text.SubSequence(0, text.Length());
- if (_inputMethod.IsActive && !_commitInProgress)
+ if (_inputMethod.IsActive && !_commitInProgress)
+ {
+ if (string.IsNullOrEmpty(compositionText))
+ _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+
+ else
+ _toplevel.TextInput(compositionText);
+ }
+ base.SetComposingText(text, newCursorPosition);
+ }
+ finally
{
- if (string.IsNullOrEmpty(compositionText))
- _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+ _editable.IgnoreChange = false;
- else
- _toplevel.TextInput(compositionText);
+ EndBatchEdit();
}
return true;
}
+ public override bool BeginBatchEdit()
+ {
+ _batchLevel = Interlocked.Increment(ref _batchLevel);
+ return base.BeginBatchEdit();
+ }
+
+ public override bool EndBatchEdit()
+ {
+ _batchLevel = Interlocked.Decrement(ref _batchLevel);
+
+ _inputMethod.OnBatchEditedEnded();
+ return base.EndBatchEdit();
+ }
+
public override bool CommitText(ICharSequence text, int newCursorPosition)
{
+ BeginBatchEdit();
_commitInProgress = true;
- var ret = base.CommitText(text, newCursorPosition);
+ var composingRegion = _editable.CurrentComposition;
- var committedText = text.SubSequence(0, text.Length());
+ var ret = base.CommitText(text, newCursorPosition);
- if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText))
+ if(composingRegion.Start != -1)
{
- if(_composingRegion != null)
- {
- _inputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End);
- }
+ InputMethod.Client.Selection = composingRegion;
+ }
- _toplevel.TextInput(committedText);
+ var committedText = text.SubSequence(0, text.Length());
- _composingRegion = null;
- }
+ if (_inputMethod.IsActive)
+ if (string.IsNullOrEmpty(committedText))
+ _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+ else
+ _toplevel.TextInput(committedText);
_commitInProgress = false;
+ EndBatchEdit();
- return ret;
- }
-
- public override bool FinishComposingText()
- {
- _composingRegion = null;
- return base.FinishComposingText();
+ return true;
}
public override bool DeleteSurroundingText(int beforeLength, int afterLength)
@@ -575,11 +600,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
EditableWrapper.IgnoreChange = true;
}
- var result = base.DeleteSurroundingText(beforeLength, afterLength);
if (InputMethod.IsActive)
{
- var selection = _selection;
+ var selection = _editable.CurrentSelection;
InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength);
@@ -588,13 +612,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
EditableWrapper.IgnoreChange = true;
}
- return result;
- }
-
- public override bool SetSelection(int start, int end)
- {
- _selection = new TextSelection(start, end);
- return base.SetSelection(start, end);
+ return true;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
@@ -630,7 +648,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return null;
}
- var selection = _selection;
+ var selection = _editable.CurrentSelection;
ExtractedText extract = new ExtractedText
{
diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
index b4a1f02f09..fb8e9488a7 100644
--- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
+++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
@@ -89,6 +89,7 @@ public class Rotate3DTransition: PageSlide
{
Easing = SlideInEasing,
Duration = Duration,
+ FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1),
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index ddc2b7effb..16eb09de65 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -43,21 +43,15 @@
-
-
-
-
-
-
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index b3f41eb420..833cd4b034 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -60,6 +60,8 @@ namespace Avalonia
}
///
+ /// The type of the values held by the .
+ /// The type of the value returned by the .
///
///
/// A method which is executed to convert each property value to .
@@ -71,6 +73,15 @@ namespace Avalonia
converter ?? throw new ArgumentNullException(nameof(converter)));
}
+ ///
+ public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property, Func