Browse Source

Merge branch 'master' into fixes/unPositionedGlyphRuns

pull/9375/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
9df3f3f3da
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject
  2. 5
      .ncrunch/Avalonia.Web.v3.ncrunchproject
  3. 6
      .ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
  4. 6
      .ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
  5. 5
      .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject
  6. 5
      .ncrunch/MobileSandbox.Android.v3.ncrunchproject
  7. 5
      .ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
  8. 5
      .ncrunch/MobileSandbox.iOS.v3.ncrunchproject
  9. 5
      .ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
  10. 5
      .ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
  11. 5
      .ncrunch/_build.v3.ncrunchproject
  12. 1
      Avalonia.sln
  13. 17
      samples/IntegrationTestApp/MainWindow.axaml
  14. 34
      samples/IntegrationTestApp/MainWindow.axaml.cs
  15. 1
      samples/MobileSandbox/MainView.xaml
  16. 46
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  17. 14
      src/Android/Avalonia.Android/AndroidPlatform.cs
  18. 92
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  19. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  20. 25
      src/Avalonia.Base/Input/Gestures.cs
  21. 6
      src/Avalonia.Base/Input/MouseDevice.cs
  22. 5
      src/Avalonia.Base/Input/PenDevice.cs
  23. 1
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  24. 7
      src/Avalonia.Base/Input/TouchDevice.cs
  25. 2
      src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
  26. 30
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  27. 22
      src/Avalonia.Base/Platform/IPlatformSettings.cs
  28. 8
      src/Avalonia.Controls/Automation/AutomationProperties.cs
  29. 14
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  30. 16
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  31. 29
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  32. 2
      src/Avalonia.Controls/TextBox.cs
  33. 11
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  34. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  35. 10
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  36. 14
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  37. 6
      src/Avalonia.Native/DoubleClickHelper.cs
  38. 6
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  39. 6
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  40. 17
      src/Avalonia.X11/Stubs.cs
  41. 2
      src/Avalonia.X11/X11Platform.cs
  42. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  43. 9
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  44. 2
      src/Web/Avalonia.Web/Avalonia.Web.targets
  45. 11
      src/Web/Avalonia.Web/WindowingPlatform.cs
  46. 31
      src/Windows/Avalonia.Win32/Win32Platform.cs
  47. 5
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  48. 17
      src/iOS/Avalonia.iOS/Platform.cs
  49. 4
      tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
  50. 175
      tests/Avalonia.IntegrationTests.Appium/GestureTests.cs

5
.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Web.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

6
.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject

@ -1,7 +1,3 @@
<ProjectConfiguration>
<Settings>
<AdditionalFilesToIncludeForProject>
<Value>..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*</Value>
</AdditionalFilesToIncludeForProject>
</Settings>
<Settings />
</ProjectConfiguration>

6
.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject

@ -1,7 +1,3 @@
<ProjectConfiguration>
<Settings>
<AdditionalFilesToIncludeForProject>
<Value>..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*</Value>
</AdditionalFilesToIncludeForProject>
</Settings>
<Settings />
</ProjectConfiguration>

5
.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.Android.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.iOS.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/_build.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

1
Avalonia.sln

@ -522,6 +522,7 @@ Global
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.Build.0 = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.Build.0 = Release|Any CPU
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

17
samples/IntegrationTestApp/MainWindow.axaml

@ -69,6 +69,23 @@
</StackPanel>
</TabItem>
<TabItem Header="Gestures">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Name="ResetGestures" DockPanel.Dock="Right">Reset</Button>
<TextBlock Name="LastGesture" />
</DockPanel>
<Panel>
<Border Name="GestureBorder" Background="Blue"
AutomationProperties.AccessibilityView="Content"
AutomationProperties.ControlTypeOverride="Image"/>
<Border Name="GestureBorder2" Background="Green" IsVisible="False"
AutomationProperties.AccessibilityView="Content"
AutomationProperties.ControlTypeOverride="Image"/>
</Panel>
</DockPanel>
</TabItem>
<TabItem Header="ListBox">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom">

