egreact
Version:
A react render for egret 一个为 egret 而生的 react 渲染器
337 lines (298 loc) • 11 kB
text/typescript
import { DevThrow, getActualInstance, is } from '../utils'
import { CONSTANTS } from '../constants'
import { DiffHandler, PropSetter, EventSet, PropSetterParameters } from '../type'
export interface IProp {
__setter: PropSetter<any>
__Class?: new (...args: any[]) => any
}
export module EventProp {
export type GetEventKeyWithoutNum<T extends string> = `${T}${'Once' | ''}${'Capture' | ''}`
export const eventSetterWithType = <T extends egret.Event>() => {
const eventHandler: EventSet<(event: T) => any> = (props) => {
type InnerListener = Function
type OuterListener = Function
const { newValue, targetKey, eInfo } = props
const instance = props.instance as typeof props.instance & {
// 内部的监听器集合 addListener 实际传入的监听器
memorizedListeners: { [k in string]: [InnerListener, OuterListener] }
}
const memorizedListeners = instance.memorizedListeners || (instance.memorizedListeners = {})
const actualInstance = getActualInstance(instance) as egret.DisplayObject
const { type, once, capture, priority, keys } = eInfo
const isMountEvent = !(targetKey in memorizedListeners)
const canDefaultTouchEnabled =
keys.includes('Touch') &&
!('touchEnabled' in instance[CONSTANTS.INFO_KEY].memoizedProps) &&
'touchEnabled' in actualInstance
if (isMountEvent) {
if (canDefaultTouchEnabled) {
// 有 touch 事件默认开启 touchEnabled
actualInstance.touchEnabled = true
}
// 通过引用来改变调用,无需多次调用 addListener 和 removeListener
const innerListener = function (...args: any[]) {
// 最新的属性值
memorizedListeners[targetKey][1].apply(this, args)
}
memorizedListeners[targetKey] = [innerListener, newValue]
actualInstance[once ? 'once' : 'addEventListener'](type, innerListener, actualInstance, capture, priority)
} else {
memorizedListeners[targetKey][1] = newValue
}
return (isRemove) => {
if (isRemove) {
if (canDefaultTouchEnabled) {
actualInstance.touchEnabled = false
}
const innerListener = memorizedListeners[targetKey][0]
actualInstance.removeEventListener(type, innerListener, instance, capture)
delete memorizedListeners[targetKey]
}
}
}
return eventHandler
}
export const eventSetter = eventSetterWithType()
export const touchEventSetter = eventSetterWithType<egret.TouchEvent>()
export const uiEventSetter = eventSetterWithType<eui.UIEvent>()
export const focusEventSetter = eventSetterWithType<egret.FocusEvent>()
export const uiEventSetters = {
onUiResize: EventProp.uiEventSetter,
onUiResizeOnce: EventProp.uiEventSetter,
onUiMove: EventProp.uiEventSetter,
onUiMoveOnce: EventProp.uiEventSetter,
onUiCreationComplete: EventProp.uiEventSetter,
onUiCreationCompleteOnce: EventProp.uiEventSetter,
}
}
export module NormalProp {
export const boo: PropSetter<boolean | `${boolean}`> = ({ newValue, target, targetKey }) => (
(target[targetKey] = newValue === 'false' ? false : Boolean(newValue)), void 0
)
export const num: PropSetter<string | number> = ({ newValue, target, targetKey }) => (
(target[targetKey] = Number(newValue)), void 0
)
export const str: PropSetter<string | number> = ({ newValue, target, targetKey }) => (
(target[targetKey] = String(newValue)), void 0
)
export const pass = <T = any>({ newValue, target, targetKey }: PropSetterParameters<T>) => (
// 4.7 不调用可以传入范型
!is.empty(target) && (target[targetKey] = newValue), void 0
)
// 解决无法传范型的问题
// export const passWithType =
// <T>(test: ((newProp: any) => boolean)[] = [], propName?: any) =>
// (args: PropSetterParameters<T>) => {
// if (!(test.length === 0 || test.some((test) => test(args.newValue)))) {
// console.error(`${propName ? 'value of' : ''} \`${propName || args.newValue}\` is not correct`)
// }
// return pass<T>(args)
// }
// 相比 passWithType 多了一个 translate 进行转换
// export const passWithTranslate =
// <P extends (value: any) => any, T = P extends (value: infer S) => any ? S : never>(
// translate: P,
// test: ((newProp: any) => boolean)[] = [],
// propName?: any,
// ) =>
// (value: T) => {
// return passWithType(test, propName)(translate(value))
// }
export const instance =
<P>(constructor: new (...args: any) => any) =>
({ newValue, target, targetKey }: PropSetterParameters<P>) => {
if (newValue instanceof constructor) {
target[targetKey] = newValue
} else {
target[targetKey] = new constructor(...(is.arr(newValue) ? newValue : [newValue]))
}
}
// 数组逐一对比
export const flatArrDiffWithLevel = (level = Infinity) => {
const flatArr = (np: any, op: any) => {
if (np === op) return true
if (is.arr(np) && is.arr(op)) {
np = np.flat(level)
op = op.flat(level)
if (np.length !== op.length) return false
for (let i = 0; i < np.length; i++) {
if (np[i] !== op[i]) return false
}
return true
} else return false
}
return flatArr
}
}
export module Graphics {
export type FunProp = (
graphics: egret.Graphics,
instance: { graphics: egret.Graphics },
) => void | ((isRemove: boolean) => void)
export type GetGraphicsProp<K extends keyof egret.Graphics> = egret.Graphics[K] extends (...args: any) => any
? [K, ...Parameters<egret.Graphics[K]>]
: never
// 利用分布条件类型
export type GenerateGraphicsProp<T extends keyof egret.Graphics> = T extends any ? GetGraphicsProp<T> : never
export type GraphicActions =
| 'beginFill'
| 'endFill'
| 'clear'
| 'lineStyle'
| 'drawRect'
| 'drawRoundRect'
| 'drawCircle'
| 'drawEllipse'
| 'moveTo'
| 'lineTo'
| 'curveTo'
| 'cubicCurveTo'
| 'drawArc'
export type Prop = GenerateGraphicsProp<GraphicActions>[] | FunProp
export const setter: PropSetter<Prop, { graphics: egret.Graphics }> = ({ newValue, instance }) => {
if (is.arr(newValue)) {
for (const action of newValue) {
// @ts-ignore
instance.graphics[action[0]](...action.slice(1))
}
return () => instance.graphics.clear()
} else if (is.fun(newValue)) {
return newValue(instance.graphics, instance)
}
}
// graphics 铺平一层来比较
export const diff = NormalProp.flatArrDiffWithLevel(1)
}
export const graphicsProp = {
__Class: Object,
__setter: Graphics.setter,
__diff: Graphics.diff,
}
export module Point {
export type Prop = [] | number[] | egret.Point
export const setter: PropSetter<Prop> = NormalProp.instance(egret.Point)
export const diff: DiffHandler<Prop> = NormalProp.flatArrDiffWithLevel()
}
export const pointProp = {
__Class: egret.Point,
__setter: Point.setter,
__diff: Point.diff,
x: NormalProp.num,
y: NormalProp.num,
}
export module Rectangle {
export type Prop = [] | number[] | egret.Rectangle | void
export const setter: PropSetter<Prop> = NormalProp.instance(egret.Rectangle)
export const diff: DiffHandler<Prop> = NormalProp.flatArrDiffWithLevel()
}
export const rectangleProp = {
__Class: egret.Rectangle,
__setter: Rectangle.setter,
__diff: Rectangle.diff,
height: NormalProp.num,
width: NormalProp.num,
left: NormalProp.num,
right: NormalProp.num,
top: NormalProp.num,
bottom: NormalProp.num,
x: NormalProp.num,
y: NormalProp.num,
bottomRight: pointProp,
topLeft: pointProp,
}
export module Mask {
export type Prop = Rectangle.Prop | egret.DisplayObject
const rectSetter = NormalProp.instance(egret.Rectangle)
export const setter: PropSetter<Prop> = (props) => {
const { newValue } = props
if (newValue instanceof egret.DisplayObject) return NormalProp.pass(props)
else return rectSetter(props)
}
export const diff: DiffHandler<Prop> = NormalProp.flatArrDiffWithLevel()
}
export const maskProp = {
...rectangleProp,
__setter: Mask.setter,
__diff: Mask.diff,
}
export module Texture {
export type Prop = egret.DisplayObject | Rectangle.Prop
export const setter: PropSetter<Prop> = NormalProp.instance(egret.Texture)
export const diff: DiffHandler<Prop> = NormalProp.flatArrDiffWithLevel()
}
export const textureProp = {
__Class: egret.Texture,
__setter: Texture.setter,
__diff: Texture.diff,
bitmapData: NormalProp.pass<egret.Bitmap | void>,
disposeBitmapData: NormalProp.boo,
}
export module LayoutBase {
export type Prop = 'basic' | 'tile' | 'horizontal' | 'vertical' | eui.LayoutBase
}
export const layoutBaseProp = {
__Class: eui.LayoutBase,
__setter: ({ newValue, target, targetKey }: PropSetterParameters<LayoutBase.Prop>) => {
let value: eui.LayoutBase
if (newValue === 'basic') {
value = new eui.BasicLayout()
} else if (newValue === 'tile') {
value = new eui.TileLayout()
} else if (newValue === 'horizontal') {
value = new eui.HorizontalLayout()
} else if (newValue === 'vertical') {
value = new eui.VerticalLayout()
} else if (newValue instanceof eui.LayoutBase) {
value = newValue
} else {
// default to BasicLayout and warning user
value = new eui.BasicLayout()
DevThrow(
`The value of prop \`LayoutBase \` must be "basic" | "tile" | "horizontal" | "vertical" | eui.LayoutBase`,
)
}
target[targetKey] = value
},
__diff: (a: any, b: any) => a === b,
// 共有的属性
horizontalAlign: NormalProp.str,
verticalAlign: NormalProp.str,
paddingBottom: NormalProp.num,
paddingLeft: NormalProp.num,
paddingRight: NormalProp.num,
paddingTop: NormalProp.num,
// Tile 的独有属性
columnAlign: NormalProp.str,
columnCount: NormalProp.num,
columnWidth: NormalProp.num,
horizontalGap: NormalProp.num,
orientation: NormalProp.str,
requestedColumnCount: NormalProp.num,
requestedRowCount: NormalProp.num,
rowAlign: NormalProp.str,
rowCount: NormalProp.num,
rowHeight: NormalProp.num,
verticalGap: NormalProp.num,
// Linear 独有属性
gap: NormalProp.num,
// 根据组件来支持的属性
useVirtualLayout: NormalProp.boo,
}
export const euiBaseLayoutProp = {
bottom: NormalProp.num,
left: NormalProp.num,
right: NormalProp.num,
top: NormalProp.num,
explicitHeight: NormalProp.num,
explicitWidth: NormalProp.num,
horizontalCenter: NormalProp.str,
verticalCenter: NormalProp.str,
includeInLayout: NormalProp.boo,
maxHeight: NormalProp.num,
maxWidth: NormalProp.num,
minHeight: NormalProp.num,
minWidth: NormalProp.num,
percentHeight: NormalProp.num,
percentWidth: NormalProp.num,
...EventProp.uiEventSetters,
}