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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml.cs b/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml.cs
new file mode 100644
index 0000000..f7ed115
--- /dev/null
+++ b/Src/Example/AOOH_Gallery/Views/TouchAndMousePage.axaml.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Specialized;
+
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.OpenHarmony;
+using Avalonia.Threading;
+
+namespace AOOH_Gallery.Views;
+
+public partial class TouchAndMousePage : UserControl
+{
+ public TouchAndMousePage()
+ {
+ InitializeComponent();
+ (OHDebugHelper.Logs as INotifyCollectionChanged).CollectionChanged += PreferencesPage_CollectionChanged;
+
+ TestButton.PointerEntered += OnPointerEntered;
+ TestButton.PointerExited += OnPointerExited;
+ TestButton.PointerMoved += OnPointerMoved;
+ TestButton.PointerPressed += OnPointerPressed;
+ TestButton.PointerReleased += OnPointerReleased;
+ TestButton.Click += OnClick;
+
+ TestTextBlock.PointerEntered += OnPointerEntered;
+ TestTextBlock.PointerExited += OnPointerExited;
+ TestTextBlock.PointerMoved += OnPointerMoved;
+ TestTextBlock.PointerPressed += OnPointerPressed;
+ TestTextBlock.PointerReleased += OnPointerReleased;
+ }
+
+ private void OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ OHDebugHelper.Info($"Button Clicked");
+ }
+
+ private void OnPointerExited(object? sender, PointerEventArgs e)
+ {
+ var position = e.GetPosition(this);
+ var pointerId = e.Pointer.Id;
+ var pointerType = e.Pointer.Type.ToString();
+ var pressure = e.GetCurrentPoint(this).Properties.Pressure;
+ var isLeftButtonPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+ var timestamp = e.Timestamp;
+ OHDebugHelper.Info($"PointerExited Pointer:{e.Pointer} Type:{pointerType} Id:{pointerId} " +
+ $"Position:({position.X:F2},{position.Y:F2}) Pressure:{pressure:F2} " +
+ $"LeftButton:{isLeftButtonPressed} Timestamp:{timestamp}");
+
+ }
+ private void OnPointerMoved(object? sender, PointerEventArgs e)
+ {
+ var position = e.GetPosition(this);
+ var pointerId = e.Pointer.Id;
+ var pointerType = e.Pointer.Type.ToString();
+ var pressure = e.GetCurrentPoint(this).Properties.Pressure;
+ var isLeftButtonPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+ var timestamp = e.Timestamp;
+ OHDebugHelper.Info($"PointerMoved Pointer:{e.Pointer} Type:{pointerType} Id:{pointerId} " +
+ $"Position:({position.X:F2},{position.Y:F2}) Pressure:{pressure:F2} " +
+ $"LeftButton:{isLeftButtonPressed} Timestamp:{timestamp}");
+
+ }
+ private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+
+ var position = e.GetPosition(this);
+ var pointerId = e.Pointer.Id;
+ var pointerType = e.Pointer.Type.ToString();
+ var pressure = e.GetCurrentPoint(this).Properties.Pressure;
+ var isLeftButtonPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+ var timestamp = e.Timestamp;
+ OHDebugHelper.Info($"PointerPressed Pointer:{e.Pointer} Type:{pointerType} Id:{pointerId} " +
+ $"Position:({position.X:F2},{position.Y:F2}) Pressure:{pressure:F2} " +
+ $"LeftButton:{isLeftButtonPressed} Timestamp:{timestamp}");
+ }
+
+ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ var position = e.GetPosition(this);
+ var pointerId = e.Pointer.Id;
+ var pointerType = e.Pointer.Type.ToString();
+ var pressure = e.GetCurrentPoint(this).Properties.Pressure;
+ var isLeftButtonPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+ var timestamp = e.Timestamp;
+ OHDebugHelper.Info($"PointerReleased Pointer:{e.Pointer} Type:{pointerType} Id:{pointerId} " +
+ $"Position:({position.X:F2},{position.Y:F2}) Pressure:{pressure:F2} " +
+ $"LeftButton:{isLeftButtonPressed} Timestamp:{timestamp}");
+ }
+
+
+
+ private void OnPointerEntered(object? sender, Avalonia.Input.PointerEventArgs e)
+ {
+ var position = e.GetPosition(this);
+ var pointerId = e.Pointer.Id;
+ var pointerType = e.Pointer.Type.ToString();
+ var pressure = e.GetCurrentPoint(this).Properties.Pressure;
+ var isLeftButtonPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+ var timestamp = e.Timestamp;
+
+ OHDebugHelper.Info($"PointerEntered Pointer:{e.Pointer} Type:{pointerType} Id:{pointerId} " +
+ $"Position:({position.X:F2},{position.Y:F2}) Pressure:{pressure:F2} " +
+ $"LeftButton:{isLeftButtonPressed} Timestamp:{timestamp}");
+ }
+
+ private void PreferencesPage_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ if (MessageListBox.ItemCount > 0)
+ {
+ MessageListBox.ScrollIntoView(MessageListBox.ItemCount - 1);
+ }
+ });
+ }
+}
diff --git a/ThirdParty/OpenHarmony.NET.Runtime b/ThirdParty/OpenHarmony.NET.Runtime
index 6f9199d..bfb1fed 160000
--- a/ThirdParty/OpenHarmony.NET.Runtime
+++ b/ThirdParty/OpenHarmony.NET.Runtime
@@ -1 +1 @@
-Subproject commit 6f9199df83bcb307d9f80e1e19f561f44cb32086
+Subproject commit bfb1fed93e76fda1a24aed36e363ce09a23508cd