diff --git a/Src/Avalonia.OpenHarmony/AvaloniaXComponent.cs b/Src/Avalonia.OpenHarmony/AvaloniaXComponent.cs index af1f62f..dad7b5a 100644 --- a/Src/Avalonia.OpenHarmony/AvaloniaXComponent.cs +++ b/Src/Avalonia.OpenHarmony/AvaloniaXComponent.cs @@ -11,139 +11,211 @@ namespace Avalonia.OpenHarmony; +/// +/// 提供一个将 Avalonia 应用程序嵌入到 OpenHarmony XComponent 的实现。 +/// 此类处理 OpenGL ES 环境初始化、Avalonia 视图的创建、生命周期管理以及输入事件的分发。 +/// +/// 要运行的 Avalonia 应用程序类型,必须继承自 并有一个无参构造函数。 public class AvaloniaXComponent : XComponent where TApp : Application, new() { + // 输入设备 private readonly MouseDevice _mouseDevice; private readonly PenDevice _penDevice; - private readonly TouchDevice _touchDevice; - private nint display; - private EglInterface? egl; - private GL? gl; + + // EGL 和 OpenGL ES 相关字段 + private nint _display; + private EglInterface? _egl; + private GL? _gl; + private nint _surface; + + /// + /// Avalonia 控件的根容器。 + /// public EmbeddableControlRoot? Root; + + /// + /// 管理应用程序的生命周期,适用于单视图场景。 + /// public SingleViewLifetime? SingleViewLifetime; - private nint surface; + + /// + /// Avalonia 的顶层实现,处理渲染和输入。 + /// public TopLevelImpl? TopLevelImpl; + /// + /// 一个标志,用于指示是否应使用软件渲染器。 + /// 注意:此标志的当前实现逻辑可能与名称不完全匹配,因为它会触发 OpenGL 环境的初始化。 + /// public bool UseSoftRenderer = false; - public AvaloniaXComponent(nint XComponentHandle, nint WindowHandle) : base(XComponentHandle, WindowHandle) + /// + /// 初始化 类的新实例。 + /// + /// 原生 XComponent 的句柄。 + /// 窗口句柄。 + public AvaloniaXComponent(nint xComponentHandle, nint windowHandle) : base(xComponentHandle, windowHandle) { _touchDevice = new TouchDevice(); _penDevice = new PenDevice(); _mouseDevice = new MouseDevice(); } + /// + /// 初始化 OpenGL ES 环境。此方法设置 EGL 显示、配置、Surface和上下文。 + /// public void InitOpenGlEnv() { - egl = new EglInterface("libEGL.so"); + _egl = new EglInterface("libEGL.so"); - display = egl.GetDisplay(0); - if (egl.Initialize(display, out var major, out var minor) == false) + _display = _egl.GetDisplay(0); + if (_egl.Initialize(_display, out _, out _) == false) { - Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "egl.Initialize fail"); + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "CSharp", "_egl.Initialize fail"); return; } - Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "egl init success"); + Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "_egl init success"); + // EGL 配置属性,请求 RGBA8888 Surface和 OpenGL ES 2 兼容上下文 int[] attributes = [0x3033, 0x0004, 0x3024, 8, 0x3023, 8, 0x3022, 8, 0x3021, 8, 0x3040, 0x0004, 0x3038]; - if (egl.ChooseConfig(display, attributes, out var configs, 1, out var choosenConfig) == false) + if (_egl.ChooseConfig(_display, attributes, out var configs, 1, out _) == false) { - Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "egl.ChooseConfig fail"); + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "CSharp", "_egl.ChooseConfig fail"); return; } + // 窗口Surface属性 int[] winAttribs = [0x309D, 0x3089, 0x3038]; - surface = egl.CreateWindowSurface(display, configs, WindowHandle, winAttribs); - if (surface == 0) + _surface = _egl.CreateWindowSurface(_display, configs, WindowHandle, winAttribs); + if (_surface == 0) { - Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "egl.CreateWindowSurface fail"); + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "CSharp", "_egl.CreateWindowSurface fail"); return; } - + // EGL 上下文属性,指定 OpenGL ES 2.0 int[] attrib3_list = [0x3098, 2, 0x3038]; var sharedEglContext = 0; - var context = egl.CreateContext(display, configs, sharedEglContext, attrib3_list); - if (egl.MakeCurrent(display, surface, surface, context) == false) + var context = _egl.CreateContext(_display, configs, sharedEglContext, attrib3_list); + if (_egl.MakeCurrent(_display, _surface, _surface, context) == false) { - Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "CSharp", "egl.MakeCurrent fail"); + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "CSharp", "_egl.MakeCurrent fail"); return; } - - gl = GL.GetApi(name => + // 获取 OpenGL ES 函数指针 + _gl = GL.GetApi(name => { var ptr = Marshal.StringToHGlobalAnsi(name); - var fun = egl.GetProcAddress(ptr); + var fun = _egl.GetProcAddress(ptr); Marshal.FreeHGlobal(ptr); return fun; }); } + /// + /// 在 XComponent Surface创建时调用。 + /// 此方法初始化渲染环境(如果需要),配置并启动 Avalonia 应用程序。 + /// public override void OnSurfaceCreated() { - if (UseSoftRenderer) InitOpenGlEnv(); + if (UseSoftRenderer) + { + InitOpenGlEnv(); + } + var builder = CreateAppBuilder(); if (UseSoftRenderer) { builder.UseSoftwareRenderer(); - if (gl != null) AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); + if (_gl != null) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(_gl); + } } SingleViewLifetime = new SingleViewLifetime(); builder.AfterApplicationSetup(CreateView).SetupWithLifetime(SingleViewLifetime); - Root?.StartRendering(); } + /// + /// 在 XComponent Surface上渲染一帧时调用。 + /// 此方法触发 Avalonia 的渲染逻辑,并在使用硬件渲染时交换缓冲区。 + /// + /// 当前帧的时间戳。 + /// 目标帧的时间戳。 public override void OnSurfaceRendered(ulong timestamp, ulong targetTimestamp) { if (TopLevelImpl == null) + { return; + } + base.OnSurfaceRendered(timestamp, targetTimestamp); TopLevelImpl.Render(); - if (UseSoftRenderer && egl != null) egl.SwapBuffers(display, surface); + if (UseSoftRenderer && _egl != null) + { + _egl.SwapBuffers(_display, _surface); + } } + /// + /// 创建 Avalonia 视图和相关的顶层实现。 + /// + /// 应用程序构建器。 private void CreateView(AppBuilder appBuilder) { if (SingleViewLifetime == null) + { return; + } + TopLevelImpl = new TopLevelImpl(XComponentHandle, WindowHandle); Root = new EmbeddableControlRoot(TopLevelImpl); SingleViewLifetime.Root = Root; Root.Prepare(); } + /// + /// 分发触摸事件。从原生 XComponent 获取触摸事件数据,并将其转换为 Avalonia 的输入事件。 + /// public override unsafe void DispatchTouchEvent() { - if (TopLevelImpl == null) - return; - if (TopLevelImpl.Input == null) - return; - if (TopLevelImpl.InputRoot == null) + if (TopLevelImpl == null || TopLevelImpl.Input == null || TopLevelImpl.InputRoot == null) + { return; + } + OH_NativeXComponent_TouchEvent touchEvent = default; var result = Ace.OH_NativeXComponent_GetTouchEvent((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &touchEvent); - if (result == (int)OH_NATIVEXCOMPONENT_RESULT.SUCCESS) - for (uint i = 0; i < touchEvent.numPoints; i++) - { - OH_NativeXComponent_TouchPointToolType toolType = default; - float tiltX = 0; - float tiltY = 0; + if (result == (int)OH_NATIVEXCOMPONENT_RESULT.SUCCESS) + { + var toolType = OH_NativeXComponent_TouchPointToolType.OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN; + result = Ace.OH_NativeXComponent_GetTouchPointToolType((OH_NativeXComponent*)XComponentHandle, 0, &toolType); - Ace.OH_NativeXComponent_GetTouchPointToolType((OH_NativeXComponent*)XComponentHandle, i, &toolType); - Ace.OH_NativeXComponent_GetTouchPointTiltX((OH_NativeXComponent*)XComponentHandle, i, &tiltX); - Ace.OH_NativeXComponent_GetTouchPointTiltY((OH_NativeXComponent*)XComponentHandle, i, &tiltY); + // 触摸事件目前支持手指触摸 + if (result != (int)OH_NATIVEXCOMPONENT_RESULT.SUCCESS || toolType != OH_NativeXComponent_TouchPointToolType.OH_NATIVEXCOMPONENT_TOOL_TYPE_FINGER) + { + return; + } + for (uint i = 0; i < touchEvent.numPoints; i++) + { + // 注意:toolType, tiltX, tiltY 当前未被使用。如果将来需要,可以取消注释或实现相关逻辑。 + // OH_NativeXComponent_TouchPointToolType toolType = default; + // float tiltX = 0; + // float tiltY = 0; + // _ = Ace.OH_NativeXComponent_GetTouchPointToolType((OH_NativeXComponent*)xComponentHandle, i, &toolType); + // _ = Ace.OH_NativeXComponent_GetTouchPointTiltX((OH_NativeXComponent*)xComponentHandle, i, &tiltX); + // _ = Ace.OH_NativeXComponent_GetTouchPointTiltY((OH_NativeXComponent*)xComponentHandle, i, &tiltY); var id = touchEvent.touchPoints[(int)i].id; - var type = touchEvent.touchPoints[(int)i].type switch { OH_NativeXComponent_TouchEventType.OH_NATIVEXCOMPONENT_DOWN => RawPointerEventType.TouchBegin, @@ -152,31 +224,101 @@ public override unsafe void DispatchTouchEvent() OH_NativeXComponent_TouchEventType.OH_NATIVEXCOMPONENT_CANCEL => RawPointerEventType.TouchCancel, _ => throw new NotImplementedException() }; - var position = new Point(touchEvent.touchPoints[(int)i].x, touchEvent.touchPoints[(int)i].y) / TopLevelImpl.RenderScaling; var modifiers = RawInputModifiers.None; - if (type == RawPointerEventType.TouchUpdate) modifiers |= RawInputModifiers.LeftMouseButton; + if (type == RawPointerEventType.TouchUpdate) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + var args = new RawTouchEventArgs(_touchDevice, (ulong)touchEvent.touchPoints[(int)i].timeStamp, TopLevelImpl.InputRoot, type, position, RawInputModifiers.LeftMouseButton, id); TopLevelImpl.Input?.Invoke(args); } + } else - Hilog.OH_LOG_ERROR(LogType.LOG_APP, "csharp", "OH_NativeXComponent_GetTouchEvent fail"); + { + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "csharp", $"OH_NativeXComponent_GetTouchEvent fail, result={result}"); + } } + /// + /// 分发鼠标事件。从原生 XComponent 获取鼠标事件数据,并将其转换为 Avalonia 的 RawPointerEventArgs, + /// 然后传递给顶层输入处理器。 + /// + public override unsafe void DispatchMouseEvent() + { + if (TopLevelImpl == null || TopLevelImpl.Input == null || TopLevelImpl.InputRoot == null) + { + return; + } + + OH_NativeXComponent_MouseEvent mouseEvent; + var result = Ace.OH_NativeXComponent_GetMouseEvent((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &mouseEvent); + + if (result == (int)OH_NATIVEXCOMPONENT_RESULT.SUCCESS) + { + if (mouseEvent.action == OH_NativeXComponent_MouseEventAction.OH_NATIVEXCOMPONENT_MOUSE_NONE) + { + return; // 如果没有鼠标事件,直接返回 + } + + var type = mouseEvent.action switch + { + OH_NativeXComponent_MouseEventAction.OH_NATIVEXCOMPONENT_MOUSE_MOVE => RawPointerEventType.Move, + OH_NativeXComponent_MouseEventAction.OH_NATIVEXCOMPONENT_MOUSE_PRESS => mouseEvent.button == OH_NativeXComponent_MouseEventButton.OH_NATIVEXCOMPONENT_LEFT_BUTTON ? RawPointerEventType.LeftButtonDown : RawPointerEventType.RightButtonDown, + OH_NativeXComponent_MouseEventAction.OH_NATIVEXCOMPONENT_MOUSE_RELEASE => mouseEvent.button == OH_NativeXComponent_MouseEventButton.OH_NATIVEXCOMPONENT_LEFT_BUTTON ? RawPointerEventType.LeftButtonUp : RawPointerEventType.RightButtonUp, + _ => throw new NotImplementedException($"Mouse event action {mouseEvent.action} is not implemented") + }; + + var position = new Point(mouseEvent.x, mouseEvent.y) / TopLevelImpl.RenderScaling; + var modifiers = mouseEvent.button == OH_NativeXComponent_MouseEventButton.OH_NATIVEXCOMPONENT_LEFT_BUTTON ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; + + var args = new RawPointerEventArgs(_mouseDevice, (ulong)mouseEvent.timestamp, TopLevelImpl.InputRoot, type, position, modifiers); + + TopLevelImpl.Input?.Invoke(args); + } + else + { + Hilog.OH_LOG_ERROR(LogType.LOG_APP, "csharp", $"OH_NativeXComponent_GetMouseEvent fail, result={result}"); + } + } + + /// + /// 分发悬停事件。 + /// + /// 指示是否处于悬停状态。 + public override unsafe void DispatchHoverEvent(bool isHover) + { + // TODO + // Hilog.OH_LOG_DEBUG(LogType.LOG_APP, "csharp", $"DispatchHoverEvent isHover:{isHover}"); + } + + /// + /// 在 XComponent Surface尺寸或属性发生变化时调用。 + /// 此方法会调整顶层视图的大小,并更新 OpenGL 视口。 + /// public override unsafe void OnSurfaceChanged() { base.OnSurfaceChanged(); ulong width = 0, height = 0; - TopLevelImpl.Resize(); - Ace.OH_NativeXComponent_GetXComponentSize((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &width, + TopLevelImpl?.Resize(); + _ = Ace.OH_NativeXComponent_GetXComponentSize((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &width, &height); - if (UseSoftRenderer && gl != null) gl.Viewport(0, 0, (uint)width, (uint)height); + if (UseSoftRenderer && _gl != null) + { + _gl.Viewport(0, 0, (uint)width, (uint)height); + } } - private AppBuilder CreateAppBuilder() + /// + /// 创建并配置 Avalonia 应用程序构建器。 + /// 派生类可以重写此方法以提供自定义的应用程序配置。 + /// + /// 一个配置好的 实例。 + protected virtual AppBuilder CreateAppBuilder() { return AppBuilder.Configure().UseOpenHarmony(); } diff --git a/Src/Avalonia.OpenHarmony/OHDebugHelper.cs b/Src/Avalonia.OpenHarmony/OHDebugHelper.cs index 6c04ab8..9a08e3f 100644 --- a/Src/Avalonia.OpenHarmony/OHDebugHelper.cs +++ b/Src/Avalonia.OpenHarmony/OHDebugHelper.cs @@ -16,6 +16,11 @@ public static void Debug(string log) AddLog(LogLevel.LOG_DEBUG, log); } + public static void Error(string log) + { + AddLog(LogLevel.LOG_ERROR, log); + } + public static void Error(string title, Exception exception) { AddLog(LogLevel.LOG_ERROR, $"{title}\n{exception}"); diff --git a/Src/Avalonia.OpenHarmony/XComponent.cs b/Src/Avalonia.OpenHarmony/XComponent.cs index 5843f2a..5766d60 100644 --- a/Src/Avalonia.OpenHarmony/XComponent.cs +++ b/Src/Avalonia.OpenHarmony/XComponent.cs @@ -1,46 +1,97 @@ -using OpenHarmony.NDK.Bindings.Native; +using OpenHarmony.NDK.Bindings.Native; namespace Avalonia.OpenHarmony; +/// +/// XComponent 封装了 OpenHarmony 原生 XComponent 的基本操作和生命周期事件。 +/// 用于管理与原生 XComponent 交互的句柄,并为派生类提供生命周期相关的虚方法。 +/// public class XComponent { - public XComponent(IntPtr XComponentHandle, IntPtr WindowHandle) + /// + /// 构造函数,初始化 XComponent 实例并保存原生句柄。 + /// + /// 原生 XComponent 句柄。 + /// 窗口句柄。 + public XComponent(IntPtr xComponentHandle, IntPtr windowHandle) { - this.XComponentHandle = XComponentHandle; - this.WindowHandle = WindowHandle; + XComponentHandle = xComponentHandle; + WindowHandle = windowHandle; } + /// + /// 获取原生 XComponent 句柄。 + /// public IntPtr XComponentHandle { get; } + + /// + /// 获取窗口句柄。 + /// public IntPtr WindowHandle { get; } + /// + /// 获取当前 XComponent 的尺寸。 + /// + /// 返回 Size 结构体,包含宽度和高度。 public virtual unsafe Size GetSize() { - ulong Width = 0; - ulong Height = 0; - Ace.OH_NativeXComponent_GetXComponentSize((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &Width, - &Height); - return new Size(Width, Height); + ulong width = 0; + ulong height = 0; + // 调用原生方法获取 XComponent 的宽高 + _ = Ace.OH_NativeXComponent_GetXComponentSize((OH_NativeXComponent*)XComponentHandle, (void*)WindowHandle, &width, + &height); + return new Size(width, height); } + /// + /// 当 XComponent Surface 创建时调用。可由派生类重写以实现自定义逻辑。 + /// public virtual void OnSurfaceCreated() { } - + /// + /// 当 XComponent Surface 销毁时调用。可由派生类重写以实现自定义逻辑。 + /// public virtual void OnSurfaceDestroyed() { } - + /// + /// 当 XComponent 渲染一帧时调用。可由派生类重写以实现自定义逻辑。 + /// + /// 当前帧时间戳。 + /// 目标帧时间戳。 public virtual void OnSurfaceRendered(ulong timestamp, ulong targetTimestamp) { } + /// + /// 当 XComponent Surface 发生变化(如尺寸变化)时调用。可由派生类重写。 + /// public virtual void OnSurfaceChanged() { } + /// + /// 分发触摸事件。可由派生类重写以处理触摸输入。 + /// public virtual void DispatchTouchEvent() { } -} \ No newline at end of file + + /// + /// 分发鼠标事件。可由派生类重写以处理鼠标输入。 + /// + public virtual void DispatchMouseEvent() + { + } + + /// + /// 分发鼠标悬停事件。可由派生类重写以处理悬停状态变化。 + /// + /// 是否悬停。 + public virtual void DispatchHoverEvent(bool isHover) + { + } +} diff --git a/Src/Entry/XComponentEntry.cs b/Src/Entry/XComponentEntry.cs index c937c9e..d15afb6 100644 --- a/Src/Entry/XComponentEntry.cs +++ b/Src/Entry/XComponentEntry.cs @@ -87,4 +87,25 @@ public static void DispatchTouchEvent(OH_NativeXComponent* component, void* wind return; xComponent.DispatchTouchEvent(); } + + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static void DispatchMouseEvent(OH_NativeXComponent* component, void* window) + { + if (XComponents.TryGetValue((nint)component, out var xComponent) == false) + { + return; + } + xComponent.DispatchMouseEvent(); + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static void DispatchHoverEvent(OH_NativeXComponent* component, bool isHover) + { + if (XComponents.TryGetValue((nint)component, out var xComponent) == false) + { + return; + } + xComponent.DispatchHoverEvent(isHover); + } } diff --git a/Src/Entry/napi_init.cs b/Src/Entry/napi_init.cs index 6e226ad..22e3942 100644 --- a/Src/Entry/napi_init.cs +++ b/Src/Entry/napi_init.cs @@ -59,6 +59,14 @@ public static unsafe napi_value Init(napi_env env, napi_value exports) g_ComponentCallback.OnSurfaceDestroyed = &XComponentEntry.OnSurfaceDestroyed; g_ComponentCallback.DispatchTouchEvent = &XComponentEntry.DispatchTouchEvent; Ace.OH_NativeXComponent_RegisterCallback(nativeXComponent, (OH_NativeXComponent_Callback*)p); + + + // 注册键盘事件回调 + var mouseEventCallbackPointer = Marshal.AllocHGlobal(sizeof(OH_NativeXComponent_MouseEvent_Callback)); + ref var g_ComponentMouseEventCallback = ref Unsafe.AsRef((void*)mouseEventCallbackPointer); + g_ComponentMouseEventCallback.DispatchMouseEvent = &XComponentEntry.DispatchMouseEvent; + g_ComponentMouseEventCallback.DispatchHoverEvent = &XComponentEntry.DispatchHoverEvent; + _ = Ace.OH_NativeXComponent_RegisterMouseEventCallback(nativeXComponent, (OH_NativeXComponent_MouseEvent_Callback*)mouseEventCallbackPointer); } } diff --git a/Src/Example/AOOH_Gallery/Views/MainView.axaml b/Src/Example/AOOH_Gallery/Views/MainView.axaml index ae95ce7..d0c3ab8 100644 --- a/Src/Example/AOOH_Gallery/Views/MainView.axaml +++ b/Src/Example/AOOH_Gallery/Views/MainView.axaml @@ -1,9 +1,10 @@ + + + diff --git a/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml b/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml new file mode 100644 index 0000000..69cf86f --- /dev/null +++ b/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml @@ -0,0 +1,32 @@ + + + + + +