34
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
@ -17,6 +18,7 @@ namespace IntegrationTestApp
{
InitializeComponent();
InitializeViewMenu();
InitializeGesturesTab();
this.AttachDevTools();
AddHandler(Button.ClickEvent, OnButtonClick);
ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
@ -120,6 +122,38 @@ namespace IntegrationTestApp
}
}
private void InitializeGesturesTab()
{
var gestureBorder = this.GetControl<Border>("GestureBorder");
var gestureBorder2 = this.GetControl<Border>("GestureBorder2");
var lastGesture = this.GetControl<TextBlock>("LastGesture");
var resetGestures = this.GetControl<Button>("ResetGestures");
gestureBorder.Tapped += (s, e) => lastGesture.Text = "Tapped";
gestureBorder.DoubleTapped += (s, e) =>
{
lastGesture.Text = "DoubleTapped";
// Testing #8733
gestureBorder.IsVisible = false;
gestureBorder2.IsVisible = true;
};
gestureBorder2.DoubleTapped += (s, e) =>
{
lastGesture.Text = "DoubleTapped2";
};
Gestures.AddRightTappedHandler(gestureBorder, (s, e) => lastGesture.Text = "RightTapped");
resetGestures.Click += (s, e) =>
{
lastGesture.Text = string.Empty;
gestureBorder.IsVisible = true;
gestureBorder2.IsVisible = false;
};
}
private void MenuClicked(object? sender, RoutedEventArgs e)
{
var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");

1
samples/MobileSandbox/MainView.xaml

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="100 50" Spacing="50">
<TextBlock Text="Login" Foreground="White" />
<TextBox Watermark="Text" />
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" AcceptsReturn="True" TextInputOptions.ReturnKeyType="Search" />
<TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" />

46
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -1,6 +1,7 @@
using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
@ -62,22 +63,21 @@ namespace Avalonia.Android
public void Reset()
{
_imm.RestartInput(_host);
}
public void SetClient(ITextInputMethodClient client)
{
if(client is null)
{
_inputConnection?.SetComposingText("", 0);
}
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
}
Reset();
if(_inputConnection != null)
{
_inputConnection.ComposingText = null;
_inputConnection.ComposingRegion = default;
}
_client = client;
@ -87,23 +87,31 @@ namespace Avalonia.Android
_host.RequestFocus();
_imm.RestartInput(View);
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
}
else
{
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None);
var surroundingText = Client.SurroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
private void SurroundingTextChanged(object sender, EventArgs e)
{
if (IsActive)
if (IsActive && _inputConnection != null)
{
var surroundingText = Client.SurroundingText;
_inputConnection.SurroundingText = surroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
{
_inputConnection.CommitText(_inputConnection.ComposingText, 0);
_inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
}
@ -153,10 +161,20 @@ namespace Avalonia.Android
return _inputConnection;
});
}
}
private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e)
public readonly struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
public ComposingRegion(int start, int end)
{
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
_start = start;
_end = end;
}
public int Start => _start;
public int End => _end;
}
}

14
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -26,21 +26,11 @@ namespace Avalonia
namespace Avalonia.Android
{
class AndroidPlatform : IPlatformSettings
class AndroidPlatform
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(4, 4);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
public Size DoubleClickSize => TouchDoubleClickSize;
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
internal static Compositor Compositor { get; private set; }
public static void Initialize()
@ -52,7 +42,7 @@ namespace Avalonia.Android
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())

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

