From ec6cb0abb4b7669895b6e96fd7581c93b5abd691 Mon Sep 17 00:00:00 2001
From: Mary Guillemard <mary@mary.zone>
Date: Sat, 2 Mar 2024 12:51:05 +0100
Subject: infra: Make Avalonia the default UI  (#6375)

* misc: Move Ryujinx project to Ryujinx.Gtk3

This breaks release CI for now but that's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* misc: Move Ryujinx.Ava project to Ryujinx

This breaks CI for now, but it's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* infra: Make Avalonia the default UI

Should fix CI after the previous changes.

GTK3 isn't build by the release job anymore, only by PR CI.

This also ensure that the test-ava update package is still generated to
allow update from the old testing channel.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix missing copy in create_app_bundle.sh

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix syntax error

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
---
 src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 294 ++++++++++++++++++++++++++++++
 1 file changed, 294 insertions(+)
 create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindow.cs

(limited to 'src/Ryujinx/UI/Renderer/EmbeddedWindow.cs')

diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs
new file mode 100644
index 00000000..3bf19b43
--- /dev/null
+++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs
@@ -0,0 +1,294 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Platform;
+using Ryujinx.Common.Configuration;
+using Ryujinx.UI.Common.Configuration;
+using Ryujinx.UI.Common.Helper;
+using SPB.Graphics;
+using SPB.Platform;
+using SPB.Platform.GLX;
+using SPB.Platform.X11;
+using SPB.Windowing;
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
+
+namespace Ryujinx.Ava.UI.Renderer
+{
+    public class EmbeddedWindow : NativeControlHost
+    {
+        private WindowProc _wndProcDelegate;
+        private string _className;
+
+        protected GLXWindow X11Window { get; set; }
+
+        protected IntPtr WindowHandle { get; set; }
+        protected IntPtr X11Display { get; set; }
+        protected IntPtr NsView { get; set; }
+        protected IntPtr MetalLayer { get; set; }
+
+        public delegate void UpdateBoundsCallbackDelegate(Rect rect);
+        private UpdateBoundsCallbackDelegate _updateBoundsCallback;
+
+        public event EventHandler<IntPtr> WindowCreated;
+        public event EventHandler<Size> BoundsChanged;
+
+        public EmbeddedWindow()
+        {
+            this.GetObservable(BoundsProperty).Subscribe(StateChanged);
+
+            Initialized += OnNativeEmbeddedWindowCreated;
+        }
+
+        public virtual void OnWindowCreated() { }
+
+        protected virtual void OnWindowDestroyed() { }
+
+        protected virtual void OnWindowDestroying()
+        {
+            WindowHandle = IntPtr.Zero;
+            X11Display = IntPtr.Zero;
+            NsView = IntPtr.Zero;
+            MetalLayer = IntPtr.Zero;
+        }
+
+        private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e)
+        {
+            OnWindowCreated();
+
+            Task.Run(() =>
+            {
+                WindowCreated?.Invoke(this, WindowHandle);
+            });
+        }
+
+        private void StateChanged(Rect rect)
+        {
+            BoundsChanged?.Invoke(this, rect.Size);
+            _updateBoundsCallback?.Invoke(rect);
+        }
+
+        protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control)
+        {
+            if (OperatingSystem.IsLinux())
+            {
+                return CreateLinux(control);
+            }
+
+            if (OperatingSystem.IsWindows())
+            {
+                return CreateWin32(control);
+            }
+
+            if (OperatingSystem.IsMacOS())
+            {
+                return CreateMacOS();
+            }
+
+            return base.CreateNativeControlCore(control);
+        }
+
+        protected override void DestroyNativeControlCore(IPlatformHandle control)
+        {
+            OnWindowDestroying();
+
+            if (OperatingSystem.IsLinux())
+            {
+                DestroyLinux();
+            }
+            else if (OperatingSystem.IsWindows())
+            {
+                DestroyWin32(control);
+            }
+            else if (OperatingSystem.IsMacOS())
+            {
+                DestroyMacOS();
+            }
+            else
+            {
+                base.DestroyNativeControlCore(control);
+            }
+
+            OnWindowDestroyed();
+        }
+
+        [SupportedOSPlatform("linux")]
+        private IPlatformHandle CreateLinux(IPlatformHandle control)
+        {
+            if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
+            {
+                X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle));
+                X11Window.Hide();
+            }
+            else
+            {
+                X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow;
+            }
+
+            WindowHandle = X11Window.WindowHandle.RawHandle;
+            X11Display = X11Window.DisplayHandle.RawHandle;
+
+            return new PlatformHandle(WindowHandle, "X11");
+        }
+
+        [SupportedOSPlatform("windows")]
+        IPlatformHandle CreateWin32(IPlatformHandle control)
+        {
+            _className = "NativeWindow-" + Guid.NewGuid();
+
+            _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
+            {
+                if (VisualRoot != null)
+                {
+                    if (msg == WindowsMessages.Lbuttondown ||
+                        msg == WindowsMessages.Rbuttondown ||
+                        msg == WindowsMessages.Lbuttonup ||
+                        msg == WindowsMessages.Rbuttonup ||
+                        msg == WindowsMessages.Mousemove)
+                    {
+                        Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value;
+                        Pointer pointer = new(0, PointerType.Mouse, true);
+
+#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete)
+                        switch (msg)
+                        {
+                            case WindowsMessages.Lbuttondown:
+                            case WindowsMessages.Rbuttondown:
+                                {
+                                    bool isLeft = msg == WindowsMessages.Lbuttondown;
+                                    RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
+                                    PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
+
+                                    var evnt = new PointerPressedEventArgs(
+                                        this,
+                                        pointer,
+                                        this,
+                                        rootVisualPosition,
+                                        (ulong)Environment.TickCount64,
+                                        properties,
+                                        KeyModifiers.None);
+
+                                    RaiseEvent(evnt);
+
+                                    break;
+                                }
+                            case WindowsMessages.Lbuttonup:
+                            case WindowsMessages.Rbuttonup:
+                                {
+                                    bool isLeft = msg == WindowsMessages.Lbuttonup;
+                                    RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
+                                    PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
+
+                                    var evnt = new PointerReleasedEventArgs(
+                                        this,
+                                        pointer,
+                                        this,
+                                        rootVisualPosition,
+                                        (ulong)Environment.TickCount64,
+                                        properties,
+                                        KeyModifiers.None,
+                                        isLeft ? MouseButton.Left : MouseButton.Right);
+
+                                    RaiseEvent(evnt);
+
+                                    break;
+                                }
+                            case WindowsMessages.Mousemove:
+                                {
+                                    var evnt = new PointerEventArgs(
+                                        PointerMovedEvent,
+                                        this,
+                                        pointer,
+                                        this,
+                                        rootVisualPosition,
+                                        (ulong)Environment.TickCount64,
+                                        new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
+                                        KeyModifiers.None);
+
+                                    RaiseEvent(evnt);
+
+                                    break;
+                                }
+                        }
+#pragma warning restore CS0618
+                    }
+                }
+
+                return DefWindowProc(hWnd, msg, wParam, lParam);
+            };
+
+            WndClassEx wndClassEx = new()
+            {
+                cbSize = Marshal.SizeOf<WndClassEx>(),
+                hInstance = GetModuleHandle(null),
+                lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
+                style = ClassStyles.CsOwndc,
+                lpszClassName = Marshal.StringToHGlobalUni(_className),
+                hCursor = CreateArrowCursor(),
+            };
+
+            RegisterClassEx(ref wndClassEx);
+
+            WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
+
+            Marshal.FreeHGlobal(wndClassEx.lpszClassName);
+
+            return new PlatformHandle(WindowHandle, "HWND");
+        }
+
+        [SupportedOSPlatform("macos")]
+        IPlatformHandle CreateMacOS()
+        {
+            // Create a new CAMetalLayer.
+            ObjectiveC.Object layerObject = new("CAMetalLayer");
+            ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc");
+            metalLayer.SendMessage("init");
+
+            // Create a child NSView to render into.
+            ObjectiveC.Object nsViewObject = new("NSView");
+            ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc");
+            child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0));
+
+            // Make its renderer our metal layer.
+            child.SendMessage("setWantsLayer:", 1);
+            child.SendMessage("setLayer:", metalLayer);
+            metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor);
+
+            // Ensure the scale factor is up to date.
+            _updateBoundsCallback = rect =>
+            {
+                metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor);
+            };
+
+            IntPtr nsView = child.ObjPtr;
+            MetalLayer = metalLayer.ObjPtr;
+            NsView = nsView;
+
+            return new PlatformHandle(nsView, "NSView");
+        }
+
+        [SupportedOSPlatform("Linux")]
+        void DestroyLinux()
+        {
+            X11Window?.Dispose();
+        }
+
+        [SupportedOSPlatform("windows")]
+        void DestroyWin32(IPlatformHandle handle)
+        {
+            DestroyWindow(handle.Handle);
+            UnregisterClass(_className, GetModuleHandle(null));
+        }
+
+        [SupportedOSPlatform("macos")]
+#pragma warning disable CA1822 // Mark member as static
+        void DestroyMacOS()
+        {
+            // TODO
+        }
+#pragma warning restore CA1822
+    }
+}
-- 
cgit v1.2.3-70-g09d2