diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs index 183604bbea..9ce599a057 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -20,6 +20,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers private readonly TopLevelImpl _view; private bool _disposed; + public MouseDevice MouseDevice => _mouseDevice; + + public TouchDevice TouchDevice => _touchDevice; + + public PenDevice PenDevice => _penDevice; + public AndroidMotionEventsHelper(TopLevelImpl view) { _touchDevice = new TouchDevice(); diff --git a/src/Android/Avalonia.Android/Previewer/Preview.cs b/src/Android/Avalonia.Android/Previewer/Preview.cs index 3d67fc092f..52cc6a9eab 100644 --- a/src/Android/Avalonia.Android/Previewer/Preview.cs +++ b/src/Android/Avalonia.Android/Previewer/Preview.cs @@ -87,10 +87,18 @@ namespace Avalonia.Android.Previewer } else if (root is TopLevel) { + // We can't host toplevels topLevel!.Content = null; } else { + if(root is Visual visual) + { + if (visual.IsSet(AvDesign.DataContextProperty)) + { + visual.DataContext = visual.GetValue(AvDesign.DataContextProperty); + } + } topLevel!.Content = root; } return true; diff --git a/src/Android/Avalonia.Android/Previewer/PreviewFactoryActivity.cs b/src/Android/Avalonia.Android/Previewer/PreviewFactoryActivity.cs index 48c972f225..63bac93eb7 100644 --- a/src/Android/Avalonia.Android/Previewer/PreviewFactoryActivity.cs +++ b/src/Android/Avalonia.Android/Previewer/PreviewFactoryActivity.cs @@ -29,12 +29,12 @@ namespace Avalonia.Android.Previewer display?.StartDisplay(); if (display != null) - { - var assembly = Assembly.GetAssembly(typeof(TApp)); - var presentation = new PreviewPresentation(this, display.Display, port, assembly); - presentation.Show(); - } + { + var assembly = Assembly.GetAssembly(typeof(TApp)); + var presentation = new PreviewPresentation(this, display.Display, port, assembly); + presentation.Show(); } + } global::Android.Util.Log.Info("AVALONIA_PREVIEW", $"Previewer started at port-{port}"); } diff --git a/src/Android/Avalonia.Android/Previewer/PreviewPresentation.cs b/src/Android/Avalonia.Android/Previewer/PreviewPresentation.cs index 2a1e3f65ef..4e955e413c 100644 --- a/src/Android/Avalonia.Android/Previewer/PreviewPresentation.cs +++ b/src/Android/Avalonia.Android/Previewer/PreviewPresentation.cs @@ -1,17 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; using System.Threading.Tasks; using Android.App; using Android.Content; -using Android.Content.PM; using Android.OS; -using Android.Runtime; using Android.Views; -using Avalonia.Media; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Remote.Protocol.Input; +using Avalonia.Threading; +using Key = Avalonia.Input.Key; +using PhysicalKey = Avalonia.Input.PhysicalKey; namespace Avalonia.Android.Previewer { @@ -26,6 +25,9 @@ namespace Avalonia.Android.Previewer private AvaloniaView? _view; private float _renderScaling = 1; + internal MouseDevice? TouchDevice => _view?.TopLevelImpl.PointerHelper.MouseDevice; + internal static KeyboardDevice? KeyboardDevice => AvaloniaLocator.Current.GetService() as KeyboardDevice; + public AvaloniaView? View { get => _view; set => _view = value; } public float RenderScaling @@ -34,7 +36,7 @@ namespace Avalonia.Android.Previewer internal set { _renderScaling = value; - if(PreviewDisplay.Instance?.Surface is { } surface) + if (PreviewDisplay.Instance?.Surface is { } surface) { surface.Scaling = _renderScaling; } @@ -50,7 +52,7 @@ namespace Avalonia.Android.Previewer _assembly = assembly; } - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] protected override void OnCreate(Bundle? savedInstanceState) { @@ -71,5 +73,157 @@ namespace Avalonia.Android.Previewer if (_preview != null) await _preview.UpdateXamlAsync(xaml); } + + public void SendInput(InputEventMessageBase input) + { + switch (input) + { + case PointerMovedEventMessage pointer: + Dispatcher.UIThread.Post(() => + { + View?.TopLevelImpl?.Input?.Invoke(new RawPointerEventArgs( + TouchDevice!, + 0, + View?.TopLevelImpl?.InputRoot!, + RawPointerEventType.Move, + new Point(pointer.X, pointer.Y), + GetAvaloniaRawInputModifiers(pointer.Modifiers))); + }, DispatcherPriority.Input); + break; + + case PointerPressedEventMessage pressed: + Dispatcher.UIThread.Post(() => + { + View?.TopLevelImpl?.Input?.Invoke(new RawPointerEventArgs( + TouchDevice!, + 0, + View?.TopLevelImpl.InputRoot!, + GetAvaloniaEventType(pressed.Button, true), + new Point(pressed.X, pressed.Y), + GetAvaloniaRawInputModifiers(pressed.Modifiers))); + }, DispatcherPriority.Input); + break; + + case PointerReleasedEventMessage released: + Dispatcher.UIThread.Post(() => + { + View?.TopLevelImpl?.Input?.Invoke(new RawPointerEventArgs( + TouchDevice!, + 0, + View?.TopLevelImpl.InputRoot!, + GetAvaloniaEventType(released.Button, false), + new Point(released.X, released.Y), + GetAvaloniaRawInputModifiers(released.Modifiers))); + }, DispatcherPriority.Input); + break; + + case ScrollEventMessage scroll: + Dispatcher.UIThread.Post(() => + { + View?.TopLevelImpl?.Input?.Invoke(new RawMouseWheelEventArgs( + TouchDevice!, + 0, + View?.TopLevelImpl.InputRoot!, + new Point(scroll.X, scroll.Y), + new Vector(scroll.DeltaX, scroll.DeltaY), + GetAvaloniaRawInputModifiers(scroll.Modifiers))); + }, DispatcherPriority.Input); + break; + + case KeyEventMessage key: + Dispatcher.UIThread.Post(() => + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + + View?.TopLevelImpl?.Input?.Invoke(new RawKeyEventArgs( + KeyboardDevice!, + 0, + View?.TopLevelImpl.InputRoot!, + key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, + (Key)key.Key, + GetAvaloniaRawInputModifiers(key.Modifiers), + (PhysicalKey)key.PhysicalKey, + key.KeySymbol)); + }, DispatcherPriority.Input); + break; + + case TextInputEventMessage text: + Dispatcher.UIThread.Post(() => + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + + View?.TopLevelImpl?.Input?.Invoke(new RawTextInputEventArgs( + KeyboardDevice!, + 0, + View?.TopLevelImpl.InputRoot!, + text.Text)); + }, DispatcherPriority.Input); + break; + } + } + + private static RawPointerEventType GetAvaloniaEventType(Remote.Protocol.Input.MouseButton button, bool pressed) + { + switch (button) + { + case Remote.Protocol.Input.MouseButton.Left: + return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp; + + case Remote.Protocol.Input.MouseButton.Middle: + return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp; + + case Remote.Protocol.Input.MouseButton.Right: + return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp; + + default: + return RawPointerEventType.Move; + } + } + + private static RawInputModifiers GetAvaloniaRawInputModifiers(InputModifiers[]? modifiers) + { + var result = RawInputModifiers.None; + + if (modifiers == null) + { + return result; + } + + foreach (var modifier in modifiers) + { + switch (modifier) + { + case InputModifiers.Control: + result |= RawInputModifiers.Control; + break; + + case InputModifiers.Alt: + result |= RawInputModifiers.Alt; + break; + + case InputModifiers.Shift: + result |= RawInputModifiers.Shift; + break; + + case InputModifiers.Windows: + result |= RawInputModifiers.Meta; + break; + + case InputModifiers.LeftMouseButton: + result |= RawInputModifiers.LeftMouseButton; + break; + + case InputModifiers.MiddleMouseButton: + result |= RawInputModifiers.MiddleMouseButton; + break; + + case InputModifiers.RightMouseButton: + result |= RawInputModifiers.RightMouseButton; + break; + } + } + + return result; + } } } diff --git a/src/Android/Avalonia.Android/Previewer/PreviewerConnection.cs b/src/Android/Avalonia.Android/Previewer/PreviewerConnection.cs index 361fc81d4e..8d202893ba 100644 --- a/src/Android/Avalonia.Android/Previewer/PreviewerConnection.cs +++ b/src/Android/Avalonia.Android/Previewer/PreviewerConnection.cs @@ -1,19 +1,12 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Input; -using Avalonia.Input.Raw; +using Avalonia.Layout; using Avalonia.Logging; -using Avalonia.Media; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Input; using Avalonia.Remote.Protocol.Viewport; using Avalonia.Threading; -using Java.Text; namespace Avalonia.Android.Previewer { @@ -90,6 +83,21 @@ namespace Avalonia.Android.Previewer }); } break; + + case MeasureViewportMessage measure: + var root = previewPresentation.View?.TopLevelImpl.InputRoot as Layoutable; + root?.Measure(new Size(measure.Width, measure.Height)); + var desiredSize = root?.DesiredSize ?? default; + + _connection?.Send(new MeasureViewportMessage + { + Width = desiredSize.Width, + Height = desiredSize.Height + }); + break; + case InputEventMessageBase inputEventMessage: + previewPresentation?.SendInput(inputEventMessage); + break; } }, arg2); }