diff --git a/packages/av-canvas/src/sprites/render-ctrl.ts b/packages/av-canvas/src/sprites/render-ctrl.ts index 71067b83..d21ddb9c 100644 --- a/packages/av-canvas/src/sprites/render-ctrl.ts +++ b/packages/av-canvas/src/sprites/render-ctrl.ts @@ -3,6 +3,12 @@ import { CTRL_KEYS, TCtrlKey } from '../types'; import { createEl, getCvsRatio, getRectCtrls } from '../utils'; import { ESpriteManagerEvt, SpriteManager } from './sprite-manager'; +const CloseSvg = ` + + + +`; + export function renderCtrls( container: HTMLElement, cvsEl: HTMLCanvasElement, @@ -43,7 +49,6 @@ export function renderCtrls( lastActSprEvtClear = s.on('propsChange', () => { syncCtrlElPos(s, cvsEl, rectEl, ctrlsEl); }); - rectEl.style.display = ''; }); return () => { @@ -76,7 +81,8 @@ function createRectAndCtrlEl(container: HTMLElement): { d.style.cssText = ` display: none; position: absolute; - border: 1px solid #3ee; border-radius: 50%; + border: 1px solid #3ee; + border-radius: 50%; box-sizing: border-box; background-color: #fff; pointer-events: auto; @@ -112,14 +118,48 @@ function syncCtrlElPos( }); Object.entries(getRectCtrls(cvsEl, s.rect)).forEach(([k, { x, y, w, h }]) => { // ctrl 是相对中心点定位的 - Object.assign(ctrlsEl[k as TCtrlKey].style, { - display: 'block', + const baseStyle = { left: '50%', top: '50%', width: `${w * cvsRatio.w}px`, height: `${h * cvsRatio.h}px`, - // border 1px, 所以要 -1 transform: `translate(${x * cvsRatio.w}px, ${y * cvsRatio.h}px)`, - }); + }; + ctrlsEl[k as TCtrlKey].innerHTML = ''; + if (k === 'rotate') { + Object.assign(ctrlsEl[k as TCtrlKey].style, { + display: s.interactable === 'interactive' ? 'block' : 'none', + ...baseStyle, + }); + } else { + if (s.interactable === 'interactive') { + Object.assign(ctrlsEl[k as TCtrlKey].style, { + display: 'block', + backgroundColor: '#fff', + border: '1px solid #3ee', + ...baseStyle, + }); + } else if (s.interactable === 'selectable') { + Object.assign(ctrlsEl[k as TCtrlKey].style, { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'transparent', + border: 'none', + ...baseStyle, + }); + ctrlsEl[k as TCtrlKey].innerHTML = CloseSvg; + } else { + Object.assign(ctrlsEl[k as TCtrlKey].style, { + display: 'none', + ...baseStyle, + }); + } + } }); + if (s.interactable === 'disabled') { + rectEl.style.display = 'none'; + } else { + rectEl.style.display = ''; + } } diff --git a/packages/av-canvas/src/sprites/sprite-manager.ts b/packages/av-canvas/src/sprites/sprite-manager.ts index f31a387a..a2cbb080 100644 --- a/packages/av-canvas/src/sprites/sprite-manager.ts +++ b/packages/av-canvas/src/sprites/sprite-manager.ts @@ -22,7 +22,7 @@ export class SpriteManager { return this.#activeSprite; } set activeSprite(s: VisibleSprite | null) { - if (s === this.#activeSprite) return; + if (s === this.#activeSprite || s?.interactable === 'disabled') return; this.#activeSprite = s; this.#evtTool.emit(ESpriteManagerEvt.ActiveSpriteChange, s); } @@ -32,7 +32,10 @@ export class SpriteManager { this.getSprites() // 排在后面的层级更高 .reverse() - .find((s) => s.visible && s.rect.checkHit(x, y)) ?? null; + .find( + (s) => + s.visible && s.interactable !== 'disabled' && s.rect.checkHit(x, y), + ) ?? null; } async addSprite(vs: VisibleSprite): Promise { diff --git a/packages/av-canvas/src/sprites/sprite-op.ts b/packages/av-canvas/src/sprites/sprite-op.ts index 59d5f8bb..d544b06a 100644 --- a/packages/av-canvas/src/sprites/sprite-op.ts +++ b/packages/av-canvas/src/sprites/sprite-op.ts @@ -51,9 +51,14 @@ export function draggabelSprite( // 移动sprite的处理函数 const onRectMouseDown = (evt: MouseEvent): void => { - if (evt.button !== 0 || sprMng.activeSprite == null) return; - const hitSpr = sprMng.activeSprite; + if ( + evt.button !== 0 || + hitSpr == null || + hitSpr.interactable !== 'interactive' + ) + return; + const { clientX, clientY } = evt; startRect = hitSpr.rect.clone(); @@ -69,16 +74,22 @@ export function draggabelSprite( const cvsRatio = getCvsRatio(cvsEl); const onMouseMove = (evt: MouseEvent): void => { - if (sprMng.activeSprite == null || startRect == null) return; + const hitSpr = sprMng.activeSprite; + if ( + hitSpr == null || + hitSpr.interactable !== 'interactive' || + startRect == null + ) + return; const { clientX, clientY } = evt; let expectX = startRect.x + (clientX - startX) / cvsRatio.w; let expectY = startRect.y + (clientY - startY) / cvsRatio.h; updateRectWithSafeMargin( - sprMng.activeSprite.rect, + hitSpr.rect, cvsEl, - refline.magneticEffect(expectX, expectY, sprMng.activeSprite.rect), + refline.magneticEffect(expectX, expectY, hitSpr.rect), ); }; @@ -116,18 +127,24 @@ function setupCtrlEvents( ctrlElements.forEach((ctrlEl, index) => { const ctrlKey = CTRL_KEYS[index]; ctrlEl.addEventListener('pointerdown', (evt: MouseEvent) => { - if (evt.button !== 0 || sprMng.activeSprite == null) return; + const hitSpr = sprMng.activeSprite; + if ( + evt.button !== 0 || + hitSpr == null || + hitSpr.interactable !== 'interactive' + ) + return; const { clientX, clientY } = evt; if (ctrlKey === 'rotate') { rotateRect( - sprMng.activeSprite.rect, - cntMap2Outer(sprMng.activeSprite.rect.center, cvsRatio, cvsEl), + hitSpr.rect, + cntMap2Outer(hitSpr.rect.center, cvsRatio, cvsEl), ); } else { scaleRect({ - sprRect: sprMng.activeSprite.rect, + sprRect: hitSpr.rect, ctrlKey, startX: clientX, startY: clientY, diff --git a/packages/av-cliper/src/sprite/visible-sprite.ts b/packages/av-cliper/src/sprite/visible-sprite.ts index a53e8d4d..524d9596 100644 --- a/packages/av-cliper/src/sprite/visible-sprite.ts +++ b/packages/av-cliper/src/sprite/visible-sprite.ts @@ -30,6 +30,15 @@ export class VisibleSprite extends BaseSprite { */ visible = true; + /** + * 控制 Sprite 的交互状态 + * - 'interactive': 可选中,可进行移动、缩放、旋转等所有交互 + * - 'selectable': 仅可选中,但不可进行移动、缩放、旋转等交互 + * - 'disabled': 不可选中,也不可交互 + * @default 'interactive' + */ + interactable: 'interactive' | 'selectable' | 'disabled' = 'interactive'; + constructor(clip: IClip) { super(); this.#clip = clip; @@ -108,6 +117,7 @@ export class VisibleSprite extends BaseSprite { super.copyStateTo(target); if (target instanceof VisibleSprite) { target.visible = this.visible; + target.interactable = this.interactable; } }