diff --git a/arkoala-arkts/arkui/src/component/builder.ets b/arkoala-arkts/arkui/src/component/builder.ets index 01c126845389d34d6dfbe3add817f2cb266219d0..b470d17831c06ad4771c1f2ab32f5cca53071fa2 100644 --- a/arkoala-arkts/arkui/src/component/builder.ets +++ b/arkoala-arkts/arkui/src/component/builder.ets @@ -1,4 +1,5 @@ import { memo } from "@koalaui/runtime/annotations" +import { conditionScopeImpl, conditionBranchImpl } from './conditionScope' export class WrappedBuilder { @memo builder: T; @@ -10,3 +11,19 @@ export class WrappedBuilder { export function wrapBuilder(builder: T): WrappedBuilder { return new WrappedBuilder(builder); } + +@memo +export function ConditionScope( + @memo + content: () => void +): void { + conditionScopeImpl(content); +} + +@memo +export function ConditionBranch( + @memo + content: () => void +): void { + conditionBranchImpl(content); +} \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/component/conditionScope.ets b/arkoala-arkts/arkui/src/component/conditionScope.ets new file mode 100644 index 0000000000000000000000000000000000000000..9850edd773ee88b6cd9401e5f909da48b9bf7ad5 --- /dev/null +++ b/arkoala-arkts/arkui/src/component/conditionScope.ets @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { memo, memo_intrinsic } from "@koalaui/runtime/annotations" +import { int32 } from '@koalaui/common' +import { KPointer } from "@koalaui/interop" +import { IncrementalNode, NodeAttach, IncrementalScope, remember, __context, __id } from '@koalaui/runtime'; +import { PeerNode } from '../PeerNode'; +// uncomment after ArkConditionScopePeer generation +//import { ArkConditionScopePeer } from "./../component/idlize" + +class ConditionBranchesNode extends IncrementalNode { + public static create(): ConditionBranchesNode { + return new ConditionBranchesNode(); + } + + private changed = false; + private _branches = new Array(); + + onChange(): void{ + this.changed = true + } + + protected constructor() { + super(); + + this.onChildInserted = this.onChange; + this.onChildRemoved = this.onChange; + } + + public get branches(): Array { + if (this.changed) { + this.changed = false; + this._branches = new Array(); + for (let child = this.firstChild; child; child = child?.nextSibling) { + this._branches.push(child as ConditionBranchNode); + } + } + return this._branches; + } +} + +class ConditionBranchNode extends IncrementalNode { + @memo + builder: () => void; + + constructor( + @memo + builder: () => void + ) { + super(); + this.builder = builder; + } +} + +function compareArray(oldValue: Array, newValue: Array): boolean { + if (oldValue === newValue) { + return true; + } + + if (oldValue.length !== newValue.length) { + return false; + } + + for (let i = 0; i < oldValue.length; i++) { + if (oldValue[i] !== newValue[i]) { + return false; + } + } + + return false; +} + +function calcIntersection(oldValue: Array, newValue: Array): Array { + const intersection = new Array(); + + let start = 0; + for (let i = 0; i < oldValue.length; i++) { + if (start >= newValue.length) { + break; + } + + for (let j = start; j < newValue.length; j++) { + if (oldValue[i] === newValue[j]) { + intersection.push(oldValue[i]); + start = j + 1; + break; + } + } + } + + if (intersection.length === oldValue.length) { + return oldValue; + } + + if (intersection.length === newValue.length) { + return newValue; + } + + return intersection; +} + +@memo_intrinsic +function buildBranches(scope: IncrementalScope, branches: Array): void { + const paramBranches = scope.paramEx(0, branches, compareArray); + scope.forceCompleteRerender() + if (scope.unchanged) { + scope.cached; + return; + } + + for (const branch of paramBranches.value) { + branch.builder(); + } + + scope.recache(); +} + +@memo_intrinsic +function buildContent(before: Array, after: Array): void { + const same = compareArray(before, after); + const intersection = same ? before : calcIntersection(before, after); + + const scope = __context().scope(__id(), 1); + buildBranches(scope, intersection); + + if (!same) { + buildBranches(scope, after); + } +} + +@memo_intrinsic +function emptyImplement(): void {} + +@memo_intrinsic +export function conditionScopeImpl( + @memo + content: () => void +): void { + console.log("conditionScopeImpl is not implemented, wait for ArkConditionScopePeer generation") + // uncomment after c generation + // NodeAttach(ArkConditionScopePeer.create, (_: ArkConditionScopePeer) => { + // const node = remember(ConditionBranchesNode.create); + + // const before = node.branches; + // NodeAttach(() => node, content); + // const after = node.branches; + + // buildContent(before, after); + // }) +} + +@memo_intrinsic +export function conditionBranchImpl( + @memo + content: () => void +): void { + NodeAttach(() => new ConditionBranchNode(content), emptyImplement); +} \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/index.ets b/arkoala-arkts/arkui/src/index.ets index aec1b58b697c87cf69038d7d23e5f624592083bd..37908c88315ce4610447ec3d61634d51fee5f427 100644 --- a/arkoala-arkts/arkui/src/index.ets +++ b/arkoala-arkts/arkui/src/index.ets @@ -34,6 +34,7 @@ export * from "./resources" export * from "./ContentModifierHooks" export * from "./TestApiHooks" export * from "./ArkStateStyle" +export * from "./component/conditionScope" export * from "./component/customComponent" export * from "./component/forEach" export * from "./component/lazyForEach" diff --git a/arkoala-arkts/arkui_components.gni b/arkoala-arkts/arkui_components.gni index 73b6fcd4f032673a5b4a689e52ca3acae340ff10..3c02d30e4c2990a050ae808b663eed9d7577bdba 100644 --- a/arkoala-arkts/arkui_components.gni +++ b/arkoala-arkts/arkui_components.gni @@ -343,6 +343,7 @@ arkui_files = [ "arkui/src/ani/arkts/ui_extension/ArkUIAniUiextensionModal.ts", "arkui/src/component/builder.ets", "arkui/src/component/common.ets", + "arkui/src/component/conditionScope.ets", "arkui/src/component/customComponent.ets", "arkui/src/component/forEach.ets", "arkui/src/component/interop.ets", diff --git a/incremental/runtime/src/states/State.ts b/incremental/runtime/src/states/State.ts index c4866a72e529cb93db53c7a116c531badf2a366a..edbd37d48f3b21a495b39e4febe4c63af82c83ac 100644 --- a/incremental/runtime/src/states/State.ts +++ b/incremental/runtime/src/states/State.ts @@ -250,6 +250,7 @@ export interface IncrementalScope { /** @returns internal state for parameter */ param(index: int32, value: V): State paramEx(index: int32, value: V, equivalent?: Equivalent, name?: string, contextLocal?: boolean): State + forceCompleteRerender(): void } /**