/* * Copyright [2019] [Doric.Pub] * * 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 { View, Group } from "./view" import { loge } from '../util/log' import { Model } from '../util/types' import { Root } from '../widget/layouts' import { BridgeContext } from '../runtime/global' export function NativeCall(target: Panel, propertyKey: string, descriptor: PropertyDescriptor) { const originVal = descriptor.value descriptor.value = function () { const ret = Reflect.apply(originVal, this, arguments) return ret } return descriptor } type Frame = { width: number, height: number } declare function nativeEmpty(): void export abstract class Panel { context!: BridgeContext onCreate() { } onDestroy() { } onShow() { } onHidden() { } abstract build(rootView: Group): void private __data__?: object private __root__ = new Root private headviews: Map> = new Map private onRenderFinishedCallback: Array<() => void> = [] addHeadView(type: string, v: View) { let map = this.headviews.get(type) if (map) { map.set(v.viewId, v) } else { map = new Map map.set(v.viewId, v) this.headviews.set(type, map) } } allHeadViews() { return this.headviews.values() } removeHeadView(type: string, v: View | string) { if (this.headviews.has(type)) { let map = this.headviews.get(type) if (map) { if (v instanceof View) { map.delete(v.viewId) } else { map.delete(v) } } } } clearHeadViews(type: string) { if (this.headviews.has(type)) { this.headviews.delete(type) } } getRootView() { return this.__root__ } getInitData() { return this.__data__ } @NativeCall private __init__(frame: Frame, data?: string) { if (data) { this.__data__ = JSON.parse(data) } this.__root__.width = frame.width this.__root__.height = frame.height this.__root__.children.length = 0 this.build(this.__root__) } @NativeCall private __onCreate__() { this.onCreate() } @NativeCall private __onDestroy__() { this.onDestroy() } @NativeCall private __onShow__() { this.onShow() } @NativeCall private __onHidden__(): void { this.onHidden() } @NativeCall private __build__() { this.build(this.__root__) } @NativeCall private __response__(viewIds: string[], callbackId: string) { const v = this.retrospectView(viewIds) if (v === undefined) { loge(`Cannot find view for ${viewIds}`) } else { const argumentsList: any = [callbackId] for (let i = 2; i < arguments.length; i++) { argumentsList.push(arguments[i]) } return Reflect.apply(v.responseCallback, v, argumentsList) } } private retrospectView(ids: string[]): View | undefined { return ids.reduce((acc: View | undefined, cur) => { if (acc === undefined) { if (cur === this.__root__.viewId) { return this.__root__ } for (let map of this.headviews.values()) { if (map.has(cur)) { return map.get(cur) } } return undefined } else { if (Reflect.has(acc, "subviewById")) { return Reflect.apply(Reflect.get(acc, "subviewById"), acc, [cur]) } return acc } }, undefined) } private nativeRender(model: Model) { return this.context.callNative("shader", "render", model) } private hookBeforeNativeCall() { if (Environment.platform !== 'web') { this.__root__.clean() for (let map of this.headviews.values()) { for (let v of map.values()) { v.clean() } } } } private hookAfterNativeCall() { const promises: Promise[] = [] if (Environment.platform !== 'web') { //Here insert a native call to ensure the promise is resolved done. nativeEmpty() if (this.__root__.isDirty()) { const model = this.__root__.toModel() promises.push(this.nativeRender(model)) } for (let map of this.headviews.values()) { for (let v of map.values()) { if (v.isDirty()) { const model = v.toModel() promises.push(this.nativeRender(model)) } } } } else { Promise.resolve().then(() => { if (this.__root__.isDirty()) { const model = this.__root__.toModel() promises.push(this.nativeRender(model)) this.__root__.clean() } for (let map of this.headviews.values()) { for (let v of map.values()) { if (v.isDirty()) { const model = v.toModel() promises.push(this.nativeRender(model)) v.clean() } } } }) } Promise.all(promises).then(_ => { this.onRenderFinished() }) } onRenderFinished() { this.onRenderFinishedCallback.forEach(e => { e() }) this.onRenderFinishedCallback.length = 0 } addOnRenderFinishedCallback(cb: () => void) { this.onRenderFinishedCallback.push(cb) } }