@ -3,7 +3,9 @@ using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL;
@ -23,6 +25,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -276,8 +279,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
throw new NotImplementedException();
}
}
internal class AvaloniaInputConnection : BaseInputConnection
@ -293,11 +294,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public TextInputMethodSurroundingText SurroundingText { get; set; }
public string ComposingText { get; private set; }
public string ComposingText { get; internal set; }
public ComposingRegion ComposingRegion { get; private set; }
public ComposingRegion? ComposingRegion { get; internal set; }
public bool IsComposing { get; private set; }
public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
public bool IsCommiting { get; private set; }
public override bool SetComposingRegion(int start, int end)
{
@ -314,8 +316,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
ComposingText = composingText;
IsComposing = true;
_inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition);
@ -323,20 +323,25 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public override bool FinishComposingText()
{
IsComposing = false;
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
if (!string.IsNullOrEmpty(ComposingText))
{
CommitText(ComposingText, ComposingText.Length);
}
else
{
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
}
return base.FinishComposingText();
}
public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
{
if (!string.IsNullOrEmpty(SurroundingText.Text))
if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
{
var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
var end = System.Math.Min(start + length, SurroundingText.CursorOffset);
var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
var text = SurroundingText.Text.Substring(start, end - start);
@ -368,11 +373,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public override bool CommitText(ICharSequence text, int newCursorPosition)
{
IsCommiting = true;
var committedText = text.ToString();
_inputMethod.Client.SetPreeditText(null);
_inputMethod.Client.SelectInSurroundingText(ComposingRegion.Start, ComposingRegion.End);
int? start, end;
if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
{
start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
}
else if (ComposingRegion != null)
{
start = ComposingRegion?.Start;
end = ComposingRegion?.End;
ComposingRegion = null;
}
else
{
start = end = _inputMethod.Client.SurroundingText.CursorOffset;
}
_inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
var time = DateTime.Now.TimeOfDay;
@ -380,15 +405,29 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_topLevel.Input(rawTextEvent);
ComposingText = null;
ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
return base.CommitText(text, newCursorPosition);
}
public override bool DeleteSurroundingText(int beforeLength, int afterLength)
{
_inputMethod.Client.SelectInSurroundingText(beforeLength, afterLength);
var surroundingText = _inputMethod.Client.SurroundingText;
var selectionStart = surroundingText.CursorOffset;
_inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
surroundingText = _inputMethod.Client.SurroundingText;
selectionStart = surroundingText.CursorOffset;
ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
return base.DeleteSurroundingText(beforeLength, afterLength);
}
@ -396,22 +435,23 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
_inputMethod.Client.SelectInSurroundingText(start, end);
ComposingRegion = new ComposingRegion(start, end);
return base.SetSelection(start, end);
}
}
public readonly struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
public ComposingRegion(int start, int end)
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
{
_start = start;
_end = end;
}
switch (actionCode)
{
case ImeAction.Done:
{
_inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
break;
}
}
public int Start => _start;
public int End => _end;
return base.PerformEditorAction(actionCode);
}
}
}

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -63,7 +63,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);

25
src/Avalonia.Base/Input/Gestures.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -42,9 +43,8 @@ namespace Avalonia.Input
RoutedEvent.Register<PointerDeltaEventArgs>(
"PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures));
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private static readonly WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
private static readonly WeakReference<IInteractive?> s_lastPress = new WeakReference<IInteractive?>(null);
private static Point s_lastPressPoint;
static Gestures()
{
@ -94,10 +94,11 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source;
if (e.ClickCount <= 1)
if (e.ClickCount % 2 == 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
s_lastPressPoint = e.GetPosition((IVisual)ev.Source);
}
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
@ -107,10 +108,6 @@ namespace Avalonia.Input
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
else
{
s_isDoubleTapped = false;
}
}
}
@ -120,9 +117,17 @@ namespace Avalonia.Input
{
var e = (PointerReleasedEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
{
if (e.InitialPressMouseButton == MouseButton.Left || e.InitialPressMouseButton == MouseButton.Right)
var point = e.GetCurrentPoint((IVisual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
if (tapRect.ContainsExclusive(point.Position))
{
if (e.InitialPressMouseButton == MouseButton.Right)
{

6
src/Avalonia.Base/Input/MouseDevice.cs

@ -127,9 +127,9 @@ namespace Avalonia.Input
_pointer.Capture(source);
if (source != null)
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings?.DoubleClickTime.TotalMilliseconds ?? 500;
var doubleClickSize = settings?.DoubleClickSize ?? new Size(4, 4);
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{

5
src/Avalonia.Base/Input/PenDevice.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Platform;
@ -77,8 +78,8 @@ namespace Avalonia.Input
{
pointer.Capture(source);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings?.DoubleClickTime.TotalMilliseconds ?? 500;
var doubleClickSize = settings?.DoubleClickSize ?? new Size(4, 4);
var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds ?? 500;
var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Pen) ?? new Size(4, 4);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{

1
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@ -29,6 +29,7 @@ namespace Avalonia.Input.TextInput
/// Sets the non-committed input string
/// </summary>
void SetPreeditText(string? text);
/// <summary>
/// Indicates if text input client is capable of providing the text around the cursor
/// </summary>

7
src/Avalonia.Base/Input/TouchDevice.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Platform;
@ -59,16 +60,18 @@ namespace Avalonia.Input
else
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds)
|| ev.Timestamp - _lastClickTime > doubleClickTime)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2));
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,

2
src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs

@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting
}
}
return length;
return Math.Min(length, text.Length);
}
}
}

