@leafer/display
Version:
688 lines (455 loc) • 24.2 kB
text/typescript
import { ILeaferBase, ILeaf, ILeafInputData, ILeafData, ILeaferCanvas, IRenderOptions, IBoundsType, ILocationType, IMatrixWithBoundsData, ILayoutBoundsData, IValue, ILeafLayout, InnerId, IHitCanvas, IRadiusPointData, IEventListenerMap, IEventListener, IEventListenerId, IEvent, IObject, IFunction, IPointData, IBoundsData, IBranch, IFindMethod, IMatrixData, IAttrDecorator, IMatrixWithBoundsScaleData, IMatrixWithScaleData, IAlign, IJSONOptions, IEventParamsMap, IEventOption, IAxis, IMotionPathData, IUnitData, IRotationPointData, ITransition, IValueFunction, IEventParams } from '@leafer/interface'
import { BoundsHelper, IncrementId, MatrixHelper, PointHelper } from '@leafer/math'
import { LeafData } from '@leafer/data'
import { LeafLayout } from '@leafer/layout'
import { LeafDataProxy, LeafMatrix, LeafBounds, LeafEventer, LeafRender } from '@leafer/display-module'
import { boundsType, useModule, defineDataProcessor } from '@leafer/decorator'
import { LeafHelper } from '@leafer/helper'
import { ChildEvent } from '@leafer/event'
import { Plugin } from '@leafer/debug'
const { LEAF, create } = IncrementId
const { toInnerPoint, toOuterPoint, multiplyParent } = MatrixHelper
const { toOuterOf } = BoundsHelper
const { copy, move } = PointHelper
const { moveLocal, zoomOfLocal, rotateOfLocal, skewOfLocal, moveWorld, zoomOfWorld, rotateOfWorld, skewOfWorld, transform, transformWorld, setTransform, getFlipTransform, getLocalOrigin, getRelativeWorld, drop } = LeafHelper
export class Leaf implements ILeaf {
public get tag(): string { return this.__tag }
public set tag(_value: string) { }
public get __tag(): string { return 'Leaf' }
public readonly innerId: InnerId // 内部唯一标识
public get innerName(): string { return this.__.name || this.tag + this.innerId }
public get __DataProcessor() { return LeafData }
public get __LayoutProcessor() { return LeafLayout }
public leafer?: ILeaferBase
public parent?: ILeaf
public get leaferIsCreated(): boolean { return this.leafer && this.leafer.created }
public get leaferIsReady(): boolean { return this.leafer && this.leafer.ready }
public get isLeafer(): boolean { return false }
public get isBranch(): boolean { return false }
public get isBranchLeaf(): boolean { return false }
public syncEventer?: ILeaf // 同步触发一样事件的元素
public lockNormalStyle?: boolean
public __: ILeafData
public __layout: ILeafLayout
public __world: IMatrixWithBoundsScaleData
public __local?: IMatrixWithBoundsData // and localStrokeBounds? localRenderBounds?
public __nowWorld?: IMatrixWithBoundsScaleData // use __world or __cameraWorld render
public __cameraWorld?: IMatrixWithBoundsScaleData // use camera matrix render
public get __localMatrix(): IMatrixData { return this.__local || this.__layout }
public get __localBoxBounds(): IBoundsData { return this.__local || this.__layout }
public __worldOpacity: number
// now transform
public get worldTransform(): IMatrixWithScaleData { return this.__layout.getTransform('world') as IMatrixWithScaleData }
public get localTransform(): IMatrixData { return this.__layout.getTransform('local') }
// now bounds
public get boxBounds(): IBoundsData { return this.getBounds('box', 'inner') }
public get renderBounds(): IBoundsData { return this.getBounds('render', 'inner') }
public get worldBoxBounds(): IBoundsData { return this.getBounds('box') }
public get worldStrokeBounds(): IBoundsData { return this.getBounds('stroke') }
public get worldRenderBounds(): IBoundsData { return this.getBounds('render') }
// now opacity
public get worldOpacity(): number { this.__layout.update(); return this.__worldOpacity }
public __level: number // layer level 0 -> branch -> branch -> deep
public __tempNumber: number // temp sort
public get __worldFlipped(): boolean { return this.__world.scaleX < 0 || this.__world.scaleY < 0 }
public __hasAutoLayout?: boolean
public __hasMask?: boolean
public __hasEraser?: boolean
public __hitCanvas?: IHitCanvas
public get __onlyHitMask(): boolean { return this.__hasMask && !this.__.hitChildren }
public get __ignoreHitWorld(): boolean { return (this.__hasMask || this.__hasEraser) && this.__.hitChildren }
public get __inLazyBounds(): boolean { return this.leaferIsCreated && this.leafer.lazyBounds.hit(this.__world) }
public get pathInputed(): boolean { return this.__.__pathInputed as unknown as boolean }
// event
public set event(map: IEventParamsMap) { this.on(map) }
public __captureMap?: IEventListenerMap
public __bubbleMap?: IEventListenerMap
public __hasLocalEvent?: boolean
public __hasWorldEvent?: boolean
// branch
public children?: ILeaf[]
public destroyed: boolean
constructor(data?: ILeafInputData) {
this.innerId = create(LEAF)
this.reset(data)
if (this.__bubbleMap) this.__emitLifeEvent(ChildEvent.CREATED)
}
public reset(data?: ILeafInputData): void {
if (this.leafer) this.leafer.forceRender(this.__world) // fix: add old bounds rendering
if (data as any !== 0) { // 设为 0 时可用于 text boxStyle 节省开销
this.__world = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, x: 0, y: 0, width: 0, height: 0, scaleX: 1, scaleY: 1 }
if (data !== null) this.__local = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, x: 0, y: 0, width: 0, height: 0 }
}
this.__worldOpacity = 1
this.__ = new this.__DataProcessor(this)
this.__layout = new this.__LayoutProcessor(this)
if (this.__level) this.resetCustom()
if (data) {
if ((data as ILeaf).__) data = (data as ILeaf).toJSON()
data.children ? this.set(data) : Object.assign(this, data)
}
}
public resetCustom(): void {
this.__hasMask = this.__hasEraser = null
this.forceUpdate()
}
public waitParent(item: IFunction, bind?: IObject): void {
if (bind) item = item.bind(bind)
this.parent ? item() : this.on(ChildEvent.ADD, item, 'once')
}
public waitLeafer(item: IFunction, bind?: IObject): void {
if (bind) item = item.bind(bind)
this.leafer ? item() : this.on(ChildEvent.MOUNTED, item, 'once')
}
public nextRender(item: IFunction, bind?: IObject, off?: 'off'): void {
this.leafer ? this.leafer.nextRender(item, bind, off) : this.waitLeafer(() => this.leafer.nextRender(item, bind, off))
}
public removeNextRender(item: IFunction): void {
this.nextRender(item, null, 'off')
}
public __bindLeafer(leafer: ILeaferBase | null): void {
if (this.isLeafer && leafer !== null) leafer = this as unknown as ILeaferBase
if (this.leafer && !leafer) this.leafer.leafs--
this.leafer = leafer
if (leafer) {
leafer.leafs++
this.__level = this.parent ? this.parent.__level + 1 : 1
if ((this as ILeaf).animation) this.__runAnimation('in')
if (this.__bubbleMap) this.__emitLifeEvent(ChildEvent.MOUNTED)
} else {
this.__emitLifeEvent(ChildEvent.UNMOUNTED)
}
if (this.isBranch) {
const { children } = this
for (let i = 0, len = children.length; i < len; i++) {
children[i].__bindLeafer(leafer)
}
}
}
// data
public set(_data: IObject, _isTemp?: boolean): void { }
public get(_name?: string): ILeafInputData | IValue { return undefined }
public setAttr(name: string, value: any): void { (this as IObject)[name] = value }
public getAttr(name: string): any { return (this as IObject)[name] }
public getComputedAttr(name: string): any { return (this.__ as IObject)[name] }
public toJSON(options?: IJSONOptions): IObject {
if (options) this.__layout.update()
return this.__.__getInputData(null, options)
}
public toString(options?: IJSONOptions): string {
return JSON.stringify(this.toJSON(options))
}
public toSVG(): string { return undefined }
public __SVG(_data: IObject): void { }
public toHTML(): string { return undefined }
// LeafDataProxy rewrite
public __setAttr(_attrName: string, _newValue: IValue): boolean { return true }
public __getAttr(_attrName: string): IValue { return undefined }
public setProxyAttr(_attrName: string, _newValue: IValue): void { }
public getProxyAttr(_attrName: string): IValue { return undefined }
// ---
// find
public find(_condition: number | string | IFindMethod, _options?: any): ILeaf[] { return undefined }
public findTag(_tag: string | string[]): ILeaf[] { return undefined }
public findOne(_condition: number | string | IFindMethod, _options?: any): ILeaf | undefined { return undefined }
public findId(_id: number | string): ILeaf | undefined { return undefined }
// ---
// @leafer-in/state rewrite
public focus(_value?: boolean): void { }
public updateState(): void { }
// ---
public updateLayout(): void {
this.__layout.update()
}
public forceUpdate(attrName?: string): void {
if (attrName === undefined) attrName = 'width'
else if (attrName === 'surface') attrName = 'blendMode'
const value = this.__.__getInput(attrName);
(this.__ as any)[attrName] = value === undefined ? null : undefined;
(this as any)[attrName] = value
}
public forceRender(_bounds?: IBoundsData, _sync?: boolean): void {
this.forceUpdate('surface')
}
public __extraUpdate(): void {
if (this.leaferIsReady) this.leafer.layouter.addExtra(this) // add part 额外更新元素
}
// LeafMatrix rewrite
public __updateWorldMatrix(): void { }
public __updateLocalMatrix(): void { }
// ---
// LeafBounds rewrite
public __updateWorldBounds(): void { }
public __updateLocalBounds(): void { }
public __updateLocalBoxBounds(): void { }
public __updateLocalStrokeBounds(): void { }
public __updateLocalRenderBounds(): void { }
// box
public __updateBoxBounds(): void { }
public __updateContentBounds(): void { }
public __updateStrokeBounds(): void { }
public __updateRenderBounds(): void { }
public __updateAutoLayout(): void { }
public __updateFlowLayout(): void { }
public __updateNaturalSize(): void { }
public __updateStrokeSpread(): number { return 0 }
public __updateRenderSpread(): number { return 0 }
public __onUpdateSize(): void { }
// ---
public __updateEraser(value?: boolean): void {
this.__hasEraser = value ? true : this.children.some(item => item.__.eraser)
}
public __renderEraser(canvas: ILeaferCanvas, options: IRenderOptions): void { // path eraser
canvas.save()
this.__clip(canvas, options)
const { renderBounds: r } = this.__layout
canvas.clearRect(r.x, r.y, r.width, r.height)
canvas.restore()
}
public __updateMask(_value?: boolean): void {
this.__hasMask = this.children.some(item => item.__.mask && item.__.visible && item.__.opacity)
}
// LeafMask rewrite
public __renderMask(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
// ---
// convert
public __getNowWorld(options: IRenderOptions): IMatrixWithBoundsScaleData {
if (options.matrix) {
if (!this.__cameraWorld) this.__cameraWorld = {} as IMatrixWithBoundsScaleData
const cameraWorld = this.__cameraWorld, world = this.__world
multiplyParent(world, options.matrix, cameraWorld, undefined, world)
toOuterOf(this.__layout.renderBounds, cameraWorld, cameraWorld)
cameraWorld.half !== world.half && (cameraWorld.half = world.half)
return cameraWorld
} else {
return this.__world
}
}
public getTransform(relative?: ILocationType | ILeaf): IMatrixData {
return this.__layout.getTransform(relative || 'local')
}
public getBounds(type?: IBoundsType, relative?: ILocationType | ILeaf): IBoundsData {
return this.__layout.getBounds(type, relative)
}
public getLayoutBounds(type?: IBoundsType, relative?: ILocationType | ILeaf, unscale?: boolean): ILayoutBoundsData {
return this.__layout.getLayoutBounds(type, relative, unscale)
}
public getLayoutPoints(type?: IBoundsType, relative?: ILocationType | ILeaf): IPointData[] {
return this.__layout.getLayoutPoints(type, relative)
}
public getWorldBounds(inner: IBoundsData, relative?: ILeaf, change?: boolean): IBoundsData {
const matrix = relative ? getRelativeWorld(this, relative) : this.worldTransform
const to = change ? inner : {} as IBoundsData
toOuterOf(inner, matrix, to)
return to
}
public worldToLocal(world: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
if (this.parent) {
this.parent.worldToInner(world, to, distance, relative)
} else {
if (to) copy(to, world)
}
}
public localToWorld(local: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
if (this.parent) {
this.parent.innerToWorld(local, to, distance, relative)
} else {
if (to) copy(to, local)
}
}
public worldToInner(world: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
if (relative) {
relative.innerToWorld(world, to, distance)
world = to ? to : world
}
toInnerPoint(this.worldTransform, world, to, distance)
}
public innerToWorld(inner: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
toOuterPoint(this.worldTransform, inner, to, distance)
if (relative) relative.worldToInner(to ? to : inner, null, distance)
}
// simple
public getBoxPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
return this.getBoxPointByInner(this.getInnerPoint(world, relative, distance, change), null, null, true)
}
public getBoxPointByInner(inner: IPointData, _relative?: ILeaf, _distance?: boolean, change?: boolean): IPointData {
const point = change ? inner : { ...inner } as IPointData, { x, y } = this.boxBounds
move(point, -x, -y)
return point
}
public getInnerPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const point = change ? world : {} as IPointData
this.worldToInner(world, point, distance, relative)
return point
}
public getInnerPointByBox(box: IPointData, _relative?: ILeaf, _distance?: boolean, change?: boolean): IPointData {
const point = change ? box : { ...box } as IPointData, { x, y } = this.boxBounds
move(point, x, y)
return point
}
public getInnerPointByLocal(local: IPointData, _relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
return this.getInnerPoint(local, this.parent, distance, change)
}
public getLocalPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const point = change ? world : {} as IPointData
this.worldToLocal(world, point, distance, relative)
return point
}
public getLocalPointByInner(inner: IPointData, _relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
return this.getWorldPoint(inner, this.parent, distance, change)
}
public getPagePoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const layer = this.leafer ? this.leafer.zoomLayer : this
return layer.getInnerPoint(world, relative, distance, change)
}
public getWorldPoint(inner: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const point = change ? inner : {} as IPointData
this.innerToWorld(inner, point, distance, relative)
return point
}
public getWorldPointByBox(box: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
return this.getWorldPoint(this.getInnerPointByBox(box, null, null, change), relative, distance, true)
}
public getWorldPointByLocal(local: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const point = change ? local : {} as IPointData
this.localToWorld(local, point, distance, relative)
return point
}
public getWorldPointByPage(page: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
const layer = this.leafer ? this.leafer.zoomLayer : this
return layer.getWorldPoint(page, relative, distance, change)
}
// transform
public setTransform(matrix: IMatrixData, resize?: boolean, transition?: ITransition): void {
setTransform(this, matrix, resize, transition)
}
public transform(matrix: IMatrixData, resize?: boolean, transition?: ITransition): void {
transform(this, matrix, resize, transition)
}
public move(x: number | IPointData, y?: number, transition?: ITransition): void {
moveLocal(this, x, y, transition)
}
public moveInner(x: number | IPointData, y?: number, transition?: ITransition): void {
moveWorld(this, x, y, true, transition)
}
public scaleOf(origin: IPointData | IAlign, scaleX: number, scaleY?: number | ITransition, resize?: boolean, transition?: ITransition): void {
zoomOfLocal(this, getLocalOrigin(this, origin), scaleX, scaleY, resize, transition)
}
public rotateOf(origin: IPointData | IAlign, rotation: number, transition?: ITransition): void {
rotateOfLocal(this, getLocalOrigin(this, origin), rotation, transition)
}
public skewOf(origin: IPointData | IAlign, skewX: number, skewY?: number, resize?: boolean, transition?: ITransition): void {
skewOfLocal(this, getLocalOrigin(this, origin), skewX, skewY, resize, transition)
}
public transformWorld(worldTransform?: IMatrixData, resize?: boolean, transition?: ITransition): void {
transformWorld(this, worldTransform, resize, transition)
}
public moveWorld(x: number | IPointData, y?: number, transition?: ITransition): void {
moveWorld(this, x, y, false, transition)
}
public scaleOfWorld(worldOrigin: IPointData, scaleX: number, scaleY?: number | ITransition, resize?: boolean, transition?: ITransition): void {
zoomOfWorld(this, worldOrigin, scaleX, scaleY, resize, transition)
}
public rotateOfWorld(worldOrigin: IPointData, rotation: number): void {
rotateOfWorld(this, worldOrigin, rotation)
}
public skewOfWorld(worldOrigin: IPointData, skewX: number, skewY?: number, resize?: boolean, transition?: ITransition): void {
skewOfWorld(this, worldOrigin, skewX, skewY, resize, transition)
}
public flip(axis: IAxis, transition?: ITransition): void {
transform(this, getFlipTransform(this, axis), false, transition)
}
// @leafer-in/resize rewrite
public scaleResize(scaleX: number, scaleY = scaleX, _noResize?: boolean): void {
(this as ILeaf).scaleX *= scaleX;
(this as ILeaf).scaleY *= scaleY
}
public __scaleResize(_scaleX: number, _scaleY: number): void { }
public resizeWidth(_width: number): void { }
public resizeHeight(_height: number): void { }
// @leafer-ui/hit LeafHit rewrite
public __hitWorld(_point: IRadiusPointData): boolean { return true }
public __hit(_local: IRadiusPointData): boolean { return true }
public __hitFill(_inner: IRadiusPointData): boolean { return true }
public __hitStroke(_inner: IRadiusPointData, _strokeWidth: number): boolean { return true }
public __hitPixel(_inner: IRadiusPointData): boolean { return true }
public __drawHitPath(_canvas: ILeaferCanvas): void { }
public __updateHitCanvas(): void { }
// ---
// LeafRender rewrite
public __render(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
public __drawFast(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
public __draw(_canvas: ILeaferCanvas, _options: IRenderOptions, _originCanvas?: ILeaferCanvas): void { }
public __clip(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
public __renderShape(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
public __drawShape(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }
public __updateWorldOpacity(): void { }
public __updateChange(): void { }
// ---
// path
public __drawPath(_canvas: ILeaferCanvas): void { }
public __drawRenderPath(_canvas: ILeaferCanvas): void { }
public __updatePath(): void { }
public __updateRenderPath(): void { }
// ---
// @leafer-in/motion-path rewrite
public getMotionPathData(): IMotionPathData {
return Plugin.need('path')
}
public getMotionPoint(_motionDistance: number | IUnitData): IRotationPointData {
return Plugin.need('path')
}
public getMotionTotal(): number {
return 0
}
public __updateMotionPath(): void { }
// ---
// @leafer-in/animate rewrite
public __runAnimation(_type: 'in' | 'out', _complete?: IFunction): void { }
// Branch rewrite
public __updateSortChildren(): void { }
public add(_child: ILeaf | ILeaf[] | ILeafInputData | ILeafInputData[], _index?: number): void { }
public remove(_child?: ILeaf | number | string | IFindMethod, destroy?: boolean): void {
if (this.parent) this.parent.remove(this, destroy)
}
public dropTo(parent: ILeaf, index?: number, resize?: boolean): void {
drop(this, parent, index, resize)
}
// ---
// LeafEventer rewrite
public on(_type: string | string[] | IEventParams[] | IEventParamsMap, _listener?: IEventListener, _options?: IEventOption): void { }
public off(_type?: string | string[], _listener?: IEventListener, _options?: IEventOption): void { }
public on_(_type: string | string[] | IEventParams[], _listener?: IEventListener, _bind?: IObject, _options?: IEventOption): IEventListenerId { return undefined }
public off_(_id: IEventListenerId | IEventListenerId[]): void { }
public once(_type: string | string[] | IEventParams[], _listener?: IEventListener, _captureOrBind?: boolean | IObject, _capture?: boolean): void { }
public emit(_type: string, _event?: IEvent | IObject, _capture?: boolean): void { }
public emitEvent(_event?: IEvent, _capture?: boolean): void { }
public hasEvent(_type: string, _capture?: boolean): boolean { return false }
// ---
static changeAttr(attrName: string, defaultValue: IValue | IValueFunction, fn?: IAttrDecorator): void {
fn ? this.addAttr(attrName, defaultValue, fn) : defineDataProcessor(this.prototype, attrName, defaultValue)
}
static addAttr(attrName: string, defaultValue: IValue | IValueFunction, fn?: IAttrDecorator, helpValue?: IValue): void {
if (!fn) fn = boundsType
fn(defaultValue, helpValue)(this.prototype, attrName)
}
public __emitLifeEvent(type: string): void {
if (this.hasEvent(type)) this.emitEvent(new ChildEvent(type, this, this.parent))
}
public destroy(): void {
if (!this.destroyed) {
if (this.parent) this.remove()
if (this.children) (this as unknown as IBranch).clear()
this.__emitLifeEvent(ChildEvent.DESTROY)
this.__.destroy()
this.__layout.destroy();
(this as ILeaf).destroyEventer()
this.destroyed = true
}
}
}