diff --git a/ArkUIKit/NdkFocus/.gitignore b/ArkUIKit/NdkFocus/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/ArkUIKit/NdkFocus/.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/ArkUIKit/NdkFocus/AppScope/app.json5 b/ArkUIKit/NdkFocus/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5f2cd25cf295014c3ab25d67e3c0b792ad7ec872 --- /dev/null +++ b/ArkUIKit/NdkFocus/AppScope/app.json5 @@ -0,0 +1,25 @@ +/* + * 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. + */ + +{ + "app": { + "bundleName": "com.sample.ndkfocus", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/ArkUIKit/NdkFocus/AppScope/resources/base/element/string.json b/ArkUIKit/NdkFocus/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..d71e5ead1ce20b4ac3384d18e6d104f1ea4d83b4 --- /dev/null +++ b/ArkUIKit/NdkFocus/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/ArkUIKit/NdkFocus/AppScope/resources/base/media/app_icon.png b/ArkUIKit/NdkFocus/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3 Binary files /dev/null and b/ArkUIKit/NdkFocus/AppScope/resources/base/media/app_icon.png differ diff --git a/ArkUIKit/NdkFocus/README_zh.md b/ArkUIKit/NdkFocus/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..89fa630b071cfd0dfa7cd785909ff776673ed3e2 --- /dev/null +++ b/ArkUIKit/NdkFocus/README_zh.md @@ -0,0 +1,68 @@ +# ArkUI指南文档示例 + +### 介绍 + +本示例通过使用[ArkUI指南文档](https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/reference)中各场景的开发示例,展示在工程中,帮助开发者更好地理解ArkUI提供的组件及组件属性并合理使用。该工程中展示的代码详细描述可查如下链接: + +1. [焦点开发指导](https://gitcode.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/native__interface__focus_8h.md)。 +### 效果预览 + +| 首页 | +|------------------------------------| +| ![](screenshots/device/image1.jpg) | + +### 使用说明 + +1. 在首页可以查看通过焦点CAPI接口实现焦点管理示例。 + +2. 通过自动测试框架可进行测试及维护。 + +### 工程目录 +``` +entry/src/main/ets/ +|---cpp +| |---types +| |---CMakeLists.txt +| |---container.cpp +| |---container.h +| |---init.cpp +| |---manager.cpp +| |---manager.h +| |---focus_manager.cpp // 通过焦点CAPI接口实现焦点管理示例代码 +| |---focus_manager.h // focus_manager头文件 +| |---napi_init.cpp +|---ets +| |---pages +| | |---Index.ets // 应用主页面 +entry/src/ohosTest/ +|---ets +| |---Focus.test.ets // 示例代码测试代码 +``` + +### 相关权限 + +不涉及。 + +### 依赖 + +不涉及。 + +### 约束与限制 + +1.本示例仅支持标准系统上运行, 支持设备:RK3568。 + +2.本示例为Stage模型,支持API20版本SDK,版本号:6.0.0.41,镜像版本号:OpenHarmony_6.0.0.41。 + +3.本示例需要使用DevEco Studio 5.0.5 Release (Build Version: 5.0.13.200, built on May 13, 2025)及以上版本才可编译运行。 + +### 下载 + +如需单独下载本工程,执行如下命令: + +```` +git init +git config core.sparsecheckout true +echo code/DocsSample/ArkUISample/NdkFocus > .git/info/sparse-checkout +git remote add origin https://gitee.com/openharmony/applications_app_samples.git +git pull origin master +```` \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/build-profile.json5 b/ArkUIKit/NdkFocus/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ba221bc20f11c76a63dc7acb8128ef1bbe905134 --- /dev/null +++ b/ArkUIKit/NdkFocus/build-profile.json5 @@ -0,0 +1,57 @@ +/* + * 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. + */ + +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compileSdkVersion": 20, + "compatibleSdkVersion": 20, + "runtimeOS": "OpenHarmony", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/code-linter.json5 b/ArkUIKit/NdkFocus/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..28586467ee7a761c737d8654a73aed6fddbc3c71 --- /dev/null +++ b/ArkUIKit/NdkFocus/code-linter.json5 @@ -0,0 +1,35 @@ +/* + * 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. + */ + +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/.gitignore b/ArkUIKit/NdkFocus/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/build-profile.json5 b/ArkUIKit/NdkFocus/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5612a9a1db33c953c5d474210a9a9576d1afd4d4 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/build-profile.json5 @@ -0,0 +1,59 @@ +/* + * 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. + */ + +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": [ + "arm64-v8a", + "x86_64", + "armeabi-v7a" + ] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/hvigorfile.ts b/ArkUIKit/NdkFocus/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3d28cb60475cede46d0aa36973096d5544dbcd1 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/hvigorfile.ts @@ -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. + */ + +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/ArkUIKit/NdkFocus/entry/obfuscation-rules.txt b/ArkUIKit/NdkFocus/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/oh-package.json5 b/ArkUIKit/NdkFocus/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5d993e5251fd56950970aa593aefef1b8d71e976 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/oh-package.json5 @@ -0,0 +1,26 @@ +/* + * 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": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libentry.so": "file:./src/main/cpp/types/libentry" + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/CMakeLists.txt b/ArkUIKit/NdkFocus/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..63d865985e15ea9200e8d32cb594ed7c1ad0bc5e --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(MyApplication) + +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}/include) + +add_library(nativeNode SHARED init.cpp manager.cpp container.cpp focus_manager.cpp) +target_link_libraries(nativeNode PUBLIC libace_napi.z.so) +target_link_libraries(nativeNode PUBLIC libace_napi.z.so) +target_link_libraries(nativeNode PUBLIC libace_ndk.z.so) +target_link_libraries(nativeNode PUBLIC libnative_drawing.so) +target_link_libraries(nativeNode PUBLIC libhilog_ndk.z.so) +target_link_libraries(nativeNode PUBLIC libhilog_ndk.z.so libpixelmap.so) \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/container.cpp b/ArkUIKit/NdkFocus/entry/src/main/cpp/container.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f7f4b15dc54c259a62b0ec1ce7411ff21c4e7bbc --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/container.cpp @@ -0,0 +1,255 @@ +/* + * 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 "container.h" + +#include +#include +#include +#include +#include + + +namespace NativeXComponentSample { +namespace { +void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceCreatedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceCreatedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceCreatedCB: Unable to get XComponent id"); + return; + } +} + +void OnSurfaceChangedCB(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChangedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChangedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChangedCB: Unable to get XComponent id"); + return; + } + std::string id(idStr); + auto container = Container::GetInstance(id); + if (container != nullptr) { + container->OnSurfaceChanged(component, window); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "surface changed"); + } +} + +void OnSurfaceDestroyedCB(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceDestroyedCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceDestroyedCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceDestroyedCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + Container::Release(id); +} + +void DispatchTouchEventCB(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB"); + if ((component == nullptr) || (window == nullptr)) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB: component or window is null"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB: Unable to get XComponent id"); + return; + } + + std::string id(idStr); + Container* render = Container::GetInstance(id); + if (render != nullptr) { + render->OnTouchEvent(component, window); + } +} + +void DispatchMouseEventCB(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchMouseEventCB"); + int32_t ret; + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize); + if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + + std::string id(idStr); + auto render = Container::GetInstance(id); + if (render != nullptr) { + render->OnMouseEvent(component, window); + } +} + +void DispatchHoverEventCB(OH_NativeXComponent* component, bool isHover) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchHoverEventCB"); + int32_t ret; + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {}; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize); + if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + return; + } + + std::string id(idStr); + auto container = Container::GetInstance(id); + if (container != nullptr) { + container->OnHoverEvent(component, isHover); + } +} +} // namespace + +std::unordered_map Container::instance_; + +Container::Container(const std::string& id) +{ + this->id_ = id; +} + +Container* Container::GetInstance(const std::string& id) +{ + if (instance_.find(id) == instance_.end()) { + Container* instance = new Container(id); + instance_[id] = instance; + return instance; + } else { + return instance_[id]; + } +} + +void Container::Release(const std::string& id) +{ + if (instance_.find(id) != instance_.end()) { + instance_[id] = nullptr; + } +} + +void Container::OnSurfaceChanged(OH_NativeXComponent* component, void* window) +{ + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChanged: Unable to get XComponent id"); + return; + } + double offsetX; + double offsetY; + OH_NativeXComponent_GetXComponentOffset(component, window, &offsetX, &offsetY); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OH_NativeXComponent_GetXComponentOffset", + "offsetX = %{public}lf, offsetY = %{public}lf", offsetX, offsetY); + uint64_t width; + uint64_t height; + OH_NativeXComponent_GetXComponentSize(component, window, &width, &height); +} + +void Container::OnTouchEvent(OH_NativeXComponent* component, void* window) +{ + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB: Unable to get XComponent id"); + return; + } + OH_NativeXComponent_TouchEvent touchEvent; + OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent); + std::string id(idStr); + Container* container = Container::GetInstance(id); + float tiltX = 0.0f; + float tiltY = 0.0f; + OH_NativeXComponent_TouchPointToolType toolType = + OH_NativeXComponent_TouchPointToolType::OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN; + OH_NativeXComponent_GetTouchPointToolType(component, 0, &toolType); + OH_NativeXComponent_GetTouchPointTiltX(component, 0, &tiltX); + OH_NativeXComponent_GetTouchPointTiltY(component, 0, &tiltY); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OnTouchEvent", + "touch info: toolType = %{public}d, tiltX = %{public}lf, tiltY = %{public}lf", toolType, tiltX, tiltY); +} + +void Container::RegisterCallback(OH_NativeXComponent* nativeXComponent) +{ + containerCallback_.OnSurfaceCreated = OnSurfaceCreatedCB; + containerCallback_.OnSurfaceChanged = OnSurfaceChangedCB; + containerCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB; + containerCallback_.DispatchTouchEvent = DispatchTouchEventCB; + OH_NativeXComponent_RegisterCallback(nativeXComponent, &containerCallback_); + + mouseCallback_.DispatchMouseEvent = DispatchMouseEventCB; + mouseCallback_.DispatchHoverEvent = DispatchHoverEventCB; + OH_NativeXComponent_RegisterMouseEventCallback(nativeXComponent, &mouseCallback_); + OH_NativeXComponent_RegisterOnTouchInterceptCallback( + nativeXComponent, [](OH_NativeXComponent*, ArkUI_UIInputEvent*) -> HitTestMode { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "callback", + "OH_NativeXComponent_RegisterOnTouchInterceptCallback"); + return HitTestMode::HTM_TRANSPARENT; + }); +} + +void Container::OnMouseEvent(OH_NativeXComponent* component, void* window) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Container", "OnMouseEvent"); + OH_NativeXComponent_MouseEvent mouseEvent; + int32_t ret = OH_NativeXComponent_GetMouseEvent(component, window, &mouseEvent); + if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Container", + "MouseEvent Info: x = %{public}f, y = %{public}f, action = %{public}d, button = %{public}d", mouseEvent.x, + mouseEvent.y, mouseEvent.action, mouseEvent.button); + } else { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Container", "GetMouseEvent error"); + } +} + +void Container::OnHoverEvent(OH_NativeXComponent* component, bool isHover) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Container", "OnHoverEvent isHover_ = %{public}d", isHover); +} +} // namespace NativeXComponentSample diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/container.h b/ArkUIKit/NdkFocus/entry/src/main/cpp/container.h new file mode 100644 index 0000000000000000000000000000000000000000..8290fa3d85fe4c58fe153e981337456dfdf03c54 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/container.h @@ -0,0 +1,53 @@ +/* + * 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 MY_APPLICATION_CONTAINER_H +#define MY_APPLICATION_CONTAINER_H + +#include +#include +#include +#include + + +const unsigned int LOG_PRINT_DOMAIN = 0xFF00; + +namespace NativeXComponentSample { + +class Container { +public: + explicit Container(const std::string& id); + ~Container() = default; + static Container* GetInstance(const std::string& id); + static void Release(const std::string& id); + void Export(napi_env env, napi_value exports); + void OnSurfaceChanged(OH_NativeXComponent* component, void* window); + void OnTouchEvent(OH_NativeXComponent* component, void* window); + void OnMouseEvent(OH_NativeXComponent* component, void* window); + void OnHoverEvent(OH_NativeXComponent* component, bool isHover); + void OnKeyEvent(OH_NativeXComponent* component, void* window); + void RegisterCallback(OH_NativeXComponent* nativeXComponent); + +public: + static std::unordered_map instance_; + std::string id_; + +private: + OH_NativeXComponent_Callback containerCallback_; + OH_NativeXComponent_MouseEvent_Callback mouseCallback_; +}; + +} // namespace NativeXComponentSample +#endif // MY_APPLICATION_CONTAINER_H diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.cpp b/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..33c20051b4350f43b12599e07aa4ecb731d1cab8 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.cpp @@ -0,0 +1,420 @@ +/* + * 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 "focus_manager.h" + +#include +#include +#include + +#include "container.h" +#include "native_interface_focus.h" + + +namespace NativeXComponentSample { + +static ArkUI_NativeNodeAPI_1* g_nodeAPI = nullptr; + +int FocusManager::RequestFocus(const char* nodeId) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "RequestFocus: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + // 获取NodeAPI实例 + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "RequestFocus: g_nodeAPI is null"); + return -1; + } + + // 使用原生接口OH_ArkUI_FocusRequest请求焦点 + ArkUI_ErrorCode result = OH_ArkUI_FocusRequest(nodeHandle); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus: OH_ArkUI_FocusRequest result=%{public}d for node %{public}s", result, nodeId); + + if (result == ARKUI_ERROR_CODE_NO_ERROR) { + currentFocusNodeId_ = std::string(nodeId); + RegisterNodeHandle(std::string(nodeId), nodeHandle); + return 0; + } else { + const char* errorMsg = "未知错误"; + switch (result) { + case ARKUI_ERROR_CODE_FOCUS_NON_FOCUSABLE: + errorMsg = "节点不可聚焦"; + break; + case ARKUI_ERROR_CODE_FOCUS_NON_FOCUSABLE_ANCESTOR: + errorMsg = "节点祖先不可聚焦"; + break; + case ARKUI_ERROR_CODE_FOCUS_NON_EXISTENT: + errorMsg = "节点不存在"; + break; + default: + errorMsg = "其他错误"; + break; + } + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus failed: %{public}s (code=%{public}d)", errorMsg, result); + return result; + } +} + +int FocusManager::RequestFocusAsync(const char* nodeId) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "RequestFocus: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + // 获取NodeAPI实例 + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "RequestFocus: g_nodeAPI is null"); + return -1; + } + + // 先设置节点为可聚焦 + ArkUI_NumberValue focusableValue[] = { { .i32 = 1 } }; + ArkUI_AttributeItem focusableItem = { focusableValue, 1 }; + g_nodeAPI->setAttribute(nodeHandle, NODE_FOCUSABLE, &focusableItem); + + // 使用原生接口OH_ArkUI_FocusRequest请求焦点 + ArkUI_NumberValue requetValue[] = { { .i32 = 1 } }; + ArkUI_AttributeItem requetItem = { requetValue, 1 }; + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_FOCUS_STATUS, &requetItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus: NODE_FOCUS_STATUS result=%{public}d for node %{public}s", result, nodeId); + return result; +} + +void FocusManager::ClearFocus(void* uiContext) +{ + // 使用原生接口OH_ArkUI_FocusClear清除焦点到root scope + if (uiContext) { + OH_ArkUI_FocusClear(static_cast(uiContext)); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", "ClearFocus: OH_ArkUI_FocusClear called"); + } else { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "ClearFocus: uiContext is null"); + } + + currentFocusNodeId_.clear(); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", "ClearFocus: success"); +} + +void FocusManager::ActivateFocus(ArkUI_ContextHandle uiContext, bool isActive, bool isAutoInactive) +{ + OH_ArkUI_FocusActivate(uiContext, isActive, !isActive); + isActive_ = isActive; + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "ActivateFocus: isActive=%{public}d, isAutoInactive=%{public}d", isActive, !isActive); +} + +void FocusManager::SetAutoTransfer(void* uiContext, bool autoTransfer) +{ + // 使用原生接口OH_ArkUI_FocusSetAutoTransfer设置焦点自动转移 + if (uiContext) { + OH_ArkUI_FocusSetAutoTransfer(static_cast(uiContext), autoTransfer); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetAutoTransfer: OH_ArkUI_FocusSetAutoTransfer called with autoTransfer=%{public}d", autoTransfer); + } else { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetAutoTransfer: uiContext is null"); + } + + autoTransfer_ = autoTransfer; + OH_LOG_Print( + LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", "SetAutoTransfer: autoTransfer=%{public}d", autoTransfer); +} + +void FocusManager::SetKeyProcessingMode(void* uiContext, FocusKeyProcessingMode mode) +{ + // 将FocusKeyProcessingMode映射到ArkUI_KeyProcessingMode + ArkUI_KeyProcessingMode arkuiMode; + switch (mode) { + case FOCUS_KEY_PROCESSING_MODE_NAVIGATION: + arkuiMode = ARKUI_KEY_PROCESSING_MODE_FOCUS_NAVIGATION; + break; + case FOCUS_KEY_PROCESSING_MODE_ANCESTOR_EVENT: + arkuiMode = ARKUI_KEY_PROCESSING_MODE_FOCUS_ANCESTOR_EVENT; + break; + default: + arkuiMode = ARKUI_KEY_PROCESSING_MODE_FOCUS_NAVIGATION; + break; + } + + // 使用原生接口OH_ArkUI_FocusSetKeyProcessingMode设置按键处理模式 + if (uiContext) { + OH_ArkUI_FocusSetKeyProcessingMode(static_cast(uiContext), arkuiMode); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetKeyProcessingMode: OH_ArkUI_FocusSetKeyProcessingMode called with mode=%{public}d", arkuiMode); + } else { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetKeyProcessingMode: uiContext is null"); + } + + keyProcessingMode_ = mode; + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", "SetKeyProcessingMode: mode=%{public}d", mode); +} + +int FocusManager::SetNodeFocusable(const char* nodeId, bool focusable) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeFocusable: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeFocusable: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeFocusable: g_nodeAPI is null"); + return -1; + } + + // 设置节点可聚焦属性 + ArkUI_NumberValue focusableValue[] = { { .i32 = focusable ? 1 : 0 } }; + ArkUI_AttributeItem focusableItem = { focusableValue, 1 }; + g_nodeAPI->setAttribute(nodeHandle, NODE_FOCUSABLE, &focusableItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeFocusable: success for node %{public}s, focusable=%{public}d", nodeId, focusable); + return 0; +} + +std::string FocusManager::GetCurrentFocusNodeId() const +{ + return currentFocusNodeId_; +} + +void FocusManager::RegisterNodeHandle(const std::string& nodeId, ArkUI_NodeHandle nodeHandle) +{ + nodeHandleMap_[nodeId] = nodeHandle; + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", "RegisterNodeHandle: registered node %{public}s", + nodeId.c_str()); +} + +int FocusManager::SetFocusOnTouch(const char* nodeId, bool focusOnTouch) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "RequestFocus: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "RequestFocus: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + // 获取NodeAPI实例 + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + // 设置FocusOnTouch + ArkUI_NumberValue focusValue[] = { { .i32 = focusOnTouch ? 1 : 0 } }; + ArkUI_AttributeItem focusItem = { focusValue, 1 }; + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_FOCUS_ON_TOUCH, &focusItem); + return result; +} + +int FocusManager::SetNodeDefaultFocus(const char* nodeId, bool isDefault) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeDefaultFocus: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeDefaultFocus: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeDefaultFocus: g_nodeAPI is null"); + return -1; + } + + // 设置默认焦点 + ArkUI_NumberValue defaultValue[] = { { .i32 = isDefault ? 1 : 0 } }; + ArkUI_AttributeItem defaultItem = { defaultValue, 1 }; + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_DEFAULT_FOCUS, &defaultItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeDefaultFocus: result=%{public}d for node %{public}s, isDefault=%{public}d", result, nodeId, isDefault); + return result; +} + +int FocusManager::SetNodeFocusBox(const char* nodeId, float distance, float width, uint32_t color) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeFocusBox: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeFocusBox: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeFocusBox: g_nodeAPI is null"); + return -1; + } + + // 设置焦点框样式 - 根据CAPI定义:distance, width, color + ArkUI_NumberValue focusBoxValues[] = { + { .f32 = distance }, // 焦点框距离组件边缘的距离 + { .f32 = width }, // 焦点框宽度 + { .u32 = color } // 焦点框颜色 + }; + ArkUI_AttributeItem focusBoxItem = { focusBoxValues, 3 }; + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_FOCUS_BOX, &focusBoxItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeFocusBox: result=%{public}d for node %{public}s, distance=%{public}f, width=%{public}f, " + "color=%{public}u", + result, nodeId, distance, width, color); + return result; +} + +int FocusManager::SetNodeNextFocus(const char* nodeId, const char* nextNodeId, int direction) +{ + if (!nodeId || !nextNodeId) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeNextFocus: nodeId or nextNodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeNextFocus: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + ArkUI_NodeHandle nextNodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nextNodeId, &nextNodeHandle); + if (!nextNodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeNextFocus: nextNodeHandle is null for id %{public}s", nextNodeId); + return -1; + } + + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeNextFocus: g_nodeAPI is null"); + return -1; + } + + // 设置下一个焦点 - 根据CAPI定义:direction和next node handle + ArkUI_NumberValue directionValue[] = { { .i32 = direction } }; + ArkUI_AttributeItem nextFocusItem = { .value = directionValue, .size = 1, .object = nextNodeHandle }; + + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_NEXT_FOCUS, &nextFocusItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeNextFocus: result=%{public}d for node %{public}s -> %{public}s, direction=%{public}d", result, nodeId, + nextNodeId, direction); + return result; +} + +int FocusManager::SetNodeTabStop(const char* nodeId, bool tabStop) +{ + if (!nodeId) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeTabStop: nodeId is null"); + return -1; + } + + ArkUI_NodeHandle nodeHandle; + OH_ArkUI_NodeUtils_GetAttachedNodeHandleById(nodeId, &nodeHandle); + if (!nodeHandle) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeTabStop: nodeHandle is null for id %{public}s", nodeId); + return -1; + } + + if (!g_nodeAPI) { + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, g_nodeAPI); + } + + if (!g_nodeAPI) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FocusManager", "SetNodeTabStop: g_nodeAPI is null"); + return -1; + } + + // 设置Tab停止属性 + ArkUI_NumberValue tabStopValue[] = { { .i32 = tabStop ? 1 : 0 } }; + ArkUI_AttributeItem tabStopItem = { tabStopValue, 1 }; + int result = g_nodeAPI->setAttribute(nodeHandle, NODE_TAB_STOP, &tabStopItem); + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "FocusManager", + "SetNodeTabStop: result=%{public}d for node %{public}s, tabStop=%{public}d", result, nodeId, tabStop); + return result; +} + +} // namespace NativeXComponentSample diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.h b/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..6c2aa30c1bfba18989caa19415d181a59e8047c8 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/focus_manager.h @@ -0,0 +1,78 @@ +/* + * 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 FOCUS_MANAGER_H +#define FOCUS_MANAGER_H + +#include +#include +#include +#include +#include + +namespace NativeXComponentSample { + +/** + * @brief 焦点处理模式枚举 + */ +typedef enum { + /** 按键事件用于焦点导航 */ + FOCUS_KEY_PROCESSING_MODE_NAVIGATION = 0, + /** 按键事件向上冒泡到祖先节点 */ + FOCUS_KEY_PROCESSING_MODE_ANCESTOR_EVENT, +} FocusKeyProcessingMode; + +/** + * @brief 焦点管理器类,负责处理焦点相关的操作 + */ +class FocusManager { +public: + static FocusManager* GetInstance() + { + static FocusManager instance; + return &instance; + } + + int RequestFocus(const char* nodeId); + int RequestFocusAsync(const char* nodeId); + void ClearFocus(void* uiContext); + void ActivateFocus(ArkUI_ContextHandle uiContext, bool isActive, bool isAutoInactive); + void SetAutoTransfer(void* uiContext, bool autoTransfer); + void SetKeyProcessingMode(void* uiContext, FocusKeyProcessingMode mode); + int SetNodeFocusable(const char* nodeId, bool focusable); + std::string GetCurrentFocusNodeId() const; + void RegisterNodeHandle(const std::string& nodeId, ArkUI_NodeHandle nodeHandle); + int SetFocusOnTouch(const char* nodeId, bool focusOnTouch); + int SetNodeDefaultFocus(const char* nodeId, bool isDefault); + int SetNodeFocusBox(const char* nodeId, float distance, float width, uint32_t color); + int SetNodeNextFocus(const char* nodeId, const char* nextNodeId, int direction); + int SetNodeTabStop(const char* nodeId, bool tabStop); + +private: + FocusManager() = default; + ~FocusManager() = default; + FocusManager(const FocusManager&) = delete; + FocusManager& operator=(const FocusManager&) = delete; + + std::string currentFocusNodeId_; + std::unordered_map nodeHandleMap_; + bool isActive_ = false; + bool autoTransfer_ = true; + FocusKeyProcessingMode keyProcessingMode_ = FOCUS_KEY_PROCESSING_MODE_NAVIGATION; +}; + +} // namespace NativeXComponentSample + +#endif // FOCUS_MANAGER_H diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/init.cpp b/ArkUIKit/NdkFocus/entry/src/main/cpp/init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e797c80aa46ad7ab064be729fa90eedbb0a9963c --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/init.cpp @@ -0,0 +1,75 @@ +/* + * 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 + +#include "manager.h" + + +namespace NativeXComponentSample { +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "Init begins"); + if ((env == nullptr) || (exports == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "env or exports is null"); + return nullptr; + } + + napi_property_descriptor desc[] = { + { "getContext", nullptr, Manager::GetContext, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "createNativeNode", nullptr, Manager::CreateNativeNode, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "upDateNativeNode", nullptr, Manager::UpdateNativeNode, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "requestFocus", nullptr, Manager::RequestFocus, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "requestFocusAsync", nullptr, Manager::RequestFocusAsync, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "clearFocus", nullptr, Manager::ClearFocus, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "activateFocus", nullptr, Manager::ActivateFocus, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "setAutoTransfer", nullptr, Manager::SetAutoTransfer, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "setKeyProcessingMode", nullptr, Manager::SetKeyProcessingMode, nullptr, nullptr, nullptr, napi_default, + nullptr }, + { "setNodeFocusable", nullptr, Manager::SetNodeFocusable, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "setNodeFocusOnTouch", nullptr, Manager::SetNodeFocusOnTouch, nullptr, nullptr, nullptr, napi_default, + nullptr }, + { "getCurrentFocusNodeId", nullptr, Manager::GetCurrentFocusNodeId, nullptr, nullptr, nullptr, napi_default, + nullptr }, + { "setNodeDefaultFocus", nullptr, Manager::SetNodeDefaultFocus, nullptr, nullptr, nullptr, napi_default, + nullptr }, + { "setNodeFocusBox", nullptr, Manager::SetNodeFocusBox, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "setNodeNextFocus", nullptr, Manager::SetNodeNextFocus, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "setNodeTabStop", nullptr, Manager::SetNodeTabStop, nullptr, nullptr, nullptr, napi_default, nullptr } }; + + if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed"); + return nullptr; + } + + Manager::GetInstance()->Export(env, exports); + return exports; +} +EXTERN_C_END + +static napi_module nativeNodeModule = { .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "nativeNode", + .nm_priv = ((void*)0), + .reserved = { 0 } }; + +extern "C" __attribute__((constructor)) void RegisterModule(void) +{ + napi_module_register(&nativeNodeModule); +} +} // namespace NativeXComponentSample diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.cpp b/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c55173e2d33cfb532287ae677afa253d8393445e --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.cpp @@ -0,0 +1,977 @@ +/* + * 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.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "container.h" +#include "focus_manager.h" +#include "napi/native_api.h" + +namespace NativeXComponentSample { +Manager Manager::manager_; + +Manager::~Manager() +{ + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "~Manager"); + for (auto& [key, component] : nativeXComponentMap_) { + component = nullptr; + } + nativeXComponentMap_.clear(); +} + +static ArkUI_NativeNodeAPI_1* nodeAPI = nullptr; +static ArkUI_Context* context_ = nullptr; + +// 颜色常量 +const uint32_t RED_COLOR = 0xFFFF0000; +const uint32_t BLACK_COLOR = 0xFF000000; +const uint32_t DEFAULT_COLOR = 0xF2F2F3F0; + +// 数字常量 +const int32_t NUMBER_ZERO = 0; +const int32_t NUMBER_ONE = 1; +const int32_t NUMBER_TWO = 2; +const int32_t NUMBER_THREE = 3; +const int32_t NUMBER_FOUR = 4; +const int32_t NUMBER_NINE = 9; + +// 获焦回调函数 +void EventReceiver(ArkUI_NodeEvent* event) +{ + if (!event || !nodeAPI) { + return; + } + // 从事件中提取关键信息(根据ArkUI_NodeEvent结构体定义) + ArkUI_NodeHandle node = OH_ArkUI_NodeEvent_GetNodeHandle(event); // 事件所属节点 + ArkUI_NodeEventType eventType = OH_ArkUI_NodeEvent_GetEventType(event); // 事件类型 + // 处理焦点相关事件 + if (eventType == NODE_ON_FOCUS) { + // 获焦:设置红色背景 + ArkUI_NumberValue colorValue[] = { { .u32 = RED_COLOR } }; + ArkUI_AttributeItem colorItem = { colorValue, 1 }; + nodeAPI->setAttribute(node, NODE_BACKGROUND_COLOR, &colorItem); + } else if (eventType == NODE_ON_BLUR) { + // 失焦:设置黑色背景 + ArkUI_NumberValue colorValue[] = { { .u32 = BLACK_COLOR } }; + ArkUI_AttributeItem colorItem = { colorValue, 1 }; + nodeAPI->setAttribute(node, NODE_BACKGROUND_COLOR, &colorItem); + } else if (eventType == NODE_ON_CLICK) { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EventReceiver", + "Button9 clicked, creating real menu for AutoTransfer test"); + bool autoTransfer = true; + auto focusManager = FocusManager::GetInstance(); + focusManager->SetAutoTransfer(context_, autoTransfer); + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EventReceiver", "AutoTransfer set to true"); + } +} + +void SetListAttribute(ArkUI_NodeHandle& list) +{ + if (!nodeAPI) { + return; + } + ArkUI_NumberValue listWidthValue[] = { 400 }; + ArkUI_AttributeItem listWidthItem = { listWidthValue, 1 }; + nodeAPI->setAttribute(list, NODE_WIDTH, &listWidthItem); + ArkUI_NumberValue listHeightValue[] = { 200 }; + ArkUI_AttributeItem listHeightItem = { listHeightValue, 1 }; + nodeAPI->setAttribute(list, NODE_HEIGHT, &listHeightItem); + ArkUI_NumberValue borderColorValue[] = { { .u32 = 0xCCCCCCCC } }; + ArkUI_AttributeItem borderColorItem = { borderColorValue, 1 }; + nodeAPI->setAttribute(list, NODE_BORDER_COLOR, &borderColorItem); + ArkUI_NumberValue borderWidthValue[] = { 5 }; + ArkUI_AttributeItem borderWidthItem = { borderWidthValue, 1 }; + nodeAPI->setAttribute(list, NODE_BORDER_WIDTH, &borderWidthItem); + ArkUI_NumberValue initialIndexValue[] = { { .i32 = 5 } }; + ArkUI_AttributeItem initialIndexItem = { initialIndexValue, 1 }; + nodeAPI->setAttribute(list, NODE_LIST_INITIAL_INDEX, &initialIndexItem); + ArkUI_NumberValue edgeEffectValue[] = { { .i32 = ARKUI_EDGE_EFFECT_NONE } }; + ArkUI_AttributeItem edgeEffectItem = { edgeEffectValue, 1 }; + nodeAPI->setAttribute(list, NODE_SCROLL_EDGE_EFFECT, &edgeEffectItem); + ArkUI_AttributeItem nodeIdItem = { .string = "inner" }; + nodeAPI->setAttribute(list, NODE_ID, &nodeIdItem); +} + +void SetScrollAttribute(ArkUI_NodeHandle& scroll) +{ + if (!nodeAPI) { + return; + } + ArkUI_NumberValue scrollWidthValue[] = { 500 }; + ArkUI_AttributeItem scrollWidthItem = { scrollWidthValue, 1 }; + nodeAPI->setAttribute(scroll, NODE_WIDTH, &scrollWidthItem); + ArkUI_NumberValue scrollHeightValue[] = { 400 }; + ArkUI_AttributeItem scrollHeightItem = { scrollHeightValue, 1 }; + nodeAPI->setAttribute(scroll, NODE_HEIGHT, &scrollHeightItem); + ArkUI_NumberValue scrollColorValue[] = { { .u32 = 0xFF2F2F4F } }; + ArkUI_AttributeItem backColorItem = { scrollColorValue, 1 }; + nodeAPI->setAttribute(scroll, NODE_BACKGROUND_COLOR, &backColorItem); + ArkUI_AttributeItem scrollNodeIdItem = { .string = "outer" }; + nodeAPI->setAttribute(scroll, NODE_ID, &scrollNodeIdItem); +} + +// 创建默认按钮 +ArkUI_NodeHandle CreateDefaultButton( + const ArkUI_AttributeItem& marginItem, const ArkUI_AttributeItem& widthItem, const ArkUI_AttributeItem& heightItem) +{ + ArkUI_NodeHandle defaultListItem = nodeAPI->createNode(ARKUI_NODE_LIST_ITEM); + nodeAPI->setAttribute(defaultListItem, NODE_MARGIN, &marginItem); + + ArkUI_NodeHandle defaultButton = nodeAPI->createNode(ARKUI_NODE_BUTTON); + nodeAPI->setAttribute(defaultButton, NODE_WIDTH, &widthItem); + nodeAPI->setAttribute(defaultButton, NODE_HEIGHT, &heightItem); + + // 设置默认按钮背景色 + ArkUI_NumberValue defaultBackColorValue[] = { { .u32 = 0xFFE6F3FF } }; + ArkUI_AttributeItem defaultBackColorItem = { defaultBackColorValue, 1 }; + nodeAPI->setAttribute(defaultButton, NODE_BACKGROUND_COLOR, &defaultBackColorItem); + + // 设置默认按钮文本和ID + ArkUI_AttributeItem defaultContentItem = { .string = "ButtonDefault" }; + nodeAPI->setAttribute(defaultButton, NODE_BUTTON_LABEL, &defaultContentItem); + nodeAPI->setAttribute(defaultButton, NODE_ID, &defaultContentItem); + + // 设置为默认焦点 + ArkUI_NumberValue defaultFocusValue[] = { { .i32 = 1 } }; + ArkUI_AttributeItem defaultFocusItem = { defaultFocusValue, 1 }; + nodeAPI->setAttribute(defaultButton, NODE_DEFAULT_FOCUS, &defaultFocusItem); + + // 注册焦点事件 + nodeAPI->registerNodeEvent(defaultButton, NODE_ON_FOCUS, 0, defaultButton); + nodeAPI->registerNodeEvent(defaultButton, NODE_ON_BLUR, 0, defaultButton); + nodeAPI->registerNodeEventReceiver(EventReceiver); + + nodeAPI->addChild(defaultListItem, defaultButton); + return defaultListItem; +} + +// 创建普通按钮 +ArkUI_NodeHandle CreateNormalButton(int index, const ArkUI_AttributeItem& marginItem, + const ArkUI_AttributeItem& widthItem, const ArkUI_AttributeItem& heightItem) +{ + ArkUI_NodeHandle listItem = nodeAPI->createNode(ARKUI_NODE_LIST_ITEM); + nodeAPI->setAttribute(listItem, NODE_MARGIN, &marginItem); + + ArkUI_NodeHandle button = nodeAPI->createNode(ARKUI_NODE_BUTTON); + nodeAPI->setAttribute(button, NODE_WIDTH, &widthItem); + nodeAPI->setAttribute(button, NODE_HEIGHT, &heightItem); + + // 设置按钮默认背景色 + ArkUI_NumberValue backColorValue[] = { { .u32 = DEFAULT_COLOR } }; + ArkUI_AttributeItem backColorItem = { backColorValue, 1 }; + nodeAPI->setAttribute(button, NODE_BACKGROUND_COLOR, &backColorItem); + + // 设置按钮文本和ID + std::string content = "Button" + std::to_string(index); + ArkUI_AttributeItem contentItem = { .string = content.c_str() }; + nodeAPI->setAttribute(button, NODE_BUTTON_LABEL, &contentItem); + nodeAPI->setAttribute(button, NODE_ID, &contentItem); + + // Button9需要注册点击事件 + if (index == NUMBER_NINE) { + nodeAPI->registerNodeEvent(button, NODE_ON_CLICK, 0, button); + } + + // 注册焦点事件 + nodeAPI->registerNodeEvent(button, NODE_ON_FOCUS, 0, button); + nodeAPI->registerNodeEvent(button, NODE_ON_BLUR, 0, button); + nodeAPI->registerNodeEventReceiver(EventReceiver); + + nodeAPI->addChild(listItem, button); + return listItem; +} + +// 创建特殊按钮(Button4包装在Column中) +ArkUI_NodeHandle CreateSpecialButton(int index, const ArkUI_AttributeItem& marginItem, + const ArkUI_AttributeItem& widthItem, const ArkUI_AttributeItem& heightItem) +{ + ArkUI_NodeHandle listItem = nodeAPI->createNode(ARKUI_NODE_LIST_ITEM); + nodeAPI->setAttribute(listItem, NODE_MARGIN, &marginItem); + + ArkUI_NodeHandle button = nodeAPI->createNode(ARKUI_NODE_BUTTON); + nodeAPI->setAttribute(button, NODE_WIDTH, &widthItem); + nodeAPI->setAttribute(button, NODE_HEIGHT, &heightItem); + + // 设置按钮默认背景色 + ArkUI_NumberValue backColorValue[] = { { .u32 = DEFAULT_COLOR } }; + ArkUI_AttributeItem backColorItem = { backColorValue, 1 }; + nodeAPI->setAttribute(button, NODE_BACKGROUND_COLOR, &backColorItem); + + // 设置按钮文本和ID + std::string content = "Button" + std::to_string(index); + ArkUI_AttributeItem contentItem = { .string = content.c_str() }; + nodeAPI->setAttribute(button, NODE_BUTTON_LABEL, &contentItem); + nodeAPI->setAttribute(button, NODE_ID, &contentItem); + + // 注册焦点事件 + nodeAPI->registerNodeEvent(button, NODE_ON_FOCUS, 0, button); + nodeAPI->registerNodeEvent(button, NODE_ON_BLUR, 0, button); + nodeAPI->registerNodeEventReceiver(EventReceiver); + + // 创建Column4包装 + ArkUI_NodeHandle column4 = nodeAPI->createNode(ARKUI_NODE_COLUMN); + nodeAPI->setAttribute(column4, NODE_WIDTH, &widthItem); + nodeAPI->setAttribute(column4, NODE_HEIGHT, &heightItem); + ArkUI_AttributeItem columnContentItem = { .string = "Column4" }; + nodeAPI->setAttribute(column4, NODE_ID, &columnContentItem); + + nodeAPI->addChild(column4, button); + nodeAPI->addChild(listItem, column4); + return listItem; +} + +void AddListChild(ArkUI_NodeHandle& list) +{ + if (!nodeAPI) { + return; + } + + // 创建公共属性 + ArkUI_NumberValue marginValues[] = { { 10 }, { 5 }, { 10 }, { 5 } }; + ArkUI_AttributeItem marginItem = { marginValues, 4 }; + ArkUI_NumberValue widthValue[] = { 370 }; + ArkUI_AttributeItem widthItem = { widthValue, 1 }; + ArkUI_NumberValue heightValue[] = { 40 }; + ArkUI_AttributeItem heightItem = { heightValue, 1 }; + + // 创建默认按钮 + ArkUI_NodeHandle defaultListItem = CreateDefaultButton(marginItem, widthItem, heightItem); + nodeAPI->addChild(list, defaultListItem); + + // 创建Button0-Button9 + for (int i = NUMBER_ZERO; i < NUMBER_NINE; i++) { + ArkUI_NodeHandle listItem; + + if (i == NUMBER_FOUR) { + listItem = CreateSpecialButton(i, marginItem, widthItem, heightItem); + } else { + listItem = CreateNormalButton(i, marginItem, widthItem, heightItem); + } + + nodeAPI->addChild(list, listItem); + } +} + +void AddScrollChild(ArkUI_NodeHandle& scroll, ArkUI_NodeHandle& list) +{ + if (!nodeAPI) { + return; + } + ArkUI_NumberValue itemWidthValue[] = { 400 }; + ArkUI_AttributeItem itemWidthItem = { itemWidthValue, 1 }; + ArkUI_NumberValue itemHeightValue[] = { 40 }; + ArkUI_AttributeItem itemHeightItem = { itemHeightValue, 1 }; + ArkUI_NumberValue itemBackColorvalue[] = { { .u32 = 0xFF00FFFF } }; + ArkUI_AttributeItem itemBackColoritem = { itemBackColorvalue, 1 }; + ArkUI_NodeHandle text = nodeAPI->createNode(ARKUI_NODE_TEXT); + nodeAPI->setAttribute(text, NODE_WIDTH, &itemWidthItem); + nodeAPI->setAttribute(text, NODE_HEIGHT, &itemHeightItem); + nodeAPI->setAttribute(text, NODE_BACKGROUND_COLOR, &itemBackColoritem); + ArkUI_AttributeItem contentItem = { .string = "TestFocus" }; + nodeAPI->setAttribute(text, NODE_TEXT_CONTENT, &contentItem); + nodeAPI->setAttribute(text, NODE_ID, &contentItem); + ArkUI_NodeHandle column = nodeAPI->createNode(ARKUI_NODE_COLUMN); + nodeAPI->addChild(column, text); + nodeAPI->addChild(column, list); + + nodeAPI->addChild(scroll, column); +} + +void CreateFocusTree(napi_env env, napi_value arg, OH_NativeXComponent* component) +{ + // 创建父子滚动容器 + ArkUI_NodeHandle scroll = nodeAPI->createNode(ARKUI_NODE_SCROLL); + ArkUI_NodeHandle list = nodeAPI->createNode(ARKUI_NODE_LIST); + // 设置属性 + SetListAttribute(list); + SetScrollAttribute(scroll); + AddListChild(list); + AddScrollChild(scroll, list); + OH_NativeXComponent_AttachNativeRootNode(component, scroll); + auto manager = NativeXComponentSample::Manager::GetInstance(); + if (!manager) { + return; + } + context_ = OH_ArkUI_GetContextByNode(scroll); +} + +napi_value Manager::CreateNativeNode(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CreateNativeNode env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CreateNativeNode napi_get_cb_info failed"); + } + + if (argCnt < 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + if (napi_typeof(env, args[0], &valuetype) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_typeof failed"); + return nullptr; + } + + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong type of arguments"); + return nullptr; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + constexpr uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + size_t length; + if (napi_get_value_string_utf8(env, args[0], idStr, idSize, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_int64 failed"); + return nullptr; + } + + auto manager = Manager::GetInstance(); + if (manager == nullptr) { + return nullptr; + } + + OH_NativeXComponent* component = manager->GetNativeXComponent(idStr); + if (component == nullptr) { + return nullptr; + } + OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, nodeAPI); + + if (nodeAPI != nullptr) { + if (nodeAPI->createNode != nullptr && nodeAPI->addChild != nullptr) { + CreateFocusTree(env, args[1], component); + } + } + return nullptr; +} + +napi_value Manager::UpdateNativeNode(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "UpdateNativeNode env or info is null"); + return nullptr; + } + + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "UpdateNativeNode 1111"); + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "UpdateNativeNode napi_get_cb_info failed"); + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + if (napi_typeof(env, args[0], &valuetype) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_typeof failed"); + return nullptr; + } + + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong type of arguments"); + return nullptr; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + constexpr uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + size_t length; + if (napi_get_value_string_utf8(env, args[0], idStr, idSize, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_int64 failed"); + return nullptr; + } + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "CreateNativeNode %{public}s", idStr); + + auto manager = Manager::GetInstance(); + if (manager == nullptr) { + return nullptr; + } + + OH_NativeXComponent* component = manager->GetNativeXComponent(idStr); + if (component == nullptr) { + return nullptr; + } + + if ((env == nullptr) || (info == nullptr || component == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "GetContext env or info is null"); + return nullptr; + } + return nullptr; +} + +napi_value Manager::GetContext(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "GetContext env or info is null"); + return nullptr; + } + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "GetContext napi_get_cb_info failed"); + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + if (napi_typeof(env, args[0], &valuetype) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_typeof failed"); + return nullptr; + } + + if (valuetype != napi_number) { + napi_throw_type_error(env, NULL, "Wrong type of arguments"); + return nullptr; + } + + int64_t value; + if (napi_get_value_int64(env, args[0], &value) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_int64 failed"); + return nullptr; + } + + napi_value exports; + if (napi_create_object(env, &exports) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_create_object failed"); + return nullptr; + } + + return exports; +} + +void Manager::Export(napi_env env, napi_value exports) +{ + if ((env == nullptr) || (exports == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "Export: env or exports is null"); + return; + } + + napi_value exportInstance = nullptr; + if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "Export: napi_get_named_property fail"); + return; + } + + OH_NativeXComponent* nativeXComponent = nullptr; + if (napi_unwrap(env, exportInstance, reinterpret_cast(&nativeXComponent)) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "Export: napi_unwrap fail"); + return; + } + + char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' }; + uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; + if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { + OH_LOG_Print( + LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "Export: OH_NativeXComponent_GetXComponentId fail"); + return; + } + + std::string id(idStr); + auto manager = Manager::GetInstance(); + if ((manager != nullptr) && (nativeXComponent != nullptr)) { + manager->SetNativeXComponent(id, nativeXComponent); + } +} + +void Manager::SetNativeXComponent(std::string& id, OH_NativeXComponent* nativeXComponent) +{ + if (nativeXComponent == nullptr) { + return; + } + + if (nativeXComponentMap_.find(id) == nativeXComponentMap_.end()) { + nativeXComponentMap_[id] = nativeXComponent; + return; + } + + if (nativeXComponentMap_[id] != nativeXComponent) { + OH_NativeXComponent* tmp = nativeXComponentMap_[id]; + tmp = nullptr; + nativeXComponentMap_[id] = nativeXComponent; + } +} + +OH_NativeXComponent* Manager::GetNativeXComponent(const std::string& id) +{ + return nativeXComponentMap_[id]; +} + +// 焦点相关接口实现 +napi_value Manager::RequestFocus(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "RequestFocus env or info is null"); + return nullptr; + } + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "RequestFocus napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + if (napi_typeof(env, args[0], &valuetype) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_typeof failed"); + return nullptr; + } + + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong type of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Manager", "接收的nodeId: %s, 长度: %zu", nodeId, length); + + auto focusManager = FocusManager::GetInstance(); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "qqh -> start request"); + int result = focusManager->RequestFocus(nodeId); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::RequestFocusAsync(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "RequestFocus env or info is null"); + return nullptr; + } + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "RequestFocus napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + napi_valuetype valuetype; + if (napi_typeof(env, args[0], &valuetype) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_typeof failed"); + return nullptr; + } + + if (valuetype != napi_string) { + napi_throw_type_error(env, NULL, "Wrong type of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Manager", "接收的nodeId: %s, 长度: %zu", nodeId, length); + + auto focusManager = FocusManager::GetInstance(); + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "qqh -> start request"); + int result = focusManager->RequestFocusAsync(nodeId); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::ClearFocus(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "ClearFocus env or info is null"); + return nullptr; + } + + auto focusManager = FocusManager::GetInstance(); + focusManager->ClearFocus(context_); + + return nullptr; +} + +napi_value Manager::ActivateFocus(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "ActivateFocus env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "ActivateFocus napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_TWO) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + bool isActive = false; + bool isAutoInactive = true; + + napi_get_value_bool(env, args[0], &isActive); + napi_get_value_bool(env, args[1], &isAutoInactive); + + auto focusManager = FocusManager::GetInstance(); + focusManager->ActivateFocus(context_, isActive, isAutoInactive); + + return nullptr; +} + +napi_value Manager::SetAutoTransfer(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetAutoTransfer env or info is null"); + return nullptr; + } + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetAutoTransfer napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + bool autoTransfer = true; + napi_get_value_bool(env, args[0], &autoTransfer); + + auto focusManager = FocusManager::GetInstance(); + focusManager->SetAutoTransfer(context_, autoTransfer); + + return nullptr; +} + +napi_value Manager::SetKeyProcessingMode(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetKeyProcessingMode env or info is null"); + return nullptr; + } + + size_t argCnt = 1; + napi_value args[1] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetKeyProcessingMode napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + int32_t mode = 0; + napi_get_value_int32(env, args[0], &mode); + + auto focusManager = FocusManager::GetInstance(); + focusManager->SetKeyProcessingMode(context_, static_cast(mode)); + + return nullptr; +} + +napi_value Manager::SetNodeFocusable(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusable env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusable napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_TWO) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + + bool focusable = false; + napi_get_value_bool(env, args[1], &focusable); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetNodeFocusable(nodeId, focusable); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::SetNodeFocusOnTouch(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusOnTouch env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusOnTouch napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_TWO) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + + bool focusable = false; + napi_get_value_bool(env, args[1], &focusable); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetFocusOnTouch(nodeId, focusable); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::GetCurrentFocusNodeId(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "GetCurrentFocusNodeId env or info is null"); + return nullptr; + } + + auto focusManager = FocusManager::GetInstance(); + std::string currentNodeId = focusManager->GetCurrentFocusNodeId(); + + napi_value returnValue; + napi_create_string_utf8(env, currentNodeId.c_str(), currentNodeId.length(), &returnValue); + return returnValue; +} + +napi_value Manager::SetNodeDefaultFocus(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeDefaultFocus env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeDefaultFocus napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_TWO) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + + bool isDefault = false; + napi_get_value_bool(env, args[1], &isDefault); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetNodeDefaultFocus(nodeId, isDefault); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::SetNodeFocusBox(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusBox env or info is null"); + return nullptr; + } + + size_t argCnt = 4; + napi_value args[4] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeFocusBox napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_FOUR) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + + double distance = 0.0; + double width = 0.0; + uint32_t color = 0; + + napi_get_value_double(env, args[NUMBER_ONE], &distance); + napi_get_value_double(env, args[NUMBER_TWO], &width); + napi_get_value_uint32(env, args[NUMBER_THREE], &color); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetNodeFocusBox(nodeId, static_cast(distance), static_cast(width), color); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::SetNodeNextFocus(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeNextFocus env or info is null"); + return nullptr; + } + + size_t argCnt = 3; + napi_value args[3] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeNextFocus napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_THREE) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + char nextNodeId[256] = { 0 }; + size_t length; + + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed for nodeId"); + return nullptr; + } + + if (napi_get_value_string_utf8(env, args[1], nextNodeId, sizeof(nextNodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed for nextNodeId"); + return nullptr; + } + + int32_t direction = 0; + napi_get_value_int32(env, args[NUMBER_TWO], &direction); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetNodeNextFocus(nodeId, nextNodeId, direction); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +napi_value Manager::SetNodeTabStop(napi_env env, napi_callback_info info) +{ + if ((env == nullptr) || (info == nullptr)) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeTabStop env or info is null"); + return nullptr; + } + + size_t argCnt = 2; + napi_value args[2] = { nullptr }; + if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "SetNodeTabStop napi_get_cb_info failed"); + return nullptr; + } + + if (argCnt != NUMBER_TWO) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return nullptr; + } + + char nodeId[256] = { 0 }; + size_t length; + if (napi_get_value_string_utf8(env, args[0], nodeId, sizeof(nodeId) - 1, &length) != napi_ok) { + napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed"); + return nullptr; + } + + bool tabStop = false; + napi_get_value_bool(env, args[1], &tabStop); + + auto focusManager = FocusManager::GetInstance(); + int result = focusManager->SetNodeTabStop(nodeId, tabStop); + + napi_value returnValue; + napi_create_int32(env, result, &returnValue); + return returnValue; +} + +} // namespace NativeXComponentSample diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.h b/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.h new file mode 100644 index 0000000000000000000000000000000000000000..ce692ab75713cb2a073971f0cc9b7f10f97f2587 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/manager.h @@ -0,0 +1,70 @@ +/* + * 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 NATIVE_XCOMPONENT_PLUGIN_MANAGER_H +#define NATIVE_XCOMPONENT_PLUGIN_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "container.h" +#include "focus_manager.h" + +namespace NativeXComponentSample { +class Manager { +public: + ~Manager(); + + static Manager* GetInstance() + { + return &Manager::manager_; + } + + static napi_value GetContext(napi_env env, napi_callback_info info); + static napi_value CreateNativeNode(napi_env env, napi_callback_info info); + static napi_value UpdateNativeNode(napi_env env, napi_callback_info info); + + // 焦点相关接口 + static napi_value RequestFocus(napi_env env, napi_callback_info info); + static napi_value RequestFocusAsync(napi_env env, napi_callback_info info); + static napi_value ClearFocus(napi_env env, napi_callback_info info); + static napi_value ActivateFocus(napi_env env, napi_callback_info info); + static napi_value SetAutoTransfer(napi_env env, napi_callback_info info); + static napi_value SetKeyProcessingMode(napi_env env, napi_callback_info info); + static napi_value SetNodeFocusable(napi_env env, napi_callback_info info); + static napi_value SetNodeFocusOnTouch(napi_env env, napi_callback_info info); + static napi_value GetCurrentFocusNodeId(napi_env env, napi_callback_info info); + static napi_value SetNodeDefaultFocus(napi_env env, napi_callback_info info); + static napi_value SetNodeFocusBox(napi_env env, napi_callback_info info); + static napi_value SetNodeNextFocus(napi_env env, napi_callback_info info); + static napi_value SetNodeTabStop(napi_env env, napi_callback_info info); + + void SetNativeXComponent(std::string& id, OH_NativeXComponent* nativeXComponent); + OH_NativeXComponent* GetNativeXComponent(const std::string& id); + + void Export(napi_env env, napi_value exports); + +private: + static Manager manager_; + + std::unordered_map nativeXComponentMap_; +}; +} // namespace NativeXComponentSample +#endif // NATIVE_XCOMPONENT_PLUGIN_MANAGER_H diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/napi_init.cpp b/ArkUIKit/NdkFocus/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ebee157faa8ccf9c15a7cd9ab28e356f824a7a6 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,64 @@ +/* + * 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 "napi/native_api.h" + +static napi_value Add(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); + + napi_valuetype valuetype0; + napi_typeof(env, args[0], &valuetype0); + + napi_valuetype valuetype1; + napi_typeof(env, args[1], &valuetype1); + + double value0; + napi_get_value_double(env, args[0], &value0); + + double value1; + napi_get_value_double(env, args[1], &value1); + + napi_value sum; + napi_create_double(env, value0 + value1, &sum); + return sum; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { { "add", nullptr, Add, 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 = "entry", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/Index.d.ts b/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/Index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c14affc63e0dc8a96335ff0c4f6256d392724e2 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/Index.d.ts @@ -0,0 +1,16 @@ +/* + * 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 add: (a: number, b: number) => number; \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/oh-package.json5 b/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..846e4c7e13ead48abe6019bd40f3a13bf8f9c083 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/cpp/types/libentry/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": "libentry.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/ets/entryability/EntryAbility.ets b/ArkUIKit/NdkFocus/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..252b7dad746191e35c874f2fc83685e3c8ae115b --- /dev/null +++ b/ArkUIKit/NdkFocus/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/ArkUIKit/NdkFocus/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/ArkUIKit/NdkFocus/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..1504a74f09dfdcfae408be979f99369a2c5affab --- /dev/null +++ b/ArkUIKit/NdkFocus/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/ArkUIKit/NdkFocus/entry/src/main/ets/pages/Index.ets b/ArkUIKit/NdkFocus/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..eb6141a74435e1f2f785e7c5f84998b198a7124b --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,315 @@ +/* + * 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 nativeNode from 'libnativeNode.so'; + +@Entry +@Component +struct Index { + @State focusActive: boolean = false; + @State autoTransfer: boolean = true; + @State nodeTabStop: boolean = true; + @State keyProcessingMode: number = 0; + + build() { + Column({ space: 15 }) { + // 标题 + Text('Native Interface Focus API 用例测试') + .fontSize(22) + .fontWeight(FontWeight.Bold) + .margin({ top: 20 }) + .textAlign(TextAlign.Center) + + // 当前焦点状态显示 + Column({ space: 8 }) { + Text('焦点状态信息') + .fontSize(16) + .fontWeight(FontWeight.Medium) + + Row() { + Text('手动激活状态: ') + .fontSize(14) + Text(this.focusActive ? '已激活' : '未激活') + .fontSize(14) + .fontColor(this.focusActive ? Color.Green : Color.Red) + .fontWeight(FontWeight.Bold) + } + .width('100%') + .justifyContent(FlexAlign.Start) + + Row() { + Text('自动转移状态: ') + .fontSize(14) + Text(this.autoTransfer ? '启用' : '禁用') + .fontSize(14) + .fontColor(this.autoTransfer ? Color.Green : Color.Red) + .fontWeight(FontWeight.Bold) + } + .width('100%') + .justifyContent(FlexAlign.Start) + } + .width('100%') + .padding(15) + .backgroundColor('#F0F8FF') + .borderRadius(10) + + // Native组件区域 + XComponent({ + id: 'focusTest', + type: XComponentType.NODE, + libraryname: 'nativeNode' + }) + .onAppear(() => { + nativeNode.createNativeNode('focusTest', this.getUIContext()); + }) + .width('100%') + .height(280) + .backgroundColor('#FFFAFA') + .border({ width: 2, color: Color.Gray, style: BorderStyle.Dashed }) + .borderRadius(8) + + // 焦点控制按钮区域 + Scroll() { + Column() { + Column({ space: 15 }) { + Text('焦点控制操作') + .fontSize(18) + .fontWeight(FontWeight.Medium) + + // Menu测试 + Row({ space: 10 }) { + Button('菜单测试按钮') + .bindMenu([ + { + value: '拉起菜单观察是否Button失焦', + action: () => { + } + } + ]) + .id('menu') + .fontSize(12) + .width(150) + .focusable(true) + } + + // 节点可聚焦控制 + Row({ space: 10 }) { + Button('设置Button0可聚焦') + .onClick(() => { + this.setNodeFocusable('Button0', true); + }) + .id('setNodeFocusableTrue') + .fontSize(12) + .width(200) + + Button('设置Button0不可聚焦') + .onClick(() => { + this.setNodeFocusable('Button0', false); + }) + .id('setNodeFocusableFalse') + .fontSize(12) + .width(200) + } + + // 请求焦点/清理焦点操作 + Row({ space: 10 }) { + Button('同步请求焦点(0)') + .onClick(() => { + this.requestFocus('Button0'); + }) + .fontSize(12) + .width(200) + .id('requestButton0') + + Button('异步请求焦点(1)') + .onClick(() => { + this.requestFocusAsync('Button1'); + }) + .fontSize(12) + .width(120) + + Button('清除焦点') + .onClick(() => { + this.clearFocus(); + }) + .fontSize(12) + .width(80) + } + + // 焦点通用属性 + + Row({ space: 10 }) { + Button('SetFocusOnTouch(2) false') + .onClick(() => { + this.setNodeFocusOnTouch('Button2', false); + }) + .fontSize(12) + .width(200) + + Button('SetFocusOnTouch(3) true') + .onClick(() => { + this.setNodeFocusOnTouch('Button3', true); + }) + .fontSize(12) + .width(200) + } + + Row({ space: 10 }) { + Button('SetTabStop(4) ' + this.nodeTabStop) + .onClick(() => { + this.setNodeTabStop('Column4', this.nodeTabStop); + this.nodeTabStop = !this.nodeTabStop; + }) + .fontSize(12) + .width(200) + .id('setTabStopButton') + } + + Row({ space: 10 }) { + Button('SetNextFocus(5→7)') + .onClick(() => { + this.setNodeNextFocus('Button5', 'Button7', 0); // 0表示tab + }) + .fontSize(12) + .width(200) + + Button('SetFocusBox(8)') + .onClick(() => { + this.setNodeFocusBox('Button8', 5.0, 2.0, 0xFF00FF00); + }) + .fontSize(12) + .width(200) + } + + // 焦点激活控制 + Row({ space: 10 }) { + Button(this.focusActive ? '取消激活' : '激活焦点') + .onClick(() => { + this.focusActive = !this.focusActive; + this.activateFocus(this.focusActive); + }) + .fontSize(12) + .width(100) + + Button(this.autoTransfer ? '禁用自动转移' : '启用自动转移') + .onClick(() => { + this.autoTransfer = !this.autoTransfer; + this.setAutoTransfer(this.autoTransfer); + }) + .fontSize(12) + .width(120) + } + } + .width('100%') + .padding(20) + .backgroundColor('#F5F5F5') + .borderRadius(10) + } + }.height(300) + } + .width('100%') + .height('100%') + .padding(20) + } + + // 请求焦点 + private requestFocus(nodeId: string) { + try { + let result: number = nativeNode.requestFocus(nodeId); + } catch (error) { + } + } + + // 请求焦点异步 + private requestFocusAsync(nodeId: string) { + try { + let result: number = nativeNode.requestFocusAsync(nodeId); + } catch (error) { + } + } + + // 清除焦点 + private clearFocus() { + try { + nativeNode.clearFocus(); + } catch (error) { + } + } + + // 激活焦点 + private activateFocus(isActive: boolean) { + try { + nativeNode.activateFocus(isActive, true); + } catch (error) { + } + } + + // 设置自动转移 + private setAutoTransfer(autoTransfer: boolean) { + try { + nativeNode.setAutoTransfer(autoTransfer); + } catch (error) { + } + } + + // 设置节点可聚焦 + private setNodeFocusable(nodeId: string, focusable: boolean) { + try { + let result: number = nativeNode.setNodeFocusable(nodeId, focusable); + } catch (error) { + } + } + + // 设置节点TabStop属性 + private setNodeTabStop(nodeId: string, tabStop: boolean) { + try { + let result: number = nativeNode.setNodeTabStop(nodeId, tabStop); + } catch (error) { + } + } + + // 设置节点可点击获焦 + private setNodeFocusOnTouch(nodeId: string, focusOnTouch: boolean) { + try { + let result: number = nativeNode.setNodeFocusOnTouch(nodeId, focusOnTouch); + } catch (error) { + } + } + + // 设置节点焦点框 + private setNodeFocusBox(nodeId: string, distance: number, width: number, color: number) { + try { + let result: number = nativeNode.setNodeFocusBox(nodeId, distance, width, color); + } catch (error) { + } + } + + // 设置节点默认焦点 + private setNodeDefaultFocus(nodeId: string, defaultFocus: boolean) { + try { + let result: number = nativeNode.setNodeDefaultFocus(nodeId, defaultFocus); + } catch (error) { + } + } + + // 设置节点下一个焦点 + private setNodeNextFocus(nodeId: string, nextNodeId: string, direction: number) { + try { + let result: number = nativeNode.setNodeNextFocus(nodeId, nextNodeId, direction); + const directionName = ['tab', 'shift+tab', 'up', 'down', 'left', 'right'][direction] || '未知'; + } catch (error) { + } + } +} diff --git a/ArkUIKit/NdkFocus/entry/src/main/module.json5 b/ArkUIKit/NdkFocus/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..1d783f389c78a73c75e6c782d07d3433877a671f --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/module.json5 @@ -0,0 +1,65 @@ +/* + * 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. + */ + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default" + ], + "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/ArkUIKit/NdkFocus/entry/src/main/resources/base/element/color.json b/ArkUIKit/NdkFocus/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..d66f9a7d4ac61fb8d215239ab3620b7bcd77bf33 --- /dev/null +++ b/ArkUIKit/NdkFocus/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/ArkUIKit/NdkFocus/entry/src/main/resources/base/element/string.json b/ArkUIKit/NdkFocus/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..bfbdf4f34792baf7edea50e8a2d4d7d9dcbd2f11 --- /dev/null +++ b/ArkUIKit/NdkFocus/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": "NdkFocus" + } + ] +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/background.png b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/background.png differ diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/foreground.png b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/foreground.png differ diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/layered_image.json b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..4f9ad6307a2bc56beb6d0fce0a49cbf213b20a74 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,6 @@ +{ + "layered-image": { + "background": "$media:background", + "foreground": "$media:foreground" + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/startIcon.png b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/ArkUIKit/NdkFocus/entry/src/main/resources/base/media/startIcon.png differ diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/backup_config.json b/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..d742c2f96e7dd0f406f499941f3147345e998f95 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/main_pages.json b/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/en_US/element/string.json b/ArkUIKit/NdkFocus/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..bfbdf4f34792baf7edea50e8a2d4d7d9dcbd2f11 --- /dev/null +++ b/ArkUIKit/NdkFocus/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": "NdkFocus" + } + ] +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/main/resources/zh_CN/element/string.json b/ArkUIKit/NdkFocus/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..2b99bdcbc7d07426fe3664d6d94c541674595686 --- /dev/null +++ b/ArkUIKit/NdkFocus/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": "NdkFocus" + } + ] +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/mock/Libentry.mock.ets b/ArkUIKit/NdkFocus/entry/src/mock/Libentry.mock.ets new file mode 100644 index 0000000000000000000000000000000000000000..133fac9027ff6bbdcccfe98838fdbb2dc34639ab --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/mock/Libentry.mock.ets @@ -0,0 +1,22 @@ +/* + * 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. + */ + +const nativeMock: Record = { + 'add': (a: number, b: number) => { + return a + b; + }, +}; + +export default nativeMock; \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/mock/mock-config.json5 b/ArkUIKit/NdkFocus/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2c7d2ba82b796a2850ced0a277d261d7d7355416 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/mock/mock-config.json5 @@ -0,0 +1,20 @@ +/* + * 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. + */ + +{ + "libentry.so": { + "source": "src/mock/Libentry.mock.ets" + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Ability.test.ets b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..0f8ce9a2c012f8fe36114cef65216ef0b6254f41 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // 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. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + 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/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Focus.test.ets b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Focus.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..29f63df42375d683516267892bd85b6a8d38cfde --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/Focus.test.ets @@ -0,0 +1,387 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { abilityDelegatorRegistry, BY, Driver, ON } from '@kit.TestKit'; +import { UIAbility, Want } from '@kit.AbilityKit'; +import nativeNode from 'libnativeNode.so'; + +const delegator: abilityDelegatorRegistry.AbilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const bundleName = abilityDelegatorRegistry.getArguments().bundleName; +let want: Want; + +interface optionsObj { + url: string +} + +export default function FocusInterfaceTest() { + describe('FocusInterfaceTest', () => { + + beforeAll(async () => { + want = { + bundleName: bundleName, + abilityName: 'EntryAbility' + }; + await delegator.startAbility(want); + let driver = Driver.create(); + await driver.delayMs(2000); + const ability: UIAbility = await delegator.getCurrentTopAbility(); + console.info('get top ability'); + expect(ability.context.abilityInfo.name).assertEqual('EntryAbility'); + }) + + beforeEach(async () => { + }) + + afterEach(() => { + hilog.info(0x0000, 'FocusTest', 'Focus interface test case completed'); + }) + + afterAll(() => { + hilog.info(0x0000, 'FocusTest', 'All focus interface tests completed'); + }) + + /** + * @tc.number FocusInterface_001 + * @tc.name test DefaultFocus + * @tc.desc 测试默认焦点接口 - 验证ButtonDefault在XComponent创建后是否获得默认焦点 + */ + it('testDefaultFocus', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testDefaultFocus begin'); + let driver = Driver.create(); + await driver.delayMs(2000); // 等待XComponent和native节点创建完成 + try { + const list = await driver.findComponent(ON.id('inner')); + await list.scrollSearch(ON.id('ButtonDefault')); // 滚动到ButtonDefault + await driver.delayMs(1500); // 等待滚动动画 + + // 检查ButtonDefault是否已经获得默认焦点(应该是红色背景) + let strJson = getInspectorByKey('ButtonDefault'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'ButtonDefault backgroundColor: %{public}s', obj.$attrs.backgroundColor); + + strJson = getInspectorByKey('ButtonDefault'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'ButtonDefault focus backgroundColor after activate: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000') + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testDefaultFocus failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testDefaultFocus end'); + done(); + }) + + /** + * @tc.number FocusInterface_002 + * @tc.name testOH_ArkUI_FocusRequest + * @tc.desc 测试OH_ArkUI_FocusRequest接口 - 测试请求焦点和focusable + */ + it('testOH_ArkUI_FocusRequest_Normal', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testOH_ArkUI_FocusRequest_Normal begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + const list = await driver.findComponent(ON.id('inner')); + await list.scrollSearch(ON.id('Button0')); // 滚动到Button0 + await driver.delayMs(500); // 等待滚动动画 + // 设置Button0不可获焦并请求Button0焦点 + let result: number = nativeNode.setNodeFocusable('Button0', false); + expect(result).assertEqual(0); + await driver.delayMs(1000); + let result2: number = nativeNode.requestFocus('Button0'); + await driver.delayMs(1000); + expect(result2).assertEqual(150001); + hilog.info(0x0000, 'FocusTest', 'Button0 focus request result: %{public}d', result); + + // 焦点设置失败 + let strJson = getInspectorByKey('Button0'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'node focus backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#F2F2F3F0') + + let result3: number = nativeNode.setNodeFocusable('Button0', true); + expect(result3).assertEqual(0); + let result4: number = nativeNode.requestFocus('Button0'); + await driver.delayMs(1000); + expect(result4).assertEqual(0); + hilog.info(0x0000, 'FocusTest', 'Button0 focus request result: %{public}d', result); + + // 焦点设置成功 + let strJson2 = getInspectorByKey('Button0'); + let obj2: ESObject = JSON.parse(strJson2); + hilog.info(0x0000, 'FocusTest', 'node focus backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj2.$attrs.backgroundColor).assertEqual('#FFFF0000') + + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testOH_ArkUI_FocusRequest_Normal failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testOH_ArkUI_FocusRequest_Normal end'); + done(); + }) + + /** + * @tc.number FocusInterface_003 + * @tc.name testOH_ArkUI_FocusClear + * @tc.desc 测试OH_ArkUI_FocusClear接口 - 异步请求和清除焦点 + */ + it('testOH_ArkUI_FocusClear', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testOH_ArkUI_FocusClear begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + const list = await driver.findComponent(ON.id('inner')); + await list.scrollSearch(ON.id('Button1')); // 滚动到Button0 + await driver.delayMs(1000); // 等待滚动动画 + // 请求Button1焦点 + let result: number = nativeNode.requestFocusAsync('Button1'); + await driver.delayMs(1000); + expect(result).assertEqual(0); + hilog.info(0x0000, 'FocusTest', 'Button1 focus request result: %{public}d', result); + let strJson = getInspectorByKey('Button1'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'node focus backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000') + + nativeNode.clearFocus(); + await driver.delayMs(1000); + let strJson2 = getInspectorByKey('Button1'); + let obj2: ESObject = JSON.parse(strJson2); + hilog.info(0x0000, 'FocusTest', 'node blur backgroundColor: %{public}s', obj2.$attrs.backgroundColor); + expect(obj2.$attrs.backgroundColor).assertEqual('#FF000000') + + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testOH_ArkUI_FocusRequest_Normal failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testOH_ArkUI_FocusClear end'); + done(); + }) + + /** + * @tc.number FocusInterface_004 + * @tc.name testFocusOnTouch + * @tc.desc 测试FocusOnTouch接口 - Button2 and Button3 + */ + it('testFocusOnTouch', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testFocusOnTouch begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + const list = await driver.findComponent(ON.id('inner')); + let result: number = nativeNode.setNodeFocusOnTouch('Button2', false); + expect(result).assertEqual(0); + await list.scrollSearch(ON.id('Button2')); // 滚动到Button2 + await driver.delayMs(2000); // 等待滚动动画 + let component = await driver.findComponent(ON.id('Button2')); + await driver.delayMs(1000); + await component.click(); + await driver.delayMs(1000); + let strJson = getInspectorByKey('Button2'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button2 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#F2F2F3F0') + + let result2: number = nativeNode.setNodeFocusOnTouch('Button3', true); + expect(result2).assertEqual(0); + + await list.scrollSearch(ON.id('Button3')); // 滚动到Button3 + await driver.delayMs(1500); // 等待滚动动画 + let component2 = await driver.findComponent(ON.id('Button3')); + await driver.delayMs(1000); + await component2.click(); + await driver.delayMs(1000); + let strJson2 = getInspectorByKey('Button3'); + let obj2: ESObject = JSON.parse(strJson2); + hilog.info(0x0000, 'FocusTest', 'Button3 focus backgroundColor: %{public}s', obj2.$attrs.backgroundColor); + expect(obj2.$attrs.backgroundColor).assertEqual('#FFFF0000') + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testFocusOnTouch failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testFocusOnTouch end'); + done(); + }) + + /** + * @tc.number FocusInterface_005 + * @tc.name test tabStop + * @tc.desc 测试tabStop接口 - tabStop + */ + it('testTabStop', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testTabStop begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + const list = await driver.findComponent(ON.id('inner')); + await list.scrollSearch(ON.id('Button3')); // 滚动到Button3 + await driver.delayMs(1500); // 等待滚动动画 + let result: number = nativeNode.requestFocus('Button3'); + expect(result).assertEqual(0); + await driver.delayMs(1000); + let strJson = getInspectorByKey('Button3'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button3 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000'); + let result2: number = nativeNode.setNodeTabStop('Column4', true); + expect(result2).assertEqual(0); + await driver.triggerKey(2049); + await driver.delayMs(1000); + await driver.triggerKey(2049); + await driver.delayMs(1000); + strJson = getInspectorByKey('Button4'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button3 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#F2F2F3F0'); + await driver.triggerKey(2054); + await driver.delayMs(1000); + strJson = getInspectorByKey('Button4'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button4 focus backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000') + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testTabStop failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testTabStop end'); + done(); + }) + + /** + * @tc.number FocusInterface_006 + * @tc.name test nextFocus + * @tc.desc 测试nextFocus接口 - nextFocus + */ + it('testNextFocus', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testNextFocus begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + let result: number = nativeNode.requestFocus('Button5'); + expect(result).assertEqual(0); + await driver.delayMs(1000); + let strJson = getInspectorByKey('Button5'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button5 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000'); + let result2: number = nativeNode.setNodeNextFocus('Button5', 'Button7', 0); + expect(result2).assertEqual(0); + nativeNode.activateFocus(true, true); + await driver.triggerKey(2049); + await driver.delayMs(3000); + strJson = getInspectorByKey('Button7'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button7 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000') + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testNextFocus failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testNextFocus end'); + done(); + }) + + /** + * @tc.number FocusInterface_007 + * @tc.name test FocusBox + * @tc.desc 测试FocusBox接口 - FocusBox + */ + it('testFocusBox', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testFocusBox begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + let result: number = nativeNode.requestFocus('Button8'); + expect(result).assertEqual(0); + await driver.delayMs(1500); + let strJson = getInspectorByKey('Button8'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button8 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000'); + let result2: number = nativeNode.setNodeFocusBox('Button8', 5.0, 2.0, 0xFF00FF00); + expect(result2).assertEqual(0); + nativeNode.activateFocus(false, true); + await driver.delayMs(1000); + nativeNode.activateFocus(true, true); + await driver.delayMs(1000); + strJson = getInspectorByKey('Button8'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button8 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000') + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testFocusBox failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testFocusBox end'); + done(); + }) + + /** + * @tc.number FocusInterface_008 + * @tc.name test OH_ArkUI_FocusSetAutoTransfer + * @tc.desc 测试OH_ArkUI_FocusSetAutoTransfer接口 - 通过Button9点击创建menu测试页面层级变化时的焦点自动转移 + */ + it('testAutoTransfer', 0, async (done: Function) => { + hilog.info(0x0000, 'FocusTest', 'testAutoTransfer begin'); + let driver = Driver.create(); + await driver.delayMs(1500); + try { + // 请求Button9焦点 + let result: number = nativeNode.requestFocus('Button9'); + expect(result).assertEqual(0); + await driver.delayMs(1000); + + // 验证Button9获得焦点 + let strJson = getInspectorByKey('Button9'); + let obj: ESObject = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button9 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000'); + + // 测试禁用AutoTransfer的情况 + nativeNode.setAutoTransfer(false); + await driver.delayMs(500); + nativeNode.activateFocus(false, true); + await driver.delayMs(1000); + // 触发menu创建(测试新层级页面的焦点转移) + let menuButton = await driver.findComponent(ON.id('menu')); + await menuButton.click(); + await driver.delayMs(1000); + + hilog.info(0x0000, 'FocusTest', 'menu should be created for AutoTransfer test'); + strJson = getInspectorByKey('Button9'); + obj = JSON.parse(strJson); + hilog.info(0x0000, 'FocusTest', 'Button9 backgroundColor: %{public}s', obj.$attrs.backgroundColor); + expect(obj.$attrs.backgroundColor).assertEqual('#FFFF0000'); + hilog.info(0x0000, 'FocusTest', 'AutoTransfer re-enabled'); + + } catch (error) { + hilog.error(0x0000, 'FocusTest', 'testAutoTransfer failed: %{public}s', error.message); + expect().assertFail(); + } + + hilog.info(0x0000, 'FocusTest', 'testAutoTransfer end'); + done(); + }) + }) +} diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/List.test.ets b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..87f34f4ee3310d57372e44e12cb53835dcb32a9d --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,22 @@ +/* + * 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 abilityTest from './Ability.test'; +import FocusInterfaceTest from './Focus.test'; + +export default function testsuite() { + abilityTest(); + FocusInterfaceTest(); +} diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/module.json5 b/ArkUIKit/NdkFocus/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2a9b780876c5d0e4ed4aaae0f8fda64d855dcf82 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/module.json5 @@ -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. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + } +} diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/color.json b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..2a604a0205123ca3cd4100ea10a183b6d488e944 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/string.json b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..3a5d56ad4a2bb76296526ec935166e21b0af4b97 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "TestAbility_desc", + "value": "Test Ability for Focus Interface Testing" + }, + { + "name": "TestAbility_label", + "value": "Focus Test" + } + ] +} diff --git a/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/media/icon.png b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/ArkUIKit/NdkFocus/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/ArkUIKit/NdkFocus/entry/src/test/List.test.ets b/ArkUIKit/NdkFocus/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f1186b1f53c3a70930921c5dbd1417332bec56c9 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/entry/src/test/LocalUnit.test.ets b/ArkUIKit/NdkFocus/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..7fc57c77dbf76d8df08a2b802a55b948e3fcf968 --- /dev/null +++ b/ArkUIKit/NdkFocus/entry/src/test/LocalUnit.test.ets @@ -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. + */ + +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/ArkUIKit/NdkFocus/hvigor/hvigor-config.json5 b/ArkUIKit/NdkFocus/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..06cd7b9ed2566b9c4676235fbf6252399ecbe651 --- /dev/null +++ b/ArkUIKit/NdkFocus/hvigor/hvigor-config.json5 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +{ + "modelVersion": "6.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*/ + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/hvigorfile.ts b/ArkUIKit/NdkFocus/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a5e543f190732c159beb574dfc9fa37bc94e156 --- /dev/null +++ b/ArkUIKit/NdkFocus/hvigorfile.ts @@ -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. + */ + +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/ArkUIKit/NdkFocus/oh-package.json5 b/ArkUIKit/NdkFocus/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f53e8a0a7a1ece7beea30f4f258eb44a2ebcbdc5 --- /dev/null +++ b/ArkUIKit/NdkFocus/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * 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. + */ + +{ + "modelVersion": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.19", + "@ohos/hamock": "1.0.0" + } +} \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/ohosTest.md b/ArkUIKit/NdkFocus/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..862bd0d01e352fa97048f0817a189b025a47bd71 --- /dev/null +++ b/ArkUIKit/NdkFocus/ohosTest.md @@ -0,0 +1,14 @@ +# NdkFocus 测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 | +| ------------------- | -------------- |-------------|-------------------------| :------- | -------- | +| NODE_DEFAULT_FOCUS示例代码验证 | 设备正常运行 | 设置Buttondefault为默认焦点 | 打开首页,Buttondefault默认获焦,背景色变为红色 | 是 | Pass | +| NODE_FOCUSABLE和OH_ArkUI_FocusRequest示例代码验证 | 设备正常运行 | 1.设置Button0不可获焦,给Button0请求焦点
2.设置Button0可获焦,给Button0请求焦点 | 1.Button0未获焦,返回错误码150002
2.Button0获焦,背景色变为红色 | 是 | Pass | +| OH_ArkUI_FocusClear和NODE_FOCUS_STATE示例代码验证 | 设备正常运行 | 1.通过NODE_FOCUS_STATE异步请求Button1焦点
2.通过OH_ArkUI_FocusClear清理焦点到根容器 | 1.Button1获焦,背景色变成红色
2.Button1失焦,背景色变为黑色 | 是 | Pass | +| NODE_FOCUS_ON_TOUCH示例代码验证 | 设备正常运行 | 1.配置Button2不可触摸获焦,并点击
2.配置Button3可触摸获焦并点击 | 1.Button2未获焦,背景色保持蓝色
2.Button3获焦,背景色变为红色 | 是 | Pass | +| NODE_TAB_STOP示例代码验证 | 设备正常运行 | 1.配置Button4父组件NODE_TAB_STOP为true后给Button3请求焦点并使用Tab走焦
2.按下Enter键 | 1.Tab走焦后,Button4未获焦,焦点停留在Button4父组件上
2.Button4获焦,背景色变为红色 | 是 | Pass | +| NODE_NEXT_FOCUS示例代码验证 | 设备正常运行 | 配置Button5的Tab键走焦的组件为Button7,让Button5获焦并使用Tab键走焦 | 焦点可以从Button5通过Tab走焦Button7,Button6未获焦过,背景色不变 | 是 | Pass | +| NODE_FOCUS_BOX示例代码验证 | 设备正常运行 | 配置Button8的NODE_FOCUS_BOX,让Button8获焦并进入走焦态 | 焦点框为自定义的黄色外框 | 是 | Pass | +| OH_ArkUI_FocusSetAutoTransfer示例代码验证 | 设备正常运行 | 给Button9请求焦点,设置AutoTransfer为false,通过菜单测试按钮拉起menu | Button9保持焦点,背景为红色 | 是 | Pass | \ No newline at end of file diff --git a/ArkUIKit/NdkFocus/screenshots/device/image1.jpg b/ArkUIKit/NdkFocus/screenshots/device/image1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c008f8af50adb2a65b48da021e4df0d09c810f3a Binary files /dev/null and b/ArkUIKit/NdkFocus/screenshots/device/image1.jpg differ