30
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -0,0 +1,30 @@
using System;
using Avalonia.Input;
namespace Avalonia.Platform
{
/// <summary>
/// A default implementation of <see cref="IPlatformSettings"/> for platforms which don't have
/// an OS-specific implementation.
/// </summary>
public class DefaultPlatformSettings : IPlatformSettings
{
public Size GetTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(10, 10),
_ => new(4, 4),
};
}
public Size GetDoubleTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(16, 16),
_ => new(4, 4),
};
}
public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
}
}

22
src/Avalonia.Base/Platform/IPlatformSettings.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Input;
using Avalonia.Metadata;
namespace Avalonia.Platform
@ -6,18 +7,25 @@ namespace Avalonia.Platform
[Unstable]
public interface IPlatformSettings
{
Size DoubleClickSize { get; }
TimeSpan DoubleClickTime { get; }
/// <summary>
/// The size of the rectangle around the location of a pointer down that a pointer up
/// must occur within in order to register a tap gesture, in device-independent pixels.
/// </summary>
/// <param name="type">The pointer type.</param>
Size GetTapSize(PointerType type);
/// <summary>
/// Determines the size of the area within that you should click twice in order for a double click to be counted.
/// The size of the rectangle around the location of a pointer down that a pointer up
/// must occur within in order to register a double-tap gesture, in device-independent
/// pixels.
/// </summary>
Size TouchDoubleClickSize { get; }
/// <param name="type">The pointer type.</param>
Size GetDoubleTapSize(PointerType type);
/// <summary>
/// Determines the time span that what will be used to determine the double-click.
/// Gets the maximum time that may occur between the first and second click of a double-
/// tap gesture.
/// </summary>
TimeSpan TouchDoubleClickTime { get; }
TimeSpan GetDoubleTapTime(PointerType type);
}
}

8
src/Avalonia.Controls/Automation/AutomationProperties.cs

