import { TypeContext , TypePoint , TypeData , TypeHelperWrapperControllerDirection , TypeElement , TypeElemDesc , } from '@idraw/types' ; import util from '@idraw/util' ; import { rotateElement } from './transform' ; import { calcRadian , calcElementCenter , parseRadianToAngle } from './calculate' ; import { limitAngle , limitNum } from './value' ; import { LIMIT_QBLIQUE_ANGLE } from './../constant/element' ; const { createUUID } = util . uuid ; const limitQbliqueAngle = LIMIT_QBLIQUE_ANGLE ; export class Element { private _ctx : TypeContext ; constructor ( ctx ) { this . _ctx = ctx ; } initData ( data ) { data . elements . forEach ( ( elem ) => { if ( ! ( elem . uuid && typeof elem . uuid === 'string' ) ) { elem . uuid = createUUID ( ) ; } } ) ; return data ; } isPointInElement ( p , data ) { const ctx = this . _ctx ; let idx = - 1 ; let uuid = null ; for ( let i = data . elements . length - 1 ; i >= 0 ; i -- ) { const ele = data . elements [ i ] ; if ( ele . operation ?. invisible === true ) continue ; let bw = 0 ; if ( ele . desc ?. borderWidth > 0 ) { bw = ele . desc . borderWidth ; } rotateElement ( ctx , ele , ( ) => { ctx . beginPath ( ) ; ctx . moveTo ( ele . x - bw , ele . y - bw ) ; ctx . lineTo ( ele . x + ele . w + bw , ele . y - bw ) ; ctx . lineTo ( ele . x + ele . w + bw , ele . y + ele . h + bw ) ; ctx . lineTo ( ele . x - bw , ele . y + ele . h + bw ) ; ctx . lineTo ( ele . x - bw , ele . y - bw ) ; ctx . closePath ( ) ; if ( ctx . isPointInPath ( p . x , p . y ) ) { idx = i ; uuid = ele . uuid ; } } ) ; if ( idx >= 0 ) { break ; } } return [ idx , uuid ] ; } dragElement ( data , uuid , point , prevPoint , scale ) { const index = this . getElementIndex ( data , uuid ) ; if ( ! data . elements [ index ] ) { return ; } const moveX = point . x - prevPoint . x ; const moveY = point . y - prevPoint . y ; data . elements [ index ] . x += ( moveX / scale ) ; data . elements [ index ] . y += ( moveY / scale ) ; this . limitElementAttrs ( data . elements [ index ] ) ; } transformElement ( data , uuid , point , prevPoint , scale , direction ) { const index = this . getElementIndex ( data , uuid ) ; if ( ! data . elements [ index ] ) { return null ; } if ( data . elements [ index ] ?. operation ?. lock === true ) { return null ; } let moveX = ( point . x - prevPoint . x ) / scale ; let moveY = ( point . y - prevPoint . y ) / scale ; const elem = data . elements [ index ] ; if ( [ 'top-left' , 'top' , 'top-right' , 'right' , 'bottom-right' , 'bottom' , 'bottom-left' , 'left' ] . includes ( direction ) ) { const p = calcuScaleElemPosition ( elem , moveX , moveY , direction , scale ) ; elem . x = p . x ; elem . y = p . y ; elem . w = p . w ; elem . h = p . h ; } else if ( direction === 'rotate' ) { const center = calcElementCenter ( elem ) ; const radian = calcRadian ( center , prevPoint , point ) ; elem . angle = ( elem . angle || 0 ) + parseRadianToAngle ( radian ) ; } this . limitElementAttrs ( elem ) ; return { width : limitNum ( elem . w ) , height : limitNum ( elem . h ) , angle : limitAngle ( elem . angle || 0 ) , } ; } getElementIndex ( data , uuid ) { let idx = - 1 ; for ( let i = 0 ; i < data . elements . length ; i ++ ) { if ( data . elements [ i ] . uuid === uuid ) { idx = i ; break ; } } return idx ; } limitElementAttrs ( elem ) { elem . x = limitNum ( elem . x ) ; elem . y = limitNum ( elem . y ) ; elem . w = limitNum ( elem . w ) ; elem . h = limitNum ( elem . h ) ; elem . angle = limitAngle ( elem . angle || 0 ) ; } } function calcuScaleElemPosition ( elem , moveX , moveY , direction , scale ) { const p = { x : elem . x , y : elem . y , w : elem . w , h : elem . h } ; let angle = elem . angle ; if ( angle < 0 ) { angle = Math . max ( 0 , 360 + angle ) ; } switch ( direction ) { case 'top-left' : { if ( elem . w - moveX > 0 && elem . h - moveY > 0 ) { p . x += moveX ; p . y += moveY ; p . w -= moveX ; p . h -= moveY ; } break ; } case 'top' : { if ( elem . angle === 0 || Math . abs ( elem . angle ) < limitQbliqueAngle ) { if ( p . h - moveY > 0 ) { p . y += moveY ; p . h -= moveY ; } } else if ( elem . angle > 0 || elem . angle < 0 ) { const angle = elem . angle > 0 ? elem . angle : Math . max ( 0 , elem . angle + 360 ) ; let moveDist = calcMoveDist ( moveX , moveY ) ; let centerX = p . x + elem . w / 2 ; let centerY = p . y + elem . h / 2 ; if ( angle < 90 ) { moveDist = 0 - changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . sin ( radian ) ; centerY = centerY - centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 180 ) { moveDist = changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 90 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . cos ( radian ) ; centerY = centerY + centerMoveDist * Math . sin ( radian ) ; } else if ( angle < 270 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle - 180 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . sin ( radian ) ; centerY = centerY + centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 360 ) { moveDist = 0 - changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 270 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . cos ( radian ) ; centerY = centerY - centerMoveDist * Math . sin ( radian ) ; } if ( p . h + moveDist > 0 ) { p . h = p . h + moveDist ; p . x = centerX - p . w / 2 ; p . y = centerY - p . h / 2 ; } } else { if ( p . h - moveY > 0 ) { p . y += moveY ; p . h -= moveY ; } } break ; } case 'top-right' : { if ( p . h - moveY > 0 && p . w + moveX > 0 ) { p . y += moveY ; p . w += moveX ; p . h -= moveY ; } break ; } case 'right' : { if ( elem . angle === 0 || Math . abs ( elem . angle ) < limitQbliqueAngle ) { if ( elem . w + moveX > 0 ) { p . w += moveX ; } } else if ( elem . angle > 0 || elem . angle < 0 ) { const angle = elem . angle > 0 ? elem . angle : Math . max ( 0 , elem . angle + 360 ) ; let moveDist = calcMoveDist ( moveX , moveY ) ; let centerX = p . x + elem . w / 2 ; let centerY = p . y + elem . h / 2 ; if ( angle < 90 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . cos ( radian ) ; centerY = centerY + centerMoveDist * Math . sin ( radian ) ; } else if ( angle < 180 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle - 90 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . sin ( radian ) ; centerY = centerY + centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 270 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle - 180 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . cos ( radian ) ; centerY = centerY + centerMoveDist * Math . sin ( radian ) ; moveDist = 0 - moveDist ; } else if ( angle < 360 ) { moveDist = changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 270 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . sin ( radian ) ; centerY = centerY - centerMoveDist * Math . cos ( radian ) ; } if ( p . w + moveDist > 0 ) { p . w = p . w + moveDist ; p . x = centerX - p . w / 2 ; p . y = centerY - p . h / 2 ; } } else { if ( elem . w + moveX > 0 ) { p . w += moveX ; } } break ; } case 'bottom-right' : { if ( elem . w + moveX > 0 && elem . h + moveY > 0 ) { p . w += moveX ; p . h += moveY ; } break ; } case 'bottom' : { if ( elem . angle === 0 || Math . abs ( elem . angle ) < limitQbliqueAngle ) { if ( elem . h + moveY > 0 ) { p . h += moveY ; } } else if ( elem . angle > 0 || elem . angle < 0 ) { const angle = elem . angle > 0 ? elem . angle : Math . max ( 0 , elem . angle + 360 ) ; let moveDist = calcMoveDist ( moveX , moveY ) ; let centerX = p . x + elem . w / 2 ; let centerY = p . y + elem . h / 2 ; if ( angle < 90 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . sin ( radian ) ; centerY = centerY + centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 180 ) { moveDist = 0 - changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 90 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . cos ( radian ) ; centerY = centerY - centerMoveDist * Math . sin ( radian ) ; } else if ( angle < 270 ) { moveDist = changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 180 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . sin ( radian ) ; centerY = centerY - centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 360 ) { moveDist = changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 270 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . cos ( radian ) ; centerY = centerY + centerMoveDist * Math . sin ( radian ) ; } if ( p . h + moveDist > 0 ) { p . h = p . h + moveDist ; p . x = centerX - p . w / 2 ; p . y = centerY - p . h / 2 ; } } else { if ( elem . h + moveY > 0 ) { p . h += moveY ; } } break ; } case 'bottom-left' : { if ( elem . w - moveX > 0 && elem . h + moveY > 0 ) { p . x += moveX ; p . w -= moveX ; p . h += moveY ; } break ; } case 'left' : { if ( elem . angle === 0 || Math . abs ( elem . angle ) < limitQbliqueAngle ) { if ( elem . w - moveX > 0 ) { p . x += moveX ; p . w -= moveX ; } } else if ( elem . angle > 0 || elem . angle < 0 ) { const angle = elem . angle > 0 ? elem . angle : Math . max ( 0 , elem . angle + 360 ) ; let moveDist = calcMoveDist ( moveX , moveY ) ; let centerX = p . x + elem . w / 2 ; let centerY = p . y + elem . h / 2 ; if ( angle < 90 ) { moveDist = 0 - changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . cos ( radian ) ; centerY = centerY - centerMoveDist * Math . sin ( radian ) ; } else if ( angle < 180 ) { moveDist = changeMoveDistDirect ( moveDist , moveX ) ; const radian = parseRadian ( angle - 90 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . sin ( radian ) ; centerY = centerY - centerMoveDist * Math . cos ( radian ) ; } else if ( angle < 270 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle - 180 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX + centerMoveDist * Math . cos ( radian ) ; centerY = centerY + centerMoveDist * Math . sin ( radian ) ; } else if ( angle < 360 ) { moveDist = changeMoveDistDirect ( moveDist , moveY ) ; const radian = parseRadian ( angle - 270 ) ; const centerMoveDist = moveDist / 2 ; centerX = centerX - centerMoveDist * Math . sin ( radian ) ; centerY = centerY + centerMoveDist * Math . cos ( radian ) ; } if ( p . w + moveDist > 0 ) { p . w = p . w + moveDist ; p . x = centerX - p . w / 2 ; p . y = centerY - p . h / 2 ; } } else { if ( elem . w - moveX > 0 ) { p . x += moveX ; p . w -= moveX ; } } break ; } default : { break ; } } return p ; } function parseRadian ( angle ) { return angle * Math . PI / 180 ; } function calcMoveDist ( moveX , moveY ) { return Math . sqrt ( moveX * moveX + moveY * moveY ) ; } function changeMoveDistDirect ( moveDist , moveDirect ) { return moveDirect > 0 ? Math . abs ( moveDist ) : 0 - Math . abs ( moveDist ) ; }