mirror of
https://gitee.com/openharmony/arkui_ace_engine
synced 2024-11-22 22:50:53 +00:00
repeat feture
Signed-off-by: chenbenzhi <chenbenzhi@huawei.com> Change-Id: Ie59b5b8d18ac833b2f44edb70be06e038c78211f
This commit is contained in:
parent
0b360b6bcc
commit
00c2b882cc
@ -45,7 +45,7 @@ BreakAfterJavaFieldAnnotations: true
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
QualifierAlignment: Leave
|
||||
ReflowComments: false
|
||||
ReflowComments: true
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
@ -299,6 +299,7 @@ template("declarative_js_engine") {
|
||||
"jsview/js_rendering_context.cpp",
|
||||
"jsview/js_rendering_context_settings.cpp",
|
||||
"jsview/js_repeat.cpp",
|
||||
"jsview/js_repeat_virtual_scroll.cpp",
|
||||
"jsview/js_richeditor.cpp",
|
||||
"jsview/js_row.cpp",
|
||||
"jsview/js_row_split.cpp",
|
||||
@ -819,6 +820,7 @@ template("declarative_js_engine_ng") {
|
||||
"jsview/js_rendering_context.cpp",
|
||||
"jsview/js_rendering_context_settings.cpp",
|
||||
"jsview/js_repeat.cpp",
|
||||
"jsview/js_repeat_virtual_scroll.cpp",
|
||||
"jsview/js_richeditor.cpp",
|
||||
"jsview/js_row.cpp",
|
||||
"jsview/js_row_split.cpp",
|
||||
|
@ -136,6 +136,7 @@
|
||||
#include "bridge/declarative_frontend/jsview/js_rendering_context.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_rendering_context_settings.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_repeat.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_richeditor.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_row.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_row_split.h"
|
||||
@ -634,6 +635,7 @@ static const std::unordered_map<std::string, std::function<void(BindingTarget)>>
|
||||
{ "Swiper", JSSwiper::JSBind },
|
||||
{ "Panel", JSSlidingPanel::JSBind },
|
||||
{ "RepeatNative", JSRepeat::JSBind },
|
||||
{ "RepeatVirtualScrollNative", JSRepeatVirtualScroll::JSBind },
|
||||
{ "NavDestination", JSNavDestination::JSBind },
|
||||
{ "Navigation", JSNavigation::JSBind },
|
||||
{ "NativeNavPathStack", JSNavPathStack::JSBind },
|
||||
|
@ -123,6 +123,7 @@
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_rendering_context.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_rendering_context_settings.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_repeat.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_row.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_row_split.h"
|
||||
#include "frameworks/bridge/declarative_frontend/jsview/js_scope_util.h"
|
||||
@ -451,6 +452,7 @@ void JsBindViews(BindingTarget globalObj, void* nativeEngine)
|
||||
JSTabsController::JSBind(globalObj);
|
||||
JSForEach::JSBind(globalObj);
|
||||
JSRepeat::JSBind(globalObj);
|
||||
JSRepeatVirtualScroll::JSBind(globalObj);
|
||||
JSIfElse::JSBind(globalObj);
|
||||
JSDivider::JSBind(globalObj);
|
||||
JSScroll::JSBind(globalObj);
|
||||
|
@ -4113,21 +4113,15 @@ class PUV2ViewBase extends NativeViewPartialUpdate {
|
||||
this.purgeDeletedElmtIds();
|
||||
Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId));
|
||||
if (deep) {
|
||||
this.childrenWeakrefMap_.forEach((weakRefChild) => {
|
||||
const child = weakRefChild.deref();
|
||||
if (child) {
|
||||
if (child instanceof ViewPU) {
|
||||
if (!child.hasBeenRecycled_) {
|
||||
child.forceCompleteRerender(true);
|
||||
} else {
|
||||
child.delayCompleteRerender(deep);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('forceCompleteRender not implemented for ViewV2, yet');
|
||||
for (const child of this.childrenWeakrefMap_.values()) {
|
||||
const childView = child.deref();
|
||||
if (childView) {
|
||||
childView.forceCompleteRerender(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/**
|
||||
* force a complete rerender / update on specific node by executing update function.
|
||||
@ -7315,6 +7309,10 @@ class ObserveV2 {
|
||||
static IsObservedObjectV2(value) {
|
||||
return (value && typeof (value) === 'object' && value[ObserveV2.V2_DECO_META]);
|
||||
}
|
||||
static getCurrentRecordedId() {
|
||||
const bound = ObserveV2.getObserve().stackOfRenderedComponents_.top();
|
||||
return bound ? bound[0] : -1;
|
||||
}
|
||||
// At the start of observeComponentCreation or
|
||||
// MonitorV2 observeObjectAccess
|
||||
startRecordDependencies(cmp, id) {
|
||||
@ -7464,8 +7462,6 @@ class ObserveV2 {
|
||||
// add dependency view model object 'target' property 'attrName'
|
||||
// to current this.bindId
|
||||
addRef(target, attrName) {
|
||||
var _a, _b, _c, _d;
|
||||
var _e, _f;
|
||||
const bound = this.stackOfRenderedComponents_.top();
|
||||
if (!bound) {
|
||||
return;
|
||||
@ -7476,7 +7472,15 @@ class ObserveV2 {
|
||||
throw new TypeError(error);
|
||||
}
|
||||
|
||||
const id = bound[0];
|
||||
this.addRef4IdInternal(bound[0], target, attrName);
|
||||
}
|
||||
addRef4Id(id, target, attrName) {
|
||||
|
||||
this.addRef4IdInternal(id, target, attrName);
|
||||
}
|
||||
addRef4IdInternal(id, target, attrName) {
|
||||
var _a, _b, _c, _d;
|
||||
var _e, _f;
|
||||
// Map: attribute/symbol -> dependent id
|
||||
const symRefs = (_a = target[_e = ObserveV2.SYMBOL_REFS]) !== null && _a !== void 0 ? _a : (target[_e] = {});
|
||||
(_b = symRefs[attrName]) !== null && _b !== void 0 ? _b : (symRefs[attrName] = new Set());
|
||||
@ -7588,10 +7592,23 @@ class ObserveV2 {
|
||||
}
|
||||
updateDirty() {
|
||||
this.startDirty_ = true;
|
||||
this.updateDirty2();
|
||||
this.updateDirty2(false);
|
||||
this.startDirty_ = false;
|
||||
}
|
||||
updateDirty2() {
|
||||
/**
|
||||
* execute /update in this order
|
||||
* - @Computed variables
|
||||
* - @Monitor functions
|
||||
* - UINode re-render
|
||||
* three nested loops, means:
|
||||
* process @Computed until no more @Computed need update
|
||||
* process @Monitor until no more @Computed and @Monitor
|
||||
* process UINode update until no more @Computed and @Monitor and UINode rerender
|
||||
*
|
||||
* @param updateUISynchronously should be set to true if called during VSYNC only
|
||||
*
|
||||
*/
|
||||
updateDirty2(updateUISynchronously = false) {
|
||||
aceTrace.begin('updateDirty2');
|
||||
|
||||
// obtain and unregister the removed elmtIds
|
||||
@ -7626,9 +7643,10 @@ class ObserveV2 {
|
||||
if (this.elmtIdsChanged_.size) {
|
||||
const elmtIds = Array.from(this.elmtIdsChanged_).sort((elmtId1, elmtId2) => elmtId1 - elmtId2);
|
||||
this.elmtIdsChanged_ = new Set();
|
||||
this.updateUINodes(elmtIds);
|
||||
updateUISynchronously ? this.updateUINodesSynchronously(elmtIds) : this.updateUINodes(elmtIds);
|
||||
}
|
||||
} while (this.elmtIdsChanged_.size + this.monitorIdsChanged_.size + this.computedPropIdsChanged_.size > 0);
|
||||
|
||||
aceTrace.end();
|
||||
}
|
||||
updateDirtyComputedProps(computed) {
|
||||
@ -7676,10 +7694,11 @@ class ObserveV2 {
|
||||
* FlushDirtyNodesUpdate to CustomNode to ViewV2.updateDirtyElements to UpdateElement
|
||||
* Code left here to reproduce benchmark measurements, compare with future optimisation
|
||||
* @param elmtIds
|
||||
*
|
||||
*/
|
||||
updateUINodesWithoutVSync(elmtIds) {
|
||||
updateUINodesSynchronously(elmtIds) {
|
||||
|
||||
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtId`);
|
||||
let view;
|
||||
let weak;
|
||||
elmtIds.forEach((elmtId) => {
|
||||
@ -7703,7 +7722,7 @@ class ObserveV2 {
|
||||
// much slower
|
||||
updateUINodes(elmtIds) {
|
||||
|
||||
aceTrace.begin(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtId`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
|
||||
let viewWeak;
|
||||
let view;
|
||||
elmtIds.forEach((elmtId) => {
|
||||
@ -9530,9 +9549,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
||||
// implementation for existing state observation system
|
||||
class __RepeatItemPU {
|
||||
constructor(owningView, initialItem, initialIndex) {
|
||||
this._observedItem = new ObservedPropertyPU(initialItem, owningView, "Repeat item");
|
||||
this._observedItem = new ObservedPropertyPU(initialItem, owningView, 'Repeat item');
|
||||
if (initialIndex !== undefined) {
|
||||
this._observedIndex = new ObservedPropertyPU(initialIndex, owningView, "Repeat index");
|
||||
this._observedIndex = new ObservedPropertyPU(initialIndex, owningView, 'Repeat index');
|
||||
}
|
||||
}
|
||||
get item() {
|
||||
@ -9597,7 +9616,7 @@ class __RepeatDefaultKeyGen {
|
||||
}
|
||||
static funcImpl(item) {
|
||||
// fast keygen logic can be used with objects/symbols only
|
||||
if (typeof item !== 'object' && typeof item !== 'symbol') {
|
||||
if (typeof item != 'object' && typeof item !== 'symbol') {
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
// generate a numeric key, store mappings in WeakMap
|
||||
@ -9610,35 +9629,130 @@ class __RepeatDefaultKeyGen {
|
||||
}
|
||||
__RepeatDefaultKeyGen.weakMap_ = new WeakMap();
|
||||
__RepeatDefaultKeyGen.lastKey_ = 0;
|
||||
;
|
||||
;
|
||||
// __Repeat implements ForEach with child re-use for both existing state observation
|
||||
// and deep observation , for non-virtual and virtual code paths (TODO)
|
||||
class __RepeatV2 {
|
||||
constructor(arr) {
|
||||
this.config = {};
|
||||
this.isVirtualScroll = false;
|
||||
this.key2Item_ = new Map();
|
||||
this.arr_ = arr !== null && arr !== void 0 ? arr : [];
|
||||
this.keyGenFunction_ = __RepeatDefaultKeyGen.func;
|
||||
}
|
||||
updateArr(arr) {
|
||||
this.arr_ = arr !== null && arr !== void 0 ? arr : [];
|
||||
return this;
|
||||
//console.log('__RepeatV2 ctor')
|
||||
this.config.arr = arr !== null && arr !== void 0 ? arr : [];
|
||||
this.config.itemGenFuncs = {};
|
||||
this.config.keyGenFunc = __RepeatDefaultKeyGen.func;
|
||||
this.config.typeGenFunc = (() => '');
|
||||
this.config.totalCount = this.config.arr.length;
|
||||
this.config.templateOptions = {};
|
||||
this.config.mkRepeatItem = this.mkRepeatItem;
|
||||
}
|
||||
each(itemGenFunc) {
|
||||
this.itemGenFunc_ = itemGenFunc;
|
||||
//console.log('__RepeatV2.each()')
|
||||
this.config.itemGenFuncs[''] = itemGenFunc;
|
||||
this.config.templateOptions[''] = Object.assign({}, this.defaultTemplateOptions());
|
||||
return this;
|
||||
}
|
||||
key(idGenFunc) {
|
||||
this.keyGenFunction_ = idGenFunc !== null && idGenFunc !== void 0 ? idGenFunc : __RepeatDefaultKeyGen.func;
|
||||
key(keyGenFunc) {
|
||||
//console.log('__RepeatV2.key()')
|
||||
this.config.keyGenFunc = keyGenFunc !== null && keyGenFunc !== void 0 ? keyGenFunc : __RepeatDefaultKeyGen.func;
|
||||
return this;
|
||||
}
|
||||
virtualScroll() {
|
||||
virtualScroll(options) {
|
||||
var _a;
|
||||
//console.log('__RepeatV2.virtualScroll()')
|
||||
this.config.totalCount = (_a = options === null || options === void 0 ? void 0 : options.totalCount) !== null && _a !== void 0 ? _a : this.config.arr.length;
|
||||
this.isVirtualScroll = true;
|
||||
return this;
|
||||
}
|
||||
onMove(handler) {
|
||||
this.onMoveHandler_ = handler;
|
||||
// function to decide which template to use, each template has an id
|
||||
templateId(typeFunc) {
|
||||
//console.log('__RepeatV2.templateId()')
|
||||
this.config.typeGenFunc = typeFunc;
|
||||
return this;
|
||||
}
|
||||
// template: id + builder function to render specific type of data item
|
||||
template(type, itemGenFunc, options) {
|
||||
//console.log('__RepeatV2.template()')
|
||||
this.config.itemGenFuncs[type] = itemGenFunc;
|
||||
this.config.templateOptions[type] = Object.assign(Object.assign({}, this.defaultTemplateOptions()), options);
|
||||
return this;
|
||||
}
|
||||
updateArr(arr) {
|
||||
//console.log('__RepeatV2.updateArr()')
|
||||
this.config.arr = arr !== null && arr !== void 0 ? arr : [];
|
||||
return this;
|
||||
}
|
||||
render(isInitialRender) {
|
||||
var _a, _b, _c;
|
||||
//console.log('__RepeatV2.render()')
|
||||
if (!((_a = this.config.itemGenFuncs) === null || _a === void 0 ? void 0 : _a[''])) {
|
||||
throw new Error(`__RepeatV2 item builder function unspecified. Usage error`);
|
||||
}
|
||||
if (!this.isVirtualScroll) {
|
||||
// Repeat
|
||||
(_b = this.impl) !== null && _b !== void 0 ? _b : (this.impl = new __RepeatImpl());
|
||||
this.impl.render(this.config, isInitialRender);
|
||||
}
|
||||
else {
|
||||
// RepeatVirtualScroll
|
||||
(_c = this.impl) !== null && _c !== void 0 ? _c : (this.impl = new __RepeatVirtualScrollImpl());
|
||||
this.impl.render(this.config, isInitialRender);
|
||||
}
|
||||
}
|
||||
onMove(handler) {
|
||||
this.config.onMoveHandler = handler;
|
||||
return this;
|
||||
}
|
||||
defaultTemplateOptions() {
|
||||
return { cachedCount: 1 };
|
||||
}
|
||||
mkRepeatItem(item, index) {
|
||||
return new __RepeatItemV2(item, index);
|
||||
}
|
||||
}
|
||||
; // __RepeatV2<T>
|
||||
// __Repeat implements ForEach with child re-use for both existing state observation
|
||||
// and deep observation , for non-virtual and virtual code paths (TODO)
|
||||
class __RepeatPU extends __RepeatV2 {
|
||||
constructor(owningView, arr) {
|
||||
super(arr);
|
||||
this.owningView_ = owningView;
|
||||
}
|
||||
mkRepeatItem(item, index) {
|
||||
return new __RepeatItemPU(this.owningView_, item, index);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2023-2024 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.
|
||||
*
|
||||
* all definitions in this file are framework internal
|
||||
*/
|
||||
class __RepeatImpl {
|
||||
/**/
|
||||
constructor() {
|
||||
this.key2Item_ = new Map();
|
||||
}
|
||||
/**/
|
||||
render(config, isInitialRender) {
|
||||
this.arr_ = config.arr;
|
||||
this.itemGenFuncs_ = config.itemGenFuncs;
|
||||
this.typeGenFunc_ = config.typeGenFunc;
|
||||
this.keyGenFunction_ = config.keyGenFunc;
|
||||
this.mkRepeatItem_ = config.mkRepeatItem;
|
||||
this.onMoveHandler_ = config.onMoveHandler;
|
||||
isInitialRender ? this.initialRender() : this.reRender();
|
||||
}
|
||||
genKeys() {
|
||||
const key2Item = new Map();
|
||||
this.arr_.forEach((item, index) => {
|
||||
@ -9646,34 +9760,20 @@ class __RepeatV2 {
|
||||
key2Item.set(key, { key, index });
|
||||
});
|
||||
if (key2Item.size < this.arr_.length) {
|
||||
stateMgmtConsole.warn("Duplicates detected, fallback to index-based keyGen.");
|
||||
stateMgmtConsole.warn("__RepeatImpl: Duplicates detected, fallback to index-based keyGen.");
|
||||
// Causes all items to be re-rendered
|
||||
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
|
||||
return this.genKeys();
|
||||
}
|
||||
return key2Item;
|
||||
}
|
||||
mkRepeatItem(item, index) {
|
||||
return new __RepeatItemV2(item, index);
|
||||
}
|
||||
render(isInitialRender) {
|
||||
if (!this.itemGenFunc_) {
|
||||
throw new Error(`itemGen function undefined. Usage error`);
|
||||
}
|
||||
if (this.isVirtualScroll) {
|
||||
// TODO: Add render for LazyforEach with child update.
|
||||
throw new Error("TODO virtual code path");
|
||||
}
|
||||
else {
|
||||
isInitialRender ? this.initialRenderNoneVirtual() : this.rerenderNoneVirtual();
|
||||
}
|
||||
}
|
||||
initialRenderNoneVirtual() {
|
||||
initialRender() {
|
||||
//console.log('__RepeatImpl initialRender() 0')
|
||||
this.key2Item_ = this.genKeys();
|
||||
RepeatNative.startRender();
|
||||
let index = 0;
|
||||
this.key2Item_.forEach((itemInfo, key) => {
|
||||
itemInfo.repeatItem = this.mkRepeatItem(this.arr_[index], index);
|
||||
itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
index++;
|
||||
});
|
||||
@ -9684,7 +9784,7 @@ class __RepeatV2 {
|
||||
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
|
||||
|
||||
}
|
||||
rerenderNoneVirtual() {
|
||||
reRender() {
|
||||
const oldKey2Item = this.key2Item_;
|
||||
this.key2Item_ = this.genKeys();
|
||||
// identify array items that have been deleted
|
||||
@ -9710,6 +9810,15 @@ class __RepeatV2 {
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldIndex);
|
||||
// TBD moveChild() only when item types are same
|
||||
//const type0 = this.typeGenFunc_(oldItemInfo.repeatItem.item, oldIndex);
|
||||
//const type1 = this.typeGenFunc_(itemInfo.repeatItem.item, index);
|
||||
//if (type0 == type1) {
|
||||
// // C++ mv from tempChildren[oldIndex] to end of children_
|
||||
// RepeatNative.moveChild(oldIndex);
|
||||
//} else {
|
||||
// this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
//}
|
||||
}
|
||||
else if (deletedKeysAndIndex.length) {
|
||||
// case #2:
|
||||
@ -9725,6 +9834,7 @@ class __RepeatV2 {
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
// update key2item_ Map
|
||||
this.key2Item_.set(key, itemInfo);
|
||||
// TBD moveChild() only when item types are same
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldKeyIndex);
|
||||
}
|
||||
@ -9732,7 +9842,7 @@ class __RepeatV2 {
|
||||
// case #3:
|
||||
// new array item, there are no deleted array items
|
||||
// render new UINode children
|
||||
itemInfo.repeatItem = this.mkRepeatItem(item, index);
|
||||
itemInfo.repeatItem = this.mkRepeatItem_(item, index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
}
|
||||
index++;
|
||||
@ -9752,26 +9862,155 @@ class __RepeatV2 {
|
||||
|
||||
}
|
||||
initialRenderItem(key, repeatItem) {
|
||||
var _a, _b;
|
||||
//console.log('__RepeatImpl initialRenderItem()')
|
||||
// render new UINode children
|
||||
|
||||
// C++: initial render will render to the end of children_
|
||||
RepeatNative.createNewChildStart(key);
|
||||
// execute the ItemGen function
|
||||
this.itemGenFunc_(repeatItem);
|
||||
// execute the itemGen function
|
||||
const itemType = (_a = this.typeGenFunc_(repeatItem.item, repeatItem.index)) !== null && _a !== void 0 ? _a : '';
|
||||
const itemFunc = (_b = this.itemGenFuncs_[itemType]) !== null && _b !== void 0 ? _b : this.itemGenFuncs_[''];
|
||||
itemFunc(repeatItem);
|
||||
RepeatNative.createNewChildFinish(key);
|
||||
}
|
||||
}
|
||||
// __Repeat implements ForEach with child re-use for both existing state observation
|
||||
// and deep observation , for non-virtual and virtual code paths (TODO)
|
||||
class __RepeatPU extends __RepeatV2 {
|
||||
constructor(owningView, arr) {
|
||||
super(arr);
|
||||
this.owningView_ = owningView;
|
||||
;
|
||||
/*
|
||||
* Copyright (c) 2023-2024 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.
|
||||
*
|
||||
* all definitions in this file are framework internal
|
||||
*/
|
||||
// Implements ForEach with child re-use for both existing state observation and
|
||||
// deep observation. For virtual-scroll code paths
|
||||
class __RepeatVirtualScrollImpl {
|
||||
/**/
|
||||
constructor() {
|
||||
}
|
||||
mkRepeatItem(item, index) {
|
||||
return new __RepeatItemPU(this.owningView_, item, index);
|
||||
render(config, isInitialRender) {
|
||||
this.arr_ = config.arr;
|
||||
this.itemGenFuncs_ = config.itemGenFuncs;
|
||||
this.keyGenFunc_ = config.keyGenFunc;
|
||||
this.typeGenFunc_ = config.typeGenFunc;
|
||||
this.totalCount_ = config.totalCount;
|
||||
this.templateOptions_ = config.templateOptions;
|
||||
this.mkRepeatItem_ = config.mkRepeatItem;
|
||||
this.onMoveHandler_ = config.onMoveHandler;
|
||||
if (isInitialRender) {
|
||||
this.initialRender(ObserveV2.getCurrentRecordedId());
|
||||
}
|
||||
else {
|
||||
this.reRender();
|
||||
}
|
||||
}
|
||||
/**/
|
||||
initialRender(repeatElmtId) {
|
||||
// Map key -> RepeatItem
|
||||
// added to closure of following lambdas
|
||||
const _repeatItem4Key = new Map();
|
||||
const repeatElmtId1 = repeatElmtId;
|
||||
const onCreateNode = (forIndex) => {
|
||||
|
||||
if (forIndex < 0) {
|
||||
// FIXME check also index < totalCount
|
||||
throw new Error(`__RepeatVirtualScrollImpl onCreateNode: for index=${forIndex} out of range error.`);
|
||||
}
|
||||
// create dependency array item [forIndex] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
|
||||
const repeatItem = this.mkRepeatItem_(this.arr_[forIndex], forIndex);
|
||||
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
|
||||
_repeatItem4Key.set(forKey, repeatItem);
|
||||
// execute the itemGen function
|
||||
this.initialRenderItem(repeatItem);
|
||||
|
||||
};
|
||||
const onUpdateNode = (fromKey, forIndex) => {
|
||||
if (!fromKey || fromKey == "" || forIndex < 0) {
|
||||
// FIXME check also index < totalCount
|
||||
throw new Error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} invalid function input error.`);
|
||||
}
|
||||
// create dependency array item [forIndex] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
|
||||
const repeatItem = _repeatItem4Key.get(fromKey);
|
||||
if (!repeatItem) {
|
||||
stateMgmtConsole.error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex}, can not find RepeatItem for key. Unrecoverable error`);
|
||||
return;
|
||||
}
|
||||
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
|
||||
|
||||
repeatItem.updateItem(this.arr_[forIndex]);
|
||||
repeatItem.updateIndex(forIndex);
|
||||
// update Map according to made update:
|
||||
// del fromKey entry and add forKey
|
||||
_repeatItem4Key.delete(fromKey);
|
||||
_repeatItem4Key.set(forKey, repeatItem);
|
||||
|
||||
// FIXME request re-render right away!
|
||||
ObserveV2.getObserve().updateDirty2(true);
|
||||
};
|
||||
const onGetKeys4Range = (from, to) => {
|
||||
|
||||
const result = new Array();
|
||||
for (let i = from; i <= to && i < this.arr_.length; i++) {
|
||||
// create dependency array item [i] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
|
||||
result.push(this.keyGenFunc_(this.arr_[i], i));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
const onGetTypes4Range = (from, to) => {
|
||||
var _a;
|
||||
|
||||
const result = new Array();
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
for (let i = from; i <= to && i < this.arr_.length; i++) {
|
||||
// ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
|
||||
result.push((_a = this.typeGenFunc_(this.arr_[i], i)) !== null && _a !== void 0 ? _a : '');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
RepeatVirtualScrollNative.create(this.totalCount_, Object.entries(this.templateOptions_), {
|
||||
onCreateNode,
|
||||
onUpdateNode,
|
||||
onGetKeys4Range,
|
||||
onGetTypes4Range
|
||||
});
|
||||
|
||||
}
|
||||
reRender() {
|
||||
|
||||
RepeatVirtualScrollNative.invalidateKeyCache(this.totalCount_);
|
||||
|
||||
}
|
||||
initialRenderItem(repeatItem) {
|
||||
var _a, _b;
|
||||
// execute the itemGen function
|
||||
const itemType = (_a = this.typeGenFunc_(repeatItem.item, repeatItem.index)) !== null && _a !== void 0 ? _a : '';
|
||||
const itemFunc = (_b = this.itemGenFuncs_[itemType]) !== null && _b !== void 0 ? _b : this.itemGenFuncs_[''];
|
||||
itemFunc(repeatItem);
|
||||
}
|
||||
}
|
||||
;
|
||||
/*
|
||||
* Copyright (c) 2024 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -106,7 +106,7 @@ void JSListItem::CreateForPartialUpdate(const JSCallbackInfo& args)
|
||||
}
|
||||
|
||||
if (!isLazy) {
|
||||
ListItemModel::GetInstance()->Create();
|
||||
ListItemModel::GetInstance()->Create(nullptr, listItemStyle);
|
||||
} else {
|
||||
RefPtr<JsFunction> jsDeepRender = AceType::MakeRefPtr<JsFunction>(args.This(), JSRef<JSFunc>::Cast(arg0));
|
||||
auto listItemDeepRenderFunc = [execCtx = args.GetExecutionContext(),
|
||||
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 "bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/log/ace_trace.h"
|
||||
#include "base/log/log_wrapper.h"
|
||||
#include "bridge/declarative_frontend/jsview/js_view_common_def.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_model_ng.h"
|
||||
|
||||
#define JSFUNC(opts, propName) (JSRef<JSFunc>::Cast((opts)->GetProperty(propName)))
|
||||
|
||||
namespace OHOS::Ace {
|
||||
|
||||
std::unique_ptr<RepeatVirtualScrollModel> RepeatVirtualScrollModel::instance_ = nullptr;
|
||||
|
||||
RepeatVirtualScrollModel* RepeatVirtualScrollModel::GetInstance()
|
||||
{
|
||||
if (!instance_) {
|
||||
instance_.reset(new NG::RepeatVirtualScrollModelNG());
|
||||
}
|
||||
return instance_.get();
|
||||
}
|
||||
} // namespace OHOS::Ace
|
||||
|
||||
namespace OHOS::Ace::Framework {
|
||||
|
||||
void JSRepeatVirtualScroll::Create(const JSCallbackInfo& info)
|
||||
{
|
||||
// arg 0
|
||||
auto totalCount = info[0]->ToNumber<uint32_t>();
|
||||
|
||||
// arg 1
|
||||
auto templateOptsArray = JSRef<JSArray>::Cast(info[1]);
|
||||
std::map<std::string, uint32_t> templateCachedCountMap;
|
||||
for (size_t i = 0; i < templateOptsArray->Length(); i++) {
|
||||
JSRef<JSArray> pair = templateOptsArray->GetValueAt(i);
|
||||
auto type = pair->GetValueAt(0)->ToString();
|
||||
auto opts = JSRef<JSObject>::Cast(pair->GetValueAt(1));
|
||||
templateCachedCountMap[type] = opts->GetProperty("cachedCount")->ToNumber<uint32_t>();
|
||||
}
|
||||
|
||||
// arg 2
|
||||
auto handlers = JSRef<JSObject>::Cast(info[2]);
|
||||
auto onCreateNode = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onCreateNode")](
|
||||
uint32_t forIndex) -> void {
|
||||
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
|
||||
auto params = ConvertToJSValues(forIndex);
|
||||
func->Call(JSRef<JSObject>(), params.size(), params.data());
|
||||
};
|
||||
|
||||
auto onUpdateNode = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onUpdateNode")](
|
||||
const std::string& fromKey, uint32_t forIndex) -> void {
|
||||
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
|
||||
auto params = ConvertToJSValues(fromKey, forIndex);
|
||||
func->Call(JSRef<JSObject>(), params.size(), params.data());
|
||||
};
|
||||
|
||||
auto onGetKeys4Range = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onGetKeys4Range")](
|
||||
uint32_t from, uint32_t to) -> std::list<std::string> {
|
||||
std::list<std::string> list;
|
||||
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, list);
|
||||
auto params = ConvertToJSValues(from, to);
|
||||
JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), params.size(), params.data());
|
||||
// convert js-array to std::list
|
||||
JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(jsVal);
|
||||
for (size_t i = 0; i < jsArr->Length(); i++) {
|
||||
list.emplace_back(jsArr->GetValueAt(i)->ToString());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
auto onGetTypes4Range = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onGetTypes4Range")](
|
||||
uint32_t from, uint32_t to) -> std::list<std::string> {
|
||||
std::list<std::string> list;
|
||||
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, list);
|
||||
auto params = ConvertToJSValues(from, to);
|
||||
JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), params.size(), params.data());
|
||||
|
||||
// convert js-array to std::list
|
||||
JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(jsVal);
|
||||
for (size_t i = 0; i < jsArr->Length(); i++) {
|
||||
list.emplace_back(jsArr->GetValueAt(i)->ToString());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
RepeatVirtualScrollModel::GetInstance()->Create(
|
||||
totalCount,
|
||||
templateCachedCountMap,
|
||||
onCreateNode,
|
||||
onUpdateNode,
|
||||
onGetKeys4Range,
|
||||
onGetTypes4Range
|
||||
);
|
||||
}
|
||||
|
||||
void JSRepeatVirtualScroll::InvalidateKeyCache(const JSCallbackInfo& info)
|
||||
{
|
||||
ACE_SCOPED_TRACE("RepeatVirtualScroll:InvalidateKeyCache");
|
||||
TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll::InvalidateKeyCache");
|
||||
auto totalCount = info[0]->ToNumber<uint32_t>();
|
||||
RepeatVirtualScrollModel::GetInstance()->InvalidateKeyCache(totalCount);
|
||||
}
|
||||
|
||||
void JSRepeatVirtualScroll::JSBind(BindingTarget globalObj)
|
||||
{
|
||||
JSClass<JSRepeatVirtualScroll>::Declare("RepeatVirtualScrollNative");
|
||||
JSClass<JSRepeatVirtualScroll>::StaticMethod("create", &JSRepeatVirtualScroll::Create);
|
||||
JSClass<JSRepeatVirtualScroll>::StaticMethod("invalidateKeyCache", &JSRepeatVirtualScroll::InvalidateKeyCache);
|
||||
JSClass<JSRepeatVirtualScroll>::Bind<>(globalObj);
|
||||
}
|
||||
|
||||
} // namespace OHOS::Ace::Framework
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_H
|
||||
#define FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_H
|
||||
|
||||
#include "bridge/declarative_frontend/engine/bindings.h"
|
||||
#include "bridge/declarative_frontend/engine/js_ref_ptr.h"
|
||||
|
||||
namespace OHOS::Ace::Framework {
|
||||
|
||||
class JSRepeatVirtualScroll {
|
||||
public:
|
||||
static void JSBind(BindingTarget globalObj);
|
||||
|
||||
static void Create(const JSCallbackInfo& info);
|
||||
static void InvalidateKeyCache(const JSCallbackInfo& info);
|
||||
};
|
||||
|
||||
} // namespace OHOS::Ace::Framework
|
||||
#endif // FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_H
|
@ -33,6 +33,7 @@ declare class ForEach {
|
||||
static createNewChildFinish(id: string, parentView: NativeViewPartialUpdate): void;
|
||||
}
|
||||
|
||||
// Repeat maps to C++ RepeatNode
|
||||
declare class RepeatNative {
|
||||
static startRender(): void;
|
||||
static finishRender(removedChildElmtIds: Array<number>): void;
|
||||
@ -40,4 +41,20 @@ declare class RepeatNative {
|
||||
static createNewChildStart(id: string): void;
|
||||
static createNewChildFinish(id: string): void;
|
||||
static onMove(handler: (from: number, to: number) => void);
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat.virtualScroll maps to C++ RepeatVirtualScrollNode
|
||||
declare class RepeatVirtualScrollNative {
|
||||
static create(
|
||||
totalCount: number,
|
||||
templateOptions: [string, RepeatTemplateOptions][],
|
||||
handlers: {
|
||||
onCreateNode: (forIndex: number) => void;
|
||||
onUpdateNode: (fromKey: string, forIndex: number) => void;
|
||||
onGetKeys4Range: (from: number, toNumber: number) => Array<string>;
|
||||
onGetTypes4Range: (from: number, toNumber: number) => Array<string>;
|
||||
}
|
||||
): void;
|
||||
// invalidate C++ side map index -> key
|
||||
static invalidateKeyCache(totalCount : number): void;
|
||||
}
|
||||
|
@ -40,9 +40,9 @@ class __RepeatItemPU<T> implements RepeatItem<T>, __IRepeatItemInternal<T> {
|
||||
private _observedIndex?: ObservedPropertyPU<number>;
|
||||
|
||||
constructor(owningView: ViewPU, initialItem: T, initialIndex?: number) {
|
||||
this._observedItem = new ObservedPropertyPU<T>(initialItem, owningView, "Repeat item");
|
||||
this._observedItem = new ObservedPropertyPU<T>(initialItem, owningView, 'Repeat item');
|
||||
if (initialIndex !== undefined) {
|
||||
this._observedIndex = new ObservedPropertyPU<number>(initialIndex, owningView, "Repeat index");
|
||||
this._observedIndex = new ObservedPropertyPU<number>(initialIndex, owningView, 'Repeat index');
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,13 +116,13 @@ class __RepeatDefaultKeyGen {
|
||||
}
|
||||
|
||||
// Return the same IDs for the same pairs <item, index>
|
||||
public static funcWithIndex<T>(item: T, index:number) {
|
||||
public static funcWithIndex<T>(item: T, index: number) {
|
||||
return `${index}__` + __RepeatDefaultKeyGen.func(item);
|
||||
}
|
||||
|
||||
private static funcImpl<T>(item: T) {
|
||||
// fast keygen logic can be used with objects/symbols only
|
||||
if (typeof item !== 'object' && typeof item !== 'symbol') {
|
||||
if (typeof item != 'object' && typeof item !== 'symbol') {
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
// generate a numeric key, store mappings in WeakMap
|
||||
@ -132,196 +132,118 @@ class __RepeatDefaultKeyGen {
|
||||
// use cached key
|
||||
return `${this.weakMap_.get(item)}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TBD comments
|
||||
interface __RepeatAPIConfig<T> {
|
||||
arr?: Array<T>;
|
||||
itemGenFuncs?: { [type: string]: RepeatItemGenFunc<T> };
|
||||
keyGenFunc?: RepeatKeyGenFunc<T>;
|
||||
typeGenFunc?: RepeatTypeGenFunc<T>;
|
||||
//
|
||||
totalCount?: number;
|
||||
templateOptions?: { [type: string]: RepeatTemplateOptions };
|
||||
//
|
||||
mkRepeatItem?: (item: T, index?: number) => __RepeatItemFactoryReturn<T>;
|
||||
onMoveHandler?: OnMoveHandler;
|
||||
};
|
||||
|
||||
// __Repeat implements ForEach with child re-use for both existing state observation
|
||||
// and deep observation , for non-virtual and virtual code paths (TODO)
|
||||
class __RepeatV2<T> implements RepeatAPI<T> {
|
||||
private arr_: Array<T>;
|
||||
private itemGenFunc_?: RepeatItemGenFunc<T>;
|
||||
private keyGenFunction_?: RepeatKeyGenFunc<T>;
|
||||
private onMoveHandler_?: OnMoveHandler;
|
||||
private isVirtualScroll: boolean = false;
|
||||
private key2Item_: Map<string, __RepeatItemInfo<T>> = new Map<string, __RepeatItemInfo<T>>();
|
||||
private config: __RepeatAPIConfig<T> = {};
|
||||
private impl: __RepeatImpl<T> | __RepeatVirtualScrollImpl<T>;
|
||||
private isVirtualScroll = false;
|
||||
|
||||
constructor(arr: Array<T>) {
|
||||
this.arr_ = arr ?? [];
|
||||
this.keyGenFunction_ = __RepeatDefaultKeyGen.func;
|
||||
}
|
||||
|
||||
public updateArr(arr: Array<T>): RepeatAPI<T> {
|
||||
this.arr_ = arr ?? [];
|
||||
return this;
|
||||
//console.log('__RepeatV2 ctor')
|
||||
this.config.arr = arr ?? [];
|
||||
this.config.itemGenFuncs = {};
|
||||
this.config.keyGenFunc = __RepeatDefaultKeyGen.func;
|
||||
this.config.typeGenFunc= (() => '');
|
||||
this.config.totalCount = this.config.arr.length;
|
||||
this.config.templateOptions = {};
|
||||
this.config.mkRepeatItem = this.mkRepeatItem;
|
||||
}
|
||||
|
||||
public each(itemGenFunc: RepeatItemGenFunc<T>): RepeatAPI<T> {
|
||||
this.itemGenFunc_ = itemGenFunc;
|
||||
//console.log('__RepeatV2.each()')
|
||||
this.config.itemGenFuncs[''] = itemGenFunc;
|
||||
this.config.templateOptions[''] = { ...this.defaultTemplateOptions() };
|
||||
return this;
|
||||
}
|
||||
|
||||
public key(idGenFunc: RepeatKeyGenFunc<T>): RepeatAPI<T> {
|
||||
this.keyGenFunction_ = idGenFunc ?? __RepeatDefaultKeyGen.func;
|
||||
public key(keyGenFunc: RepeatKeyGenFunc<T>): RepeatAPI<T> {
|
||||
//console.log('__RepeatV2.key()')
|
||||
this.config.keyGenFunc = keyGenFunc ?? __RepeatDefaultKeyGen.func;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtualScroll(): RepeatAPI<T> {
|
||||
public virtualScroll(options? : { totalCount?: number }): RepeatAPI<T> {
|
||||
//console.log('__RepeatV2.virtualScroll()')
|
||||
this.config.totalCount = options?.totalCount ?? this.config.arr.length;
|
||||
this.isVirtualScroll = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public onMove(handler: OnMoveHandler): RepeatAPI<T> {
|
||||
this.onMoveHandler_ = handler;
|
||||
// function to decide which template to use, each template has an id
|
||||
public templateId(typeFunc: RepeatTypeGenFunc<T>): RepeatAPI<T> {
|
||||
//console.log('__RepeatV2.templateId()')
|
||||
this.config.typeGenFunc = typeFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
private genKeys(): Map<string, __RepeatItemInfo<T>> {
|
||||
const key2Item = new Map<string, __RepeatItemInfo<T>>();
|
||||
this.arr_.forEach((item, index) => {
|
||||
const key = this.keyGenFunction_(item, index);
|
||||
key2Item.set(key, { key, index });
|
||||
});
|
||||
if (key2Item.size < this.arr_.length) {
|
||||
stateMgmtConsole.warn("Duplicates detected, fallback to index-based keyGen.");
|
||||
// Causes all items to be re-rendered
|
||||
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
|
||||
return this.genKeys();
|
||||
// template: id + builder function to render specific type of data item
|
||||
public template(type: string, itemGenFunc: RepeatItemGenFunc<T>,
|
||||
options?: RepeatTemplateOptions): RepeatAPI<T>
|
||||
{
|
||||
//console.log('__RepeatV2.template()')
|
||||
this.config.itemGenFuncs[type] = itemGenFunc;
|
||||
this.config.templateOptions[type] = { ...this.defaultTemplateOptions(), ...options };
|
||||
return this;
|
||||
}
|
||||
|
||||
public updateArr(arr: Array<T>): RepeatAPI<T> {
|
||||
//console.log('__RepeatV2.updateArr()')
|
||||
this.config.arr = arr ?? [];
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(isInitialRender: boolean): void {
|
||||
//console.log('__RepeatV2.render()')
|
||||
if (!this.config.itemGenFuncs?.['']) {
|
||||
throw new Error(`__RepeatV2 item builder function unspecified. Usage error`);
|
||||
}
|
||||
return key2Item;
|
||||
if (!this.isVirtualScroll) {
|
||||
// Repeat
|
||||
this.impl ??= new __RepeatImpl<T>();
|
||||
this.impl.render(this.config, isInitialRender);
|
||||
} else {
|
||||
// RepeatVirtualScroll
|
||||
this.impl ??= new __RepeatVirtualScrollImpl<T>();
|
||||
this.impl.render(this.config, isInitialRender);
|
||||
}
|
||||
}
|
||||
|
||||
public onMove(handler: OnMoveHandler): RepeatAPI<T> {
|
||||
this.config.onMoveHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
private defaultTemplateOptions (): RepeatTemplateOptions {
|
||||
return { cachedCount: 1 };
|
||||
}
|
||||
|
||||
protected mkRepeatItem<T>(item: T, index?: number): __RepeatItemFactoryReturn<T> {
|
||||
return new __RepeatItemV2(item as T, index);
|
||||
}
|
||||
|
||||
public render(isInitialRender: boolean): void {
|
||||
if (!this.itemGenFunc_) {
|
||||
throw new Error(`itemGen function undefined. Usage error`);
|
||||
}
|
||||
if (this.isVirtualScroll) {
|
||||
// TODO: Add render for LazyforEach with child update.
|
||||
throw new Error("TODO virtual code path");
|
||||
} else {
|
||||
isInitialRender ? this.initialRenderNoneVirtual() : this.rerenderNoneVirtual();
|
||||
}
|
||||
}
|
||||
|
||||
private initialRenderNoneVirtual(): void {
|
||||
this.key2Item_ = this.genKeys();
|
||||
|
||||
RepeatNative.startRender();
|
||||
|
||||
let index = 0;
|
||||
this.key2Item_.forEach((itemInfo, key) => {
|
||||
itemInfo.repeatItem = this.mkRepeatItem(this.arr_[index], index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
index++;
|
||||
});
|
||||
let removedChildElmtIds = new Array<number>();
|
||||
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
|
||||
RepeatNative.onMove(this.onMoveHandler_);
|
||||
RepeatNative.finishRender(removedChildElmtIds);
|
||||
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
|
||||
stateMgmtConsole.debug(`RepeatPU: initialRenderNoneVirtual elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
|
||||
}
|
||||
|
||||
private rerenderNoneVirtual(): void {
|
||||
const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_;
|
||||
this.key2Item_ = this.genKeys();
|
||||
|
||||
// identify array items that have been deleted
|
||||
// these are candidates for re-use
|
||||
const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>();
|
||||
for (const [key, feInfo] of oldKey2Item) {
|
||||
if (!this.key2Item_.has(key)) {
|
||||
deletedKeysAndIndex.push(feInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// C++: mv children_ aside to tempchildren_
|
||||
RepeatNative.startRender();
|
||||
|
||||
let index = 0;
|
||||
this.key2Item_.forEach((itemInfo, key) => {
|
||||
const item = this.arr_[index];
|
||||
let oldItemInfo = oldKey2Item.get(key);
|
||||
|
||||
if (oldItemInfo) {
|
||||
// case #1 retained array item
|
||||
// moved from oldIndex to index
|
||||
const oldIndex = oldItemInfo.index;
|
||||
itemInfo.repeatItem = oldItemInfo!.repeatItem!;
|
||||
stateMgmtConsole.debug(`retained: key ${key} ${oldIndex}->${index}`);
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldIndex);
|
||||
|
||||
} else if (deletedKeysAndIndex.length) {
|
||||
// case #2:
|
||||
// new array item, there is an deleted array items whose
|
||||
// UINode children cab re-used
|
||||
const oldItemInfo = deletedKeysAndIndex.pop();
|
||||
const reuseKey = oldItemInfo!.key;
|
||||
const oldKeyIndex = oldItemInfo!.index;
|
||||
const oldRepeatItem = oldItemInfo!.repeatItem!;
|
||||
itemInfo.repeatItem = oldRepeatItem;
|
||||
stateMgmtConsole.debug(`new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`);
|
||||
|
||||
itemInfo.repeatItem.updateItem(item);
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
|
||||
// update key2item_ Map
|
||||
this.key2Item_.set(key, itemInfo);
|
||||
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldKeyIndex);
|
||||
} else {
|
||||
// case #3:
|
||||
// new array item, there are no deleted array items
|
||||
// render new UINode children
|
||||
itemInfo.repeatItem = this.mkRepeatItem(item, index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
}
|
||||
|
||||
index++;
|
||||
})
|
||||
|
||||
// keep this.id2item_. by removing all entries for remaining
|
||||
// deleted items
|
||||
deletedKeysAndIndex.forEach(delItem => {
|
||||
this.key2Item_.delete(delItem!.key);
|
||||
});
|
||||
|
||||
// Finish up for.each update
|
||||
// C++ tempChildren.clear() , trigger re-layout
|
||||
let removedChildElmtIds = new Array<number>();
|
||||
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
|
||||
RepeatNative.onMove(this.onMoveHandler_);
|
||||
RepeatNative.finishRender(removedChildElmtIds);
|
||||
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
|
||||
stateMgmtConsole.debug(`RepeatPU: rerenderNoneVirtual elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
|
||||
}
|
||||
|
||||
private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void {
|
||||
// render new UINode children
|
||||
stateMgmtConsole.debug(`new: key ${key} n/a->${repeatItem.index}`);
|
||||
|
||||
// C++: initial render will render to the end of children_
|
||||
RepeatNative.createNewChildStart(key);
|
||||
|
||||
// execute the ItemGen function
|
||||
this.itemGenFunc_!(repeatItem);
|
||||
|
||||
RepeatNative.createNewChildFinish(key);
|
||||
}
|
||||
|
||||
}
|
||||
}; // __RepeatV2<T>
|
||||
|
||||
// __Repeat implements ForEach with child re-use for both existing state observation
|
||||
// and deep observation , for non-virtual and virtual code paths (TODO)
|
||||
|
||||
class __RepeatPU<T> extends __RepeatV2<T> implements RepeatAPI<T> {
|
||||
private owningView_ : ViewPU;
|
||||
private owningView_: ViewPU;
|
||||
|
||||
constructor(owningView: ViewPU, arr: Array<T>) {
|
||||
super(arr);
|
||||
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2024 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.
|
||||
*
|
||||
* all definitions in this file are framework internal
|
||||
*/
|
||||
|
||||
class __RepeatImpl<T> {
|
||||
private arr_: Array<T>;
|
||||
private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> };
|
||||
private keyGenFunction_?: RepeatKeyGenFunc<T>;
|
||||
private typeGenFunc_: RepeatTypeGenFunc<T>;
|
||||
//
|
||||
private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>;
|
||||
private onMoveHandler_?: OnMoveHandler;
|
||||
|
||||
private key2Item_ = new Map<string, __RepeatItemInfo<T>>();
|
||||
|
||||
/**/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**/
|
||||
public render(config: __RepeatAPIConfig<T>, isInitialRender: boolean): void {
|
||||
this.arr_ = config.arr;
|
||||
this.itemGenFuncs_ = config.itemGenFuncs;
|
||||
this.typeGenFunc_ = config.typeGenFunc;
|
||||
this.keyGenFunction_ = config.keyGenFunc;
|
||||
this.mkRepeatItem_ = config.mkRepeatItem;
|
||||
this.onMoveHandler_ = config.onMoveHandler;
|
||||
|
||||
isInitialRender ? this.initialRender() : this.reRender();
|
||||
}
|
||||
|
||||
private genKeys(): Map<string, __RepeatItemInfo<T>> {
|
||||
const key2Item = new Map<string, __RepeatItemInfo<T>>();
|
||||
this.arr_.forEach((item, index) => {
|
||||
const key = this.keyGenFunction_(item, index);
|
||||
key2Item.set(key, { key, index })
|
||||
});
|
||||
if (key2Item.size < this.arr_.length) {
|
||||
stateMgmtConsole.warn("__RepeatImpl: Duplicates detected, fallback to index-based keyGen.")
|
||||
// Causes all items to be re-rendered
|
||||
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
|
||||
return this.genKeys();
|
||||
}
|
||||
return key2Item;
|
||||
}
|
||||
|
||||
private initialRender(): void {
|
||||
//console.log('__RepeatImpl initialRender() 0')
|
||||
this.key2Item_ = this.genKeys();
|
||||
|
||||
RepeatNative.startRender();
|
||||
|
||||
let index = 0;
|
||||
this.key2Item_.forEach((itemInfo, key) => {
|
||||
itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
index++;
|
||||
})
|
||||
let removedChildElmtIds = new Array<number>();
|
||||
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
|
||||
RepeatNative.onMove(this.onMoveHandler_);
|
||||
RepeatNative.finishRender(removedChildElmtIds);
|
||||
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
|
||||
stateMgmtConsole.debug(`__RepeatImpl: initialRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
|
||||
}
|
||||
|
||||
private reRender(): void {
|
||||
const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_;
|
||||
this.key2Item_ = this.genKeys();
|
||||
|
||||
// identify array items that have been deleted
|
||||
// these are candidates for re-use
|
||||
const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>();
|
||||
for (const [key, feInfo] of oldKey2Item) {
|
||||
if (!this.key2Item_.has(key)) {
|
||||
deletedKeysAndIndex.push(feInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// C++: mv children_ aside to tempchildren_
|
||||
RepeatNative.startRender();
|
||||
|
||||
let index = 0;
|
||||
this.key2Item_.forEach((itemInfo, key) => {
|
||||
const item = this.arr_[index];
|
||||
let oldItemInfo = oldKey2Item.get(key);
|
||||
|
||||
if (oldItemInfo) {
|
||||
// case #1 retained array item
|
||||
// moved from oldIndex to index
|
||||
const oldIndex = oldItemInfo.index;
|
||||
itemInfo.repeatItem = oldItemInfo!.repeatItem!;
|
||||
stateMgmtConsole.debug(`__RepeatImpl: retained: key ${key} ${oldIndex}->${index}`)
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldIndex);
|
||||
|
||||
// TBD moveChild() only when item types are same
|
||||
//const type0 = this.typeGenFunc_(oldItemInfo.repeatItem.item, oldIndex);
|
||||
//const type1 = this.typeGenFunc_(itemInfo.repeatItem.item, index);
|
||||
//if (type0 == type1) {
|
||||
// // C++ mv from tempChildren[oldIndex] to end of children_
|
||||
// RepeatNative.moveChild(oldIndex);
|
||||
//} else {
|
||||
// this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
//}
|
||||
} else if (deletedKeysAndIndex.length) {
|
||||
// case #2:
|
||||
// new array item, there is an deleted array items whose
|
||||
// UINode children cab re-used
|
||||
const oldItemInfo = deletedKeysAndIndex.pop();
|
||||
const reuseKey = oldItemInfo!.key;
|
||||
const oldKeyIndex = oldItemInfo!.index;
|
||||
const oldRepeatItem = oldItemInfo!.repeatItem!;
|
||||
itemInfo.repeatItem = oldRepeatItem;
|
||||
stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`)
|
||||
|
||||
itemInfo.repeatItem.updateItem(item);
|
||||
itemInfo.repeatItem.updateIndex(index);
|
||||
|
||||
// update key2item_ Map
|
||||
this.key2Item_.set(key, itemInfo);
|
||||
|
||||
// TBD moveChild() only when item types are same
|
||||
|
||||
// C++ mv from tempChildren[oldIndex] to end of children_
|
||||
RepeatNative.moveChild(oldKeyIndex);
|
||||
} else {
|
||||
// case #3:
|
||||
// new array item, there are no deleted array items
|
||||
// render new UINode children
|
||||
itemInfo.repeatItem = this.mkRepeatItem_(item, index);
|
||||
this.initialRenderItem(key, itemInfo.repeatItem);
|
||||
}
|
||||
|
||||
index++;
|
||||
})
|
||||
|
||||
// keep this.id2item_. by removing all entries for remaining
|
||||
// deleted items
|
||||
deletedKeysAndIndex.forEach(delItem => {
|
||||
this.key2Item_.delete(delItem!.key);
|
||||
});
|
||||
|
||||
// Finish up for.each update
|
||||
// C++ tempChildren.clear() , trigger re-layout
|
||||
let removedChildElmtIds = new Array<number>();
|
||||
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
|
||||
RepeatNative.onMove(this.onMoveHandler_);
|
||||
RepeatNative.finishRender(removedChildElmtIds);
|
||||
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
|
||||
stateMgmtConsole.debug(`__RepeatImpl: reRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
|
||||
}
|
||||
|
||||
private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void {
|
||||
//console.log('__RepeatImpl initialRenderItem()')
|
||||
// render new UINode children
|
||||
stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} n/a->${repeatItem.index}`)
|
||||
|
||||
// C++: initial render will render to the end of children_
|
||||
RepeatNative.createNewChildStart(key);
|
||||
|
||||
// execute the itemGen function
|
||||
const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? '';
|
||||
const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[''];
|
||||
itemFunc(repeatItem);
|
||||
|
||||
RepeatNative.createNewChildFinish(key);
|
||||
}
|
||||
|
||||
};
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2024 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.
|
||||
*
|
||||
* all definitions in this file are framework internal
|
||||
*/
|
||||
|
||||
// Implements ForEach with child re-use for both existing state observation and
|
||||
// deep observation. For virtual-scroll code paths
|
||||
|
||||
class __RepeatVirtualScrollImpl<T> {
|
||||
private arr_: Array<T>;
|
||||
private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> };
|
||||
private keyGenFunc_?: RepeatKeyGenFunc<T>;
|
||||
private typeGenFunc_: RepeatTypeGenFunc<T>;
|
||||
//
|
||||
private totalCount_: number;
|
||||
private templateOptions_: { [type: string]: RepeatTemplateOptions };
|
||||
//
|
||||
private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>;
|
||||
private onMoveHandler_?: OnMoveHandler;
|
||||
|
||||
/**/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public render(config: __RepeatAPIConfig<T>, isInitialRender: boolean): void {
|
||||
this.arr_ = config.arr;
|
||||
this.itemGenFuncs_ = config.itemGenFuncs;
|
||||
this.keyGenFunc_ = config.keyGenFunc;
|
||||
this.typeGenFunc_ = config.typeGenFunc;
|
||||
|
||||
this.totalCount_ = config.totalCount;
|
||||
this.templateOptions_ = config.templateOptions;
|
||||
|
||||
this.mkRepeatItem_ = config.mkRepeatItem;
|
||||
this.onMoveHandler_ = config.onMoveHandler;
|
||||
|
||||
if (isInitialRender) {
|
||||
this.initialRender(ObserveV2.getCurrentRecordedId());
|
||||
} else {
|
||||
this.reRender();
|
||||
}
|
||||
}
|
||||
|
||||
/**/
|
||||
private initialRender(repeatElmtId: number): void {
|
||||
// Map key -> RepeatItem
|
||||
// added to closure of following lambdas
|
||||
const _repeatItem4Key = new Map<string, __RepeatItemFactoryReturn<T>>();
|
||||
const repeatElmtId1 = repeatElmtId;
|
||||
|
||||
const onCreateNode = (forIndex: number): void => {
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onCreateNode index ${forIndex} - start`);
|
||||
if (forIndex < 0) {
|
||||
// FIXME check also index < totalCount
|
||||
throw new Error(`__RepeatVirtualScrollImpl onCreateNode: for index=${forIndex} out of range error.`)
|
||||
}
|
||||
|
||||
// create dependency array item [forIndex] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
|
||||
|
||||
const repeatItem = this.mkRepeatItem_(this.arr_[forIndex], forIndex);
|
||||
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
|
||||
_repeatItem4Key.set(forKey, repeatItem);
|
||||
|
||||
// execute the itemGen function
|
||||
this.initialRenderItem(repeatItem);
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onCreateNode for index ${forIndex} key "${forKey}" - end `);
|
||||
};
|
||||
|
||||
const onUpdateNode = (fromKey: string, forIndex: number): void => {
|
||||
if (!fromKey || fromKey=="" || forIndex < 0) {
|
||||
// FIXME check also index < totalCount
|
||||
throw new Error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} invalid function input error.`)
|
||||
}
|
||||
// create dependency array item [forIndex] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
|
||||
const repeatItem = _repeatItem4Key.get(fromKey);
|
||||
if (!repeatItem) {
|
||||
stateMgmtConsole.error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex}, can not find RepeatItem for key. Unrecoverable error`);
|
||||
return;
|
||||
}
|
||||
const forKey= this.keyGenFunc_(this.arr_[forIndex], forIndex);
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} forKey="${forKey}". Updating RepeatItem ...`);
|
||||
repeatItem.updateItem(this.arr_[forIndex]);
|
||||
repeatItem.updateIndex(forIndex);
|
||||
|
||||
// update Map according to made update:
|
||||
// del fromKey entry and add forKey
|
||||
_repeatItem4Key.delete(fromKey);
|
||||
_repeatItem4Key.set(forKey, repeatItem);
|
||||
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} forKey="${forKey}". Initiating update synchronously (TODO)...`);
|
||||
// FIXME request re-render right away!
|
||||
ObserveV2.getObserve().updateDirty2(true);
|
||||
};
|
||||
|
||||
const onGetKeys4Range = (from: number, to: number): Array<string> => {
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetKeys4Range from ${from} to ${to} - start`);
|
||||
|
||||
const result = new Array<string>();
|
||||
for (let i = from; i <= to && i < this.arr_.length; i++) {
|
||||
// create dependency array item [i] -> Repeat
|
||||
// so Repeat updates when the array item changes
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
|
||||
result.push(this.keyGenFunc_(this.arr_[i], i));
|
||||
}
|
||||
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetKeys4Range from ${from} to ${to} - returns ${result.toString()}`);
|
||||
return result;
|
||||
};
|
||||
|
||||
const onGetTypes4Range = (from: number, to: number): Array<string> => {
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetTypes4Range from ${from} to ${to} - start`);
|
||||
const result = new Array<string>();
|
||||
// FIXME observe dependencies, adding the array is insurgent for Array of objects
|
||||
for (let i = from; i <= to && i < this.arr_.length; i++) {
|
||||
// ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
|
||||
result.push(this.typeGenFunc_(this.arr_[i], i) ?? '');
|
||||
}
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetTypes4Range from ${from} to ${to} - returns ${result.toString()}`);
|
||||
return result;
|
||||
};
|
||||
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: initialRenderVirtualScroll`);
|
||||
|
||||
RepeatVirtualScrollNative.create(this.totalCount_, Object.entries(this.templateOptions_), {
|
||||
onCreateNode,
|
||||
onUpdateNode,
|
||||
onGetKeys4Range,
|
||||
onGetTypes4Range
|
||||
});
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: initialRenderVirtualScroll 2`);
|
||||
}
|
||||
|
||||
private reRender() {
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: reRender ...`);
|
||||
RepeatVirtualScrollNative.invalidateKeyCache(this.totalCount_);
|
||||
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: reRender - done`);
|
||||
}
|
||||
|
||||
private initialRenderItem(repeatItem: __RepeatItemFactoryReturn<T>): void {
|
||||
// execute the itemGen function
|
||||
const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? '';
|
||||
const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[''];
|
||||
itemFunc(repeatItem);
|
||||
}
|
||||
|
||||
};
|
@ -23,7 +23,9 @@ interface RepeatItem<T> {
|
||||
}
|
||||
|
||||
type RepeatItemGenFunc<T> = (i: RepeatItem<T>) => void;
|
||||
type RepeatTypeGenFunc<T> = (item: T, index: number) => string;
|
||||
type RepeatKeyGenFunc<T> = (item: T, index?: number) => string;
|
||||
type RepeatTemplateOptions = { cachedCount?: number };
|
||||
type OnMoveHandler = (from: number, to: number) => void;
|
||||
|
||||
/*
|
||||
@ -43,11 +45,26 @@ const Repeat: <T>(arr: Array<T>, owningView?: PUV2ViewBase) => RepeatAPI<T> =
|
||||
repeat attribute function and internal function render()
|
||||
*/
|
||||
interface RepeatAPI<T> {
|
||||
each: (itemGenFunc: RepeatItemGenFunc<T>) => RepeatAPI<T>; // chainable, call in this order
|
||||
// unique key from array item, to identify changed, added array items
|
||||
key: (keyGenFunc: RepeatKeyGenFunc<T>) => RepeatAPI<T>;
|
||||
virtualScroll: () => RepeatAPI<T>;
|
||||
onMove: (handler: OnMoveHandler) => RepeatAPI<T>;
|
||||
|
||||
// default render function for data items, framework will use when no template found
|
||||
each: (itemGenFunc: RepeatItemGenFunc<T>) => RepeatAPI<T>;
|
||||
|
||||
// when call virtualScroll, framework will use virtual scroll
|
||||
// totalCount: number of logical items, can be larger than number of loaded
|
||||
// data items of lazy loading array source, can be larger than that array.length
|
||||
virtualScroll: (options?: { totalCount?: number }) => RepeatAPI<T>;
|
||||
|
||||
// function to decide which template to use, each template has an id
|
||||
templateId: (typeFunc: RepeatTypeGenFunc<T>) => RepeatAPI<T>;
|
||||
|
||||
// template: id + builder function to render specific type of data item
|
||||
template: (type: string, itemGenFunc: RepeatItemGenFunc<T>, options?: RepeatTemplateOptions) => RepeatAPI<T>;
|
||||
|
||||
// do NOT use in app, transpiler adds as last of chained call
|
||||
render(isInitialRender: boolean): void;
|
||||
|
||||
// not used by Repeat
|
||||
onMove: (handler: OnMoveHandler) => RepeatAPI<T>;
|
||||
}
|
||||
|
@ -125,6 +125,11 @@ class ObserveV2 {
|
||||
return (value && typeof (value) === 'object' && value[ObserveV2.V2_DECO_META]);
|
||||
}
|
||||
|
||||
public static getCurrentRecordedId(): number {
|
||||
const bound = ObserveV2.getObserve().stackOfRenderedComponents_.top();
|
||||
return bound ? bound[0] : -1;
|
||||
}
|
||||
|
||||
// At the start of observeComponentCreation or
|
||||
// MonitorV2 observeObjectAccess
|
||||
public startRecordDependencies(cmp: IView | MonitorV2 | ComputedV2 | PersistenceV2Impl, id: number): void {
|
||||
@ -291,8 +296,15 @@ class ObserveV2 {
|
||||
}
|
||||
|
||||
stateMgmtConsole.propertyAccess(`ObserveV2.addRef '${attrName}' for id ${bound[0]}...`);
|
||||
const id = bound[0];
|
||||
this.addRef4IdInternal(bound[0], target, attrName);
|
||||
}
|
||||
|
||||
public addRef4Id(id: number, target: object, attrName: string): void {
|
||||
stateMgmtConsole.propertyAccess(`ObserveV2.addRef4Id '${attrName}' for id ${id} ...`);
|
||||
this.addRef4IdInternal(id, target, attrName);
|
||||
}
|
||||
|
||||
private addRef4IdInternal(id: number, target: object, attrName: string): void {
|
||||
// Map: attribute/symbol -> dependent id
|
||||
const symRefs = target[ObserveV2.SYMBOL_REFS] ??= {};
|
||||
symRefs[attrName] ??= new Set();
|
||||
@ -411,15 +423,29 @@ class ObserveV2 {
|
||||
} // for
|
||||
}
|
||||
|
||||
private updateDirty(): void {
|
||||
public updateDirty(): void {
|
||||
this.startDirty_ = true;
|
||||
this.updateDirty2();
|
||||
this.updateDirty2(false);
|
||||
this.startDirty_ = false;
|
||||
}
|
||||
|
||||
private updateDirty2(): void {
|
||||
/**
|
||||
* execute /update in this order
|
||||
* - @Computed variables
|
||||
* - @Monitor functions
|
||||
* - UINode re-render
|
||||
* three nested loops, means:
|
||||
* process @Computed until no more @Computed need update
|
||||
* process @Monitor until no more @Computed and @Monitor
|
||||
* process UINode update until no more @Computed and @Monitor and UINode rerender
|
||||
*
|
||||
* @param updateUISynchronously should be set to true if called during VSYNC only
|
||||
*
|
||||
*/
|
||||
|
||||
public updateDirty2(updateUISynchronously: boolean = false): void {
|
||||
aceTrace.begin('updateDirty2');
|
||||
stateMgmtConsole.debug(`ObservedV3.updateDirty2 ... `);
|
||||
stateMgmtConsole.debug(`ObservedV3.updateDirty2 updateUISynchronously=${updateUISynchronously} ... `);
|
||||
// obtain and unregister the removed elmtIds
|
||||
UINodeRegisterProxy.obtainDeletedElmtIds();
|
||||
UINodeRegisterProxy.unregisterElmtIdsFromIViews();
|
||||
@ -456,9 +482,11 @@ class ObserveV2 {
|
||||
if (this.elmtIdsChanged_.size) {
|
||||
const elmtIds = Array.from(this.elmtIdsChanged_).sort((elmtId1, elmtId2) => elmtId1 - elmtId2);
|
||||
this.elmtIdsChanged_ = new Set<number>();
|
||||
this.updateUINodes(elmtIds);
|
||||
updateUISynchronously ? this.updateUINodesSynchronously(elmtIds) : this.updateUINodes(elmtIds);
|
||||
}
|
||||
} while (this.elmtIdsChanged_.size + this.monitorIdsChanged_.size + this.computedPropIdsChanged_.size > 0);
|
||||
|
||||
stateMgmtConsole.debug(`ObservedV3.updateDirty2 updateUISynchronously=${updateUISynchronously} - DONE `);
|
||||
aceTrace.end();
|
||||
}
|
||||
|
||||
@ -508,10 +536,11 @@ class ObserveV2 {
|
||||
* FlushDirtyNodesUpdate to CustomNode to ViewV2.updateDirtyElements to UpdateElement
|
||||
* Code left here to reproduce benchmark measurements, compare with future optimisation
|
||||
* @param elmtIds
|
||||
*
|
||||
*/
|
||||
private updateUINodesWithoutVSync(elmtIds: Array<number>): void {
|
||||
stateMgmtConsole.debug(`ObserveV2.updateUINodes: ${elmtIds.length} elmtIds: ${JSON.stringify(elmtIds)} ...`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
|
||||
private updateUINodesSynchronously(elmtIds: Array<number>): void {
|
||||
stateMgmtConsole.debug(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtIds: ${JSON.stringify(elmtIds)} ...`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtId`);
|
||||
let view: Object;
|
||||
let weak: any;
|
||||
elmtIds.forEach((elmtId) => {
|
||||
@ -534,8 +563,8 @@ class ObserveV2 {
|
||||
// On next VSYNC runs FlushDirtyNodesUpdate to call rerender to call UpdateElement. Much longer code path
|
||||
// much slower
|
||||
private updateUINodes(elmtIds: Array<number>): void {
|
||||
stateMgmtConsole.debug(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtIds need rerender: ${JSON.stringify(elmtIds)} ...`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtId`);
|
||||
stateMgmtConsole.debug(`ObserveV2.updateUINodes: ${elmtIds.length} elmtIds need rerender: ${JSON.stringify(elmtIds)} ...`);
|
||||
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
|
||||
let viewWeak: WeakRef<Object>;
|
||||
let view: Object | undefined;
|
||||
elmtIds.forEach((elmtId) => {
|
||||
@ -874,5 +903,3 @@ const trackInternal = (
|
||||
// used by IsObservedObjectV2
|
||||
target[ObserveV2.V2_DECO_META] ??= {};
|
||||
}; // trackInternal
|
||||
|
||||
|
||||
|
@ -88,6 +88,8 @@
|
||||
|
||||
// partial_update specific
|
||||
"src/lib/partial_update/pu_repeat.ts",
|
||||
"src/lib/partial_update/pu_repeat_impl.ts",
|
||||
"src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts",
|
||||
|
||||
// sdk
|
||||
"src/lib/sdk/v2_persistence.ts",
|
||||
|
@ -56,7 +56,6 @@
|
||||
"src/lib/full_update/fu_synced_property_object_nested.ts",
|
||||
"src/lib/full_update/fu_view.ts",
|
||||
|
||||
|
||||
// partial update and v2 common
|
||||
"src/lib/puv2_common/puv2_viewstack_processor.d.ts",
|
||||
"src/lib/puv2_common/iview.ts",
|
||||
@ -77,6 +76,8 @@
|
||||
"src/lib/partial_update/pu_view.ts",
|
||||
"src/lib/partial_update/pu_recycle_manager.ts",
|
||||
"src/lib/partial_update/pu_builder_proxy.ts",
|
||||
"src/lib/partial_update/pu_repeat_impl.ts",
|
||||
"src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts",
|
||||
|
||||
// 'new' stateMgmt vers 2
|
||||
"src/lib/v2/v2_change_observation.ts",
|
||||
|
@ -88,6 +88,8 @@
|
||||
|
||||
// partial_update specific
|
||||
"src/lib/partial_update/pu_repeat.ts",
|
||||
"src/lib/partial_update/pu_repeat_impl.ts",
|
||||
"src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts",
|
||||
|
||||
// sdk
|
||||
"src/lib/sdk/v2_persistence.ts",
|
||||
|
@ -76,6 +76,8 @@
|
||||
|
||||
// partial_update specific
|
||||
"src/lib/partial_update/pu_repeat.ts",
|
||||
"src/lib/partial_update/pu_repeat_impl.ts",
|
||||
"src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts",
|
||||
|
||||
// sdk
|
||||
"src/lib/sdk/v2_persistence.ts",
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "core/components_ng/property/property.h"
|
||||
#include "core/components_ng/render/paint_wrapper.h"
|
||||
#include "core/components_ng/syntax/lazy_for_each_node.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
|
||||
#include "core/components_v2/inspector/inspector_constants.h"
|
||||
#include "core/event/touch_event.h"
|
||||
#include "core/gestures/gesture_info.h"
|
||||
@ -142,9 +143,7 @@ public:
|
||||
cursor_ = children_.end();
|
||||
if (prevFrameProxy_->cursor_ != prevFrameProxy_->children_.end()) {
|
||||
cursor_ = std::find_if(children_.begin(), children_.end(),
|
||||
[this](FrameChildNode& node) {
|
||||
return prevFrameProxy_->cursor_->node == node.node;
|
||||
});
|
||||
[this](FrameChildNode& node) { return prevFrameProxy_->cursor_->node == node.node; });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,8 +177,11 @@ public:
|
||||
return;
|
||||
}
|
||||
auto lazyForEachNode = AceType::DynamicCast<LazyForEachNode>(UiNode);
|
||||
auto repeatVirtualScrollNode = AceType::DynamicCast<RepeatVirtualScrollNode>(UiNode);
|
||||
if (lazyForEachNode) {
|
||||
lazyForEachNode->BuildAllChildren();
|
||||
} else if (repeatVirtualScrollNode) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "repeatVirtualScroll not support in non scoll container!");
|
||||
} else {
|
||||
auto customNode = AceType::DynamicCast<CustomNode>(UiNode);
|
||||
if (customNode) {
|
||||
@ -225,9 +227,8 @@ public:
|
||||
}
|
||||
|
||||
if (cursor_->startIndex + cursor_->count > index) {
|
||||
auto frameNode = AceType::DynamicCast<FrameNode>(
|
||||
cursor_->node->GetFrameChildByIndex(index - cursor_->startIndex,
|
||||
needBuild, isCache, addToRenderTree));
|
||||
auto frameNode = AceType::DynamicCast<FrameNode>(cursor_->node->GetFrameChildByIndex(
|
||||
index - cursor_->startIndex, needBuild, isCache, addToRenderTree));
|
||||
return frameNode;
|
||||
}
|
||||
cursor_++;
|
||||
@ -272,8 +273,8 @@ public:
|
||||
void ResetChildren(bool needResetChild = false)
|
||||
{
|
||||
if (inUse_) {
|
||||
LOGF("[%{public}d:%{public}s] reset children while in use",
|
||||
hostNode_->GetId(), hostNode_->GetTag().c_str());
|
||||
LOGF(
|
||||
"[%{public}d:%{public}s] reset children while in use", hostNode_->GetId(), hostNode_->GetTag().c_str());
|
||||
if (SystemProperties::GetLayoutDetectEnabled()) {
|
||||
abort();
|
||||
} else {
|
||||
@ -320,12 +321,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void SetActiveChildRange(int32_t start, int32_t end)
|
||||
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
for (auto itor = partFrameNodeChildren_.begin(); itor != partFrameNodeChildren_.end();) {
|
||||
int32_t index = itor->first;
|
||||
if ((start <= end && index >= start && index <= end) ||
|
||||
(start > end && (index <= end || start <= index))) {
|
||||
if ((start <= end && index >= start && index <= end) || (start > end && (index <= end || start <= index))) {
|
||||
itor++;
|
||||
} else {
|
||||
partFrameNodeChildren_.erase(itor++);
|
||||
@ -333,7 +333,7 @@ public:
|
||||
}
|
||||
auto guard = GetGuard();
|
||||
for (const auto& child : children_) {
|
||||
child.node->DoSetActiveChildRange(start - child.startIndex, end - child.startIndex);
|
||||
child.node->DoSetActiveChildRange(start - child.startIndex, end - child.startIndex, cacheStart, cacheEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,11 +652,11 @@ void FrameNode::DumpSafeAreaInfo()
|
||||
auto manager = pipeline->GetSafeAreaManager();
|
||||
CHECK_NULL_VOID(manager);
|
||||
DumpLog::GetInstance().AddDesc(std::string("ignoreSafeArea: ")
|
||||
.append(std::to_string(manager->IsIgnoreAsfeArea()))
|
||||
.append(std::string(", isNeedAvoidWindow: ").c_str())
|
||||
.append(std::to_string(manager->IsNeedAvoidWindow()))
|
||||
.append(std::string(", isFullScreen: ").c_str())
|
||||
.append(std::to_string(manager->IsFullScreen())));
|
||||
.append(std::to_string(manager->IsIgnoreAsfeArea()))
|
||||
.append(std::string(", isNeedAvoidWindow: ").c_str())
|
||||
.append(std::to_string(manager->IsNeedAvoidWindow()))
|
||||
.append(std::string(", isFullScreen: ").c_str())
|
||||
.append(std::to_string(manager->IsFullScreen())));
|
||||
}
|
||||
|
||||
void FrameNode::DumpAlignRulesInfo()
|
||||
@ -665,8 +665,7 @@ void FrameNode::DumpAlignRulesInfo()
|
||||
CHECK_NULL_VOID(flexItemProperties);
|
||||
auto rulesToString = flexItemProperties->AlignRulesToString();
|
||||
CHECK_NULL_VOID(!rulesToString.empty());
|
||||
DumpLog::GetInstance().AddDesc(std::string("AlignRules: ")
|
||||
.append(rulesToString));
|
||||
DumpLog::GetInstance().AddDesc(std::string("AlignRules: ").append(rulesToString));
|
||||
}
|
||||
|
||||
void FrameNode::DumpExtensionHandlerInfo()
|
||||
@ -674,11 +673,10 @@ void FrameNode::DumpExtensionHandlerInfo()
|
||||
if (!extensionHandler_) {
|
||||
return;
|
||||
}
|
||||
DumpLog::GetInstance().AddDesc(
|
||||
std::string("ExtensionHandler: HasCustomerMeasure: ")
|
||||
.append(extensionHandler_->HasCustomerMeasure() ? "true" : "false")
|
||||
.append(", HasCustomerLayout: ")
|
||||
.append(extensionHandler_->HasCustomerLayout() ? "true" : "false"));
|
||||
DumpLog::GetInstance().AddDesc(std::string("ExtensionHandler: HasCustomerMeasure: ")
|
||||
.append(extensionHandler_->HasCustomerMeasure() ? "true" : "false")
|
||||
.append(", HasCustomerLayout: ")
|
||||
.append(extensionHandler_->HasCustomerLayout() ? "true" : "false"));
|
||||
}
|
||||
|
||||
void FrameNode::DumpCommonInfo()
|
||||
@ -692,7 +690,7 @@ void FrameNode::DumpCommonInfo()
|
||||
}
|
||||
if (geometryNode_->GetParentLayoutConstraint().has_value())
|
||||
DumpLog::GetInstance().AddDesc(std::string("ParentLayoutConstraint: ")
|
||||
.append(geometryNode_->GetParentLayoutConstraint().value().ToString()));
|
||||
.append(geometryNode_->GetParentLayoutConstraint().value().ToString()));
|
||||
if (!(NearZero(GetOffsetRelativeToWindow().GetY()) && NearZero(GetOffsetRelativeToWindow().GetX()))) {
|
||||
DumpLog::GetInstance().AddDesc(std::string("top: ")
|
||||
.append(std::to_string(GetOffsetRelativeToWindow().GetY()))
|
||||
@ -738,8 +736,9 @@ void FrameNode::DumpCommonInfo()
|
||||
layoutProperty_->GetMarginProperty() || layoutProperty_->GetCalcLayoutConstraint()) {
|
||||
DumpLog::GetInstance().AddDesc(
|
||||
std::string("ContentConstraint: ")
|
||||
.append(layoutProperty_->GetContentLayoutConstraint().has_value() ?
|
||||
layoutProperty_->GetContentLayoutConstraint().value().ToString() : "NA"));
|
||||
.append(layoutProperty_->GetContentLayoutConstraint().has_value()
|
||||
? layoutProperty_->GetContentLayoutConstraint().value().ToString()
|
||||
: "NA"));
|
||||
}
|
||||
DumpAlignRulesInfo();
|
||||
DumpDragInfo();
|
||||
@ -753,11 +752,11 @@ void FrameNode::DumpDragInfo()
|
||||
{
|
||||
DumpLog::GetInstance().AddDesc("------------start print dragInfo");
|
||||
DumpLog::GetInstance().AddDesc(std::string("Draggable: ")
|
||||
.append(draggable_ ? "true" : "false")
|
||||
.append(" UserSet: ")
|
||||
.append(userSet_ ? "true" : "false")
|
||||
.append(" CustomerSet: ")
|
||||
.append(customerSet_ ? "true" : "false"));
|
||||
.append(draggable_ ? "true" : "false")
|
||||
.append(" UserSet: ")
|
||||
.append(userSet_ ? "true" : "false")
|
||||
.append(" CustomerSet: ")
|
||||
.append(customerSet_ ? "true" : "false"));
|
||||
auto dragPreviewStr =
|
||||
std::string("DragPreview: Has customNode: ").append(dragPreviewInfo_.customNode ? "YES" : "NO");
|
||||
dragPreviewStr.append(" Has pixelMap: ").append(dragPreviewInfo_.pixelMap ? "YES" : "NO");
|
||||
@ -766,30 +765,30 @@ void FrameNode::DumpDragInfo()
|
||||
DumpLog::GetInstance().AddDesc(dragPreviewStr);
|
||||
auto eventHub = GetEventHub<EventHub>();
|
||||
DumpLog::GetInstance().AddDesc(std::string("Event: ")
|
||||
.append("OnDragStart: ")
|
||||
.append(eventHub->HasOnDragStart() ? "YES" : "NO")
|
||||
.append(" OnDragEnter: ")
|
||||
.append(eventHub->HasOnDragEnter() ? "YES" : "NO")
|
||||
.append(" OnDragLeave: ")
|
||||
.append(eventHub->HasOnDragLeave() ? "YES" : "NO")
|
||||
.append(" OnDragMove: ")
|
||||
.append(eventHub->HasOnDragMove() ? "YES" : "NO")
|
||||
.append(" OnDrop: ")
|
||||
.append(eventHub->HasOnDrop() ? "YES" : "NO")
|
||||
.append(" OnDragEnd: ")
|
||||
.append(eventHub->HasOnDragEnd() ? "YES" : "NO"));
|
||||
.append("OnDragStart: ")
|
||||
.append(eventHub->HasOnDragStart() ? "YES" : "NO")
|
||||
.append(" OnDragEnter: ")
|
||||
.append(eventHub->HasOnDragEnter() ? "YES" : "NO")
|
||||
.append(" OnDragLeave: ")
|
||||
.append(eventHub->HasOnDragLeave() ? "YES" : "NO")
|
||||
.append(" OnDragMove: ")
|
||||
.append(eventHub->HasOnDragMove() ? "YES" : "NO")
|
||||
.append(" OnDrop: ")
|
||||
.append(eventHub->HasOnDrop() ? "YES" : "NO")
|
||||
.append(" OnDragEnd: ")
|
||||
.append(eventHub->HasOnDragEnd() ? "YES" : "NO"));
|
||||
DumpLog::GetInstance().AddDesc(std::string("DefaultOnDragStart: ")
|
||||
.append(eventHub->HasDefaultOnDragStart() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragEnter: ")
|
||||
.append(eventHub->HasCustomerOnDragEnter() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragLeave: ")
|
||||
.append(eventHub->HasCustomerOnDragLeave() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragMove: ")
|
||||
.append(eventHub->HasCustomerOnDragMove() ? "YES" : "NO")
|
||||
.append(" CustomerOnDrop: ")
|
||||
.append(eventHub->HasCustomerOnDrop() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragEnd: ")
|
||||
.append(eventHub->HasCustomerOnDragEnd() ? "YES" : "NO"));
|
||||
.append(eventHub->HasDefaultOnDragStart() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragEnter: ")
|
||||
.append(eventHub->HasCustomerOnDragEnter() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragLeave: ")
|
||||
.append(eventHub->HasCustomerOnDragLeave() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragMove: ")
|
||||
.append(eventHub->HasCustomerOnDragMove() ? "YES" : "NO")
|
||||
.append(" CustomerOnDrop: ")
|
||||
.append(eventHub->HasCustomerOnDrop() ? "YES" : "NO")
|
||||
.append(" CustomerOnDragEnd: ")
|
||||
.append(eventHub->HasCustomerOnDragEnd() ? "YES" : "NO"));
|
||||
DumpLog::GetInstance().AddDesc("------------end print dragInfo");
|
||||
}
|
||||
|
||||
@ -797,11 +796,11 @@ void FrameNode::DumpOnSizeChangeInfo()
|
||||
{
|
||||
for (auto it = onSizeChangeDumpInfos.rbegin(); it != onSizeChangeDumpInfos.rend(); ++it) {
|
||||
DumpLog::GetInstance().AddDesc(std::string("onSizeChange Time: ")
|
||||
.append(ConvertTimestampToStr(it->onSizeChangeTimeStamp))
|
||||
.append(" lastFrameRect: ")
|
||||
.append(it->lastFrameRect.ToString())
|
||||
.append(" currFrameRect: ")
|
||||
.append(it->currFrameRect.ToString()));
|
||||
.append(ConvertTimestampToStr(it->onSizeChangeTimeStamp))
|
||||
.append(" lastFrameRect: ")
|
||||
.append(it->lastFrameRect.ToString())
|
||||
.append(" currFrameRect: ")
|
||||
.append(it->currFrameRect.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -963,8 +962,7 @@ void FrameNode::GeometryNodeToJsonValue(std::unique_ptr<JsonValue>& json, const
|
||||
auto jsonSize = json->GetValue("size");
|
||||
if (!hasIdealWidth) {
|
||||
auto idealWidthVpStr = std::to_string(Dimension(geometryNode_->GetFrameSize().Width()).ConvertToVp());
|
||||
auto widthStr =
|
||||
(idealWidthVpStr.substr(0, idealWidthVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
|
||||
auto widthStr = (idealWidthVpStr.substr(0, idealWidthVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
|
||||
json->PutExtAttr("width", widthStr.c_str(), filter);
|
||||
if (jsonSize) {
|
||||
jsonSize->Put("width", widthStr.c_str());
|
||||
@ -973,8 +971,7 @@ void FrameNode::GeometryNodeToJsonValue(std::unique_ptr<JsonValue>& json, const
|
||||
|
||||
if (!hasIdealHeight) {
|
||||
auto idealHeightVpStr = std::to_string(Dimension(geometryNode_->GetFrameSize().Height()).ConvertToVp());
|
||||
auto heightStr =
|
||||
(idealHeightVpStr.substr(0, idealHeightVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
|
||||
auto heightStr = (idealHeightVpStr.substr(0, idealHeightVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
|
||||
json->PutExtAttr("height", heightStr.c_str(), filter);
|
||||
if (jsonSize) {
|
||||
jsonSize->Put("height", heightStr.c_str());
|
||||
@ -1031,7 +1028,7 @@ void FrameNode::UpdateGeometryTransition()
|
||||
MarkDirtyNode();
|
||||
}
|
||||
auto children = GetChildren();
|
||||
for (const auto& child: children) {
|
||||
for (const auto& child : children) {
|
||||
child->UpdateGeometryTransition();
|
||||
}
|
||||
}
|
||||
@ -1123,8 +1120,7 @@ void FrameNode::NotifyVisibleChange(bool isVisible)
|
||||
void FrameNode::TryVisibleChangeOnDescendant(bool isVisible)
|
||||
{
|
||||
auto layoutProperty = GetLayoutProperty();
|
||||
if (layoutProperty &&
|
||||
layoutProperty->GetVisibilityValue(VisibleType::VISIBLE) != VisibleType::VISIBLE) {
|
||||
if (layoutProperty && layoutProperty->GetVisibilityValue(VisibleType::VISIBLE) != VisibleType::VISIBLE) {
|
||||
return;
|
||||
}
|
||||
NotifyVisibleChange(isVisible);
|
||||
@ -1357,8 +1353,8 @@ RectF FrameNode::GetRectWithRender()
|
||||
}
|
||||
if (renderContext_ && renderContext_->GetPositionProperty()) {
|
||||
if (renderContext_->GetPositionProperty()->HasPosition()) {
|
||||
auto renderPosition = ContextPositionConvertToPX(
|
||||
renderContext_, layoutProperty_->GetLayoutConstraint()->percentReference);
|
||||
auto renderPosition =
|
||||
ContextPositionConvertToPX(renderContext_, layoutProperty_->GetLayoutConstraint()->percentReference);
|
||||
currFrameRect.SetOffset(
|
||||
{ static_cast<float>(renderPosition.first), static_cast<float>(renderPosition.second) });
|
||||
}
|
||||
@ -1529,7 +1525,8 @@ void FrameNode::ThrottledVisibleTask()
|
||||
GetVisibleRect(visibleRect, frameRect);
|
||||
double ratio = IsFrameDisappear() ? VISIBLE_RATIO_MIN
|
||||
: std::clamp(CalculateCurrentVisibleRatio(visibleRect, frameRect),
|
||||
VISIBLE_RATIO_MIN, VISIBLE_RATIO_MAX);
|
||||
VISIBLE_RATIO_MIN,
|
||||
VISIBLE_RATIO_MAX);
|
||||
if (NearEqual(ratio, lastThrottledVisibleRatio_)) {
|
||||
throttledCallbackOnTheWay_ = false;
|
||||
return;
|
||||
@ -1800,7 +1797,7 @@ RefPtr<ContentModifier> FrameNode::GetContentModifier()
|
||||
auto wrapper = CreatePaintWrapper();
|
||||
CHECK_NULL_RETURN(wrapper, nullptr);
|
||||
auto paintMethod = pattern_->CreateNodePaintMethod();
|
||||
if (!paintMethod || extensionHandler_ || renderContext_->GetAccessibilityFocus().value_or(false)) {
|
||||
if (!paintMethod || extensionHandler_ || renderContext_->GetAccessibilityFocus().value_or(false)) {
|
||||
paintMethod = pattern_->CreateDefaultNodePaintMethod();
|
||||
}
|
||||
CHECK_NULL_RETURN(paintMethod, nullptr);
|
||||
@ -1969,8 +1966,8 @@ RefPtr<FrameNode> FrameNode::GetFirstAutoFillContainerNode()
|
||||
return AceType::DynamicCast<FrameNode>(parent);
|
||||
}
|
||||
|
||||
void FrameNode::NotifyFillRequestSuccess(RefPtr<ViewDataWrap> viewDataWrap,
|
||||
RefPtr<PageNodeInfoWrap> nodeWrap, AceAutoFillType autoFillType)
|
||||
void FrameNode::NotifyFillRequestSuccess(
|
||||
RefPtr<ViewDataWrap> viewDataWrap, RefPtr<PageNodeInfoWrap> nodeWrap, AceAutoFillType autoFillType)
|
||||
{
|
||||
if (pattern_) {
|
||||
pattern_->NotifyFillRequestSuccess(viewDataWrap, nodeWrap, autoFillType);
|
||||
@ -2100,8 +2097,7 @@ void FrameNode::OnGenerateOneDepthVisibleFrameWithTransition(std::list<RefPtr<Fr
|
||||
visibleList.emplace_back(Claim(this));
|
||||
}
|
||||
|
||||
void FrameNode::OnGenerateOneDepthVisibleFrameWithOffset(
|
||||
std::list<RefPtr<FrameNode>>& visibleList, OffsetF& offset)
|
||||
void FrameNode::OnGenerateOneDepthVisibleFrameWithOffset(std::list<RefPtr<FrameNode>>& visibleList, OffsetF& offset)
|
||||
{
|
||||
if (isLayoutNode_) {
|
||||
isFind_ = true;
|
||||
@ -2189,8 +2185,7 @@ bool FrameNode::IsOutOfTouchTestRegion(const PointF& parentRevertPoint, int32_t
|
||||
auto subRevertPoint = revertPoint - paintRect.GetOffset();
|
||||
auto clip = renderContext_->GetClipEdge().value_or(false);
|
||||
if (!InResponseRegionList(revertPoint, responseRegionList) || !GetTouchable() ||
|
||||
NearZero(paintRectWithTransform.Width() ||
|
||||
NearZero(paintRectWithTransform.Height()))) {
|
||||
NearZero(paintRectWithTransform.Width() || NearZero(paintRectWithTransform.Height()))) {
|
||||
if (clip) {
|
||||
return true;
|
||||
}
|
||||
@ -2382,7 +2377,7 @@ HitTestResult FrameNode::TouchTest(const PointF& globalPoint, const PointF& pare
|
||||
if (childHitResult == HitTestResult::BUBBLING &&
|
||||
((child->GetHitTestMode() == HitTestMode::HTMDEFAULT) ||
|
||||
(child->GetHitTestMode() == HitTestMode::HTMTRANSPARENT_SELF) ||
|
||||
((child->GetHitTestMode() != HitTestMode::HTMTRANSPARENT) && IsExclusiveEventForChild()))) {
|
||||
((child->GetHitTestMode() != HitTestMode::HTMTRANSPARENT) && IsExclusiveEventForChild()))) {
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
@ -3026,9 +3021,8 @@ RefPtr<FrameNode> FrameNode::FindChildByName(const RefPtr<FrameNode>& parentNode
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FrameNode::CreateAnimatablePropertyFloat(
|
||||
const std::string& propertyName, float value, const std::function<void(float)>& onCallbackEvent,
|
||||
const PropertyUnit& propertyType)
|
||||
void FrameNode::CreateAnimatablePropertyFloat(const std::string& propertyName, float value,
|
||||
const std::function<void(float)>& onCallbackEvent, const PropertyUnit& propertyType)
|
||||
{
|
||||
auto context = GetRenderContext();
|
||||
CHECK_NULL_VOID(context);
|
||||
@ -3166,7 +3160,7 @@ int32_t FrameNode::GetNodeExpectedRate()
|
||||
void FrameNode::AddFRCSceneInfo(const std::string& scene, float speed, SceneStatus status)
|
||||
{
|
||||
if (SystemProperties::GetDebugEnabled()) {
|
||||
const std::string sceneStatusStrs[] = {"START", "RUNNING", "END"};
|
||||
const std::string sceneStatusStrs[] = { "START", "RUNNING", "END" };
|
||||
LOGI("%{public}s AddFRCSceneInfo scene:%{public}s speed:%{public}f status:%{public}s", GetTag().c_str(),
|
||||
scene.c_str(), std::abs(speed), sceneStatusStrs[static_cast<int32_t>(status)].c_str());
|
||||
}
|
||||
@ -3299,8 +3293,8 @@ void FrameNode::UpdatePercentSensitive()
|
||||
// This will call child and self measure process.
|
||||
void FrameNode::Measure(const std::optional<LayoutConstraintF>& parentConstraint)
|
||||
{
|
||||
ACE_LAYOUT_TRACE_BEGIN("Measure[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
|
||||
GetId(), GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
ACE_LAYOUT_TRACE_BEGIN("Measure[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
|
||||
GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
ArkUIPerfMonitor::GetInstance().RecordLayoutNode();
|
||||
isLayoutComplete_ = false;
|
||||
if (!oldGeometryNode_) {
|
||||
@ -3406,8 +3400,8 @@ void FrameNode::Measure(const std::optional<LayoutConstraintF>& parentConstraint
|
||||
// Called to perform layout children.
|
||||
void FrameNode::Layout()
|
||||
{
|
||||
ACE_LAYOUT_TRACE_BEGIN("Layout[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
|
||||
GetId(), GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
ACE_LAYOUT_TRACE_BEGIN("Layout[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
|
||||
GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
if (layoutProperty_->GetLayoutRect()) {
|
||||
GetGeometryNode()->SetFrameOffset(layoutProperty_->GetLayoutRect().value().GetOffset());
|
||||
}
|
||||
@ -3604,8 +3598,8 @@ bool FrameNode::OnLayoutFinish(bool& needSyncRsNode, DirtySwapConfig& config)
|
||||
void FrameNode::SyncGeometryNode(bool needSyncRsNode, const DirtySwapConfig& config)
|
||||
{
|
||||
if (SystemProperties::GetSyncDebugTraceEnabled()) {
|
||||
ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
|
||||
GetId(), GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
|
||||
GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str());
|
||||
ACE_LAYOUT_TRACE_END()
|
||||
}
|
||||
|
||||
@ -3727,9 +3721,9 @@ void FrameNode::RemoveAllChildInRenderTree()
|
||||
frameProxy_->RemoveAllChildInRenderTree();
|
||||
}
|
||||
|
||||
void FrameNode::SetActiveChildRange(int32_t start, int32_t end)
|
||||
void FrameNode::SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
frameProxy_->SetActiveChildRange(start, end);
|
||||
frameProxy_->SetActiveChildRange(start, end, cacheStart, cacheEnd);
|
||||
}
|
||||
|
||||
void FrameNode::RecycleItemsByIndex(int32_t start, int32_t end)
|
||||
@ -3858,7 +3852,7 @@ void FrameNode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
|
||||
SetActive(false);
|
||||
}
|
||||
|
||||
void FrameNode::DoSetActiveChildRange(int32_t start, int32_t end)
|
||||
void FrameNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
if (start <= end) {
|
||||
if (start > 0 || end < 0) {
|
||||
@ -3949,16 +3943,14 @@ void FrameNode::AddFrameNodeSnapshot(bool isHit, int32_t parentId, std::vector<R
|
||||
auto eventMgr = context->GetEventManager();
|
||||
CHECK_NULL_VOID(eventMgr);
|
||||
|
||||
FrameNodeSnapshot info = {
|
||||
.nodeId = GetId(),
|
||||
FrameNodeSnapshot info = { .nodeId = GetId(),
|
||||
.parentNodeId = parentId,
|
||||
.tag = GetTag(),
|
||||
.comId = propInspectorId_.value_or(""),
|
||||
.monopolizeEvents = GetMonopolizeEvents(),
|
||||
.isHit = isHit,
|
||||
.hitTestMode = static_cast<int32_t>(GetHitTestMode()),
|
||||
.responseRegionList = responseRegionList
|
||||
};
|
||||
.responseRegionList = responseRegionList };
|
||||
eventMgr->GetEventTreeRecord().AddFrameNodeSnapshot(std::move(info));
|
||||
}
|
||||
|
||||
@ -3978,40 +3970,40 @@ int64_t FrameNode::WrapExtensionAbilityId(int64_t extensionOffset, int64_t abili
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FrameNode::SearchExtensionElementInfoByAccessibilityIdNG(int64_t elementId, int32_t mode,
|
||||
int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
|
||||
void FrameNode::SearchExtensionElementInfoByAccessibilityIdNG(
|
||||
int64_t elementId, int32_t mode, int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
|
||||
{
|
||||
if (pattern_) {
|
||||
pattern_->SearchExtensionElementInfoByAccessibilityId(elementId, mode, offset, output);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameNode::SearchElementInfosByTextNG(int64_t elementId, const std::string& text,
|
||||
int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
|
||||
void FrameNode::SearchElementInfosByTextNG(int64_t elementId, const std::string& text, int64_t offset,
|
||||
std::list<Accessibility::AccessibilityElementInfo>& output)
|
||||
{
|
||||
if (pattern_) {
|
||||
pattern_->SearchElementInfosByText(elementId, text, offset, output);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameNode::FindFocusedExtensionElementInfoNG(int64_t elementId, int32_t focusType,
|
||||
int64_t offset, Accessibility::AccessibilityElementInfo& output)
|
||||
void FrameNode::FindFocusedExtensionElementInfoNG(
|
||||
int64_t elementId, int32_t focusType, int64_t offset, Accessibility::AccessibilityElementInfo& output)
|
||||
{
|
||||
if (pattern_) {
|
||||
pattern_->FindFocusedElementInfo(elementId, focusType, offset, output);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameNode::FocusMoveSearchNG(int64_t elementId, int32_t direction,
|
||||
int64_t offset, Accessibility::AccessibilityElementInfo& output)
|
||||
void FrameNode::FocusMoveSearchNG(
|
||||
int64_t elementId, int32_t direction, int64_t offset, Accessibility::AccessibilityElementInfo& output)
|
||||
{
|
||||
if (pattern_) {
|
||||
pattern_->FocusMoveSearch(elementId, direction, offset, output);
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameNode::TransferExecuteAction(int64_t elementId, const std::map<std::string, std::string>& actionArguments,
|
||||
int32_t action, int64_t offset)
|
||||
bool FrameNode::TransferExecuteAction(
|
||||
int64_t elementId, const std::map<std::string, std::string>& actionArguments, int32_t action, int64_t offset)
|
||||
{
|
||||
bool isExecuted = false;
|
||||
if (pattern_) {
|
||||
@ -4551,11 +4543,7 @@ OPINC_TYPE_E FrameNode::FindSuggestOpIncNode(std::string& path, const SizeF& bou
|
||||
path = path + " --> " + hostTag;
|
||||
LOGI("FindSuggestOpIncNode : %{public}s, with depth %{public}d, boundary: %{public}f, self: %{public}f, "
|
||||
"status: %{public}d",
|
||||
path.c_str(),
|
||||
depth,
|
||||
boundary.Height(),
|
||||
GetGeometryNode()->GetFrameSize().Height(),
|
||||
status);
|
||||
path.c_str(), depth, boundary.Height(), GetGeometryNode()->GetFrameSize().Height(), status);
|
||||
}
|
||||
if (status == OPINC_NODE) {
|
||||
MarkSuggestOpIncGroup(true, true);
|
||||
|
@ -716,8 +716,8 @@ public:
|
||||
void RemoveChildInRenderTree(uint32_t index) override;
|
||||
void RemoveAllChildInRenderTree() override;
|
||||
void DoRemoveChildInRenderTree(uint32_t index, bool isAll) override;
|
||||
void SetActiveChildRange(int32_t start, int32_t end) override;
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end) override;
|
||||
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) override;
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
|
||||
void RecycleItemsByIndex(int32_t start, int32_t end) override;
|
||||
const std::string& GetHostTag() const override
|
||||
{
|
||||
|
@ -1233,7 +1233,7 @@ RefPtr<UINode> UINode::GetFrameChildByIndexWithoutExpanded(uint32_t index)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32_t UINode::GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded)
|
||||
int32_t UINode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded)
|
||||
{
|
||||
int32_t index = 0;
|
||||
for (const auto& child : GetChildren()) {
|
||||
@ -1271,11 +1271,11 @@ void UINode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
|
||||
}
|
||||
}
|
||||
|
||||
void UINode::DoSetActiveChildRange(int32_t start, int32_t end)
|
||||
void UINode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
for (const auto& child : children_) {
|
||||
uint32_t count = static_cast<uint32_t>(child->FrameCount());
|
||||
child->DoSetActiveChildRange(start, end);
|
||||
child->DoSetActiveChildRange(start, end, cacheStart, cacheEnd);
|
||||
start -= static_cast<int32_t>(count);
|
||||
end -= static_cast<int32_t>(count);
|
||||
}
|
||||
|
@ -420,7 +420,7 @@ public:
|
||||
bool addToRenderTree = false);
|
||||
virtual RefPtr<UINode> GetFrameChildByIndexWithoutExpanded(uint32_t index);
|
||||
// Get current frameNode index with or without expanded all LazyForEachNode;
|
||||
virtual int32_t GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded = true);
|
||||
virtual int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true);
|
||||
void SetDebugLine(const std::string& line)
|
||||
{
|
||||
debugLine_ = line;
|
||||
@ -538,7 +538,7 @@ public:
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
virtual void DoRemoveChildInRenderTree(uint32_t index, bool isAll = false);
|
||||
virtual void DoSetActiveChildRange(int32_t start, int32_t end);
|
||||
virtual void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd);
|
||||
virtual void OnSetCacheCount(int32_t cacheCount, const std::optional<LayoutConstraintF>& itemConstraint);
|
||||
|
||||
// return value: true if the node can be removed immediately.
|
||||
|
@ -16,8 +16,10 @@
|
||||
#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_LAYOUTS_LAYOUT_WRAPPER_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_LAYOUTS_LAYOUT_WRAPPER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -48,9 +50,19 @@ public:
|
||||
|
||||
class RecursionGuard final {
|
||||
public:
|
||||
RecursionGuard(RecursiveLock& lock) : lock_(lock) { lock_.Lock(); }
|
||||
~RecursionGuard() { lock_.Unlock(); }
|
||||
RecursionGuard(const RecursionGuard& rhs) : lock_(rhs.lock_) { lock_.Lock(); }
|
||||
RecursionGuard(RecursiveLock& lock) : lock_(lock)
|
||||
{
|
||||
lock_.Lock();
|
||||
}
|
||||
~RecursionGuard()
|
||||
{
|
||||
lock_.Unlock();
|
||||
}
|
||||
RecursionGuard(const RecursionGuard& rhs) : lock_(rhs.lock_)
|
||||
{
|
||||
lock_.Lock();
|
||||
}
|
||||
|
||||
private:
|
||||
RecursiveLock& lock_;
|
||||
};
|
||||
@ -58,16 +70,45 @@ private:
|
||||
class ChildrenListWithGuard final {
|
||||
public:
|
||||
ChildrenListWithGuard(const std::list<RefPtr<LayoutWrapper>>& children, RecursiveLock& lock)
|
||||
: children_(children), guard_(lock) {}
|
||||
auto begin() const { return children_.begin(); }
|
||||
auto end() const { return children_.end(); }
|
||||
auto rbegin() const { return children_.rbegin(); }
|
||||
auto rend() const { return children_.rend(); }
|
||||
auto empty() const { return children_.empty(); }
|
||||
auto size() const { return children_.size(); }
|
||||
auto& front() const { return children_.front(); }
|
||||
auto& back() const { return children_.back(); }
|
||||
operator std::list<RefPtr<LayoutWrapper>>() const { return children_; }
|
||||
: children_(children), guard_(lock)
|
||||
{}
|
||||
auto begin() const
|
||||
{
|
||||
return children_.begin();
|
||||
}
|
||||
auto end() const
|
||||
{
|
||||
return children_.end();
|
||||
}
|
||||
auto rbegin() const
|
||||
{
|
||||
return children_.rbegin();
|
||||
}
|
||||
auto rend() const
|
||||
{
|
||||
return children_.rend();
|
||||
}
|
||||
auto empty() const
|
||||
{
|
||||
return children_.empty();
|
||||
}
|
||||
auto size() const
|
||||
{
|
||||
return children_.size();
|
||||
}
|
||||
auto& front() const
|
||||
{
|
||||
return children_.front();
|
||||
}
|
||||
auto& back() const
|
||||
{
|
||||
return children_.back();
|
||||
}
|
||||
operator std::list<RefPtr<LayoutWrapper>>() const
|
||||
{
|
||||
return children_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::list<RefPtr<LayoutWrapper>>& children_;
|
||||
RecursionGuard guard_;
|
||||
@ -96,9 +137,12 @@ public:
|
||||
virtual ChildrenListWithGuard GetAllChildrenWithBuild(bool addToRenderTree = true) = 0;
|
||||
virtual void RemoveChildInRenderTree(uint32_t index) = 0;
|
||||
virtual void RemoveAllChildInRenderTree() = 0;
|
||||
virtual void SetActiveChildRange(int32_t start, int32_t end) = 0;
|
||||
virtual void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) = 0;
|
||||
virtual void RecycleItemsByIndex(int32_t start, int32_t end) = 0;
|
||||
|
||||
virtual void SetActiveChildRange(const std::set<int32_t>& activeIndexes, const std::set<int32_t>& cachedIndexes) {}
|
||||
virtual void RecycleItemsByIndex(const std::set<int32_t>& indexes) {}
|
||||
|
||||
RefPtr<FrameNode> GetHostNode() const;
|
||||
virtual const std::string& GetHostTag() const = 0;
|
||||
virtual bool IsActive() const = 0;
|
||||
|
@ -119,7 +119,7 @@ public:
|
||||
|
||||
void RemoveChildInRenderTree(uint32_t index) override;
|
||||
void RemoveAllChildInRenderTree() override;
|
||||
void SetActiveChildRange(int32_t start, int32_t end) override {}
|
||||
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) override {}
|
||||
void RecycleItemsByIndex(int32_t start, int32_t end) override {}
|
||||
|
||||
void ResetHostNode();
|
||||
|
@ -172,7 +172,7 @@ RefPtr<UINode> CustomNode::GetFrameChildByIndex(uint32_t index, bool needBuild,
|
||||
return UINode::GetFrameChildByIndex(index, needBuild, isCache, addToRenderTree);
|
||||
}
|
||||
|
||||
void CustomNode::DoSetActiveChildRange(int32_t start, int32_t end)
|
||||
void CustomNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
if (start <= end) {
|
||||
if (start > 0 || end < 0) {
|
||||
|
@ -114,7 +114,7 @@ public:
|
||||
return extraInfos_;
|
||||
}
|
||||
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end) override;
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
|
||||
|
||||
const WeakPtr<UINode>& GetNavigationNode() const
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "core/components_ng/base/ui_node.h"
|
||||
#include "core/components_ng/syntax/lazy_for_each_node.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
|
||||
#include "core/components_ng/pattern/list/list_item_group_pattern.h"
|
||||
#include "core/components_ng/pattern/list/list_item_pattern.h"
|
||||
#include "core/components_ng/pattern/list/list_layout_algorithm.h"
|
||||
@ -95,16 +96,16 @@ public:
|
||||
if (AceType::InstanceOf<FrameNode>(child)) {
|
||||
auto frameNode = AceType::DynamicCast<FrameNode>(child);
|
||||
CalculateFrameNode(frameNode);
|
||||
} else if (AceType::InstanceOf<LazyForEachNode>(child)) {
|
||||
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(child);
|
||||
CalculateLazyForEachNode(lazyForEach);
|
||||
} else if (AceType::InstanceOf<LazyForEachNode>(child) ||
|
||||
AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
|
||||
CalculateLazyForEachNode(child);
|
||||
} else {
|
||||
CalculateUINode(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float GetLazyForEachIndexAverageHeight(RefPtr<LazyForEachNode> node,
|
||||
float GetLazyForEachIndexAverageHeight(RefPtr<UINode> node,
|
||||
int32_t startIndex, int32_t endIndex, bool &hasGroup)
|
||||
{
|
||||
auto itor = itemPosition_.find(startIndex);
|
||||
@ -143,7 +144,7 @@ public:
|
||||
return totalHeight / itemCount;
|
||||
}
|
||||
|
||||
float CalculateOffset(RefPtr<LazyForEachNode> node, float averageHeight)
|
||||
float CalculateOffset(RefPtr<UINode> node, float averageHeight)
|
||||
{
|
||||
auto itor = itemPosition_.begin();
|
||||
float skipHeight = 0.0f;
|
||||
@ -163,7 +164,7 @@ public:
|
||||
return estimateOffset_ - targetPos_.first;
|
||||
}
|
||||
|
||||
void CalculateLazyForEachNode(RefPtr<LazyForEachNode> node)
|
||||
void CalculateLazyForEachNode(RefPtr<UINode> node)
|
||||
{
|
||||
int32_t count = node->FrameCount();
|
||||
if (count <= 0) {
|
||||
|
@ -33,18 +33,24 @@ void ListItemModelNG::Create(std::function<void(int32_t)>&& deepRenderFunc, V2::
|
||||
{
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto nodeId = stack->ClaimNodeId();
|
||||
auto deepRender = [nodeId, deepRenderFunc = std::move(deepRenderFunc)]() -> RefPtr<UINode> {
|
||||
CHECK_NULL_RETURN(deepRenderFunc, nullptr);
|
||||
ScopedViewStackProcessor scopedViewStackProcessor;
|
||||
deepRenderFunc(nodeId);
|
||||
return ViewStackProcessor::GetInstance()->Finish();
|
||||
};
|
||||
ACE_LAYOUT_SCOPED_TRACE("Create[%s][self:%d]", V2::LIST_ITEM_ETS_TAG, nodeId);
|
||||
auto frameNode = ScrollableItemPool::GetInstance().Allocate(V2::LIST_ITEM_ETS_TAG, nodeId,
|
||||
[shallowBuilder = AceType::MakeRefPtr<ShallowBuilder>(std::move(deepRender)), itemStyle = listItemStyle]() {
|
||||
return AceType::MakeRefPtr<ListItemPattern>(shallowBuilder, itemStyle);
|
||||
});
|
||||
stack->Push(frameNode);
|
||||
if (deepRenderFunc) {
|
||||
auto deepRender = [nodeId, deepRenderFunc = std::move(deepRenderFunc)]() -> RefPtr<UINode> {
|
||||
CHECK_NULL_RETURN(deepRenderFunc, nullptr);
|
||||
ScopedViewStackProcessor scopedViewStackProcessor;
|
||||
deepRenderFunc(nodeId);
|
||||
return ViewStackProcessor::GetInstance()->Finish();
|
||||
};
|
||||
auto frameNode = ScrollableItemPool::GetInstance().Allocate(V2::LIST_ITEM_ETS_TAG, nodeId,
|
||||
[shallowBuilder = AceType::MakeRefPtr<ShallowBuilder>(std::move(deepRender)), itemStyle = listItemStyle]() {
|
||||
return AceType::MakeRefPtr<ListItemPattern>(shallowBuilder, itemStyle);
|
||||
});
|
||||
stack->Push(frameNode);
|
||||
} else {
|
||||
auto frameNode = FrameNode::GetOrCreateFrameNode(V2::LIST_ITEM_ETS_TAG, nodeId,
|
||||
[listItemStyle]() { return AceType::MakeRefPtr<ListItemPattern>(nullptr, listItemStyle); });
|
||||
stack->Push(frameNode);
|
||||
}
|
||||
}
|
||||
|
||||
void ListItemModelNG::Create()
|
||||
|
@ -348,9 +348,18 @@ float ListLanesLayoutAlgorithm::CalculateLaneCrossOffset(float crossSize, float
|
||||
int32_t ListLanesLayoutAlgorithm::GetLazyForEachIndex(const RefPtr<FrameNode>& host)
|
||||
{
|
||||
CHECK_NULL_RETURN(host, -1);
|
||||
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(host->GetParent());
|
||||
CHECK_NULL_RETURN(lazyForEach, -1);
|
||||
return lazyForEach->GetIndexByUINode(host);
|
||||
auto parent = host->GetParent();
|
||||
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(parent);
|
||||
if (lazyForEach) {
|
||||
return lazyForEach->GetIndexByUINode(host);
|
||||
}
|
||||
while (parent && !AceType::InstanceOf<FrameNode>(parent)) {
|
||||
if (AceType::InstanceOf<RepeatVirtualScrollNode>(parent)) {
|
||||
return parent->GetFrameNodeIndex(host);
|
||||
}
|
||||
parent = parent->GetParent();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t ListLanesLayoutAlgorithm::FindLanesStartIndex(LayoutWrapper* layoutWrapper, int32_t startIndex, int32_t index)
|
||||
|
@ -162,10 +162,11 @@ void ListLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
auto cacheCount = listLayoutProperty->GetCachedCountValue(1) * GetLanes();
|
||||
if (itemPosition_.empty()) {
|
||||
layoutWrapper->SetActiveChildRange(-1, -1);
|
||||
} else {
|
||||
layoutWrapper->SetActiveChildRange(itemPosition_.begin()->first, itemPosition_.rbegin()->first);
|
||||
layoutWrapper->SetActiveChildRange(itemPosition_.begin()->first, itemPosition_.rbegin()->first, cacheCount, cacheCount);
|
||||
}
|
||||
|
||||
auto crossSize = contentIdealSize.CrossSize(axis_);
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "base/utils/utils.h"
|
||||
#include "core/components_ng/base/ui_node.h"
|
||||
#include "core/components_ng/syntax/lazy_for_each_node.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
|
||||
#include "core/components_ng/pattern/list/list_children_main_size.h"
|
||||
#include "core/components_ng/pattern/list/list_item_group_pattern.h"
|
||||
#include "core/components_ng/pattern/list/list_layout_algorithm.h"
|
||||
@ -162,28 +163,17 @@ public:
|
||||
if (AceType::InstanceOf<FrameNode>(child)) {
|
||||
auto frameNode = AceType::DynamicCast<FrameNode>(child);
|
||||
CalculateFrameNode(frameNode);
|
||||
} else if (AceType::InstanceOf<LazyForEachNode>(child)) {
|
||||
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(child);
|
||||
} else if (AceType::InstanceOf<LazyForEachNode>(child) ||
|
||||
AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
|
||||
// Rules: only one type node(ListItem or ListItemGroup) can exist in LazyForEach.
|
||||
CalculateLazyForEachNode(lazyForEach);
|
||||
CalculateLazyForEachNode(child);
|
||||
} else {
|
||||
CalculateUINode(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<FrameNode> GetListFrameNode(RefPtr<LazyForEachNode> node) const
|
||||
{
|
||||
auto parent = node->GetParent();
|
||||
RefPtr<FrameNode> frameNode = AceType::DynamicCast<FrameNode>(parent);
|
||||
while (parent && (!frameNode)) {
|
||||
parent = parent->GetParent();
|
||||
frameNode = AceType::DynamicCast<FrameNode>(parent);
|
||||
}
|
||||
return frameNode;
|
||||
}
|
||||
|
||||
std::optional<bool> GetLazyForEachChildIsGroup(RefPtr<LazyForEachNode> node)
|
||||
std::optional<bool> GetLazyForEachChildIsGroup(RefPtr<UINode> node)
|
||||
{
|
||||
std::optional<bool> isGroup;
|
||||
auto children = node->GetChildren();
|
||||
@ -198,7 +188,7 @@ public:
|
||||
}
|
||||
}
|
||||
if (!(isGroup.has_value())) {
|
||||
auto listNode = GetListFrameNode(node);
|
||||
auto listNode = node->GetParentFrameNode();
|
||||
CHECK_NULL_RETURN(listNode, isGroup);
|
||||
auto wrapper = listNode->GetOrCreateChildByIndex(curIndex_);
|
||||
CHECK_NULL_RETURN(wrapper, isGroup);
|
||||
@ -207,7 +197,7 @@ public:
|
||||
return isGroup;
|
||||
}
|
||||
|
||||
void CalculateLazyForEachNode(RefPtr<LazyForEachNode> node)
|
||||
void CalculateLazyForEachNode(RefPtr<UINode> node)
|
||||
{
|
||||
int32_t count = node->FrameCount();
|
||||
if (count <= 0) {
|
||||
|
@ -13,24 +13,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "core/components_ng/pattern/scrollable/scrollable_utils.h"
|
||||
|
||||
#include "core/components_ng/syntax/for_each_base_node.h"
|
||||
#include "core/components_ng/syntax/lazy_for_each_node.h"
|
||||
#include "core/pipeline_ng/pipeline_context.h"
|
||||
namespace OHOS::Ace::NG {
|
||||
namespace {
|
||||
std::vector<RefPtr<LazyForEachNode>> GetLazyForEachNodes(RefPtr<FrameNode>& host)
|
||||
std::vector<RefPtr<ForEachBaseNode>> GetForEachNodes(RefPtr<FrameNode>& host)
|
||||
{
|
||||
std::vector<RefPtr<LazyForEachNode>> lazyNodes;
|
||||
std::vector<RefPtr<ForEachBaseNode>> foreachNodes;
|
||||
for (const auto& child : host->GetChildren()) {
|
||||
if (!AceType::InstanceOf<LazyForEachNode>(child)) {
|
||||
if (!AceType::InstanceOf<ForEachBaseNode>(child)) {
|
||||
continue;
|
||||
}
|
||||
auto lazyNode = AceType::DynamicCast<LazyForEachNode>(child);
|
||||
if (!lazyNode) {
|
||||
auto node = AceType::DynamicCast<ForEachBaseNode>(child);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
lazyNodes.push_back(lazyNode);
|
||||
foreachNodes.push_back(node);
|
||||
}
|
||||
return lazyNodes;
|
||||
return foreachNodes;
|
||||
}
|
||||
|
||||
bool OutOfBottomOrRightBoundary(
|
||||
@ -93,8 +95,8 @@ int32_t GetScrollUpOrLeftItemIndex(Axis axis, float offset, int32_t start, int32
|
||||
return outIndex;
|
||||
}
|
||||
|
||||
void RecycleItemsByIndex(int32_t start, int32_t end,
|
||||
std::vector<RefPtr<LazyForEachNode>>& lazyNodes, LayoutWrapper* wrapper)
|
||||
void RecycleItemsByIndex(
|
||||
int32_t start, int32_t end, std::vector<RefPtr<ForEachBaseNode>>& lazyNodes, LayoutWrapper* wrapper)
|
||||
{
|
||||
wrapper->RecycleItemsByIndex(start, end);
|
||||
for (const auto& node : lazyNodes) {
|
||||
@ -129,8 +131,8 @@ void ScrollableUtils::RecycleItemsOutOfBoundary(
|
||||
}
|
||||
|
||||
auto host = wrapper->GetHostNode();
|
||||
std::vector<RefPtr<LazyForEachNode>> lazyNodes = GetLazyForEachNodes(host);
|
||||
if (lazyNodes.empty()) {
|
||||
std::vector<RefPtr<ForEachBaseNode>> foreachNodes = GetForEachNodes(host);
|
||||
if (foreachNodes.empty()) {
|
||||
return;
|
||||
}
|
||||
if (offset >= 0) {
|
||||
@ -138,13 +140,13 @@ void ScrollableUtils::RecycleItemsOutOfBoundary(
|
||||
if (inIndex >= end) {
|
||||
return;
|
||||
}
|
||||
RecycleItemsByIndex(inIndex + 1, end + 1, lazyNodes, wrapper);
|
||||
RecycleItemsByIndex(inIndex + 1, end + 1, foreachNodes, wrapper);
|
||||
} else {
|
||||
int32_t outIndex = GetScrollUpOrLeftItemIndex(axis, offset, start, end, host);
|
||||
if (outIndex <= start) {
|
||||
return;
|
||||
}
|
||||
RecycleItemsByIndex(start, outIndex, lazyNodes, wrapper);
|
||||
RecycleItemsByIndex(start, outIndex, foreachNodes, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,5 +27,9 @@ build_component_ng("syntax_ng") {
|
||||
"lazy_layout_wrapper_builder.cpp",
|
||||
"node_content.cpp",
|
||||
"repeat_model_ng.cpp",
|
||||
"repeat_node.cpp",
|
||||
"repeat_virtual_scroll_caches.cpp",
|
||||
"repeat_virtual_scroll_model_ng.cpp",
|
||||
"repeat_virtual_scroll_node.cpp",
|
||||
]
|
||||
}
|
||||
|
@ -23,16 +23,17 @@ class ACE_EXPORT ForEachBaseNode : public UINode {
|
||||
DECLARE_ACE_TYPE(ForEachBaseNode, UINode);
|
||||
|
||||
public:
|
||||
ForEachBaseNode(const std::string& tag, int32_t nodeId, bool isRoot = false)
|
||||
: UINode(tag, nodeId, isRoot) {}
|
||||
ForEachBaseNode(const std::string& tag, int32_t nodeId, bool isRoot = false) : UINode(tag, nodeId, isRoot) {}
|
||||
virtual void MoveData(int32_t from, int32_t to) = 0;
|
||||
virtual RefPtr<FrameNode> GetFrameNode(int32_t index) = 0;
|
||||
virtual void RecycleItems(int32_t from, int32_t to) {}
|
||||
virtual void FireOnMove(int32_t from, int32_t to)
|
||||
{
|
||||
if (onMoveEvent_) {
|
||||
onMoveEvent_(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<void(int32_t, int32_t)> onMoveEvent_;
|
||||
};
|
||||
|
@ -388,7 +388,7 @@ void LazyForEachNode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
|
||||
}
|
||||
}
|
||||
|
||||
void LazyForEachNode::DoSetActiveChildRange(int32_t start, int32_t end)
|
||||
void LazyForEachNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
if (!builder_) {
|
||||
return;
|
||||
@ -473,7 +473,7 @@ RefPtr<FrameNode> LazyForEachNode::GetFrameNode(int32_t index)
|
||||
return AceType::DynamicCast<FrameNode>(child.second->GetFrameChildByIndex(0, true));
|
||||
}
|
||||
|
||||
int32_t LazyForEachNode::GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded)
|
||||
int32_t LazyForEachNode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded)
|
||||
{
|
||||
if (!isExpanded) {
|
||||
return UINode::GetFrameNodeIndex(node, false);
|
||||
|
@ -112,7 +112,7 @@ public:
|
||||
RefPtr<UINode> GetFrameChildByIndex(uint32_t index, bool needBuild, bool isCache = false,
|
||||
bool addToRenderTree = false) override;
|
||||
void DoRemoveChildInRenderTree(uint32_t index, bool isAll) override;
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end) override;
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
|
||||
|
||||
const std::list<RefPtr<UINode>>& GetChildren() const override;
|
||||
void OnSetCacheCount(int32_t cacheCount, const std::optional<LayoutConstraintF>& itemConstraint) override
|
||||
@ -141,7 +141,7 @@ public:
|
||||
startIndex_ = start;
|
||||
count_ = count;
|
||||
}
|
||||
void RecycleItems(int32_t from, int32_t to);
|
||||
void RecycleItems(int32_t from, int32_t to) override;
|
||||
|
||||
const RefPtr<LazyForEachBuilder>& GetBuilder() const
|
||||
{
|
||||
@ -160,7 +160,7 @@ public:
|
||||
void MoveData(int32_t from, int32_t to) override;
|
||||
void FireOnMove(int32_t from, int32_t to) override;
|
||||
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
|
||||
int32_t GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded = true) override;
|
||||
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true) override;
|
||||
void InitDragManager(const RefPtr<FrameNode>& childNode);
|
||||
void InitAllChilrenDragManager(bool init);
|
||||
private:
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "base/utils/utils.h"
|
||||
#include "core/common/container.h"
|
||||
#include "core/components_ng/base/view_stack_processor.h"
|
||||
#include "core/components_ng/syntax/for_each_node.h"
|
||||
#include "core/components_ng/syntax/repeat_node.h"
|
||||
#include "core/components_ng/syntax/syntax_item.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
@ -29,7 +29,7 @@ void RepeatModelNG::StartRender()
|
||||
ACE_SCOPED_TRACE("RepeatModelNG::StartRender");
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto nodeId = stack->ClaimNodeId();
|
||||
auto repeatNode = ForEachNode::GetOrCreateRepeatNode(nodeId);
|
||||
auto repeatNode = RepeatNode::GetOrCreateRepeatNode(nodeId);
|
||||
stack->Push(repeatNode);
|
||||
|
||||
// move current id array and children to temp
|
||||
@ -39,7 +39,7 @@ void RepeatModelNG::StartRender()
|
||||
void RepeatModelNG::FinishRender(std::list<int32_t>& removedElmtId)
|
||||
{
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto repeatNode = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
|
||||
auto repeatNode = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
|
||||
CHECK_NULL_VOID(repeatNode);
|
||||
repeatNode->FinishRepeatRender(removedElmtId);
|
||||
stack->PopContainer();
|
||||
@ -49,7 +49,7 @@ void RepeatModelNG::MoveChild(uint32_t fromIndex)
|
||||
{
|
||||
ACE_SCOPED_TRACE("RepeatModelNG::MoveChild()");
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto repeatNode = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
|
||||
auto repeatNode = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
|
||||
CHECK_NULL_VOID(repeatNode);
|
||||
repeatNode->MoveChild(fromIndex);
|
||||
}
|
||||
@ -76,7 +76,7 @@ void RepeatModelNG::CreateNewChildFinish(const std::string& key)
|
||||
void RepeatModelNG::OnMove(std::function<void(int32_t, int32_t)>&& onMove)
|
||||
{
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto node = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
|
||||
auto node = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
|
||||
CHECK_NULL_VOID(node);
|
||||
node->SetOnMove(std::move(onMove));
|
||||
}
|
||||
|
133
frameworks/core/components_ng/syntax/repeat_node.cpp
Normal file
133
frameworks/core/components_ng/syntax/repeat_node.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2024 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 "core/components_ng/syntax/repeat_node.h"
|
||||
|
||||
#include "base/log/ace_trace.h"
|
||||
#include "core/components_ng/base/frame_node.h"
|
||||
#include "core/components_ng/pattern/list/list_item_pattern.h"
|
||||
#include "core/pipeline/base/element_register.h"
|
||||
#include "core/pipeline_ng/pipeline_context.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
// REPEAT
|
||||
RefPtr<RepeatNode> RepeatNode::GetOrCreateRepeatNode(int32_t nodeId)
|
||||
{
|
||||
auto node = ElementRegister::GetInstance()->GetSpecificItemById<RepeatNode>(nodeId);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
node = MakeRefPtr<RepeatNode>(nodeId);
|
||||
ElementRegister::GetInstance()->AddUINode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
// REPEAT
|
||||
void RepeatNode::CreateTempItems()
|
||||
{
|
||||
std::swap(ids_, tempIds_);
|
||||
std::swap(ModifyChildren(), tempChildren_);
|
||||
|
||||
tempChildrenOfRepeat_ = std::vector<RefPtr<UINode>>(tempChildren_.begin(), tempChildren_.end());
|
||||
}
|
||||
|
||||
// RepeatNode only
|
||||
void RepeatNode::FinishRepeatRender(std::list<int32_t>& removedElmtId)
|
||||
{
|
||||
ACE_SCOPED_TRACE("RepeatNode::FinishRepeatRender");
|
||||
|
||||
// Required to build unordered_set of RefPtr<UINodes>
|
||||
struct Hash {
|
||||
size_t operator()(const RefPtr<UINode>& node) const
|
||||
{
|
||||
return node->GetId();
|
||||
}
|
||||
};
|
||||
|
||||
// includes "newly-added" and "reused" children
|
||||
const auto& children = GetChildren();
|
||||
|
||||
std::unordered_set<RefPtr<UINode>, Hash>
|
||||
newNodeSet(children.begin(), children.end());
|
||||
|
||||
// remove "unused" children
|
||||
for (const auto& oldNode: tempChildrenOfRepeat_) {
|
||||
if (newNodeSet.find(oldNode) == newNodeSet.end()) {
|
||||
// Adding silently, so that upon removal node is a part the tree.
|
||||
AddChild(oldNode, DEFAULT_NODE_SLOT, true);
|
||||
// Remove and trigger all Detach callback.
|
||||
RemoveChild(oldNode, true);
|
||||
// Collect IDs of removed nodes starting from 'oldNode' (incl.)
|
||||
CollectRemovedChildren({ oldNode }, removedElmtId, false);
|
||||
}
|
||||
}
|
||||
|
||||
tempChildren_.clear();
|
||||
tempChildrenOfRepeat_.clear();
|
||||
|
||||
auto frameNode = GetParentFrameNode();
|
||||
if (frameNode) {
|
||||
frameNode->ChildrenUpdatedFrom(0);
|
||||
}
|
||||
}
|
||||
|
||||
// RepeatNode only
|
||||
void RepeatNode::MoveChild(uint32_t fromIndex)
|
||||
{
|
||||
// copy child from tempChildrenOfRepeat_[fromIndex] and append to children_
|
||||
if (fromIndex < tempChildrenOfRepeat_.size()) {
|
||||
auto& node = tempChildrenOfRepeat_.at(fromIndex);
|
||||
AddChild(node, DEFAULT_NODE_SLOT, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat
|
||||
void RepeatNode::SetOnMove(std::function<void(int32_t, int32_t)>&& onMove)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
// FOREACH
|
||||
void RepeatNode::MoveData(int32_t from, int32_t to)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
void RepeatNode::FlushUpdateAndMarkDirty()
|
||||
{
|
||||
tempIds_.clear();
|
||||
// mark parent dirty to flush measure.
|
||||
MarkNeedSyncRenderTree(true);
|
||||
MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE_SELF_AND_PARENT | PROPERTY_UPDATE_BY_CHILD_REQUEST);
|
||||
}
|
||||
|
||||
// FIXME called from where ?
|
||||
RefPtr<FrameNode> RepeatNode::GetFrameNode(int32_t index)
|
||||
{
|
||||
return AceType::DynamicCast<FrameNode>(GetFrameChildByIndex(index, false, false));
|
||||
}
|
||||
|
||||
void RepeatNode::InitDragManager(const RefPtr<UINode>& child)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
void RepeatNode::InitAllChildrenDragManager(bool init)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
87
frameworks/core/components_ng/syntax/repeat_node.h
Normal file
87
frameworks/core/components_ng/syntax/repeat_node.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_NODE_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_NODE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/utils/macros.h"
|
||||
#include "core/components_ng/base/ui_node.h"
|
||||
#include "core/components_ng/syntax/for_each_base_node.h"
|
||||
#include "core/components_v2/inspector/inspector_constants.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
class ACE_EXPORT RepeatNode : public ForEachBaseNode {
|
||||
DECLARE_ACE_TYPE(RepeatNode, ForEachBaseNode);
|
||||
|
||||
public:
|
||||
static RefPtr<RepeatNode> GetOrCreateRepeatNode(int32_t nodeId);
|
||||
|
||||
explicit RepeatNode(int32_t nodeId) : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId) {}
|
||||
|
||||
~RepeatNode() override = default;
|
||||
|
||||
bool IsAtomicNode() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void CreateTempItems();
|
||||
|
||||
// RepeatNode only
|
||||
void FinishRepeatRender(std::list<int32_t>& removedElmtId);
|
||||
|
||||
void FlushUpdateAndMarkDirty() override;
|
||||
|
||||
// RepeatNode only
|
||||
void MoveChild(uint32_t fromIndex);
|
||||
|
||||
const std::list<std::string>& GetTempIds() const
|
||||
{
|
||||
return tempIds_;
|
||||
}
|
||||
|
||||
void SetIds(std::list<std::string>&& ids)
|
||||
{
|
||||
ids_ = std::move(ids);
|
||||
}
|
||||
|
||||
void SetOnMove(std::function<void(int32_t, int32_t)>&& onMove);
|
||||
void MoveData(int32_t from, int32_t to) override;
|
||||
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
|
||||
void InitDragManager(const RefPtr<UINode>& childNode);
|
||||
void InitAllChildrenDragManager(bool init);
|
||||
|
||||
private:
|
||||
std::list<std::string> ids_;
|
||||
|
||||
// temp items use to compare each update.
|
||||
std::list<std::string> tempIds_;
|
||||
std::list<RefPtr<UINode>> tempChildren_;
|
||||
|
||||
// RepeatNode only
|
||||
std::vector<RefPtr<UINode>> tempChildrenOfRepeat_;
|
||||
|
||||
ACE_DISALLOW_COPY_AND_MOVE(RepeatNode);
|
||||
};
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
||||
|
||||
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_NODE_H
|
@ -0,0 +1,647 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 "core/components_ng/syntax/repeat_virtual_scroll_caches.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "base/log/log_wrapper.h"
|
||||
#include "core/components_ng/base/frame_node.h"
|
||||
#include "core/components_ng/base/view_stack_processor.h"
|
||||
#include "core/pipeline/base/element_register.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
bool KeySorterClass::operator()(const std::string& left, const std::string& right) const
|
||||
{
|
||||
return virtualScroll_->CompareKeyByIndexDistance(left, right);
|
||||
}
|
||||
|
||||
RepeatVirtualScrollCaches::RepeatVirtualScrollCaches(const std::map<std::string, uint32_t>& cacheCountL24ttype,
|
||||
const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
|
||||
: // each ttype incl default has own L2 cache size
|
||||
cacheCountL24ttype_(cacheCountL24ttype),
|
||||
// request TS to create new sub-tree for given index or update existing
|
||||
// update subtree cached for (old) index
|
||||
// API might need to change to tell which old item to update
|
||||
onCreateNode_(onCreateNode),
|
||||
onUpdateNode_(onUpdateNode),
|
||||
onGetTypes4Range_(onGetTypes4Range),
|
||||
onGetKeys4Range_(onGetKeys4Range)
|
||||
{}
|
||||
|
||||
std::optional<std::string> RepeatVirtualScrollCaches::GetKey4Index(uint32_t index)
|
||||
{
|
||||
auto it = key4index_.find(index);
|
||||
if (it == key4index_.end()) {
|
||||
// request more keys from TS key gen
|
||||
if (!FetchMoreKeysTTypes(index, index)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return key4index_[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* get more index -> key and index -> ttype from TS side
|
||||
*/
|
||||
bool RepeatVirtualScrollCaches::FetchMoreKeysTTypes(uint32_t from, uint32_t to)
|
||||
{
|
||||
// always request the same range for keys and ttype
|
||||
// optimism by merging the two calls into one
|
||||
std::list<std::string> keysFrom = onGetKeys4Range_(from, to);
|
||||
std::list<std::string> ttypesFrom = onGetTypes4Range_(from, to);
|
||||
auto requestCount = to - from + 1;
|
||||
auto keySize = keysFrom.size();
|
||||
if (keySize != requestCount) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to fetch keys: requst %{public}d, fetch %{public}d",
|
||||
static_cast<int32_t>(keySize), requestCount);
|
||||
return false;
|
||||
}
|
||||
auto ttypeSize = ttypesFrom.size();
|
||||
if (ttypeSize != requestCount) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to fetch keys: requst %{public}d, fetch %{public}d",
|
||||
static_cast<int32_t>(keySize), requestCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// fill-in index maps
|
||||
auto from1 = from;
|
||||
for (const auto& key : keysFrom) {
|
||||
key4index_[from1] = key;
|
||||
index4Key_[key] = from1;
|
||||
from1++;
|
||||
}
|
||||
from1 = from;
|
||||
for (const auto& ttype : ttypesFrom) {
|
||||
ttype4index_[from1] = ttype;
|
||||
index4ttype_[ttype] = from1;
|
||||
from1++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// get UINode for given index without create.
|
||||
RefPtr<UINode> RepeatVirtualScrollCaches::GetNode4Index(uint32_t index) const
|
||||
{
|
||||
const auto it = key4index_.find(index);
|
||||
if (it == key4index_.end()) {
|
||||
TAG_LOGD(AceLogTag::ACE_REPEAT, "no UINode for index %{public}d", static_cast<int32_t>(index));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& key4Index = it->second;
|
||||
const auto nodeIter = node4key_.find(key4Index);
|
||||
if (nodeIter == node4key_.end()) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "no UINode for index %{public}d and key %{public}s",
|
||||
static_cast<int32_t>(index), key4Index.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
return nodeIter->second;
|
||||
}
|
||||
|
||||
/** scenario:
|
||||
* Repeat gets updated due to data change.
|
||||
* 1. TS calls RepeatVirtualScrollNode,
|
||||
* then calls this function.
|
||||
* 2. RepeatVirtualScrollNode requests layout to rebuild the UI
|
||||
* 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
|
||||
* 4. how to service GetFrameChild call:
|
||||
* - first check for L1 keys (same template type) if any can be updated.
|
||||
* These UINodes remain in the render tree.
|
||||
* - if no L1 item, the look for L2 keys (same template type)
|
||||
*/
|
||||
void RepeatVirtualScrollCaches::InvalidateKeyAndTTypeCaches()
|
||||
{
|
||||
key4index_.clear();
|
||||
index4Key_.clear();
|
||||
ttype4index_.clear();
|
||||
index4ttype_.clear();
|
||||
|
||||
// request new index -> key and index -> ttype
|
||||
// only fetch keys for the active range
|
||||
// note the updated key -> index reverse lookup is used
|
||||
// to decide if a key is still used and its UINode
|
||||
// should be packed. If key is not in the reverse map then
|
||||
// its UINode is subject to update.
|
||||
//
|
||||
FetchMoreKeysTTypes(lastActiveRanges_[0].first, lastActiveRanges_[0].second);
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: scroll, try to update an existing UINode
|
||||
*
|
||||
* find an key / UINode in L2 and update the UINode to
|
||||
* render the data source item 'forIndex'.
|
||||
*/
|
||||
RefPtr<UINode> RepeatVirtualScrollCaches::UpdateFromL2(uint32_t forIndex)
|
||||
{
|
||||
const auto iterTType = ttype4index_.find(forIndex);
|
||||
if (iterTType == ttype4index_.end()) {
|
||||
TAG_LOGD(AceLogTag::ACE_REPEAT, "no ttype for index %{public}d", forIndex);
|
||||
return nullptr;
|
||||
}
|
||||
const auto& ttype = iterTType->second;
|
||||
const auto iterNewKey = key4index_.find(forIndex);
|
||||
if (iterNewKey == key4index_.end()) {
|
||||
TAG_LOGW(AceLogTag::ACE_REPEAT, "no key for index %{public}d", forIndex);
|
||||
return nullptr;
|
||||
}
|
||||
const std::string& forKey = iterNewKey->second;
|
||||
|
||||
const auto& oldKey = GetL2KeyToUpdate(ttype);
|
||||
if (!oldKey) {
|
||||
// no key for this ttype available to update
|
||||
TAG_LOGW(AceLogTag::ACE_REPEAT, "for index %{public}d, ttype %{public}s, no UINode found to update", forIndex,
|
||||
ttype.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// call TS to do the RepeatItem update
|
||||
onUpdateNode_(oldKey.value(), forIndex);
|
||||
|
||||
return HasUINodeBeenUpdated(ttype, oldKey.value(), forKey);
|
||||
}
|
||||
|
||||
RefPtr<UINode> RepeatVirtualScrollCaches::CreateNewNode(uint32_t forIndex)
|
||||
{
|
||||
// key key
|
||||
const auto iter = key4index_.find(forIndex);
|
||||
if (iter == key4index_.end()) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create node of %{public}d", forIndex);
|
||||
return nullptr;
|
||||
}
|
||||
const auto& forKey = iter->second;
|
||||
|
||||
// see if node already created, just for safety
|
||||
const auto nodeIter = node4key_.find(forKey);
|
||||
if (nodeIter != node4key_.end()) {
|
||||
// have a node for this key already, just return
|
||||
return nodeIter->second;
|
||||
}
|
||||
|
||||
// need to create a new node for key
|
||||
|
||||
// get ttype
|
||||
const auto ttypeIter = ttype4index_.find(forIndex);
|
||||
if (ttypeIter == ttype4index_.end()) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create %{public}d node due to type is missing", forIndex);
|
||||
return nullptr;
|
||||
}
|
||||
const auto& ttype = ttypeIter->second;
|
||||
|
||||
// swap the ViewStackProcessor instance for secondary while we run the item builder function
|
||||
// so that its results can easily be obtained from it, does not disturb main ViewStackProcessor
|
||||
NG::ScopedViewStackProcessor scopedViewStackProcessor;
|
||||
auto* viewStack = NG::ViewStackProcessor::GetInstance();
|
||||
|
||||
// call TS side
|
||||
onCreateNode_(forIndex);
|
||||
|
||||
const auto& node4Index = viewStack->Finish();
|
||||
|
||||
if (!node4Index) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT,
|
||||
"New Node create: For index %{public}d -> key %{public}s -> ttype %{public}s item builder FAILED to gen "
|
||||
"FrameNode. ERROR",
|
||||
forIndex, forKey.c_str(), ttype.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// add node to node4key4ttype_
|
||||
const auto node4KeyIter = node4key4ttype_.find(ttype);
|
||||
if (node4KeyIter != node4key4ttype_.end()) {
|
||||
node4KeyIter->second.emplace(forKey, node4Index);
|
||||
} else {
|
||||
std::unordered_map<std::string, RefPtr<UINode>> node4Key;
|
||||
node4Key[forKey] = node4Index;
|
||||
node4key4ttype_.emplace(ttype, std::move(node4Key));
|
||||
}
|
||||
|
||||
// add node to node4key_
|
||||
node4key_.emplace(forKey, node4Index);
|
||||
return node4Index;
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollCaches::ForEachL1IndexUINode(
|
||||
const std::function<void(uint32_t index, const RefPtr<UINode>& node)>& cbFunc)
|
||||
{
|
||||
for (const auto& key : activeNodeKeysInL1_) {
|
||||
const RefPtr<UINode>& node = node4key_[key];
|
||||
const auto& indexIter = index4Key_.find(key);
|
||||
if (indexIter == index4Key_.end()) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to get index for %{public}s key", key.c_str());
|
||||
continue;
|
||||
}
|
||||
cbFunc(indexIter->second, node);
|
||||
}
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollCaches::RecycleItemsByIndex(int32_t index)
|
||||
{
|
||||
auto keyIter = key4index_.find(index);
|
||||
if (keyIter != key4index_.end()) {
|
||||
activeNodeKeysInL1_.erase(keyIter->second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iterate over all entries of L1 and call function for each entry
|
||||
* if function returns true, entry is added to rebuild L1
|
||||
* cbFunc return true, key
|
||||
* cbFunc returns false drop from L1
|
||||
*/
|
||||
bool RepeatVirtualScrollCaches::RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc)
|
||||
{
|
||||
std::unordered_set<std::string> l1Copy;
|
||||
std::swap(l1Copy, activeNodeKeysInL1_);
|
||||
bool modified = false;
|
||||
for (const auto& key : l1Copy) {
|
||||
const auto& indexIter = index4Key_.find(key);
|
||||
if (indexIter == index4Key_.end()) {
|
||||
continue;
|
||||
}
|
||||
const auto& node = node4key_[key];
|
||||
int32_t index = indexIter->second;
|
||||
if (cbFunc(index, node)) {
|
||||
activeNodeKeysInL1_.emplace(key);
|
||||
} else {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollCaches::SetLastActiveRange(uint32_t from, uint32_t to)
|
||||
{
|
||||
lastActiveRanges_[1] = lastActiveRanges_[0];
|
||||
lastActiveRanges_[0] = { from, to };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Index of frameNode
|
||||
* return -1 if not find the frameNode
|
||||
*/
|
||||
int32_t RepeatVirtualScrollCaches::GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const
|
||||
{
|
||||
for (const auto& key : activeNodeKeysInL1_) {
|
||||
const auto nodeIter = node4key_.find(key);
|
||||
if (nodeIter == node4key_.end() || !nodeIter->second) {
|
||||
continue;
|
||||
}
|
||||
const auto& node = nodeIter->second->GetFrameChildByIndex(0, true);
|
||||
if (node != frameNode) {
|
||||
continue;
|
||||
}
|
||||
const auto& indexIter = index4Key_.find(key);
|
||||
if (indexIter == index4Key_.end()) {
|
||||
return -1;
|
||||
}
|
||||
return indexIter->second;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* intended scenario: scroll
|
||||
* servicing GetFrameChild, search for key that can be updated.
|
||||
*
|
||||
* return a key whose UINode can be updated
|
||||
* the key must not be in L1, i.e. activeNodeKeysInL1_
|
||||
* the given ttype must match the template type the UINode for this key
|
||||
* has been rendered for (this info is available from node4key4ttype_)
|
||||
*/
|
||||
std::optional<std::string> RepeatVirtualScrollCaches::GetL2KeyToUpdate(const std::string& ttype) const
|
||||
{
|
||||
const auto itNodes = node4key4ttype_.find(ttype);
|
||||
if (itNodes == node4key4ttype_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto& keys2UINode = itNodes->second;
|
||||
std::set<std::string, KeySorterClass> l2Keys = GetSortedL2KeysForTType(keys2UINode);
|
||||
auto keyIter = l2Keys.rbegin();
|
||||
if (keyIter == l2Keys.rend()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *keyIter;
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: UI rebuild following key invalidation by TS side
|
||||
* L1 includes keys that are no longer used, the linked UINodes
|
||||
* should be updated.
|
||||
*
|
||||
* This function checks all L1 keys (of active UINodes) if the key
|
||||
* can still be found from
|
||||
* (previously updated following invalidation) key -> index map and
|
||||
*
|
||||
*/
|
||||
std::optional<std::string> RepeatVirtualScrollCaches::GetL1KeyToUpdate(const std::string& ttype) const
|
||||
{
|
||||
for (const auto& keyIter : activeNodeKeysInL1_) {
|
||||
const std::string& key = keyIter;
|
||||
if (index4Key_.find(key) == index4Key_.end()) {
|
||||
// key is no longer used
|
||||
// check if key rendered the expected ttype
|
||||
const auto ttypeIter = node4key4ttype_.find(ttype);
|
||||
if (ttypeIter != node4key4ttype_.end()) {
|
||||
const std::unordered_map<std::string, RefPtr<UINode>>& node4Key = ttypeIter->second;
|
||||
if (node4Key.find(key) != node4Key.end()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: UINode of fromKey has been updated to render data for 'forKey'
|
||||
* the template type (ttype) remains unchanged
|
||||
* update node4key4ttype_ and node4key_ entries to use new key point to same UINode
|
||||
*/
|
||||
RefPtr<UINode> RepeatVirtualScrollCaches::HasUINodeBeenUpdated(
|
||||
const std::string& ttype, const std::string& fromKey, const std::string& forKey)
|
||||
{
|
||||
// 1. update fromKey -> forKey in node4key4ttype_
|
||||
const auto nodesIter = node4key4ttype_.find(ttype);
|
||||
if (nodesIter != node4key4ttype_.end()) {
|
||||
auto& node4key = nodesIter->second;
|
||||
auto iter = node4key.find(fromKey);
|
||||
if (iter != node4key.end()) {
|
||||
const auto& node = iter->second;
|
||||
node4key.erase(iter);
|
||||
node4key.emplace(forKey, node);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. update the key: fromKey to forKey in node4key_
|
||||
auto iter = node4key_.find(fromKey);
|
||||
if (iter != node4key_.end()) {
|
||||
const auto& node = iter->second;
|
||||
node4key_.erase(iter);
|
||||
node4key_.emplace(forKey, node);
|
||||
return node;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** scenario: keys cache has been updated
|
||||
*
|
||||
* find which keys in key -> UINode map are no longer used
|
||||
* returned set entries are pairs:
|
||||
* pair.first: is this key a L1 item,
|
||||
* pair.second: key
|
||||
*/
|
||||
void RepeatVirtualScrollCaches::FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const
|
||||
{
|
||||
for (const auto& iter : node4key_) {
|
||||
const std::string key = iter.first;
|
||||
const auto indexIter = index4Key_.find(key);
|
||||
if (indexIter == index4Key_.end()) {
|
||||
// key is no longer used
|
||||
// is it in L1 ?
|
||||
const bool keyInL1 = (index4Key_.find(key) != index4Key_.end());
|
||||
result.emplace(keyInL1, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: in idle process , following GetChildren()
|
||||
* execute purge()
|
||||
*
|
||||
* enforce L2 cacheCount for each ttype
|
||||
* logical L2 cache is map key->UINode map filtered out L1 keys
|
||||
* purge by by deleting UINodes, delete their entry from
|
||||
* node4key4ttype_ and node4key_
|
||||
*
|
||||
*/
|
||||
bool RepeatVirtualScrollCaches::Purge()
|
||||
{
|
||||
bool didMakeChanges = false;
|
||||
for (auto& itTType : node4key4ttype_) {
|
||||
const auto& ttype = itTType.first;
|
||||
auto& uiNode4Key = itTType.second;
|
||||
const uint32_t cacheCount = cacheCountL24ttype_[ttype];
|
||||
std::set<std::string, KeySorterClass> l2Keys = GetSortedL2KeysForTType(uiNode4Key);
|
||||
|
||||
// l2_keys is sorted by increasing distance from lastActiveRange
|
||||
// will drop those keys and their UINodes with largest distance
|
||||
// improvement idea: in addition to distance from range use the
|
||||
// scroll direction for selecting these keys
|
||||
auto safeDist = std::min(cacheCount, static_cast<uint32_t>(l2Keys.size()));
|
||||
auto itL2Key = std::next(l2Keys.begin(), safeDist);
|
||||
|
||||
while (itL2Key != l2Keys.end()) {
|
||||
// delete remaining keys
|
||||
uiNode4Key.erase(*itL2Key);
|
||||
node4key_.erase(*itL2Key);
|
||||
// check out transition case.
|
||||
itL2Key++;
|
||||
didMakeChanges = true;
|
||||
}
|
||||
}
|
||||
return didMakeChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* given key return the index position (reverse lookup)
|
||||
* invalidated keys (after Repeat rerender/ data change)
|
||||
* are keys for which no index exists anymore,
|
||||
* method returns int max value for these.
|
||||
* int max value causes that distance from active range is max
|
||||
* these keys will be selected for update first.
|
||||
*/
|
||||
uint32_t RepeatVirtualScrollCaches::GetIndex4Key(const std::string& key) const
|
||||
{
|
||||
auto it = index4Key_.find(key);
|
||||
if (it != index4Key_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
// key is no longer used
|
||||
// return max uint32_t value
|
||||
return std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
|
||||
/**
|
||||
* for given index return distance from active range,
|
||||
* or 0 if within active range
|
||||
* distance is int max for invalidated keys
|
||||
*
|
||||
* instead of just using previous active range
|
||||
* use the ranges informed by previous two SetActiveRaneg calls.
|
||||
* Obtain the scroll direction and use it to calc the distance.
|
||||
* Items left 'behind' when scrolling get larger distance and are more
|
||||
* likely updated or purged from L2 cache.
|
||||
*/
|
||||
int32_t RepeatVirtualScrollCaches::GetDistanceFromRange(uint32_t index) const
|
||||
{
|
||||
int32_t last[2] = { lastActiveRanges_[0].first, lastActiveRanges_[0].second };
|
||||
int32_t prev[2] = { lastActiveRanges_[1].first, lastActiveRanges_[1].second };
|
||||
|
||||
// this is experimental optimization, based on scrolling detection
|
||||
// here we assume this is a scrolling, if previous range and last range has
|
||||
// not empty intersection
|
||||
|
||||
// if scrolling up, return 0 for any lower index
|
||||
if (last[0] < prev[0] && prev[0] < last[1]) {
|
||||
if (index < last[0]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// if scrolling down, return 0 for any greater index
|
||||
if (last[0] < prev[1] && prev[1] < last[1]) {
|
||||
if (index > last[1]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// this is not scrolling
|
||||
if (index < last[0]) {
|
||||
return last[0] - index;
|
||||
}
|
||||
|
||||
if (index > last[1]) {
|
||||
return index - last[1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: find L1 key that should be updated
|
||||
* choose the key whose index is the furthest away from active range
|
||||
* given two keys compare their distFromRange
|
||||
*/
|
||||
bool RepeatVirtualScrollCaches::CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const
|
||||
{
|
||||
return GetDistanceFromRange(GetIndex4Key(key1)) < GetDistanceFromRange(GetIndex4Key(key2));
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario: find L1 key(s) that should be updated
|
||||
*
|
||||
* return a sorted set of L2 keys, sorted by increasing distance from active range
|
||||
*/
|
||||
std::set<std::string, KeySorterClass> RepeatVirtualScrollCaches::GetSortedL2KeysForTType(
|
||||
const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const
|
||||
{
|
||||
KeySorterClass sorter(this);
|
||||
std::set<std::string, KeySorterClass> l2Keys(sorter);
|
||||
for (const auto& itUINode : uiNode4Key) {
|
||||
const auto& key = itUINode.first;
|
||||
if (activeNodeKeysInL1_.find(key) == activeNodeKeysInL1_.end()) {
|
||||
// key is not in L1
|
||||
// add to l2Keys
|
||||
l2Keys.emplace(key);
|
||||
}
|
||||
}
|
||||
return l2Keys;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpL1() const
|
||||
{
|
||||
std::string result = "activeNodeKeysInL1_: size=" + std::to_string(activeNodeKeysInL1_.size()) + "--------------\n";
|
||||
for (const auto& it : activeNodeKeysInL1_) {
|
||||
const std::string& key = it;
|
||||
result += "\", node: " + DumpUINodeWithKey(key) + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpL2() const
|
||||
{
|
||||
std::set<std::string, KeySorterClass> l2KeyResult = GetSortedL2KeysForTType(node4key_);
|
||||
|
||||
std::string result =
|
||||
"l2_keys (sorted by distance): size=" + std::to_string(l2KeyResult.size()) + "--------------\n";
|
||||
for (const auto& it : l2KeyResult) {
|
||||
result += " \"" + it + "\", node: " + DumpUINodeWithKey(it) + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpKey4Index() const
|
||||
{
|
||||
std::string result = "key4index_: size=" + std::to_string(key4index_.size()) + "--------------\n";
|
||||
for (const auto& it : key4index_) {
|
||||
result += " " + std::to_string(it.first) + " -> \"" + it.second +
|
||||
"\", node: " + DumpUINodeWithKey(it.second) + "\n";
|
||||
}
|
||||
result += "index4Key_: size=" + std::to_string(index4Key_.size()) + "--------------\n";
|
||||
for (const auto& it : index4Key_) {
|
||||
result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpTType4Index() const
|
||||
{
|
||||
std::string result = "ttype4index_: size=" + std::to_string(ttype4index_.size()) + "--------------\n";
|
||||
for (const auto& it : ttype4index_) {
|
||||
result += " " + std::to_string(it.first) + " -> \"" + it.second + "\n";
|
||||
}
|
||||
result += "index4ttype_: size=" + std::to_string(index4ttype_.size()) + "--------------\n";
|
||||
for (const auto& it : index4ttype_) {
|
||||
result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpUINode4Key4TType() const
|
||||
{
|
||||
std::string result = "node4key_: size=" + std::to_string(node4key_.size()) + "--------------\n";
|
||||
for (const auto& it : node4key_) {
|
||||
result += " \"" + it.first + "\" -> node: " + it.second->GetTag() + "(" + std::to_string(it.second->GetId()) +
|
||||
") \n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpUINode4Key() const
|
||||
{
|
||||
std::string result = "node4key4ttype_: size=" + std::to_string(node4key4ttype_.size()) + "--------------\n";
|
||||
for (const auto& ttypeIter : node4key4ttype_) {
|
||||
const auto& ttype = ttypeIter.first;
|
||||
const auto& node4key = ttypeIter.second;
|
||||
result += "ttype " + ttype + ": node4key: size=" + std::to_string(node4key.size()) + "--------------\n";
|
||||
for (const auto& it : node4key) {
|
||||
result += " \"" + it.first + "\" -> node: " + it.second->GetTag() + "(" +
|
||||
std::to_string(it.second->GetId()) + ") \n";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpUINodeWithKey(const std::string& key) const
|
||||
{
|
||||
const auto it = node4key_.find(key);
|
||||
return (it == node4key_.end()) ? "no UINode on file"
|
||||
: it->second->GetTag() + "(" + std::to_string(it->second->GetId()) + ")";
|
||||
}
|
||||
|
||||
std::string RepeatVirtualScrollCaches::DumpUINode(const RefPtr<UINode>& node) const
|
||||
{
|
||||
return (node == nullptr) ? "UINode nullptr" : node->GetTag() + "(" + std::to_string(node->GetId()) + ")";
|
||||
}
|
||||
} // namespace OHOS::Ace::NG
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "core/components_ng/base/ui_node.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
// custom sorting for std::set only works with struct
|
||||
// with operator() inside
|
||||
class RepeatVirtualScrollCaches;
|
||||
struct KeySorterClass {
|
||||
const RepeatVirtualScrollCaches* virtualScroll_;
|
||||
|
||||
explicit KeySorterClass(const RepeatVirtualScrollCaches* virtualScroll) : virtualScroll_(virtualScroll) {}
|
||||
bool operator()(const std::string& left, const std::string& right) const;
|
||||
};
|
||||
|
||||
class RepeatVirtualScrollCaches {
|
||||
friend struct KeySorterClass;
|
||||
|
||||
public:
|
||||
RepeatVirtualScrollCaches(const std::map<std::string, uint32_t>& cacheCountL24ttype,
|
||||
const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
|
||||
|
||||
/** scenario:
|
||||
* Repeat gets updated due to data change.
|
||||
* 1. TS calls RepeatVirtualScrollNode,
|
||||
* then calls this function.
|
||||
* 2. RepeatVirtualScrollNode requests layout to rebuild the UI
|
||||
* 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
|
||||
* 4. how to service GetFrameChild call:
|
||||
* - first check for L1 keys (same template type) if any can be updated.
|
||||
* These UINodes remain in the render tree.
|
||||
* - if no L1 item, the look for L2 keys (same template type)
|
||||
*/
|
||||
void InvalidateKeyAndTTypeCaches();
|
||||
|
||||
/**
|
||||
* scenario: scroll, try to update an existing UINode
|
||||
*
|
||||
* find an key / UINode in L2 and update the UINode to
|
||||
* render the data source item 'forIndex'.
|
||||
*/
|
||||
RefPtr<UINode> UpdateFromL2(uint32_t forIndex);
|
||||
|
||||
/**
|
||||
* request TS to create a new node for given index / key/
|
||||
*/
|
||||
RefPtr<UINode> CreateNewNode(uint32_t forIndex);
|
||||
|
||||
// iterate over L1 keys, not allowed to modify L1
|
||||
void ForEachL1IndexUINode(const std::function<void(uint32_t index, const RefPtr<UINode>& node)>& cbFunc);
|
||||
|
||||
void RecycleItemsByIndex(int32_t index);
|
||||
|
||||
/**
|
||||
* for given index get key
|
||||
* fetch from TS if not in cache
|
||||
* return false if index out of range
|
||||
*/
|
||||
std::optional<std::string> GetKey4Index(uint32_t index);
|
||||
|
||||
/**
|
||||
* iterate over all entries of L1 and call function for each entry
|
||||
* if function returns true, entry is added to rebuild L1, otherwise it is moved to L2
|
||||
* cbFunc return true, [index, key] pair stays in L1 (index remains unchanged)
|
||||
* cbFunc returns false, enqueue key in L2
|
||||
*/
|
||||
bool RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc);
|
||||
|
||||
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const;
|
||||
|
||||
/**
|
||||
* scenario: in idle process , following GetChildren()
|
||||
*
|
||||
* enforce L2 cacheCount for each ttype
|
||||
* by deleting UINodes, delete their entry from
|
||||
* node4key4ttype_ and node4key_
|
||||
* any other processing steps needed before UINode
|
||||
* tree can be deleted
|
||||
*/
|
||||
bool Purge();
|
||||
|
||||
/**
|
||||
* return the UINode for given index
|
||||
* bool return indicates if in L1
|
||||
*
|
||||
* resolve index -> key -> UINode
|
||||
*
|
||||
*/
|
||||
RefPtr<UINode> GetNode4Index(uint32_t forIndex) const;
|
||||
|
||||
void AddKeyToL1(const std::string& key)
|
||||
{
|
||||
activeNodeKeysInL1_.emplace(key);
|
||||
}
|
||||
|
||||
bool IsInL1Cache(const std::string& key) const
|
||||
{
|
||||
return activeNodeKeysInL1_.find(key) != activeNodeKeysInL1_.end();
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, RefPtr<UINode>>& GetAllNodes() const
|
||||
{
|
||||
return node4key_;
|
||||
}
|
||||
|
||||
/**
|
||||
* memorize last active range(s)
|
||||
*/
|
||||
void SetLastActiveRange(uint32_t from, uint32_t to);
|
||||
|
||||
// formatting internal structures to string for debug output
|
||||
// and possibly in modified form for DFX in the future
|
||||
std::string DumpL1() const;
|
||||
std::string DumpL2() const;
|
||||
std::string DumpKey4Index() const;
|
||||
std::string DumpTType4Index() const;
|
||||
std::string DumpUINode4Key4TType() const;
|
||||
std::string DumpUINode4Key() const;
|
||||
|
||||
std::string DumpUINodeWithKey(const std::string& key) const;
|
||||
std::string DumpUINode(const RefPtr<UINode>& node) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* intended scenario: scroll
|
||||
* servicing GetFrameChild, search for key that can be updated.
|
||||
*
|
||||
* return a key whose UINode can be updated
|
||||
* the key must not be in L1, i.e. activeNodeKeysInL1_
|
||||
* the given ttype must match the template type the UINode for this key
|
||||
* has been rendered for (this info is available from node4key4ttype_)
|
||||
*/
|
||||
std::optional<std::string> GetL2KeyToUpdate(const std::string& ttype) const;
|
||||
|
||||
/**
|
||||
* scenario: UI rebuild following key invalidation by TS side
|
||||
* L1 includes keys that are no longer used, the linked UINodes
|
||||
* should be updated.
|
||||
*
|
||||
* This function checks all L1 keys (of active UINodes) if the key
|
||||
* can still be found from
|
||||
* (previously updated following invalidation) key -> index map and
|
||||
*
|
||||
*/
|
||||
std::optional<std::string> GetL1KeyToUpdate(const std::string& ttype) const;
|
||||
|
||||
/**
|
||||
* scenario: UINode of fromKey has been updated to render data for 'forKey'
|
||||
* the template type (ttype) remains unchanged
|
||||
* update node4key4ttype_ and node4key_ entries to use new key point to same UINode
|
||||
*/
|
||||
RefPtr<UINode> HasUINodeBeenUpdated(
|
||||
const std::string& ttype, const std::string& fromKey, const std::string& forKey);
|
||||
|
||||
/** scenario: keys cache has been updated
|
||||
*
|
||||
* find which keys in key -> UINode map are no longer used
|
||||
* returned set entries are pairs:
|
||||
* pair.first: is this key a L1 item,
|
||||
* pair.second: key
|
||||
*/
|
||||
void FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const;
|
||||
|
||||
/**
|
||||
* given key return the index position (reverse lookup)
|
||||
* invalidated keys (after Repeat rerender/ data change)
|
||||
* are keys for which no index exists anymore,
|
||||
* method returns int max value for these.
|
||||
* int max value causes that distance from active range is max
|
||||
* these keys will be selected for update first.
|
||||
*/
|
||||
uint32_t GetIndex4Key(const std::string& key) const;
|
||||
|
||||
/**
|
||||
* for given index return distance from active range,
|
||||
* or 0 if within active range
|
||||
* distance is int max for invalidated keys
|
||||
*/
|
||||
int32_t GetDistanceFromRange(uint32_t index) const;
|
||||
/**
|
||||
* scenario: find L1 key that should be updated
|
||||
* choose the key whose index is the furthest away from active range
|
||||
* given two keys compare their distFromRange
|
||||
*/
|
||||
bool CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const;
|
||||
|
||||
std::set<std::string, KeySorterClass> GetSortedL2KeysForTType(
|
||||
const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const;
|
||||
|
||||
/**
|
||||
* get more index -> key and index -> ttype from TS side
|
||||
*/
|
||||
bool FetchMoreKeysTTypes(uint32_t from, uint32_t to);
|
||||
|
||||
// Map ttype -> cacheSize. Each ttype incl default has own L2 size
|
||||
std::map<std::string, uint32_t> cacheCountL24ttype_;
|
||||
|
||||
// request TS to create new sub-tree for given index or update existing
|
||||
// update subtree cached for (old) index
|
||||
// API might need to change to tell which old item to update
|
||||
std::function<void(uint32_t)> onCreateNode_;
|
||||
std::function<void(const std::string&, uint32_t)> onUpdateNode_;
|
||||
|
||||
// get index -> key for given range
|
||||
// resulting list starts with 'from' but might end before 'to' if Array shorter
|
||||
std::function<std::list<std::string>(uint32_t, uint32_t)> onGetTypes4Range_;
|
||||
|
||||
// get index -> ttype for given range
|
||||
// resulting list starts with 'from' but might end before 'to' if Array shorter
|
||||
std::function<std::list<std::string>(uint32_t, uint32_t)> onGetKeys4Range_;
|
||||
|
||||
// memorize active ranges of past 2 (last, prev)
|
||||
// SetActiveRange calls and use to calc scroll direction
|
||||
std::pair<uint32_t, uint32_t> lastActiveRanges_[2] = { { 0, 0 }, { 0, 0 } };
|
||||
|
||||
// keys of active nodes, UINodes must be on the UI tree,
|
||||
// this list is also known as L1
|
||||
// all keys not in this set are in "L2"
|
||||
std::unordered_set<std::string> activeNodeKeysInL1_;
|
||||
|
||||
// L1
|
||||
// index -> key and reverse
|
||||
// lazy request from TS side can be invalidated
|
||||
std::unordered_map<uint32_t, std::string> key4index_;
|
||||
std::unordered_map<std::string, uint32_t> index4Key_;
|
||||
|
||||
// index -> ttype
|
||||
// lazy request from TS side can be invalidated
|
||||
std::unordered_map<uint32_t, std::string> ttype4index_;
|
||||
std::unordered_map<std::string, uint32_t> index4ttype_;
|
||||
|
||||
// Map ttype -> Map key -> UINode
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, RefPtr<UINode>>> node4key4ttype_;
|
||||
|
||||
// Map Map key -> UINode
|
||||
std::unordered_map<std::string, RefPtr<UINode>> node4key_;
|
||||
}; // class NodeCache
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
||||
|
||||
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_SYNTAX_REPEAT_MODEL_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_SYNTAX_REPEAT_MODEL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/utils/macros.h"
|
||||
|
||||
namespace OHOS::Ace {
|
||||
|
||||
class ACE_EXPORT RepeatVirtualScrollModel {
|
||||
public:
|
||||
RepeatVirtualScrollModel() = default;
|
||||
virtual ~RepeatVirtualScrollModel() = default;
|
||||
|
||||
static RepeatVirtualScrollModel* GetInstance();
|
||||
virtual void Create(
|
||||
uint32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap,
|
||||
const std::function<void(uint32_t forIndex)>& onCreateNode,
|
||||
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range) = 0;
|
||||
virtual void InvalidateKeyCache(uint32_t totalCount) = 0;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<RepeatVirtualScrollModel> instance_;
|
||||
};
|
||||
} // namespace OHOS::Ace
|
||||
|
||||
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 "core/components_ng/syntax/repeat_virtual_scroll_model_ng.h"
|
||||
|
||||
#include "base/utils/utils.h"
|
||||
#include "core/common/container.h"
|
||||
#include "core/components_ng/base/view_stack_processor.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
|
||||
#include "core/components_ng/syntax/syntax_item.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
void RepeatVirtualScrollModelNG::Create(
|
||||
uint32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap,
|
||||
const std::function<void(uint32_t forIndex)>& onCreateNode,
|
||||
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range)
|
||||
{
|
||||
ACE_SCOPED_TRACE("RepeatVirtualScrollModelNG::Create");
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto nodeId = stack->ClaimNodeId();
|
||||
auto repeatNode = RepeatVirtualScrollNode::GetOrCreateRepeatNode(
|
||||
nodeId,
|
||||
totalCount,
|
||||
templateCachedCountMap,
|
||||
onCreateNode,
|
||||
onUpdateNode,
|
||||
onGetKeys4Range,
|
||||
onGetTypes4Range
|
||||
);
|
||||
stack->Push(repeatNode);
|
||||
stack->PopContainer();
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollModelNG::InvalidateKeyCache(uint32_t totalCount)
|
||||
{
|
||||
auto* stack = ViewStackProcessor::GetInstance();
|
||||
auto nodeId = stack->ClaimNodeId();
|
||||
auto repeatNode = ElementRegister::GetInstance()->GetSpecificItemById<RepeatVirtualScrollNode>(nodeId);
|
||||
CHECK_NULL_VOID(repeatNode);
|
||||
repeatNode->UpdateTotalCount(totalCount);
|
||||
repeatNode->InvalidateKeyCache();
|
||||
}
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/utils/macros.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_model.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
class ACE_EXPORT RepeatVirtualScrollModelNG : public RepeatVirtualScrollModel {
|
||||
public:
|
||||
void Create(
|
||||
uint32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap,
|
||||
const std::function<void(uint32_t forIndex)>& onCreateNode,
|
||||
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range) override;
|
||||
|
||||
void InvalidateKeyCache(uint32_t totalCount) override;
|
||||
};
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
||||
|
||||
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H
|
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2024 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 "core/components_ng/syntax/repeat_virtual_scroll_node.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "base/log/ace_trace.h"
|
||||
#include "base/log/log_wrapper.h"
|
||||
#include "core/components_ng/base/frame_node.h"
|
||||
#include "core/components_ng/pattern/list/list_item_pattern.h"
|
||||
#include "core/pipeline/base/element_register.h"
|
||||
#include "core/pipeline_ng/pipeline_context.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
|
||||
// REPEAT
|
||||
RefPtr<RepeatVirtualScrollNode> RepeatVirtualScrollNode::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap, const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
|
||||
{
|
||||
auto node = ElementRegister::GetInstance()->GetSpecificItemById<RepeatVirtualScrollNode>(nodeId);
|
||||
if (node) {
|
||||
node->UpdateTotalCount(totalCount);
|
||||
return node;
|
||||
}
|
||||
node = MakeRefPtr<RepeatVirtualScrollNode>(
|
||||
nodeId, totalCount, templateCachedCountMap, onCreateNode, onUpdateNode, onGetKeys4Range, onGetTypes4Range);
|
||||
|
||||
ElementRegister::GetInstance()->AddUINode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
RepeatVirtualScrollNode::RepeatVirtualScrollNode(int32_t nodeId, int32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap, const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
|
||||
: ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount),
|
||||
caches_(templateCachedCountMap, onCreateNode, onUpdateNode, onGetKeys4Range, onGetTypes4Range),
|
||||
postUpdateTaskHasBeenScheduled_(false)
|
||||
{
|
||||
// no preduct task scheduled
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
|
||||
{
|
||||
TAG_LOGD(AceLogTag::ACE_REPEAT,
|
||||
"DoSetActiveChildRange: nodeId: %{public}d: start: %{public}d, end: %{public}d, cacheStart: %{public}d, "
|
||||
"cacheEnd: %{public}d",
|
||||
GetId(), start, end, cacheStart, cacheEnd);
|
||||
|
||||
// memorize active range
|
||||
caches_.SetLastActiveRange(static_cast<uint32_t>(start), static_cast<uint32_t>(end));
|
||||
|
||||
bool needSync =
|
||||
caches_.RebuildL1([start, end, cacheStart, cacheEnd, this](int32_t index, const RefPtr<UINode>& node) -> bool {
|
||||
if (node == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Get the first child of FrameNode.
|
||||
auto frameNode = AceType::DynamicCast<FrameNode>(node->GetFrameChildByIndex(0, true));
|
||||
if (!frameNode) {
|
||||
return false;
|
||||
}
|
||||
// DoSetActiveChildRange uses int32_t , while other functions use uint32_t
|
||||
// need to convert
|
||||
if ((start <= index) && (index <= end)) {
|
||||
frameNode->SetActive(true);
|
||||
} else {
|
||||
frameNode->SetActive(false);
|
||||
}
|
||||
if ((start - cacheStart <= index) && (index <= end + cacheEnd)) {
|
||||
return true;
|
||||
}
|
||||
// active node moved into L2 cached.
|
||||
// check transition flag.
|
||||
if (node->OnRemoveFromParent(true)) {
|
||||
// OnRemoveFromParent returns true means the child can be removed from tree immediately.
|
||||
RemoveDisappearingChild(node);
|
||||
} else {
|
||||
// else move child into disappearing children, skip syncing render tree
|
||||
AddDisappearingChild(node);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// TODO see if loop leads to any changes to active states
|
||||
// only in that case do the re-sync , re-assembly of children
|
||||
if (needSync) {
|
||||
UINode::MarkNeedSyncRenderTree(false);
|
||||
children_.clear();
|
||||
// re-assemble children_
|
||||
PostIdleTask();
|
||||
}
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::InvalidateKeyCache()
|
||||
{
|
||||
// empty the cache index -> key
|
||||
// C++ will need to ask all new keys from JS side
|
||||
caches_.InvalidateKeyAndTTypeCaches();
|
||||
children_.clear();
|
||||
|
||||
auto frameNode = GetParentFrameNode();
|
||||
if (frameNode) {
|
||||
frameNode->ChildrenUpdatedFrom(0);
|
||||
}
|
||||
|
||||
MarkNeedSyncRenderTree(true);
|
||||
MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE_SELF_AND_PARENT | PROPERTY_UPDATE_BY_CHILD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* a index -> key -> node does not exists, caller has verified before calling this function
|
||||
*
|
||||
* Ask TS to update a Node, if possible
|
||||
* If no suitable node, request to crete a new node
|
||||
*/
|
||||
RefPtr<UINode> RepeatVirtualScrollNode::CreateOrUpdateFrameChild4Index(uint32_t forIndex, const std::string& forKey)
|
||||
{
|
||||
RefPtr<UINode> node4Index = caches_.UpdateFromL2(forIndex);
|
||||
if (!node4Index) {
|
||||
return node4Index;
|
||||
}
|
||||
|
||||
return caches_.CreateNewNode(forIndex);
|
||||
}
|
||||
|
||||
// FIXME added
|
||||
// index N-th item
|
||||
// needBuild: true - if found in cache, then return, if not in cache then return newly build
|
||||
// false: - if found in cache, then return, if not found in cache then return nullptr
|
||||
// isCache: true indicates prebuild item (only used by List/Grid/Waterflow, this item should go to L2 cache,
|
||||
// do not add to the tree,
|
||||
// isCache==false this item is for display or near display area
|
||||
// addToRenderTree: true - set it to active state, call SetActive
|
||||
RefPtr<UINode> RepeatVirtualScrollNode::GetFrameChildByIndex(
|
||||
uint32_t index, bool needBuild, bool isCache, bool addToRenderTree)
|
||||
{
|
||||
// It will get or create new key.
|
||||
const auto& key = caches_.GetKey4Index(index);
|
||||
if (!key) {
|
||||
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to get key for %{public}d", index);
|
||||
return nullptr;
|
||||
}
|
||||
// search if index -> key -> Node exist
|
||||
// pair.first tells of key is in L1
|
||||
auto node4Index = GetFromCaches(index);
|
||||
if (!node4Index && !needBuild) {
|
||||
TAG_LOGD(AceLogTag::ACE_REPEAT,
|
||||
"index %{public}d not in caches && needBuild==false, GetFrameChildByIndex returns nullptr .", index);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// node4Index needs to be created or updated on JS side
|
||||
if (!node4Index) {
|
||||
// TS to either make new or update existing nodes
|
||||
node4Index = CreateOrUpdateFrameChild4Index(index, key.value());
|
||||
|
||||
if (!node4Index) {
|
||||
TAG_LOGW(AceLogTag::ACE_REPEAT, "index %{public}d not in caches and failed to build.", index);
|
||||
return nullptr;
|
||||
}
|
||||
// move item to L1 cache.
|
||||
caches_.AddKeyToL1(key.value());
|
||||
} else {
|
||||
// TODO need update existed node4Index with new index.
|
||||
}
|
||||
|
||||
// if the item was in L2 cache, remove it from there
|
||||
|
||||
if (isActive_) {
|
||||
node4Index->SetJSViewActive(true);
|
||||
}
|
||||
|
||||
if (addToRenderTree && !isCache) {
|
||||
node4Index->SetActive(true);
|
||||
}
|
||||
if (node4Index->GetDepth() != GetDepth() + 1) {
|
||||
node4Index->SetDepth(GetDepth() + 1);
|
||||
}
|
||||
// attach to repeat node and pass context to it.
|
||||
node4Index->SetParent(WeakClaim(this));
|
||||
if (IsOnMainTree()) {
|
||||
node4Index->AttachToMainTree(false, GetContext());
|
||||
}
|
||||
|
||||
MarkNeedSyncRenderTree();
|
||||
children_.clear();
|
||||
// re-assemble children_
|
||||
PostIdleTask();
|
||||
|
||||
auto childNode = node4Index->GetFrameChildByIndex(0, needBuild);
|
||||
if (onMoveEvent_) {
|
||||
InitDragManager(AceType::DynamicCast<FrameNode>(childNode));
|
||||
}
|
||||
|
||||
return childNode;
|
||||
}
|
||||
|
||||
int32_t RepeatVirtualScrollNode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool /*isExpanded*/)
|
||||
{
|
||||
return caches_.GetFrameNodeIndex(node);
|
||||
}
|
||||
|
||||
const std::list<RefPtr<UINode>>& RepeatVirtualScrollNode::GetChildren() const
|
||||
{
|
||||
if (!children_.empty()) {
|
||||
return children_;
|
||||
}
|
||||
|
||||
// can not modify l1_cache while iterating
|
||||
// GetChildren is overloaded, can not change it to non-const
|
||||
// need to order the child.
|
||||
std::map<int32_t, RefPtr<UINode>> children;
|
||||
caches_.ForEachL1IndexUINode(
|
||||
[&children](int32_t index, const RefPtr<UINode>& node) -> void { children.emplace(index, node); });
|
||||
for (const auto& [index, child] : children) {
|
||||
children_.emplace_back(child);
|
||||
}
|
||||
return children_;
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::RecycleItems(int32_t from, int32_t to)
|
||||
{
|
||||
offscreenItems_.from = from;
|
||||
offscreenItems_.to = to;
|
||||
for (auto i = from; i < to; i++) {
|
||||
if (i >= startIndex_ && i < startIndex_ + totalCount_) {
|
||||
caches_.RecycleItemsByIndex(i - startIndex_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::SetNodeIndexOffset(int32_t start, int32_t /*count*/)
|
||||
{
|
||||
startIndex_ = start;
|
||||
}
|
||||
|
||||
int32_t RepeatVirtualScrollNode::FrameCount() const
|
||||
{
|
||||
return totalCount_;
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::PostIdleTask()
|
||||
{
|
||||
if (postUpdateTaskHasBeenScheduled_) {
|
||||
return;
|
||||
}
|
||||
postUpdateTaskHasBeenScheduled_ = true;
|
||||
auto* context = GetContext();
|
||||
CHECK_NULL_VOID(context);
|
||||
|
||||
context->AddPredictTask([weak = AceType::WeakClaim(this)](int64_t /*deadline*/, bool /*canUseLongPredictTask*/) {
|
||||
ACE_SCOPED_TRACE("RepeatVirtualScrollNode predict");
|
||||
auto node = weak.Upgrade();
|
||||
CHECK_NULL_VOID(node);
|
||||
node->postUpdateTaskHasBeenScheduled_ = false;
|
||||
node->GetChildren();
|
||||
node->caches_.Purge();
|
||||
});
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::OnConfigurationUpdate(const ConfigurationChange& configurationChange)
|
||||
{
|
||||
if ((configurationChange.colorModeUpdate || configurationChange.fontUpdate)) {
|
||||
const auto& children = caches_.GetAllNodes();
|
||||
for (const auto& [key, child] : children) {
|
||||
if (child) {
|
||||
child->UpdateConfigurationUpdate(configurationChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME Which of the following methods are actually needed ?
|
||||
|
||||
void RepeatVirtualScrollNode::SetOnMove(std::function<void(int32_t, int32_t)>&& onMove)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
// FOREAch
|
||||
void RepeatVirtualScrollNode::MoveData(int32_t from, int32_t to)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
RefPtr<FrameNode> RepeatVirtualScrollNode::GetFrameNode(int32_t index)
|
||||
{
|
||||
return AceType::DynamicCast<FrameNode>(GetFrameChildByIndex(index, false, false));
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::InitDragManager(const RefPtr<UINode>& child)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
void RepeatVirtualScrollNode::InitAllChildrenDragManager(bool init)
|
||||
{
|
||||
// To do
|
||||
}
|
||||
|
||||
} // namespace OHOS::Ace::NG
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_NODE_H
|
||||
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_NODE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/referenced.h"
|
||||
#include "base/utils/macros.h"
|
||||
#include "core/components_ng/base/ui_node.h"
|
||||
#include "core/components_ng/syntax/for_each_base_node.h"
|
||||
#include "core/components_ng/syntax/repeat_virtual_scroll_caches.h"
|
||||
|
||||
namespace OHOS::Ace::NG {
|
||||
struct OffscreenItems {
|
||||
int32_t from = -1;
|
||||
int32_t to = -1;
|
||||
};
|
||||
|
||||
class ACE_EXPORT RepeatVirtualScrollNode : public ForEachBaseNode {
|
||||
DECLARE_ACE_TYPE(RepeatVirtualScrollNode, ForEachBaseNode);
|
||||
|
||||
public:
|
||||
static RefPtr<RepeatVirtualScrollNode> GetOrCreateRepeatNode(int32_t nodeId,
|
||||
uint32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCachedCountMap,
|
||||
const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
|
||||
|
||||
RepeatVirtualScrollNode(int32_t nodeId, int32_t totalCount,
|
||||
const std::map<std::string, uint32_t>& templateCacheCountMap, const std::function<void(uint32_t)>& onCreateNode,
|
||||
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
|
||||
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
|
||||
|
||||
~RepeatVirtualScrollNode() override = default;
|
||||
|
||||
void UpdateTotalCount(uint32_t totalCount)
|
||||
{
|
||||
totalCount_ = totalCount;
|
||||
}
|
||||
|
||||
// Number of children that Repeat can product
|
||||
// returns TotalCount
|
||||
int32_t FrameCount() const override;
|
||||
|
||||
// called from TS upon Repeat rerender
|
||||
void InvalidateKeyCache();
|
||||
|
||||
/**
|
||||
* GetChildren re-assembles children_ and cleanup the L1 cache
|
||||
* active items remain in L1 cache and are added to RepeatVirtualScroll.children_
|
||||
* inactive items are moved from L1 to L2 cache, not added to children_
|
||||
* function returns children_
|
||||
* function runs as part of idle task
|
||||
*/
|
||||
const std::list<RefPtr<UINode>>& GetChildren() const override;
|
||||
|
||||
/**
|
||||
* update range of Active items inside Repeat:
|
||||
* iterative L1 cache entries
|
||||
* those items with index in range [ start ... end ] are marked active
|
||||
* those out of range marked inactive.
|
||||
* retests idle task
|
||||
*/
|
||||
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
|
||||
|
||||
// largely unknown when it is expected to be called
|
||||
// meant to inform which items with index [ from .. to ] can be recycled / updated
|
||||
void RecycleItems(int32_t from, int32_t to) override;
|
||||
|
||||
// Called by parent generate frame child.
|
||||
void SetNodeIndexOffset(int32_t start, int32_t count) override;
|
||||
|
||||
/** Called by Layout to request ListItem and child subtree
|
||||
for given index
|
||||
either returns existing item for index from L1 or L2 cache, or gets a new item created
|
||||
update of L2 cache item to new index)
|
||||
result is in L1 cache if isCache is false, and L2 cache if isCache is true.
|
||||
|
||||
meaning of parameters
|
||||
needBuild: true - if found in cache, then return, if not in cache then return newly build
|
||||
false: - if found in cache, then return, if not found in cache then return nullptr
|
||||
isCache: true indicates prebuild item (only used by List/Grid/Waterflow, this item should go to L2 cache,
|
||||
do not add to the tree,
|
||||
isCaxche==false this item is for display or near display area
|
||||
addToRenderTree: true - set it to active state, call SetActive
|
||||
*/
|
||||
RefPtr<UINode> GetFrameChildByIndex(
|
||||
uint32_t index, bool needBuild, bool isCache = false, bool addToRenderTree = false) override;
|
||||
|
||||
bool IsAtomicNode() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// used for drag move operation.
|
||||
void SetOnMove(std::function<void(int32_t, int32_t)>&& onMove);
|
||||
void MoveData(int32_t from, int32_t to) override;
|
||||
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
|
||||
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true) override;
|
||||
void InitDragManager(const RefPtr<UINode>& childNode);
|
||||
void InitAllChildrenDragManager(bool init);
|
||||
|
||||
void OnConfigurationUpdate(const ConfigurationChange& configurationChange) override;
|
||||
|
||||
void SetJSViewActive(bool active = true, bool isLazyForEachNode = false) override
|
||||
{
|
||||
const auto& children = caches_.GetAllNodes();
|
||||
for (const auto& [key, child] : children) {
|
||||
child->SetJSViewActive(active);
|
||||
}
|
||||
isActive_ = active;
|
||||
}
|
||||
void PaintDebugBoundaryTreeAll(bool flag) override
|
||||
{
|
||||
const auto& children = caches_.GetAllNodes();
|
||||
for (const auto& [key, child] : children) {
|
||||
child->PaintDebugBoundaryTreeAll(flag);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void PostIdleTask();
|
||||
|
||||
// try to find entry for given index in L1 or L2 cache
|
||||
RefPtr<UINode> GetFromCaches(uint32_t forIndex)
|
||||
{
|
||||
return caches_.GetNode4Index(forIndex);
|
||||
}
|
||||
|
||||
// index is not in L1 or L2 cache, need to make it
|
||||
// either by TS rendering new children or by TS updating
|
||||
// a L2 cache item from old to new index
|
||||
RefPtr<UINode> CreateOrUpdateFrameChild4Index(uint32_t index, const std::string& forKey);
|
||||
|
||||
// get farthest (from L1 indexes) index in L2 cache or -1
|
||||
int32_t GetFarthestL2CacheIndex();
|
||||
|
||||
// RepeatVirtualScrollNode is not instance of FrameNode
|
||||
// needs to propagate active state to all items inside
|
||||
bool isActive_ = true;
|
||||
|
||||
// size of data source when all data items loaded
|
||||
uint32_t totalCount_ = 0;
|
||||
|
||||
// caches:
|
||||
mutable RepeatVirtualScrollCaches caches_;
|
||||
|
||||
// used by one of the unknown functions
|
||||
std::list<std::string> ids_;
|
||||
|
||||
// re-assembled by GetChildren called from idle task
|
||||
mutable std::list<RefPtr<UINode>> children_;
|
||||
|
||||
// true in the time from requesting idle / predict task until exec predict tsk.
|
||||
bool postUpdateTaskHasBeenScheduled_;
|
||||
|
||||
OffscreenItems offscreenItems_;
|
||||
int32_t startIndex_ = 0;
|
||||
|
||||
ACE_DISALLOW_COPY_AND_MOVE(RepeatVirtualScrollNode);
|
||||
};
|
||||
} // namespace OHOS::Ace::NG
|
||||
|
||||
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_NODE_H
|
@ -70,7 +70,9 @@ const char JS_LAZY_FOR_EACH_ETS_TAG[] = "LazyForEach";
|
||||
const char JS_FOR_EACH_ETS_TAG[] = "ForEach";
|
||||
// js lazy foreach node
|
||||
const char JS_SYNTAX_ITEM_ETS_TAG[] = "SyntaxItem";
|
||||
// js if lese node
|
||||
// js repeat node
|
||||
const char JS_REPEAT_ETS_TAG[] = "Repeat";
|
||||
// js if else node
|
||||
const char JS_IF_ELSE_ETS_TAG[] = "IfElse";
|
||||
// js node slot
|
||||
const char JS_NODE_SLOT_ETS_TAG[] = "NodeSlot";
|
||||
|
@ -75,7 +75,9 @@ ACE_EXPORT extern const char JS_LAZY_FOR_EACH_ETS_TAG[];
|
||||
ACE_EXPORT extern const char JS_FOR_EACH_ETS_TAG[];
|
||||
// js lazy foreach node
|
||||
ACE_EXPORT extern const char JS_SYNTAX_ITEM_ETS_TAG[];
|
||||
// js if lese node
|
||||
// js repeat node
|
||||
ACE_EXPORT extern const char JS_REPEAT_ETS_TAG[];
|
||||
// js if else node
|
||||
ACE_EXPORT extern const char JS_IF_ELSE_ETS_TAG[];
|
||||
// js node slot node
|
||||
ACE_EXPORT extern const char JS_NODE_SLOT_ETS_TAG[];
|
||||
|
@ -1346,7 +1346,7 @@ HWTEST_F(LazyForEachSyntaxTestNg, ForEachSyntaxDoSetActiveChildRangeTest001, Tes
|
||||
* @tc.steps: step3. Invoke DoSetActiveChildRange.
|
||||
* @tc.expected: LazyForEachNode ids_ will be cleared.
|
||||
*/
|
||||
lazyForEachNode->DoSetActiveChildRange(0, 0);
|
||||
lazyForEachNode->DoSetActiveChildRange(0, 0, 0, 0);
|
||||
EXPECT_TRUE(lazyForEachNode->ids_.empty());
|
||||
|
||||
/**
|
||||
@ -1354,7 +1354,7 @@ HWTEST_F(LazyForEachSyntaxTestNg, ForEachSyntaxDoSetActiveChildRangeTest001, Tes
|
||||
* @tc.expected: LazyForEachNode ids_ will be cleared.
|
||||
*/
|
||||
lazyForEachNode->builder_ = nullptr;
|
||||
lazyForEachNode->DoSetActiveChildRange(0, 0);
|
||||
lazyForEachNode->DoSetActiveChildRange(0, 0, 0, 0);
|
||||
EXPECT_TRUE(lazyForEachNode->ids_.empty());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user