@ -9,6 +9,11 @@ namespace Avalonia.Automation
/// </summary>
public enum AccessibilityView
{
/// <summary>
/// The control's view is defined by its automation peer.
/// </summary>
Default,
/// <summary>
/// The control is included in the Raw view of the automation tree.
/// </summary>
@ -44,8 +49,7 @@ namespace Avalonia.Automation
public static readonly AttachedProperty<AccessibilityView> AccessibilityViewProperty =
AvaloniaProperty.RegisterAttached<StyledElement, AccessibilityView>(
"AccessibilityView",
typeof(AutomationProperties),
defaultValue: AccessibilityView.Content);
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.AccessKey attached property

14
src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs

@ -128,13 +128,13 @@ namespace Avalonia.Automation.Peers
/// Gets a value that indicates whether the element that is associated with this automation
/// peer contains data that is presented to the user.
/// </summary>
public bool IsContentElement() => IsControlElement() && IsContentElementCore();
public bool IsContentElement() => IsContentElementOverrideCore();
/// <summary>
/// Gets a value that indicates whether the element is understood by the user as
/// interactive or as contributing to the logical structure of the control in the GUI.
/// </summary>
public bool IsControlElement() => IsControlElementCore();
public bool IsControlElement() => IsControlElementOverrideCore();
/// <summary>
/// Gets a value indicating whether the control is enabled for user interaction.
@ -247,6 +247,16 @@ namespace Avalonia.Automation.Peers
return GetAutomationControlTypeCore();
}
protected virtual bool IsContentElementOverrideCore()
{
return IsControlElement() && IsContentElementCore();
}
protected virtual bool IsControlElementOverrideCore()
{
return IsControlElementCore();
}
protected virtual object? GetProviderCore(Type providerType)
{
return providerType.IsAssignableFrom(this.GetType()) ? this : null;

16
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@ -149,8 +149,8 @@ namespace Avalonia.Automation.Peers
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner);
protected override string GetClassNameCore() => Owner.GetType().Name;
protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Content;
protected override bool IsControlElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Control;
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
protected override bool IsEnabledCore() => Owner.IsEnabled;
protected override bool IsKeyboardFocusableCore() => Owner.Focusable;
protected override void SetFocusCore() => Owner.Focus();
@ -160,6 +160,18 @@ namespace Avalonia.Automation.Peers
return AutomationProperties.GetControlTypeOverride(Owner) ?? GetAutomationControlTypeCore();
}
protected override bool IsContentElementOverrideCore()
{
var view = AutomationProperties.GetAccessibilityView(Owner);
return view == AccessibilityView.Default ? IsContentElementCore() : view >= AccessibilityView.Content;
}
protected override bool IsControlElementOverrideCore()
{
var view = AutomationProperties.GetAccessibilityView(Owner);
return view == AccessibilityView.Default ? IsControlElementCore() : view >= AccessibilityView.Control;
}
private static Rect GetBounds(Control control)
{
var root = control.GetVisualRoot();

29
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -9,6 +9,7 @@ using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
using Avalonia.Input.TextInput;
namespace Avalonia.Controls.Presenters
{
@ -514,7 +515,12 @@ namespace Avalonia.Controls.Presenters
{
if (!string.IsNullOrEmpty(_preeditText))
{
var text = _text?.Substring(0, _caretIndex) + _preeditText + _text?.Substring(_caretIndex);
if (string.IsNullOrEmpty(_text) || _caretIndex > _text.Length)
{
return _preeditText;
}
var text = _text.Substring(0, _caretIndex) + _preeditText + _text.Substring(_caretIndex);
return text;
}
@ -868,28 +874,11 @@ namespace Avalonia.Controls.Presenters
if (string.IsNullOrEmpty(newValue))
{
if (!string.IsNullOrEmpty(oldValue))
{
var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0;
var characterHit = GetCharacterHitFromTextPosition(textPosition);
UpdateCaret(characterHit, true);
}
_compositionStartHit = new CharacterHit(-1);
UpdateCaret(_lastCharacterHit);
}
else
{
if (_compositionStartHit.FirstCharacterIndex == -1)
{
_compositionStartHit = _lastCharacterHit;
}
}
if (_compositionStartHit.FirstCharacterIndex != -1)
{
var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0;
var textPosition = _caretIndex + newValue?.Length ?? 0;
var characterHit = GetCharacterHitFromTextPosition(textPosition);

2
src/Avalonia.Controls/TextBox.cs

@ -1212,7 +1212,7 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (_presenter == null || !string.IsNullOrEmpty(_presenter.PreeditText))
if (_presenter == null )
{
return;
}

11
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -9,7 +9,7 @@ using Avalonia.Rendering;
namespace Avalonia.DesignerSupport.Remote
{
class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings
class PreviewerWindowingPlatform : IWindowingPlatform
{
static readonly IKeyboardDevice Keyboard = new KeyboardDevice();
private static IAvaloniaRemoteTransportConnection s_transport;
@ -51,7 +51,7 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
@ -60,12 +60,5 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -63,7 +63,7 @@ namespace Avalonia.Headless
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())

10
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -65,16 +65,6 @@ namespace Avalonia.Headless
}
}
class HeadlessPlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics

14
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -14,7 +14,7 @@ using MicroCom.Runtime;
namespace Avalonia.Native
{
class AvaloniaNativePlatform : IPlatformSettings, IWindowingPlatform
class AvaloniaNativePlatform : IWindowingPlatform
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
@ -26,16 +26,6 @@ namespace Avalonia.Native
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
[CanBeNull] internal static Compositor Compositor { get; private set; }
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true));
@ -113,7 +103,7 @@ namespace Avalonia.Native
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))

6
src/Avalonia.Native/DoubleClickHelper.cs

@ -1,3 +1,4 @@
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Native
@ -13,7 +14,8 @@ namespace Avalonia.Native
Point p)
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings.DoubleClickTime.TotalMilliseconds;
var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds ?? 500;
var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Mouse) ?? new Size(4, 4);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{
@ -23,7 +25,7 @@ namespace Avalonia.Native
++_clickCount;
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
return _clickCount == 2;
}

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

