import { logger } from '@motajs/common'; import { ERenderItemEvent, RenderItem, RenderItemPosition, MotaOffscreenCanvas2D, Transform } from '@motajs/render-core'; import { SizedCanvasImageSource } from '@motajs/render-elements'; import { isNil } from 'lodash-es'; import { RenderableData, AutotileRenderable, texture } from './cache'; import { IAnimateFrame, renderEmits } from './frame'; export interface EIconEvent extends ERenderItemEvent {} export class Icon extends RenderItem implements IAnimateFrame { /** 图标id */ icon: AllNumbers = 0; /** 帧数 */ frame: number = 0; /** 是否启用动画 */ animate: boolean = false; /** 图标的渲染信息 */ private renderable?: RenderableData | AutotileRenderable; private pendingIcon?: AllNumbers; constructor(type: RenderItemPosition, cache?: boolean, fall?: boolean) { super(type, cache, fall); this.setAntiAliasing(false); this.setHD(false); } protected render( canvas: MotaOffscreenCanvas2D, _transform: Transform ): void { const ctx = canvas.ctx; const renderable = this.renderable; if (!renderable) return; const [x, y, w, h] = renderable.render[0]; const cw = this.width; const ch = this.height; const frame = this.animate ? RenderItem.animatedFrame % renderable.frame : this.frame; if (!this.animate) { if (renderable.autotile) { ctx.drawImage(renderable.image[0], x, y, w, h, 0, 0, cw, ch); } else { ctx.drawImage(renderable.image, x, y, w, h, 0, 0, cw, ch); } } else { const [x1, y1, w1, h1] = renderable.render[frame]; if (renderable.autotile) { const img = renderable.image[0]; ctx.drawImage(img, x1, y1, w1, h1, 0, 0, cw, ch); } else { ctx.drawImage(renderable.image, x1, y1, w1, h1, 0, 0, cw, ch); } } } /** * 设置图标 * @param id 图标id */ setIcon(id: AllIds | AllNumbers) { if (id === 0) { this.renderable = void 0; return; } const num = typeof id === 'number' ? id : texture.idNumberMap[id]; const { loading } = Mota.require('@user/data-base'); if (loading.loaded) { this.setIconRenderable(num); } else { if (isNil(this.pendingIcon)) { loading.once('loaded', () => { this.setIconRenderable(this.pendingIcon ?? 0); delete this.pendingIcon; }); } this.pendingIcon = num; } } private setIconRenderable(num: AllNumbers) { const renderable = texture.getRenderable(num); if (!renderable) { logger.warn(43, num.toString()); return; } else { this.icon = num; this.renderable = renderable; this.frame = renderable.frame; } this.update(); } /** * 更新动画帧 */ updateFrameAnimate(): void { if (this.animate) this.update(this); } destroy(): void { renderEmits.removeFramer(this); super.destroy(); } protected handleProps( key: string, _prevValue: any, nextValue: any ): boolean { switch (key) { case 'icon': this.setIcon(nextValue); return true; case 'animate': if (!this.assertType(nextValue, 'boolean', key)) return false; this.animate = nextValue; if (nextValue) renderEmits.addFramer(this); else renderEmits.removeFramer(this); this.update(); return true; case 'frame': if (!this.assertType(nextValue, 'number', key)) return false; this.frame = nextValue; this.update(); return true; } return false; } } interface WinskinPatterns { top: CanvasPattern; left: CanvasPattern; bottom: CanvasPattern; right: CanvasPattern; } export interface EWinskinEvent extends ERenderItemEvent {} export class Winskin extends RenderItem { image: SizedCanvasImageSource; /** 边框宽度,32表示原始宽度 */ borderSize: number = 32; /** 图片名称 */ imageName?: string; private pendingImage?: ImageIds; private patternCache?: WinskinPatterns; private patternTransform: DOMMatrix; private static patternMap: Map = new Map(); constructor( image: SizedCanvasImageSource, type: RenderItemPosition = 'static' ) { super(type, false, false); this.image = image; this.setAntiAliasing(false); if (window.DOMMatrix) { this.patternTransform = new DOMMatrix(); } else if (window.WebKitCSSMatrix) { this.patternTransform = new WebKitCSSMatrix(); } else { this.patternTransform = new SVGMatrix(); } } private generatePattern() { const pattern = this.requireCanvas(true, false); pattern.setScale(1); const img = this.image; pattern.size(32, 16); pattern.setHD(false); pattern.setAntiAliasing(false); const ctx = pattern.ctx; ctx.drawImage(img, 144, 0, 32, 16, 0, 0, 32, 16); const topPattern = ctx.createPattern(pattern.canvas, 'repeat'); ctx.clearRect(0, 0, 32, 16); ctx.drawImage(img, 144, 48, 32, 16, 0, 0, 32, 16); const bottomPattern = ctx.createPattern(pattern.canvas, 'repeat'); ctx.clearRect(0, 0, 32, 16); pattern.size(16, 32); ctx.drawImage(img, 128, 16, 16, 32, 0, 0, 16, 32); const leftPattern = ctx.createPattern(pattern.canvas, 'repeat'); ctx.clearRect(0, 0, 16, 32); ctx.drawImage(img, 176, 16, 16, 32, 0, 0, 16, 32); const rightPattern = ctx.createPattern(pattern.canvas, 'repeat'); if (!topPattern || !bottomPattern || !leftPattern || !rightPattern) { return null; } const winskinPattern: WinskinPatterns = { top: topPattern, bottom: bottomPattern, left: leftPattern, right: rightPattern }; if (this.imageName) { Winskin.patternMap.set(this.imageName, winskinPattern); } this.patternCache = winskinPattern; return winskinPattern; } private getPattern() { if (!this.imageName) { if (this.patternCache) return this.patternCache; return this.generatePattern(); } else { const pattern = Winskin.patternMap.get(this.imageName); if (pattern) return pattern; return this.generatePattern(); } } protected render( canvas: MotaOffscreenCanvas2D, _transform: Transform ): void { const ctx = canvas.ctx; const img = this.image; const w = this.width; const h = this.height; const pad = this.borderSize / 2; // 背景 ctx.drawImage(img, 0, 0, 128, 128, 2, 2, w - 4, h - 4); const pattern = this.getPattern(); if (!pattern) return; const { top, left, right, bottom } = pattern; top.setTransform(this.patternTransform); left.setTransform(this.patternTransform); right.setTransform(this.patternTransform); bottom.setTransform(this.patternTransform); // 上下左右边框 ctx.save(); ctx.fillStyle = top; ctx.translate(pad, 0); ctx.fillRect(0, 0, w - pad * 2, pad); ctx.fillStyle = bottom; ctx.translate(0, h - pad); ctx.fillRect(0, 0, w - pad * 2, pad); ctx.fillStyle = left; ctx.translate(-pad, pad * 2 - h); ctx.fillRect(0, 0, pad, h - pad * 2); ctx.fillStyle = right; ctx.translate(w - pad, 0); ctx.fillRect(0, 0, pad, h - pad * 2); ctx.restore(); // 四个角的边框 ctx.drawImage(img, 128, 0, 16, 16, 0, 0, pad, pad); ctx.drawImage(img, 176, 0, 16, 16, w - pad, 0, pad, pad); ctx.drawImage(img, 128, 48, 16, 16, 0, h - pad, pad, pad); ctx.drawImage(img, 176, 48, 16, 16, w - pad, h - pad, pad, pad); } /** * 设置winskin图片 * @param image winskin图片 */ setImage(image: SizedCanvasImageSource) { this.image = image; this.patternCache = void 0; this.update(); } /** * 通过图片名称设置winskin * @param name 图片名称 */ setImageByName(name: ImageIds) { const { loading } = Mota.require('@user/data-base'); if (loading.loaded) { const image = core.material.images.images[name]; this.setImage(image); } else { if (isNil(this.pendingImage)) { loading.once('loaded', () => { const id = this.pendingImage; if (!id) return; const image = core.material.images.images[id]; this.setImage(image); delete this.pendingImage; }); } this.pendingImage = name; } this.imageName = name; } /** * 设置边框大小 * @param size 边框大小 */ setBorderSize(size: number) { this.borderSize = size; this.patternTransform.a = size / 32; this.patternTransform.d = size / 32; this.update(); } protected handleProps( key: string, _prevValue: any, nextValue: any ): boolean { switch (key) { case 'image': if (!this.assertType(nextValue, 'string', key)) return false; this.setImageByName(nextValue); return true; case 'borderSize': if (!this.assertType(nextValue, 'number', key)) return false; this.setBorderSize(nextValue); return true; } return false; } }