mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-28 05:07:51 +08:00
* feat(dom): add MoveRenderNode * feat(dom): Implement the MoveRenderNode interface * feat(dom): adjust layer optimization algorithm * feat(ios): support moving nodes for ios * feat(ios): support moving nodes for ios * feat(ios): support moving views for ios * feat(android): support move child node * feat(android): support move child node * fix(android): virtual node move should mark dirty * feat(voltron): support moving views for voltron * fix(android): add update node when move * feat(voltron): modify private code syntax * feat(voltron): update voltron recombine test --------- Co-authored-by: luomy <ozonelmy@163.com> Co-authored-by: maxli <maxli@tencent.com> Co-authored-by: henryjin0511 <443836568@qq.com> Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com>
377 lines
12 KiB
Dart
377 lines
12 KiB
Dart
//
|
|
// Tencent is pleased to support the open source community by making
|
|
// Hippy available.
|
|
//
|
|
// Copyright (C) 2022 THL A29 Limited, a Tencent company.
|
|
// All rights reserved.
|
|
//
|
|
// 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 'dart:typed_data';
|
|
|
|
import '../voltron_renderer.dart';
|
|
|
|
typedef RenderOpTaskGenerator = RenderOpTask Function(int instanceId, int nodeId, Map params);
|
|
|
|
const int kInvalidIndex = -1;
|
|
const int kInvalidId = -1;
|
|
|
|
extension IdIntEx on int {
|
|
bool isValidId() {
|
|
return this != kInvalidId;
|
|
}
|
|
}
|
|
|
|
class RenderOperatorRunner implements Destroyable {
|
|
late RenderContext _renderContext;
|
|
|
|
final Map<int, RenderOpTaskGenerator> _taskGeneratorMap = {
|
|
_RenderOpType.addNode.index: _AddNodeOpTask.new,
|
|
_RenderOpType.deleteNode.index: (instanceId, nodeId, params) =>
|
|
_DeleteNodeOpTask(instanceId, nodeId),
|
|
_RenderOpType.recombineNode.index: _RecombineNodeOpTask.new,
|
|
_RenderOpType.moveNode.index: _MoveNodeOpTask.new,
|
|
_RenderOpType.updateNode.index: _UpdateNodeOpTask.new,
|
|
_RenderOpType.updateLayout.index: _UpdateLayoutOpTask.new,
|
|
_RenderOpType.batch.index: (instanceId, nodeId, params) => _BatchOpTask(instanceId),
|
|
_RenderOpType.dispatchUiFunc.index: _CallUiFunctionOpTask.new,
|
|
_RenderOpType.addEvent.index: _AddEventOpTask.new,
|
|
_RenderOpType.removeEvent.index: _RemoveEventOpTask.new,
|
|
};
|
|
|
|
RenderOperatorRunner();
|
|
|
|
void bindRenderContext(RenderContext renderContext) {
|
|
_renderContext = renderContext;
|
|
}
|
|
|
|
void consumeRenderOp(int instanceId, List renderOpList) {
|
|
if (renderOpList.isNotEmpty) {
|
|
for (var op in renderOpList) {
|
|
try {
|
|
_parseOp(op as List, instanceId)?._run();
|
|
// ignore: avoid_catching_errors
|
|
} on Error catch (e) {
|
|
LogUtils.dOperate('consume render op error, op:$op, error:$e');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RenderOpTask? _parseOp(List opData, int instanceId) {
|
|
var type = opData[0] as int;
|
|
var nodeId = opData[1] as int;
|
|
var args = opData.length > 2 ? opData[2] as Map : {};
|
|
return _taskGeneratorMap[type]?.call(instanceId, nodeId, args)
|
|
?.._renderContext = _renderContext;
|
|
}
|
|
|
|
@override
|
|
void destroy() {}
|
|
}
|
|
|
|
abstract class RenderOpTask {
|
|
final int _instanceId;
|
|
|
|
RenderOpTask(this._instanceId);
|
|
|
|
late RenderContext _renderContext;
|
|
|
|
VirtualNodeManager get virtualNodeManager => _renderContext.virtualNodeManager;
|
|
|
|
RenderManager get renderManager => _renderContext.renderManager;
|
|
|
|
VoltronRenderBridgeManager get bridgeManager => _renderContext.renderBridgeManager;
|
|
|
|
void _run();
|
|
}
|
|
|
|
abstract class _NodeOpTask extends RenderOpTask {
|
|
final int _nodeId;
|
|
final Map _params;
|
|
|
|
_NodeOpTask(int instanceId, this._nodeId, this._params) : super(instanceId);
|
|
}
|
|
|
|
class _AddNodeOpTask extends _NodeOpTask {
|
|
_AddNodeOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
var className = _params[_RenderOpParamsKey.kClassNameKey] ?? '';
|
|
var childIndex = _params[_RenderOpParamsKey.kChildIndexKey] ?? kInvalidIndex;
|
|
var parentId = _params[_RenderOpParamsKey.kParentNodeIdKey] ?? kInvalidId;
|
|
var styleMap = _params[_RenderOpParamsKey.kStylesKey] ?? {};
|
|
var propMap = _params[_RenderOpParamsKey.kPropsKey] ?? {};
|
|
LogUtils.dOperate(
|
|
'addNode ID:$_nodeId, className:$className, childIndex:$childIndex, parentId: $parentId, styleMap: ${styleMap.toString()}, propMap: ${propMap.toString()}');
|
|
var composePropMap = VoltronMap.fromMap(propMap);
|
|
composePropMap.pushAll(VoltronMap.fromMap(styleMap));
|
|
onCreateNode(_nodeId, className);
|
|
virtualNodeManager.createNode(
|
|
_instanceId,
|
|
_nodeId,
|
|
parentId,
|
|
childIndex,
|
|
className,
|
|
composePropMap,
|
|
);
|
|
if (virtualNodeManager.hasVirtualParent(_nodeId)) return;
|
|
renderManager.addUITask(() {
|
|
renderManager.createNode(
|
|
_instanceId,
|
|
_nodeId,
|
|
parentId,
|
|
childIndex,
|
|
className,
|
|
composePropMap,
|
|
);
|
|
});
|
|
}
|
|
|
|
void onCreateNode(int id, String className) {
|
|
if (className == ModalController.kClassName) {
|
|
var width = ScreenUtil.getInstance().screenWidth;
|
|
var height = ScreenUtil.getInstance().screenHeight;
|
|
if (width > 0 && height > 0) {
|
|
bridgeManager.updateNodeSize(
|
|
_instanceId,
|
|
nodeId: _nodeId,
|
|
width: width,
|
|
height: height,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class _DeleteNodeOpTask extends _NodeOpTask {
|
|
_DeleteNodeOpTask(int instanceId, int nodeId) : super(instanceId, nodeId, {});
|
|
|
|
@override
|
|
void _run() {
|
|
LogUtils.dOperate('deleteNode ID:$_nodeId}');
|
|
if (virtualNodeManager.hasVirtualParent(_nodeId)) {
|
|
virtualNodeManager.deleteNode(_nodeId);
|
|
return;
|
|
}
|
|
virtualNodeManager.deleteNode(_nodeId);
|
|
renderManager.addUITask(() {
|
|
renderManager.deleteNode(_instanceId, _nodeId);
|
|
});
|
|
}
|
|
}
|
|
|
|
class _UpdateNodeOpTask extends _NodeOpTask {
|
|
_UpdateNodeOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
var propMap = _params[_RenderOpParamsKey.kPropsKey] ?? {};
|
|
LogUtils.dOperate('updateNode ID:$_nodeId, propMap:${propMap.toString()}');
|
|
virtualNodeManager.updateNode(_nodeId, VoltronMap.fromMap(propMap));
|
|
if (virtualNodeManager.hasVirtualParent(_nodeId)) return;
|
|
renderManager.addUITask(() {
|
|
renderManager.updateNode(
|
|
_instanceId,
|
|
_nodeId,
|
|
VoltronMap.fromMap(propMap),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
class _UpdateLayoutOpTask extends _NodeOpTask {
|
|
_UpdateLayoutOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
var layoutNodeList = (_params[_RenderOpParamsKey.kLayoutNodesKey] ?? []) as List;
|
|
for (var layoutNode in layoutNodeList) {
|
|
if (layoutNode is List) {
|
|
var nodeId = (layoutNode[0] ?? kInvalidId) as int;
|
|
if (nodeId.isValidId()) {
|
|
var left = layoutNode[1] ?? 0;
|
|
var top = layoutNode[2] ?? 0;
|
|
var width = layoutNode[3] ?? 0;
|
|
var height = layoutNode[4] ?? 0;
|
|
LogUtils.dOperate(
|
|
'updateLayout ID:$nodeId, top:$top, left:$left, width: $width, height: $height');
|
|
if (virtualNodeManager.hasVirtualParent(nodeId)) continue;
|
|
final TextExtra? supplier = virtualNodeManager.updateLayout(nodeId, width, layoutNode);
|
|
renderManager.addUITask(() {
|
|
if (supplier != null) {
|
|
renderManager.updateExtra(_instanceId, nodeId, supplier);
|
|
}
|
|
renderManager.updateLayout(
|
|
_instanceId,
|
|
nodeId,
|
|
left,
|
|
top,
|
|
width,
|
|
height,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class _RecombineNodeOpTask extends _NodeOpTask {
|
|
_RecombineNodeOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
var moveIdList = _params[_RenderOpParamsKey.kMoveIdListKey] ?? [];
|
|
var movePid = _params[_RenderOpParamsKey.kMovePidKey];
|
|
var moveIndex = _params[_RenderOpParamsKey.kMoveIndexKey];
|
|
LogUtils.dOperate(
|
|
'recombineNode ID:$_nodeId, movePid:$movePid, moveIdList:${moveIdList.toString()}, moveIndex:$moveIndex',
|
|
);
|
|
renderManager.addUITask(() {
|
|
renderManager.recombineNode(_instanceId, moveIdList, movePid, moveIndex, _nodeId);
|
|
});
|
|
}
|
|
}
|
|
|
|
class _MoveNodeOpTask extends _NodeOpTask {
|
|
_MoveNodeOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
var moveId = _params[_RenderOpParamsKey.kNodeIdKey] ?? [];
|
|
var movePid = _params[_RenderOpParamsKey.kParentNodeIdKey];
|
|
var moveIndex = _params[_RenderOpParamsKey.kChildIndexKey];
|
|
LogUtils.dOperate(
|
|
'moveNode ID:$_nodeId, movePid:$movePid, moveIndex:$moveIndex',
|
|
);
|
|
var virtualParent = virtualNodeManager.virtualNodes[movePid];
|
|
if (virtualParent == null) {
|
|
renderManager.addUITask(() {
|
|
renderManager.moveNode(_instanceId, moveId, movePid, moveIndex);
|
|
});
|
|
} else {
|
|
virtualNodeManager.moveNode(moveId, virtualParent, moveIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _BatchOpTask extends RenderOpTask {
|
|
_BatchOpTask(int instanceId) : super(instanceId);
|
|
|
|
@override
|
|
void _run() {
|
|
LogUtils.dOperate('batch');
|
|
Map<int, TextData>? layoutToUpdate = virtualNodeManager.endBatch();
|
|
if (layoutToUpdate != null) {
|
|
layoutToUpdate.forEach((int id, TextData textData) {
|
|
renderManager.addUITask(() {
|
|
renderManager.updateExtra(_instanceId, id, textData);
|
|
});
|
|
});
|
|
}
|
|
renderManager.renderBatchEnd();
|
|
}
|
|
}
|
|
|
|
class _CallUiFunctionOpTask extends _NodeOpTask {
|
|
_CallUiFunctionOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
String funcName = _params[_RenderOpParamsKey.kFuncNameKey] ?? '';
|
|
if (funcName.isNotEmpty) {
|
|
Uint8List funcParams = _params[_RenderOpParamsKey.kFuncParamsKey] ?? [];
|
|
var realParams = funcParams.decodeType<VoltronArray>() ?? VoltronArray();
|
|
String callbackId = _params[_RenderOpParamsKey.kFuncIdKey] ?? Promise.kCallIdNoCallback;
|
|
var promise = NativePromise(_renderContext, callId: callbackId);
|
|
LogUtils.dOperate(
|
|
'callUIFunction ID:$_nodeId, funcName:$funcName, realParams: ${realParams.toString()}');
|
|
renderManager.addNulUITask(() {
|
|
renderManager.dispatchUIFunction(
|
|
_instanceId,
|
|
_nodeId,
|
|
funcName,
|
|
realParams,
|
|
promise,
|
|
);
|
|
});
|
|
renderManager.renderBatchEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
class _AddEventOpTask extends _NodeOpTask {
|
|
_AddEventOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
String eventName = _params[_RenderOpParamsKey.kFuncNameKey] ?? '';
|
|
LogUtils.dOperate('addEvent ID:$_nodeId, eventName:$eventName');
|
|
virtualNodeManager.addEvent(_instanceId, _nodeId, eventName);
|
|
if (virtualNodeManager.hasVirtualParent(_nodeId)) return;
|
|
renderManager.addNulUITask(() {
|
|
renderManager.setEventListener(_instanceId, _nodeId, eventName);
|
|
});
|
|
}
|
|
}
|
|
|
|
class _RemoveEventOpTask extends _NodeOpTask {
|
|
_RemoveEventOpTask(int instanceId, int nodeId, Map params) : super(instanceId, nodeId, params);
|
|
|
|
@override
|
|
void _run() {
|
|
String eventName = _params[_RenderOpParamsKey.kFuncNameKey] ?? '';
|
|
LogUtils.dOperate('removeEvent ID:$_nodeId, eventName:$eventName');
|
|
virtualNodeManager.removeEvent(_instanceId, _nodeId, eventName);
|
|
if (virtualNodeManager.hasVirtualParent(_nodeId)) return;
|
|
renderManager.addNulUITask(() {
|
|
renderManager.removeEventListener(_instanceId, _nodeId, eventName);
|
|
});
|
|
}
|
|
}
|
|
|
|
enum EventType { click, longClick, touchStart, touchMove, touchEnd, touchCancel, show, dismiss }
|
|
|
|
enum _RenderOpType {
|
|
addNode,
|
|
deleteNode,
|
|
// 旧版本的moveNode,其实是updateNode内部的衍生操作
|
|
recombineNode,
|
|
moveNode,
|
|
updateNode,
|
|
updateLayout,
|
|
batch,
|
|
dispatchUiFunc,
|
|
addEvent,
|
|
removeEvent,
|
|
}
|
|
|
|
class _RenderOpParamsKey {
|
|
static const String kNodeIdKey = "id";
|
|
static const String kParentNodeIdKey = "pid";
|
|
static const String kChildIndexKey = "index";
|
|
static const String kClassNameKey = "name";
|
|
static const String kFuncNameKey = "func_name";
|
|
static const String kFuncParamsKey = "func_params";
|
|
static const String kFuncIdKey = "callback_id";
|
|
static const String kPropsKey = "props";
|
|
static const String kStylesKey = "styles";
|
|
static const String kMoveIdListKey = "move_id";
|
|
static const String kMovePidKey = "move_pid";
|
|
static const String kMoveIndexKey = "move_index";
|
|
static const String kLayoutNodesKey = "layout_nodes";
|
|
}
|