diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index d9f34198508..ec8b56c14a6 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -87,6 +87,7 @@ public static void Initialize() .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) + .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(new AndroidActivatableLifetime()); diff --git a/src/Avalonia.Base/Input/Platform/PlatformPointerConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformPointerConfiguration.cs new file mode 100644 index 00000000000..fd170568fe4 --- /dev/null +++ b/src/Avalonia.Base/Input/Platform/PlatformPointerConfiguration.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Avalonia.Metadata; + +namespace Avalonia.Input.Platform; + +public sealed class PlatformPointerConfiguration +{ + public List OpenContextMenu { get; set; } + + [PrivateApi] + public PlatformPointerConfiguration() + { + OpenContextMenu = new List + { + new PointerGesture(MouseButton.Right, KeyModifiers.None) + }; + } + + [PrivateApi] + public PlatformPointerConfiguration(params PointerGesture[] additionalContextGestures) : this() + { + OpenContextMenu.AddRange(additionalContextGestures); + } +} diff --git a/src/Avalonia.Base/Input/PointerGesture.cs b/src/Avalonia.Base/Input/PointerGesture.cs new file mode 100644 index 00000000000..1f53dd54327 --- /dev/null +++ b/src/Avalonia.Base/Input/PointerGesture.cs @@ -0,0 +1,103 @@ +using System; +using System.Text; +using Avalonia.VisualTree; + +namespace Avalonia.Input; + +public class PointerGesture : IEquatable +{ + public PointerGesture(MouseButton button, KeyModifiers modifiers = KeyModifiers.None) + { + Button = button; + KeyModifiers = modifiers; + } + + public bool Equals(PointerGesture? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Button == other.Button && KeyModifiers == other.KeyModifiers; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + + return obj is KeyGesture gesture && Equals(gesture); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)Button * 397) ^ (int)KeyModifiers; + } + } + + public static bool operator ==(PointerGesture? left, PointerGesture? right) + { + return Equals(left, right); + } + + public static bool operator !=(PointerGesture? left, PointerGesture? right) + { + return !Equals(left, right); + } + + public MouseButton Button { get; } + + public KeyModifiers KeyModifiers { get; } + + public override string ToString() + { + var s = new StringBuilder(); + + static void Plus(StringBuilder s) + { + if (s.Length > 0) + { + s.Append("+"); + } + } + + if (KeyModifiers.HasAllFlags(KeyModifiers.Control)) + { + s.Append("Ctrl"); + } + + if (KeyModifiers.HasAllFlags(KeyModifiers.Shift)) + { + Plus(s); + s.Append("Shift"); + } + + if (KeyModifiers.HasAllFlags(KeyModifiers.Alt)) + { + Plus(s); + s.Append("Alt"); + } + + if (KeyModifiers.HasAllFlags(KeyModifiers.Meta)) + { + Plus(s); + s.Append("Cmd"); + } + + Plus(s); + s.Append(Button); + + return s.ToString(); + } + + public bool Matches(PointerEventArgs pointerEvent) => + pointerEvent != null && + pointerEvent.KeyModifiers == KeyModifiers && + pointerEvent.GetCurrentPoint(pointerEvent.Source as Visual).Properties.PointerUpdateKind.GetMouseButton() == Button; + + public bool Matches(PointerReleasedEventArgs pointerEvent) => + pointerEvent != null && + pointerEvent.KeyModifiers == KeyModifiers && + pointerEvent.InitialPressMouseButton == Button; +} diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index dc836a08716..bf2c4dc4a16 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -419,7 +419,7 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); - if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) + if (!e.Handled && IsPressed && e.InitialPressMouseButton == MouseButton.Left) { IsPressed = false; e.Handled = true; diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 3076d7a5eb1..c979ed22418 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using Avalonia.Automation.Peers; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -480,9 +481,11 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); + var contextGestures = AvaloniaLocator.Current.GetService()?.OpenContextMenu; + if (e.Source == this && !e.Handled - && e.InitialPressMouseButton == MouseButton.Right) + && contextGestures?.Any(x => x.Matches(e)) == true) { var args = new ContextRequestedEventArgs(e); RaiseEvent(args); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index ba9dd592ced..120d780f017 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -55,6 +55,7 @@ public static void Initialize(IAvaloniaRemoteTransportConnection transport) .Bind().ToConstant(new UiThreadRenderTimer(60)) .Bind().ToConstant(instance) .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToSingleton(); } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index d8ab8363b58..db65e3b9ca7 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -122,7 +122,8 @@ void DoInitialize(AvaloniaNativePlatformOptions options) hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys) + .Bind().ToConstant(new PlatformPointerConfiguration(new PointerGesture(MouseButton.Left, KeyModifiers.Control)));; foreach (var mode in _options.RenderingMode) { diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index e89a5088206..18d046a01c5 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -80,6 +80,7 @@ public void Initialize(X11PlatformOptions options) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) .Bind().ToConstant(timer) + .Bind().ToSingleton() .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) .Bind().ToConstant(new X11CursorFactory(Display)) diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index e344db3d73c..f67e3094650 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -69,6 +69,7 @@ public static void Register() .Bind().ToSingleton() .Bind().ToConstant(instance) .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToSingleton(); AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); diff --git a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 9bb587c9b1a..5fdbcfbcb48 100644 --- a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -76,6 +76,7 @@ internal static void Initialize(AvaloniaHeadlessPlatformOptions opts) .Bind().ToConstant(new KeyboardDevice()) .Bind().ToConstant(new RenderTimer(60)) .Bind().ToConstant(new HeadlessWindowingPlatform(opts.FrameBufferFormat)) + .Bind().ToSingleton() .Bind().ToSingleton(); Compositor = new Compositor( null); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index d226a89e7d6..9e994db4fd5 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -72,6 +72,7 @@ void Initialize() .Bind().ToConstant(new KeyboardDevice()) .Bind().ToSingleton() .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToSingleton(); Compositor = new Compositor(AvaloniaLocator.Current.GetService()); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d923f382754..7133547e93f 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -93,6 +93,7 @@ public static void Initialize(Win32PlatformOptions options) .Bind().ToConstant(s_instance._dispatcher) .Bind().ToConstant(renderTimer) .Bind().ToConstant(s_instance) + .Bind().ToSingleton() .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control) { OpenContextMenu = diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 80066394060..4b4a914c91a 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -82,6 +82,7 @@ public static void Register(IAvaloniaAppDelegate? appDelegate) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() .Bind().ToConstant(new PlatformIconLoaderStub()) + .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(Timer) .Bind().ToConstant(DispatcherImpl.Instance) diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 453c6c05d1d..642b2994a19 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -77,6 +77,7 @@ public override void RegisterServices() .Bind().ToConstant(Services.DispatcherImpl) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.WindowingPlatform) + .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(Services.AccessKeyHandler)