@ -235,7 +235,8 @@
<Setter Property="InnerRightContent">
<Template>
<ToggleButton Theme="{StaticResource FluentTextBoxToggleButton}"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}">
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}"
ClipToBounds="True">
<Panel>
<PathIcon Data="{StaticResource PasswordBoxRevealButtonData}"
Height="8" Width="12"
@ -253,7 +254,8 @@
<Setter Property="InnerRightContent">
<Template>
<Button Theme="{StaticResource FluentTextBoxButton}"
Command="{Binding $parent[TextBox].Clear}">
Command="{Binding $parent[TextBox].Clear}"
ClipToBounds="True">
<PathIcon Data="{StaticResource TextBoxClearButtonData}" Height="10" Width="10"/>
</Button>
</Template>

6
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -192,7 +192,8 @@
<Template>
<Button Command="{Binding $parent[TextBox].Clear}"
Focusable="False"
Theme="{StaticResource SimpleTextBoxClearButtonTheme}" />
Theme="{StaticResource SimpleTextBoxClearButtonTheme}"
ClipToBounds="True" />
</Template>
</Setter>
</Style>
@ -212,7 +213,8 @@
<ToggleButton Background="Transparent"
Focusable="False"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}"
Theme="{StaticResource SimplePasswordBoxRevealButtonTheme}" />
Theme="{StaticResource SimplePasswordBoxRevealButtonTheme}"
ClipToBounds="True" />
</Panel>
</Template>
</Setter>

17
src/Avalonia.X11/Stubs.cs

@ -1,17 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.X11
{
class PlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -80,7 +80,7 @@ namespace Avalonia.X11
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents(this));

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -51,7 +51,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
Compositor = new Compositor(

9
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -14,13 +14,4 @@ namespace Avalonia.LinuxFramebuffer
public void Dispose() { }
}
}
internal class PlatformSettings : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(4, 4);
public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Web/Avalonia.Web/Avalonia.Web.targets

@ -12,7 +12,7 @@
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True'">
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True' And '$(Configuration)' == 'Release'">
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>

11
src/Web/Avalonia.Web/WindowingPlatform.cs

@ -8,7 +8,7 @@ using Avalonia.Threading;
namespace Avalonia.Web
{
internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface
{
private bool _signaled;
private static KeyboardDevice? s_keyboard;
@ -36,7 +36,7 @@ namespace Avalonia.Web
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)
@ -45,13 +45,6 @@ namespace Avalonia.Web
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotSupportedException();

31
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -130,17 +130,6 @@ namespace Avalonia.Win32
internal static Compositor Compositor { get; private set; }
public Size DoubleClickSize => new Size(
UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK),
UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK));
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime());
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16,16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static void Initialize()
{
Initialize(new Win32PlatformOptions());
@ -398,5 +387,25 @@ namespace Avalonia.Win32
SetProcessDPIAware();
}
Size IPlatformSettings.GetTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(10, 10),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)),
};
}
Size IPlatformSettings.GetDoubleTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(16, 16),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)),
};
}
TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime());
}
}

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

@ -672,7 +672,10 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_IME_SETCONTEXT:
{
DefWindowProc(Hwnd, msg, wParam, (IntPtr)(lParam.ToInt64() & ~ISC_SHOWUICOMPOSITIONWINDOW));
unchecked
{
DefWindowProc(Hwnd, msg, wParam, lParam & ~(nint)ISC_SHOWUICOMPOSITIONWINDOW);
}
UpdateInputMethod(GetKeyboardLayout(0));

17
src/iOS/Avalonia.iOS/Platform.cs

@ -28,32 +28,19 @@ namespace Avalonia.iOS
public static EaglFeature GlFeature;
public static DisplayLinkTimer Timer;
internal static Compositor Compositor { get; private set; }
class PlatformSettings : IPlatformSettings
{
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(10, 10);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500);
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TouchDoubleClickTime;
}
public static void Register()
{
GlFeature ??= new EaglFeature();
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformOpenGlInterface>().ToConstant(GlFeature)
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToConstant(new PlatformSettings())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>()

4
tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs

@ -209,7 +209,9 @@ namespace Avalonia.Input.UnitTests
var unitTestApp = UnitTestApplication.Start(
new TestServices(inputManager: new InputManager()));
var iSettingsMock = new Mock<IPlatformSettings>();
iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime);
iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny<PointerType>())).Returns(doubleClickTime);
iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny<PointerType>())).Returns(new Size(16, 16));
iSettingsMock.Setup(x => x.GetTapSize(It.IsAny<PointerType>())).Returns(new Size(16, 16));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
return unitTestApp;

