Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

Pointer events don't work on Win32 `EmbeddableControlRoot`

Open jp2masa opened this issue 1 year ago • 6 comments

Describe the bug

Pointer events don't work on Win32 EmbeddableControlRoot.

EDIT: Related to #9268.

To Reproduce

Steps to reproduce the behavior:

  1. Repro control

    Code
    public sealed class Embed : NativeControlHost
    {
        private sealed class GraphicsRoot : EmbeddableControlRoot
        {
            public GraphicsRoot(ITopLevelImpl impl)
                : base(impl)
            {
                AddHandler(PointerPressedEvent, (sender, e) => OnPointerPressed(e), handledEventsToo: true);
            }
    
            protected override void OnPointerPressed(PointerPressedEventArgs e)
            {
                Debug.WriteLine("DEBUG");
            }
        }
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr CreateWindowEx(
            int dwExStyle,
            uint lpClassName,
            string? lpWindowName,
            uint dwStyle,
            int x,
            int y,
            int nWidth,
            int nHeight,
            IntPtr hWndParent,
            IntPtr hMenu,
            IntPtr hInstance,
            IntPtr lpParam
        );
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetParent(
            IntPtr hWndChild,
            IntPtr hWndNewParent
        );
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetWindowLongPtrA(
            IntPtr hWnd,
            int nIndex,
            IntPtr dwNewLong
        );
    
        private sealed class Win32EmbedWindowImpl : Avalonia.Win32.WindowImpl
        {
            private const int GWL_STYLE = -16;
    
            private const long WS_CHILD = 0x40000000L;
    
            public Win32EmbedWindowImpl(IPlatformHandle parent)
            {
                Embed.SetParent(Handle.Handle, parent.Handle);
                SetWindowLongPtrA(Handle.Handle, GWL_STYLE, (IntPtr)WS_CHILD);
            }
    
            protected override IntPtr CreateWindowOverride(ushort atom) =>
                CreateWindowEx(
                    0,
                    atom,
                    null,
                    0,
                    0,
                    0,
                    1,
                    1,
                    IntPtr.Zero,
                    IntPtr.Zero,
                    IntPtr.Zero,
                    IntPtr.Zero
                );
        }
    
        private GraphicsRoot _root;
    
        protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
        {
            var embedImpl = new Win32EmbedWindowImpl(parent);
            _root = new GraphicsRoot(embedImpl);
    
            _root.Prepare();
    
            _root.GotFocus += (sender, e) =>
            {
                var y = 3;
            };
    
            _root.Background = Brushes.Transparent;
    
            _root.PointerPressed += (sender, e) =>
            {
                Debug.WriteLine("DEBUG");
            };
    
            _root.PointerWheelChanged += (sender, e) =>
            {
                Debug.WriteLine("DEBUG");
            };
    
            _root.KeyDown += (sender, e) =>
            {
                Debug.WriteLine("DEBUG_KEY");
            };
    
            return embedImpl.Handle;
        }
    
        protected override void DestroyNativeControlCore(IPlatformHandle control)
        {
            _root.Dispose();
            _root = null;
        }
    }
    
  2. On MainWindow.xaml set the content to <v:Embed />.

  3. Run the app

  4. Press the black area, nothing gets printed on debug output

  5. Press some keyboard keys, something gets printed on debug output

Expected behavior

Pointer events should be raised.

Desktop (please complete the following information):

  • OS: Windows 11
  • Version: 11.0.0-preview2

jp2masa avatar Oct 28 '22 04:10 jp2masa

Check if pointer events are raised from ITopLevelImpl.Input

kekekeks avatar Oct 28 '22 06:10 kekekeks

I added this to the GraphicsRoot constructor:

impl.Input = e =>
{
    Debug.WriteLine("DEBUG_TOPLEVEL_INPUT");
};

And it prints the message for all pointer events.

jp2masa avatar Oct 28 '22 18:10 jp2masa

That probably means that hit-testing has failed to find any controls to trigger pointer events on. Are you sure that you have started the renderer? It doesn't happen automatically.

kekekeks avatar Oct 29 '22 01:10 kekekeks

Tried to add _root.Renderer.Start(); after _root.Prepare(); and it still doesn't work...

Also, I'm guessing the renderer will conflict with the Vulkan rendering, and it may also cause performance issue, so is it possible to have pointer events on EmbeddableControlRoot without rendering?

jp2masa avatar Oct 29 '22 01:10 jp2masa

UI elements have to be processed by the renderer to have hit-testing information.

kekekeks avatar Oct 29 '22 01:10 kekekeks

Then maybe I should create an implementation of ITopLevelImpl for Vulkan, with a custom renderer? I guess that would work?

jp2masa avatar Oct 29 '22 01:10 jp2masa

private sealed class Win32EmbedWindowImpl : Avalonia.Win32.WindowImpl

I don't think we want to keep WindowImpl a not internal class.

@jp2masa hit testing is processed by renderer, yes. In case of compositing renderer - https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs#L87

maxkatz6 avatar Nov 03 '22 19:11 maxkatz6

I just tried to replace that class with PlatformManager.CreateEmbeddableWindow(), and it works, but not sure what's the role of the parent handle now (is parent set automatically later or it doesn't have to be set at all?). Since my last comment, I also created a custom top level impl and renderer, which seem to work fine, so I'll close this.

jp2masa avatar Nov 04 '22 03:11 jp2masa