diff --git a/OAT.xml b/OAT.xml index 8c9842e9ed6a58789c5f21add8b50fbbae20560e..896fc38a47595b8c31b0406959ca1d9aa7cfddf6 100644 --- a/OAT.xml +++ b/OAT.xml @@ -189,6 +189,12 @@ Note:If the text contains special characters, please escape them according to th + + + + + + diff --git a/code/BasicFeature/Native/NativeXComponent/.gitignore b/code/BasicFeature/Native/NativeXComponent/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5 b/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..33e40e31af85a79aaa9242b628f577e0157a7ee4 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.nativexcomponent", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..79421a57da43fb2a471e3ade57c18b8d845b3647 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "NativeXComponent" + } + ] +} diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3 Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png differ diff --git a/code/BasicFeature/Native/NativeXComponent/README_zh.md b/code/BasicFeature/Native/NativeXComponent/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..521314df5fa9518c035b68f9c044ef21d5883e08 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/README_zh.md @@ -0,0 +1,111 @@ +# XComponent + +### 介绍 + +本示例主要介绍开发者如何使用ArkUI NDK API创建XComponent组件进行自定义绘制。主要包括:创建组件,获取ArkUI_NodeHandle和OH_ArkUI_SurfaceHolder实例,注册Surface生命周期和无障碍回调,设置可变帧率,获取NativeWindow后使用OpenGL ES/EGL接口在XComponent组件上进行图形绘制。功能主要包括绘制一个五角星,期望帧率设置,点击按钮开启/关闭软键盘,并可以通过点击XComponent区域改变五角星的颜色。 + +### 效果预览 + +| 绘制五角星 | 改变颜色 | +|-----------------------------------------------|-----------------------------------------------------| +| ![draw star](screenshots/device/drawStar.jpeg) | ![change color](screenshots/device/changeColor.jpeg) | + +使用说明 + +1. 安装编译生成的hap包,并打开应用。 +3. 点击XComponent组件区域(页面中蓝色五角星所在区域)改变五角星颜色。 +3. 点击“单指点击XComponent软键盘消失”对应区域,软键盘拉起;点击XComponent组件区域,软键盘消失。 +4. 点击needSoftKeyboard=false 按钮,点击“单指点击XComponent软键盘消失”对应区域,软键盘拉起;点击XComponent组件区域,软键盘不消失。 +5. 拖动期望帧率设置下的滑动条,可以控制期望帧率,具体通过打印日志的速度来衡量,日志检索"OnFrameCallback count"关键字。 + + +### 工程目录 + +``` +├──entry/src/main +│ ├──cpp // C++代码区 +│ │ ├──CMakeLists.txt // CMake配置文件 +│ │ ├──napi_init.cpp // Napi模块注册 +│ │ ├──manager // 生命周期管理模块 +│ │ │ ├──plugin_manager.cpp +│ │ │ └──plugin_manager.h +│ │ ├──render // 渲染模块 +│ │ │ ├──EGLConst.h +│ │ │ ├──EGLRender.cpp +│ │ │ └──EGLRender.h +| | ├──types +| | | ├──libnativerender +| | | | ├──Index.d.ts +| | | | ├──oh-package.json5 +│ ├──ets // ets代码区 +│ │ ├──entryability +│ │ │ ├──EntryAbility.ts // 程序入口类 +| | ├──entrybackupability +| | | ├──EntryBackupAbility.ets +│ │ ├──pages // 页面文件 +│ │ ├──Index.ets // 主界面 +| ├──resources // 资源文件目录 +``` + +### 具体实现 + +通过在IDE中创建Native c++ 工程,在c++代码中定义对外接口为BindNode,在ArkTS侧调用该接口用于注册Surface生命周期、事件和无障碍等回调,在回调逻辑中实现绘制五角星的逻辑。 + +基于OH_ArkUI_SurfaceHolder实例,在Native侧调用OH_ArkUI_XComponent_GetNativeWindow创建NativeWindow实例并初始化 +EGL环境。在Native侧的OnsurfaceChanged回调中,传入OH_ArkUI_SurfaceHolder、宽和高,并以此为输入调用EGL相关的接口改变对应NativeWindow的尺寸和内容。 + +源码参考:[main目录](entry/src/main/)下的文件。涉及到的相关接口: + +| 接口名 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| OH_ArkUI_QueryModuleInterfaceByName(ArkUI_NativeAPIVariantKind type, const char* structName) | 获取指定类型的Native模块接口集合。 | +| OH_ArkUI_XComponent_GetNativeWindow(OH_ArkUI_SurfaceHolder* surfaceHolder) | 获取与OH_ArkUI_SurfaceHolder实例关联的nativeWindow。 | +| OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | 从OH_ArkUI_SurfaceHolder实例中移除先前添加的Surface生命周期回调。 | +| OH_ArkUI_SurfaceCallback_Dispose(OH_ArkUI_SurfaceCallback* callback) | 释放OH_ArkUI_SurfaceCallback对象。 | +| OH_ArkUI_SurfaceHolder_Dispose(OH_ArkUI_SurfaceHolder* surfaceHolder) | 释放OH_ArkUI_SurfaceHolder对象。 | +| OH_ArkUI_NodeEvent_GetEventType(ArkUI_NodeEvent* event) | 从组件事件获取事件类型。 | +| OH_ArkUI_NodeEvent_GetNodeHandle(ArkUI_NodeEvent* event) | 获取触发组件事件的组件对象。 | +| OH_ArkUI_GetNodeHandleFromNapiValue(napi_env env, napi_value frameNode, ArkUI_NodeHandle* handle) | 获取ArkTS侧创建的FrameNode节点对象映射到native侧的ArkUI_NodeHandle。 | +| OH_ArkUI_SurfaceHolder_Create(ArkUI_NodeHandle node) | 从XComponent节点创建一个OH_ArkUI_SurfaceHolder对象。 | +| OH_ArkUI_SurfaceCallback_Create() | 创建一个OH_ArkUI_SurfaceCallback对象。 | +| OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceCreated)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceCreated回调。 | +| OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceChanged)(OH_ArkUI_SurfaceHolder* surfaceHolder, uint64_t width, uint64_t height)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceChanged回调。 | +| OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceDestroyed)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceDestroyed回调。 | +| OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceShow)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceShow回调。 | +| OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceHide)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceHide回调。 | +| OH_ArkUI_XComponent_RegisterOnFrameCallback(ArkUI_NodeHandle node, void (*callback)(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp)) | 为XComponent节点注册onFrame回调。 | +| OH_ArkUI_SurfaceHolder_AddSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | 往OH_ArkUI_SurfaceHolder实例注册OH_ArkUI_SurfaceCallback对象。 | +| OH_ArkUI_AccessibilityProvider_Create(ArkUI_NodeHandle node) | 从XComponent节点创建一个ArkUI_AccessibilityProvider对象。 | +| OH_ArkUI_XComponent_UnregisterOnFrameCallback(ArkUI_NodeHandle node) | 取消注册XComponent节点的onFrame回调。 | +| OH_ArkUI_AccessibilityProvider_Dispose(ArkUI_AccessibilityProvider* provider) | 释放ArkUI_AccessibilityProvider对象。 | +| OH_ArkUI_XComponent_SetExpectedFrameRateRange(ArkUI_NodeHandle node, OH_NativeXComponent_ExpectedRateRange range) | 为XComponent节点设置预期的帧率范围。 | +| OH_ArkUI_XComponent_SetNeedSoftKeyboard(ArkUI_NodeHandle node, bool needSoftKeyboard) | 设置XComponent节点在获得焦点时是否需要显示软键盘。 | + + +### 相关权限 + +不涉及。 + +### 依赖 + +不涉及。 + +### 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:rk3568 + +2. 本示例为Stage模型,支持API20版本SDK,SDK版本号(API Version 20 Release),镜像版本号(6.0 Release) + +3. 本示例需要使用DevEco Studio 版本号(6.0.0 Release)及以上版本才可编译运行 + +### 下载 + +如需单独下载本工程,执行如下命令: + +``` +git init +git config core.sparsecheckout true +echo code/BasicFeature/Native/NativeXComponent/ > .git/info/sparse-checkout +git remote add origin https://gitee.com/openharmony/applications_app_samples.git +git pull origin master +``` diff --git a/code/BasicFeature/Native/NativeXComponent/build-profile.json5 b/code/BasicFeature/Native/NativeXComponent/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4d722c4fd8dbfcbfde8df7af59d9aa69c9fbbc58 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/build-profile.json5 @@ -0,0 +1,49 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "material": { + "certpath": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.cer", + "storePassword": "0000001BEC892530464D6962B4D77B1E79E860C5875717DE664123F7D2E55BEC70B43186A43D9DD1956329", + "keyAlias": "debugKey", + "keyPassword": "0000001BE98DB5A9C0AF95C89DF9A44792F28CBCC21C552EABE1D78459C56AC35BB46E34BEED2E591E1202", + "profile": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.p12" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compileSdkVersion": 12, + "compatibleSdkVersion": 12, + "runtimeOS": "OpenHarmony", + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/.gitignore b/code/BasicFeature/Native/NativeXComponent/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5 b/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b57619b89ac0041c92a141816fea427abb42bacd --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5 @@ -0,0 +1,40 @@ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": ["arm64-v8a", "armeabi-v7a", "x86_64"] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts b/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..811a0b10e04f84946f75b69e8b72f3bf7ec35000 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libnativerender.so": "file:./src/main/cpp/types/libnativerender" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..382141fc2d6da550b6a5108b36455c3e910f03a4 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,67 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(LCNXComponent2) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED PACKAGE_FIND_FILE) + include(${PACKAGE_FIND_FILE}) +endif() + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/render + ${NATIVERENDER_ROOT_PATH}/manager) + +add_library(nativerender SHARED + render/EGLRender.cpp + manager/plugin_manager.cpp + napi_init.cpp) +find_library( + # Sets the name of the path variable. + EGL-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + EGL +) + +find_library( + # Sets the name of the path variable. + GLES-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + GLESv3 +) + +find_library( + # Sets the name of the path variable. + hilog-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + hilog_ndk.z +) + +find_library( + # Sets the name of the path variable. + libace-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_ndk.z +) + +find_library( + # Sets the name of the path variable. + libnapi-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + ace_napi.z +) + +find_library( + # Sets the name of the path variable. + libuv-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + uv +) + +target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative_window.so) diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ed521a4eb735340c953dc7e94d8001b5ee867e4f --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "plugin_manager.h" +#include +#include +#include +#include + +namespace NativeXComponentSample { +std::unordered_map PluginManager::nodeHandleMap_; +std::unordered_map PluginManager::renderMap_; +std::unordered_map PluginManager::callbackMap_; +std::unordered_map PluginManager::surfaceHolderMap_; +ArkUI_AccessibilityProvider *PluginManager::provider_ = nullptr; +ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast( + OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1")); +namespace { +std::string value2String(napi_env env, napi_value value) // 将napi_value转化为string类型的变量 +{ + size_t stringSize = 0; + napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize); + std::string valueString; + valueString.resize(stringSize); + napi_get_value_string_utf8(env, value, &valueString[0], stringSize + 1, &stringSize); + return valueString; +} + +void OnSurfaceCreated(OH_ArkUI_SurfaceHolder *holder) +{ + auto window = OH_ArkUI_XComponent_GetNativeWindow(holder); // 获取native window + auto render = new EGLRender(); + PluginManager::renderMap_[holder] = render; + render->SetUpEGLContext(window); +} + +void OnSurfaceChanged(OH_ArkUI_SurfaceHolder *holder, uint64_t width, uint64_t height) +{ + if (PluginManager::renderMap_.count(holder)) { + auto render = PluginManager::renderMap_[holder]; + render->SetEGLWindowSize(width, height); // 设置绘制区域大小 + render->DrawStar(true); // 绘制五角星 + } +} + +void OnSurfaceDestroyed(OH_ArkUI_SurfaceHolder *holder) +{ + OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "onBind", "on destroyed"); + if (PluginManager::renderMap_.count(holder)) { // 销毁render对象 + auto render = PluginManager::renderMap_[holder]; + delete render; + PluginManager::renderMap_.erase(holder); + } + if (PluginManager::callbackMap_.count(holder)) { + auto callback = PluginManager::callbackMap_[holder]; + OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(holder, callback); // 移除SurfaceCallback + OH_ArkUI_SurfaceCallback_Dispose(callback); // 销毁surfaceCallback + PluginManager::callbackMap_.erase(holder); + } + OH_ArkUI_SurfaceHolder_Dispose(holder); // 销毁surfaceHolder +} + +void OnSurfaceShow(OH_ArkUI_SurfaceHolder *holder) +{ + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface show"); +} + +void OnSurfaceHide(OH_ArkUI_SurfaceHolder *holder) +{ + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface hide"); +} + +void OnFrameCallback(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp) +{ + if (!PluginManager::surfaceHolderMap_.count(node)) { + return; + } + static uint64_t count = 0; + count++; + if (count % FRAME_COUNT == 0) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "OnFrameCallback count = %{public}ld", count); + } +} + +void onEvent(ArkUI_NodeEvent *event) +{ + auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); // 获取组件事件类型 + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on event"); + if (eventType == NODE_TOUCH_EVENT) { + ArkUI_NodeHandle handle = OH_ArkUI_NodeEvent_GetNodeHandle(event); // 获取触发该事件的组件对象 + auto holder = PluginManager::surfaceHolderMap_[handle]; + if (PluginManager::renderMap_.count(holder)) { + auto render = PluginManager::renderMap_[holder]; + render->DrawStar(false); // 绘制五角星 + } + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on touch"); + } +} +} // namespace + +napi_value PluginManager::BindNode(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + napi_value args[2] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + std::string nodeId = value2String(env, args[0]); + ArkUI_NodeHandle handle; + OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取 nodeHandle + OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取 SurfaceHolder + nodeHandleMap_[nodeId] = handle; + surfaceHolderMap_[handle] = holder; + auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建 SurfaceCallback + callbackMap_[holder] = callback; + OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreated); // 注册OnSurfaceCreated回调 + OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChanged); // 注册OnSurfaceChanged回调 + OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyed); // 注册OnSurfaceDestroyed回调 + OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(callback, OnSurfaceShow); // 注册OnSurfaceShow回调 + OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(callback, OnSurfaceHide); // 注册OnSurfaceHide回调 + OH_ArkUI_XComponent_RegisterOnFrameCallback(handle, OnFrameCallback); // 注册OnFrameCallback回调 + OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调 + if (!nodeAPI->addNodeEventReceiver(handle, onEvent)) { // 添加事件监听,返回成功码 0 + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error"); + } + if (!nodeAPI->registerNodeEvent(handle, NODE_TOUCH_EVENT, 0, nullptr)) { // 用C接口注册touch事件,返回成功码 0 + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error"); + } + provider_ = OH_ArkUI_AccessibilityProvider_Create(handle); // 创建一个ArkUI_AccessibilityProvider类型的对象 + /** + * 获取ArkUI_AccessibilityProvider后,如果注册无障碍回调函数请参考: + * https://gitee.com/openharmony/docs/blob/OpenHarmony-5.1.0-Release/zh-cn/application-dev/ui/ndk-accessibility-xcomponent.md + * **/ + return nullptr; +} + +napi_value PluginManager::UnbindNode(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + std::string nodeId = value2String(env, args[0]); + auto node = nodeHandleMap_[nodeId]; + OH_ArkUI_XComponent_UnregisterOnFrameCallback(node); // 解注册帧回调 + OH_ArkUI_AccessibilityProvider_Dispose(provider_); // 销毁 ArkUI_AccessibilityProvider + nodeAPI->disposeNode(node); // 销毁nodeHandle + nodeHandleMap_.erase(nodeId); + return nullptr; +} + +napi_value PluginManager::SetFrameRate(napi_env env, napi_callback_info info) +{ + size_t argc = 4; + napi_value args[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + std::string nodeId = value2String(env, args[0]); + auto node = nodeHandleMap_[nodeId]; + + int32_t min = 0; + napi_get_value_int32(env, args[FIRST_ARG], &min); + + int32_t max = 0; + napi_get_value_int32(env, args[SECOND_ARG], &max); + + int32_t expected = 0; + napi_get_value_int32(env, args[THIRD_ARG], &expected); + OH_NativeXComponent_ExpectedRateRange range = {.min = min, .max = max, .expected = expected}; + OH_ArkUI_XComponent_SetExpectedFrameRateRange(node, range); // 设置期望帧率 + return nullptr; +} + +napi_value PluginManager::SetNeedSoftKeyboard(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + napi_value args[2] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + std::string nodeId = value2String(env, args[0]); + auto node = nodeHandleMap_[nodeId]; + + bool needSoftKeyboard = false; + napi_get_value_bool(env, args[1], &needSoftKeyboard); + OH_ArkUI_XComponent_SetNeedSoftKeyboard(node, needSoftKeyboard); // 设置是否需要软键盘 + return nullptr; +} +} // namespace NativeXComponentSample \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..ac59f7b645c786f2cf556a892197f9a8845292a0 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEXCOMPONENT_PLUGIN_MANAGER_H +#define NATIVEXCOMPONENT_PLUGIN_MANAGER_H + +#include "render/EGLRender.h" +#include +#include "napi/native_api.h" +#include +#include +#include + +namespace NativeXComponentSample { +constexpr const int FIRST_ARG = 1; +constexpr const int SECOND_ARG = 2; +constexpr const int THIRD_ARG = 3; +constexpr const int FRAME_COUNT = 50; +class PluginManager { +public: + ~PluginManager(); + static napi_value BindNode(napi_env env, napi_callback_info info); + static napi_value UnbindNode(napi_env env, napi_callback_info info); + static napi_value SetFrameRate(napi_env env, napi_callback_info info); + static napi_value SetNeedSoftKeyboard(napi_env env, napi_callback_info info); + +public: + static std::unordered_map nodeHandleMap_; + static std::unordered_map renderMap_; + static std::unordered_map callbackMap_; + static std::unordered_map surfaceHolderMap_; + static ArkUI_AccessibilityProvider *provider_; +}; +} // namespace NativeXComponentSample +#endif // NATIVEXCOMPONENT_PLUGIN_MANAGER_H diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..290c5c093d0564c05f8ab76ab94e2822aa1b0c82 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "manager/plugin_manager.h" + +namespace NativeXComponentSample { + +// 在napi_init.cpp文件中,Init方法注册接口函数,从而将封装的C++方法传递出来,供ArkTS侧调用 +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + // 向ArkTS侧暴露接口 + napi_property_descriptor desc[] = { + {"bindNode", nullptr, PluginManager::BindNode, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"unbindNode", nullptr, PluginManager::UnbindNode, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"setFrameRate", nullptr, PluginManager::SetFrameRate, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"setNeedSoftKeyboard", nullptr, PluginManager::SetNeedSoftKeyboard, nullptr, nullptr, nullptr, napi_default, + nullptr}, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +// 编写接口的描述信息,根据实际需要可以修改对应参数 +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + // 入口函数 + .nm_register_func = Init, // 指定加载对应模块时的回调函数 + // 模块名称 + .nm_modname = + "nativerender", // 指定模块名称,对于XComponent相关开发,这个名称必须和ArkTS侧XComponent中libraryname的值保持一致 + .nm_priv = ((void *)0), + .reserved = {0}, +}; +} // namespace NativeXComponentSample + +// __attribute__((constructor))修饰的方法由系统自动调用,使用Node-API接口napi_module_register()传入模块描述信息进行模块注册 +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&NativeXComponentSample::demoModule); +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h new file mode 100644 index 0000000000000000000000000000000000000000..60c0ad2c3a3c8e6b98b2ac0e016f7037609f8614 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEXCOMPONENT_EGLCONST_H +#define NATIVEXCOMPONENT_EGLCONST_H +#include +#include +#include + +const unsigned int LOG_PRINT_DOMAIN = 0xFF00; + +/** + * Program error. + */ +const GLuint PROGRAM_ERROR = 0; + +/** + * Position error. + */ +const GLint POSITION_ERROR = -1; + +/** + * Default x position. + */ +const int DEFAULT_X_POSITION = 0; + +/** + * Default y position. + */ +const int DEFAULT_Y_POSITION = 0; + +/** + * Gl red default. + */ +const GLfloat GL_RED_DEFAULT = 0.0; + +/** + * Gl green default. + */ +const GLfloat GL_GREEN_DEFAULT = 0.0; + +/** + * Gl blue default. + */ +const GLfloat GL_BLUE_DEFAULT = 0.0; + +/** + * Gl alpha default. + */ +const GLfloat GL_ALPHA_DEFAULT = 1.0; + +/** + * Pointer size. + */ +const GLint POINTER_SIZE = 2; + +/** + * Triangle fan size. + */ +const GLsizei TRIANGLE_FAN_SIZE = 4; + +/** + * Fifty percent. + */ +const float FIFTY_PERCENT = 0.5; + +/** + * Position handle name. + */ +const char POSITION_NAME[] = "a_position"; + +/** + * Background color #f4f4f4. + */ +const GLfloat BACKGROUND_COLOR[] = {244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f}; + +/** + * Draw color #7E8FFB. + */ +const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f}; + +/** + * Change color #92D6CC. + */ +const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f}; + +/** + * Background area. + */ +const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f}; + +const EGLint ATTRIB_LIST[] = { + // Key,value. + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + // End. + EGL_NONE}; + +const EGLint CONTEXT_ATTRIBS[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + +/** + * Vertex shader. + */ +const char VERTEX_SHADER[] = "#version 300 es\n" + "layout(location = 0) in vec4 a_position;\n" + "layout(location = 1) in vec4 a_color; \n" + "out vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_Position = a_position; \n" + " v_color = a_color; \n" + "} \n"; + +/** + * Fragment shader. + */ +const char FRAGMENT_SHADER[] = "#version 300 es\n" + "precision mediump float; \n" + "in vec4 v_color; \n" + "out vec4 fragColor; \n" + "void main() \n" + "{ \n" + " fragColor = v_color; \n" + "} \n"; +#endif // NATIVEXCOMPONENT_EGLCONST_H diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..46b80cedd77341ae7cf18adee6e7959f401f2ed9 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EGLRender.h" +#include "EGLConst.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +void Rotate2d(GLfloat centerX, GLfloat centerY, GLfloat *rotateX, GLfloat *rotateY, GLfloat theta) +{ + GLfloat tempX = cos(theta) * (*rotateX - centerX) - sin(theta) * (*rotateY - centerY); + GLfloat tempY = sin(theta) * (*rotateX - centerX) + cos(theta) * (*rotateY - centerY); + *rotateX = tempX + centerX; + *rotateY = tempY + centerY; +} + +GLuint LoadShader(GLenum type, const char *shaderSrc) +{ + if ((type <= 0) || (shaderSrc == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader type or shaderSrc error"); + return PROGRAM_ERROR; + } + + GLuint shader = glCreateShader(type); + if (shader == 0) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader unable to load shader"); + return PROGRAM_ERROR; + } + + // The gl function has no return value. + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled != 0) { + return shader; + } + + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen <= 1) { + glDeleteShader(shader); + return PROGRAM_ERROR; + } + + char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); + if (infoLog != nullptr) { + memset(infoLog, 0, infoLen + 1); + glGetShaderInfoLog(shader, infoLen, nullptr, infoLog); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCompileShader error = %s", infoLog); + free(infoLog); + infoLog = nullptr; + } + glDeleteShader(shader); + return PROGRAM_ERROR; +} + +GLuint CreateProgram(const char *vertexShader, const char *fragShader) +{ + if ((vertexShader == nullptr) || (fragShader == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", + "createProgram: vertexShader or fragShader is null"); + return PROGRAM_ERROR; + } + + GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader); + if (vertex == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram vertex error"); + return PROGRAM_ERROR; + } + + GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader); + if (fragment == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram fragment error"); + return PROGRAM_ERROR; + } + + GLuint program = glCreateProgram(); + if (program == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram program error"); + glDeleteShader(vertex); + glDeleteShader(fragment); + return PROGRAM_ERROR; + } + + // The gl function has no return value. + glAttachShader(program, vertex); + glAttachShader(program, fragment); + glLinkProgram(program); + + GLint linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (linked != 0) { + glDeleteShader(vertex); + glDeleteShader(fragment); + return program; + } + + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram linked error"); + GLint infoLen = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen > 1) { + char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); + memset(infoLog, 0, infoLen + 1); + glGetProgramInfoLog(program, infoLen, nullptr, infoLog); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glLinkProgram error = %s", infoLog); + free(infoLog); + infoLog = nullptr; + } + glDeleteShader(vertex); + glDeleteShader(fragment); + glDeleteProgram(program); + return PROGRAM_ERROR; +} +} // namespace + +bool EGLRender::SetUpEGLContext(void *window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "EglContextInit execute"); + eglWindow_ = (EGLNativeWindowType)(window); + // Init display. + eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (eglDisplay_ == EGL_NO_DISPLAY) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglGetDisplay: unable to get EGL display"); + return false; + } + EGLint majorVersion; + EGLint minorVersion; + if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", + "eglInitialize: unable to get initialize EGL display"); + return false; + }; + // Select configuration. + const EGLint maxConfigSize = 1; + EGLint numConfigs; + if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglChooseConfig: unable to choose configs"); + return false; + }; + // CreateEnvironment. + // Create surface. + eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL); + if (eglSurface_ == nullptr) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", + "eglCreateWindowSurface: unable to create surface"); + return false; + } + if (eglSurface_ == nullptr) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", + "eglCreateWindowSurface: unable to create surface"); + return false; + } + // Create context. + eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS); + if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglMakeCurrent failed"); + return false; + } + // Create program. + program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (program_ == PROGRAM_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "CreateProgram: unable to create program"); + return false; + } + return true; +} + +GLint EGLRender::PrepareDraw() +{ + if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) || + (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "PrepareDraw: param error"); + return POSITION_ERROR; + } + + // The gl function has no return value. + glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_); + glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT); + glClear(GL_COLOR_BUFFER_BIT); + glUseProgram(program_); + + return glGetAttribLocation(program_, POSITION_NAME); +} + +void EGLRender::DrawStar(bool drawColor) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "Draw"); + GLint position = PrepareDraw(); + if (position == POSITION_ERROR) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw get position failed"); + return; + } + + if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw background failed"); + return; + } + + // Divided into five quadrilaterals and calculate one of the quadrilateral's Vertices + GLfloat rotateX = 0; + GLfloat rotateY = FIFTY_PERCENT * height_; + GLfloat centerX = 0; + // Convert DEG(54° & 18°) to RAD + GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18); + // Convert DEG(18°) to RAD + GLfloat leftX = -rotateY * (M_PI / 180 * 18); + GLfloat leftY = 0; + // Convert DEG(18°) to RAD + GLfloat rightX = rotateY * (M_PI / 180 * 18); + GLfloat rightY = 0; + + const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_, + rotateX / width_, rotateY / height_, rightX / width_, rightY / height_}; + auto color = drawColor ? DRAW_COLOR : CHANGE_COLOR; + if (!ExecuteDraw(position, color, shapeVertices)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed"); + return; + } + + // Convert DEG(72°) to RAD + GLfloat rad = M_PI / 180 * 72; + // Rotate four times + for (int i = 0; i < 4; ++i) { + Rotate2d(centerX, centerY, &rotateX, &rotateY, rad); + Rotate2d(centerX, centerY, &leftX, &leftY, rad); + Rotate2d(centerX, centerY, &rightX, &rightY, rad); + + const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_, + rotateX / width_, rotateY / height_, rightX / width_, rightY / height_}; + + if (!ExecuteDraw(position, color, shapeVertices)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed"); + return; + } + } + // 将绘制命令提交给GPU,GPU执行完成后将渲染结果显示到屏幕 + glFlush(); + glFinish(); + if (!eglSwapBuffers(eglDisplay_, eglSurface_)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw FinishDraw failed"); + return; + } +} + +bool EGLRender::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]) +{ + if ((position > 0) || (color == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "ExecuteDraw: param error"); + return false; + } + + // The gl function has no return value. + glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices); + glEnableVertexAttribArray(position); + glVertexAttrib4fv(1, color); + glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE); + glDisableVertexAttribArray(position); + + return true; +} + +void EGLRender::SetEGLWindowSize(int width, int height) +{ + width_ = width; + height_ = height; +} + +EGLRender::~EGLRender() +{ + if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); + } + + if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); + } + + if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) { + OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h new file mode 100644 index 0000000000000000000000000000000000000000..7eb3bb812b2ed37687898e5bf9c66e2de44e1733 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVEXCOMPONENT_EGLRENDER_H +#define NATIVEXCOMPONENT_EGLRENDER_H + +#include "EGLConst.h" +#include +#include +#include +#include +#include + +class EGLRender { +public: + bool SetUpEGLContext(void *window); + void SetEGLWindowSize(int width, int height); + void DrawStar(bool drawColor); + + std::string xcomponentId; + EGLNativeWindowType eglWindow_; + + EGLDisplay eglDisplay_ = EGL_NO_DISPLAY; + EGLConfig eglConfig_ = EGL_NO_CONFIG_KHR; + EGLSurface eglSurface_ = EGL_NO_SURFACE; + EGLContext eglContext_ = EGL_NO_CONTEXT; + GLuint program_; + int width_ = 0; + int height_ = 0; + ~EGLRender(); + +private: + GLint PrepareDraw(); + bool ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]); +}; +#endif // NATIVEXCOMPONENT_EGLRENDER_H diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ede5fd44a7b0569ed2e24d1e0b6bd39a11ebd509 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const bindNode: (id: string, node: object) => void; +export const unbindNode: (id: string) => void; +export const setFrameRate: (id: string, min: number, max: number, expected: number) => void; +export const setNeedSoftKeyboard: (id: string, needSoftKeyborad: boolean) => void; diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..78545bfb4030791657a728b8cc790495fd7e29be --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5 @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "name": "libnativerender.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..6a1b23532e867a0d7c63e1b6c1b9c21b0350ae88 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +}; diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..2a89be4fd2886b169f4a62f1a1f3cbf7b9211c08 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(0x0000, 'testTag', 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..b2fe86027d78fa2e4b17aa0c54fac5c48106b129 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import native from 'libnativerender.so'; + +@Entry +@Component +struct Index { + xcomponentId: string = 'xcp' + (new Date().getTime()); + @State isShow: boolean = true; + @State minRate: number = 0; + @State maxRate: number = 120; + @State expected: number = 60; + needSoftKeyboard: boolean = false; + @State needSoftKeyboardState: string = 'needSoftKeyboard=' + this.needSoftKeyboard; + @State text: string = '单指点击XComponent软键盘消失' + controller: TextInputController = new TextInputController() + + build() { + Column() { + TextInput({ text: this.text, placeholder: 'please input ...', controller: this.controller }) + .id('textInput') + .placeholderColor(Color.Grey) + .placeholderFont({ size: 14, weight: 400 }) + .caretColor(Color.Blue) + .width(400) + .height(40) + .margin(20) + .fontSize(14) + .fontColor(Color.Black) + .onChange((value: string) => { + this.text = value + }) + Column() { + if (this.isShow) { + XComponent({ + type: XComponentType.SURFACE + }) + .id(this.xcomponentId) + .onAttach(() => { + let node = this.getUIContext().getFrameNodeById(this.xcomponentId) + native.bindNode(this.xcomponentId, node) + }) + .onDetach(() => { + native.unbindNode(this.xcomponentId) + }) + .width(200) + .height(200) + .focusable(true) + .focusOnTouch(true) + .defaultFocus(true) + } + }.height(200) + + Button('创建/销毁').onClick(() => { + this.isShow = !this.isShow; + }) + + Column() { + Text('期望帧率设置:') + .textAlign(TextAlign.Start) + .fontSize(15) + .border({ width: 1 }) + .padding(10) + .width('100%') + .margin(5) + Text('min: ' + this.minRate) + Slider({ + value: this.minRate, + min: 0, + max: 240, + step: 1 + }).onChange((value: number, mode: SliderChangeMode) => { + this.minRate = value; + native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) + }).width('100%') + .id('minSlider') + Text('max: ' + this.maxRate) + Slider({ + value: this.maxRate, + min: 0, + max: 240, + step: 1 + }).onChange((value: number, mode: SliderChangeMode) => { + this.maxRate = value; + native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) + }).width('100%') + .id('maxSlider') + Text('expected: ' + this.expected) + Slider({ + value: this.expected, + min: 0, + max: 240, + step: 1 + }).onChange((value: number, mode: SliderChangeMode) => { + this.expected = value; + native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) + }).width('100%') + .id('expectedSlider') + }.backgroundColor("#F0FAFF") + + Button(this.needSoftKeyboardState) + .onClick(() => { + this.needSoftKeyboard = !this.needSoftKeyboard; + this.needSoftKeyboardState = 'needSoftKeyboard=' + this.needSoftKeyboard; + native.setNeedSoftKeyboard(this.xcomponentId, this.needSoftKeyboard); + this.text = this.needSoftKeyboard ? '单指点击XComponent软键盘不消失' : '单指点击XComponent软键盘消失' + }) + } + .width('100%') + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2cd3a6c601073f86f4789ab9db7529f84f705f3f --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5 @@ -0,0 +1,51 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png differ diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png differ diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png differ diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..597ecf95e61d7e30367c22fe2f8638008361b044 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2171716d040a605ef6af71e90b937a945f2677d --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets @@ -0,0 +1,7 @@ +const NativeMock: Record = { + 'add': (a: number, b: number) => { + return a + b; + }, +}; + +export default NativeMock; \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6540976c9acc8afbd45895db6404334cff195465 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5 @@ -0,0 +1,5 @@ +{ + "libentry.so": { + "source": "src/mock/Libentry.mock.ets" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..d0a983484dc47ee92663bc333b29e68a933fcb87 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import { describe, it, expect } from '@ohos/hypium'; +import { Driver, MatchPattern, ON, MouseButton } from '@ohos.UiTest'; + +const TAG = '[Sample_NDK_XComponent]'; + +export default function abilityTest() { + + describe('ActsAbilityTest', () => { + /** + * 打开应用 + */ + it('StartAbility_001', 0, async (done: Function) => { + console.info(TAG, 'StartAbility_001 begin'); + let driver = Driver.create(); + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + try { + await abilityDelegator.startAbility({ + bundleName: 'com.example.nativexcomponent', + abilityName: 'EntryAbility' + }); + } catch (exception) { + console.info(TAG, `StartAbility_001 exception = ${JSON.stringify(exception)}`); + expect().assertFail(); + } + await driver.delayMs(1000); + // 判断XComponent onLoad回调执行成功 + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard=',MatchPattern.STARTS_WITH)); + done(); + console.info(TAG, 'StartAbility_001 end'); + }) + + /** + * 点击按钮,按钮 + */ + it('DrawShape_001', 2, async (done: Function) => { + console.info(TAG, 'CreateFiles_001 begin'); + let driver = Driver.create(); + // 判断XComponent onLoad回调执行成功 + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH)); + // 判断是否有按键 + await driver.assertComponentExist(ON.text('创建/销毁')); + let drawStarBtn = await driver.findComponent(ON.text('创建/销毁')); + // 点击'创建/销毁'按钮 + await drawStarBtn.click(); + await driver.delayMs(1000); + + // 判断xcomponent组件已经消失 + let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH)); + expect(xcomponent === null).assertTrue(); + + await drawStarBtn.click(); + await driver.delayMs(1000); + // 判断xcomponent组件复现 + let xcomponent2 = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH)); + expect(xcomponent2 === null).assertFalse(); + done(); + console.info(TAG, 'DrawShape_001 end'); + }) + + it('DrawShape_002', 2, async (done: Function) => { + console.info(TAG, 'DrawShape_002 begin'); + let driver = Driver.create(); + // 判断XComponent onLoad回调执行成功 + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH)); + // 判断是否有按键 + await driver.assertComponentExist(ON.text('创建/销毁')); + + // 点击XComponent组件 + let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH)); + expect(xcomponent === null).assertFalse(); + await xcomponent.click(); + await driver.delayMs(1000); + // 判断touch回调已执行 + await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH)); + done(); + console.info(TAG, 'DrawShape_002 end'); + }) + + it('SetExpectedFrameRate_001 ', 2, async (done: Function) => { + console.info(TAG, 'SetExpectedFrameRate_001 start'); + let driver = await Driver.create(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + + let minBtn = await driver.findComponent(ON.text('min', MatchPattern.STARTS_WITH)); + expect(minBtn === null).assertFalse(); + + let minSlider = await driver.findComponent(ON.id('minSlider')); + expect(minSlider === null).assertFalse(); + let point = await minSlider.getBoundsCenter(); + await driver.mouseClick(point, MouseButton.MOUSE_BUTTON_LEFT); + await driver.delayMs(1000); + + let text = await minBtn.getText(); + expect(text.toString() == 'min: 120').assertTrue(); + + done(); + console.info(TAG, 'SetExpectedFrameRate_001 end'); + }) + + it('SetExpectedFrameRate_002 ', 2, async (done: Function) => { + console.info(TAG, 'SetExpectedFrameRate_002 start'); + let driver = await Driver.create(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + + let maxBtn = await driver.findComponent(ON.text('max', MatchPattern.STARTS_WITH)); + expect(maxBtn === null).assertFalse(); + + let maxSlider = await driver.findComponent(ON.id('maxSlider')); + expect(maxSlider === null).assertFalse(); + let rect = await maxSlider.getBounds(); + await driver.mouseClick({x:rect.right, y:(rect.top+rect.bottom)/2}, MouseButton.MOUSE_BUTTON_LEFT); + await driver.delayMs(1000); + + let text = await maxBtn.getText(); + console.info('yys', text); + expect(text.toString() == 'max: 240').assertTrue(); + + done(); + console.info(TAG, 'SetExpectedFrameRate_002 end'); + }) + + it('SetExpectedFrameRate_003 ', 2, async (done: Function) => { + console.info(TAG, 'SetExpectedFrameRate_003 start'); + let driver = await Driver.create(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + + let expectedBtn = await driver.findComponent(ON.text('expected', MatchPattern.STARTS_WITH)); + expect(expectedBtn === null).assertFalse(); + + let expectedSlider = await driver.findComponent(ON.id('expectedSlider')); + expect(expectedSlider === null).assertFalse(); + let point = await expectedSlider.getBoundsCenter(); + await driver.mouseClick(point, MouseButton.MOUSE_BUTTON_LEFT); + await driver.delayMs(1000); + + let text = await expectedBtn.getText(); + expect(text.toString() == 'expected: 120').assertTrue(); + + done(); + console.info(TAG, 'SetExpectedFrameRate_003 end'); + }) + + it('SetSoftKeyboard_001', 2, async (done: Function) => { + console.info(TAG, 'SetSoftKeyboard_001 start'); + let driver = Driver.create(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + + let text = await driver.findComponent(ON.id('textInput')); + expect(text === null).assertFalse(); + await text.click(); + await driver.delayMs(1000); + expect(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH) == null).assertFalse(); + + // 点击XComponent组件 + let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH)); + expect(xcomponent === null).assertFalse(); + await xcomponent.click(); + await driver.delayMs(1000); + + done(); + console.info(TAG, 'SetSoftKeyboard_001 end'); + }) + + it('SetSoftKeyboard_002', 2, async (done: Function) => { + console.info(TAG, 'SetSoftKeyboard_002 start'); + let driver = Driver.create(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.text('期望帧率设置:')); + await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失')); + await driver.assertComponentExist(ON.text('创建/销毁')); + await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + + let button_softKeyboard = await driver.findComponent(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH)); + expect(button_softKeyboard === null).assertFalse(); + await button_softKeyboard.click(); + let text = await driver.findComponent(ON.id('textInput')); + expect(text === null).assertFalse(); + await text.click(); + await driver.delayMs(1000); + expect(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH) == null).assertFalse(); + + // 点击XComponent组件 + let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH)); + expect(xcomponent === null).assertFalse(); + await xcomponent.click(); + await driver.delayMs(1000); + + done(); + console.info(TAG, 'SetSoftKeyboard_002 end'); + }) + }) +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..69026872775eebd0844900b225c411959ae5608b --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5 b/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..06b2783670a348f95533b352c1ceda909a842bbc --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts b/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/BasicFeature/Native/NativeXComponent/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ebdda7e54d1c41e952f1c7f6993c6d15ea3d146d --- /dev/null +++ b/code/BasicFeature/Native/NativeXComponent/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.18", + "@ohos/hamock": "1.0.0" + } +} diff --git a/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg b/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1cf3eadd18c945d30fe7d092a847fb693e488ed1 Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg differ diff --git a/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg b/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4a75340bb29b5813dc6d827001fd06577431c35e Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg differ