175
tests/Avalonia.IntegrationTests.Appium/GestureTests.cs

@ -0,0 +1,175 @@
using System;
using System.Threading;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions;
using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
[Collection("Default")]
public class GestureTests
{
private readonly AppiumDriver<AppiumWebElement> _session;
public GestureTests(TestAppFixture fixture)
{
_session = fixture.Session;
var tabs = _session.FindElementByAccessibilityId("MainTabs");
var tab = tabs.FindElementByName("Gestures");
tab.Click();
var clear = _session.FindElementByAccessibilityId("ResetGestures");
clear.Click();
}
[Fact]
public void Tapped_Is_Raised()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).Click(border).Perform();
Assert.Equal("Tapped", lastGesture.Text);
}
[Fact]
public void Tapped_Is_Raised_Slow()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).ClickAndHold(border).Perform();
Thread.Sleep(2000);
new Actions(_session).Release(border).Perform();
Assert.Equal("Tapped", lastGesture.Text);
}
[Fact]
public void Tapped_Is_Not_Raised_For_Drag()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session)
.ClickAndHold(border)
.MoveByOffset(50, 50)
.Release()
.Perform();
Assert.Equal(string.Empty, lastGesture.Text);
}
[Fact]
public void DoubleTapped_Is_Raised()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).DoubleClick(border).Perform();
Assert.Equal("DoubleTapped", lastGesture.Text);
}
[Fact]
public void DoubleTapped_Is_Raised_2()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).ClickAndHold(border).Release().Perform();
Thread.Sleep(100);
// DoubleTapped is raised on second pointer press, not release.
new Actions(_session).ClickAndHold(border).Perform();
try
{
Assert.Equal("DoubleTapped", lastGesture.Text);
}
finally
{
new Actions(_session).Release(border).Perform();
}
}
[Fact]
public void DoubleTapped_Is_Raised_Not_Raised_If_Too_Slow()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).ClickAndHold(border).Release().Perform();
Thread.Sleep(2000);
new Actions(_session).ClickAndHold(border).Release().Perform();
Assert.Equal("Tapped", lastGesture.Text);
}
[Fact]
public void DoubleTapped_Is_Raised_After_Control_Changes()
{
// #8733
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session)
.MoveToElement(border)
.DoubleClick()
.DoubleClick()
.Perform();
Assert.Equal("DoubleTapped2", lastGesture.Text);
}
[Fact]
public void RightTapped_Is_Raised()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
new Actions(_session).ContextClick(border).Perform();
Assert.Equal("RightTapped", lastGesture.Text);
}
[PlatformFact(TestPlatforms.MacOS)]
public void RightTapped_Is_Raised_2()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
var device = new PointerInputDevice(PointerKind.Mouse);
var b = new ActionBuilder();
b.AddAction(device.CreatePointerMove(border, 50, 50, TimeSpan.FromMilliseconds(100)));
b.AddAction(device.CreatePointerDown(MouseButton.Right));
b.AddAction(device.CreatePointerMove(border, 2, 2, TimeSpan.FromMilliseconds(100)));
b.AddAction(device.CreatePointerUp(MouseButton.Right));
_session.PerformActions(b.ToActionSequenceList());
Assert.Equal("RightTapped", lastGesture.Text);
}
[PlatformFact(TestPlatforms.MacOS)]
public void RightTapped_Is_Not_Raised_For_Drag()
{
var border = _session.FindElementByAccessibilityId("GestureBorder");
var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
var device = new PointerInputDevice(PointerKind.Mouse);
var b = new ActionBuilder();
b.AddAction(device.CreatePointerMove(border, 50, 50, TimeSpan.FromMilliseconds(100)));
b.AddAction(device.CreatePointerDown(MouseButton.Right));
b.AddAction(device.CreatePointerMove(CoordinateOrigin.Pointer, 50, 50, TimeSpan.FromMilliseconds(100)));
b.AddAction(device.CreatePointerUp(MouseButton.Right));
Assert.Equal(string.Empty, lastGesture.Text);
}
}
}
Loading…
Cancel
Save