diff --git a/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/builder.ets b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/builder.ets index 123805da09336aed28b7b4da4299ba25a8fb7bd0..7079885ee64839f7dd02102832931d6a6082005e 100644 --- a/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/builder.ets +++ b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/builder.ets @@ -2,6 +2,7 @@ import { int32, int64, float32 } from "@koalaui/common" import { KInt, KPointer, KBoolean, NativeBuffer, KStringPtr, wrapCallback } from "@koalaui/interop" import { memo, memo_stable } from "@koalaui/runtime/annotations" import { ComponentBuilder } from "@koalaui/builderLambda" +import { conditionScopeImpl, conditionBranchImpl } from './conditionScope' export class WrappedBuilder { /** @memo */ @@ -13,4 +14,20 @@ export class WrappedBuilder { export function wrapBuilder(builder: T): WrappedBuilder { return new WrappedBuilder(builder); -} \ No newline at end of file +} + +/** @memo:intrinsic */ +export function ConditionScope( + /** @memo */ + content: () => void +): void { + conditionScopeImpl(content); +} + +/** @memo:intrinsic */ +export function ConditionBranch( + /** @memo */ + content: () => void +): void { + conditionBranchImpl(content); +} diff --git a/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/conditionScope.ets b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/conditionScope.ets new file mode 100644 index 0000000000000000000000000000000000000000..4dd2aa0140d62089211484a8c02af1b3b7765a17 --- /dev/null +++ b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/arkui-ohos/src/component/conditionScope.ets @@ -0,0 +1,204 @@ +/* + * 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 { KoalaCallsiteKey, int32 } from '@koalaui/common' +import { KPointer } from "@koalaui/interop" +import { __context, __id, Disposable, IncrementalNode, InternalScope, NodeAttach } from '@koalaui/runtime'; +import { ArkUIAniModule } from "arkui.ani" +import { PeerNode } from '../PeerNode'; + +class ConditionScopePeer extends PeerNode { + public static create(): ConditionScopePeer { + const peerId = PeerNode.nextId(); + const _peerPtr = ArkUIAniModule._ConditionScopeNode_Construct(peerId); + if (!_peerPtr) { + throw new Error('create ConditionScope fail'); + } + return new ConditionScopePeer(_peerPtr, peerId, 'ConditionScope'); + } + + private _branchesNode: ConditionBranchesNode = new ConditionBranchesNode(); + private scopes: Map = new Map(); + + protected constructor(peerPtr: KPointer, id: int32, name: string = '', flags: int32 = 0) { + super(peerPtr, id, name, flags); + } + + get branchesNode(): ConditionBranchesNode { + return this._branchesNode; + } + + /* reuse and recycle object on RootPeers */ + override reuse(reuseKey: string | undefined, id: KoalaCallsiteKey): Disposable | undefined { + if (reuseKey !== undefined) { + return super.reuse(reuseKey, id); + } + + const result = this.scopes.get(id); + if (result !== undefined) { + this.scopes.delete(id); + } + + return result; + } + + override recycle(reuseKey: string | undefined, child: Disposable, id: KoalaCallsiteKey): boolean { + if (reuseKey !== undefined) { + return super.recycle(reuseKey, child, id); + } + + if (!this.scopes.has(id)) { + this.scopes.set(id, child); + return true; + } + + return false; + } + + private clearScopes(): void { + for (const item of this.scopes.values()) { + item.dispose(); + } + this.scopes.clear(); + } + + override dispose() { + this.clearScopes(); + super.dispose(); + } +} + +class ConditionBranchesNode extends IncrementalNode { + private _changed = false; + private _branches = new Array(); + + public constructor() { + super(); + + this.onChildInserted = this.onChange; + this.onChildRemoved = this.onChange; + } + + public onChange(): void { + this._changed = true + } + + 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; + } + + public get changed(): boolean { + return this._changed; + } +} + +class ConditionBranchNode extends IncrementalNode { + /** @memo */ + builder: () => void; + + constructor( + /** @memo */ + builder: () => void + ) { + super(); + this.builder = builder; + } +} + +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: InternalScope, branches: Array): void { + const paramBranches = scope.param(0, branches); + if (scope.unchanged) { + scope.cached; + return; + } + + for (const branch of paramBranches.value) { + branch.builder(); + } + + scope.recache(); +} + +/** @memo:intrinsic */ +function buildContent(before: Array, node: ConditionBranchesNode): void { + const branchGroup = node.changed ? [calcIntersection(before, node.branches), node.branches] : [before]; + + const scope = __context().scope(__id(), 1); + for (const branches of branchGroup) { + buildBranches(scope, branches); + } +} + +/** @memo:intrinsic */ +function emptyImplement(): void {} + +/** @memo:intrinsic */ +export function conditionScopeImpl( + /** @memo */ + content: () => void +): void { + NodeAttach(ConditionScopePeer.create, (peer: ConditionScopePeer) => { + const node = peer.branchesNode; + + const before = node.branches; + NodeAttach(() => node, content); + buildContent(before, node); + }) +} + +/** @memo:intrinsic */ +export function conditionBranchImpl( + /** @memo */ + content: () => void +): void { + NodeAttach(() => new ConditionBranchNode(content), emptyImplement); +} diff --git a/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/components.gni b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/components.gni index 8b56b1c4a3c5a3b6463ef70f697ffa7fb0ec1fc1..bc5aee340ef1a513a17d10746f60da8f8e37be0e 100644 --- a/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/components.gni +++ b/frameworks/bridge/arkts_frontend/koala_projects/arkoala-arkts/components.gni @@ -209,6 +209,7 @@ arkui_files = [ "arkui-preprocessed/arkui/component/columnSplit.ets", "arkui-preprocessed/arkui/component/common.ets", "arkui-preprocessed/arkui/component/component3d.ets", + "arkui-preprocessed/arkui/component/conditionScope.ets", "arkui-preprocessed/arkui/component/containerSpan.ets", "arkui-preprocessed/arkui/component/contentSlot.ets", "arkui-preprocessed/arkui/component/counter.ets",