import { Renderer } from '@pixi/core' ; import { DisplayObject , Container } from '@pixi/display' ; import { Point , Matrix , Transform , Rectangle } from '@pixi/math' ; import { OrientedBounds } from '@pixi-essentials/bounds' ; import { ObjectPoolFactory } from '@pixi-essentials/object-pool' ; import { TransformerHandle } from './TransformerHandle' ; import { TransformerWireframe } from './TransformerWireframe' ; import { createHorizontalSkew , createVerticalSkew } from './utils/skewTransform' ; import { decomposeTransform } from './utils/decomposeTransform' ; import { multiplyTransform } from './utils/multiplyTransform' ; import type { InteractionEvent } from '@pixi/interaction' ; import type { ITransformerHandleStyle } from './TransformerHandle' ; const tempTransform = new Transform ( ) ; const tempCorners = [ new Point ( ) , new Point ( ) , new Point ( ) , new Point ( ) ] ; const tempMatrix = new Matrix ( ) ; const tempPoint = new Point ( ) ; const tempBounds = new OrientedBounds ( ) ; const tempRect = new Rectangle ( ) ; const tempHull = [ new Point ( ) , new Point ( ) , new Point ( ) , new Point ( ) ] ; const tempPointer = new Point ( ) ; const emitMatrix = new Matrix ( ) ; const pointPool = ObjectPoolFactory . build < Point > ( Point as any ) ; public ignore export type RotateHandle = 'rotator' | 'boxRotateTopLeft' | 'boxRotateTopRight' | 'boxRotateBottomLeft' | 'boxRotateBottomRight' ; public ignore export type ScaleHandle = 'topLeft' | 'topCenter' | 'topRight' | 'middleLeft' | 'middleCenter' | 'middleRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' ; public ignore export type SkewHandle = 'skewHorizontal' | 'skewVertical' ; /**
 * All the handles provided by  Transformer .
 *
 *  public ignore export type Handle = RotateHandle | ScaleHandle | SkewHandle ; ignore export const HANDLE_TO_CURSOR = { topLeft : 'nw-resize' , topCenter : 'n-resize' , topRight : 'ne-resize' , middleLeft : 'w-resize' , middleRight : 'e-resize' , bottomLeft : 'sw-resize' , bottomCenter : 's-resize' , bottomRight : 'se-resize' , } ; /**
 * An array of all  ScaleHandle values.
 *
 *  internal ignore const SCALE_HANDLES = [ 'topLeft' , 'topCenter' , 'topRight' , 'middleLeft' , 'middleCenter' , 'middleRight' , 'bottomLeft' , 'bottomCenter' , 'bottomRight' , ] ; internal ignore const SCALE_COMPONENTS = { topLeft : { x : - 1 , y : - 1 } , topCenter : { x : 0 , y : - 1 } , topRight : { x : 1 , y : - 1 } , middleLeft : { x : - 1 , y : 0 } , middleCenter : { x : 0 , y : 0 } , middleRight : { x : 1 , y : 0 } , bottomLeft : { x : - 1 , y : 1 } , bottomCenter : { x : 0 , y : 1 } , bottomRight : { x : 1 , y : 1 } , } ; /**
 * All possible values of  Handle .
 *
 *  ignore const HANDLES = [ ... SCALE_HANDLES , 'rotator' , 'skewHorizontal' , 'skewVertical' , ] ; ignore const DEFAULT_BOX_SCALING_TOLERANCE = 4 ; ignore const DEFUALT_BOX_ROTATION_TOLERANCE = 16 ; ignore const DEFAULT_ROTATION_SNAPS = [ Math . PI / 4 , Math . PI / 2 , Math . PI * 3 / 4 , Math . PI , 0 , - Math . PI / 4 , - Math . PI / 2 , - Math . PI * 3 / 4 , - Math . PI , ] ; ignore const DEFAULT_ROTATION_SNAP_TOLERANCE = Math . PI / 90 ; ignore const DEFAULT_SKEW_SNAPS = [ Math . PI / 4 , - Math . PI / 4 , ] ; ignore const DEFAULT_SKEW_SNAP_TOLERANCE = Math . PI / 90 ; ignore export interface ITransformerStyle { color : number ; thickness : number ; } /**
 * The default wireframe style for  Transformer .
 *
 *  ignore const DEFAULT_WIREFRAME_STYLE = { color : 0x000000 , thickness : 2 , } ; public export interface ITransformerCursors { default : string ; boxRotate ? : string ; boxScaleLeft ? : string ; boxScaleTop ? : string ; boxScaleRight ? : string ; boxScaleBottom ? : string ; translate ? : string ; } public export interface ITransformerOptions { /** "all" or "groupOnly". "groupOnly" won't show individual bounding boxes. */ boundingBoxes ? : 'all' | 'groupOnly' | 'none' ; /** Enable box rotation by dragging outside of corners */ boxRotationEnabled ? : boolean ; /** The radial tolerance for box rotation */ boxRotationTolerance ? : number ; /** Enable box scaling by dragging at the edges */ boxScalingEnabled ? : boolean ; /** The tolerance for scaling at the edges */ boxScalingTolerance : number ; /** Whether scaling should always be centered */ centeredScaling : boolean ; /** */ cursors ? : ITransformerCursors ; /** Specifically define which handles are to be enabled */ enabledHandles ? : Array < Handle > ; /** The group of display-objects being transformed */ group : DisplayObject [ ] ; /** A custom transformer-handle class */ handleConstructor : typeof TransformerHandle ; /** Styling options for the handle. These cannot be modified afterwards! */ handleStyle : Partial < ITransformerHandleStyle > ; /** Lock aspect ratio when scaling using one of the corner handles. */ lockAspectRatio ? : boolean ; /** Whether rotate handles are enabled */ rotateEnabled ? : boolean ; /**
     * The rotation snap angles, in radians. By default, transformer will
     * snap for each 1/8th of a revolution.
     */ rotationSnaps ? : number [ ] ; /** The snap tolerance for rotation in radians */ rotationSnapTolerance ? : number ; /** Whether scale handles are enabled */ scaleEnabled ? : boolean ; /** Whether skew handles are enabled */ skewEnabled ? : boolean ; /**
     * Distance of skew handles from center of transformer box
     *      (`skewTransform` should be enabled)
     */ skewRadius ? : number ; /** The skew snap angles, in radians. */ skewSnaps ? : number [ ] ; /** The skew snap tolerance angle. */ skewSnapTolerance ? : number ; /** The root object in your scene in which the transformed objects exist. */ stage : Container ; /** Whether dragging the transformer should move the group */ translateEnabled ? : boolean ; /**
     * Whether the transformer should reset the wireframe's rotation
     * after a rotator handle is "defocused".
     */ transientGroupTilt ? : boolean ; /** Styling options for the wireframe. */ wireframeStyle : Partial < ITransformerStyle > ; } /**
 * {@code Transformer} provides an interactive interface for editing the transforms in a group. It supports translating,
 * scaling, rotating, and skewing display-objects both through interaction and code.
 *
 * A transformer operates in world-space, and it is best to not position, scale, rotate, or skew one. If you do so, the
 * wireframe itself will not distort (i.e. will adapt _against_ your transforms). However, the wireframe may become
 * thinner/thicker and the handles will scale & rotate. For example, setting `transformer.scale.set(2)` will make the handles
 * twice as big, but will not scale the wireframe (assuming the display-object group itself has not been
 * scaled up).
 *
 * To enable scaling via dragging the edges of the wireframe, set `boxScalingEnabled` to `true`.
 *
 * NOTE: The transformer needs to capture all interaction events that would otherwise go to the display-objects in the
 * group. Hence, it must be placed after them in the scene graph.
 */ export class Transformer extends Container { /** The group of display-objects under transformation. */ public group : DisplayObject [ ] ; default public boundingBoxes : 'all' | 'groupOnly' | 'none' ; /** Set this to enable rotation at the four corners */ public boxRotationEnabled : boolean ; /** The thickness of the box rotation area */ public boxRotationTolerance : number ; /** Set this to enable scaling by dragging at the edges of the bounding box */ public boxScalingEnabled : boolean ; /** The padding around the bounding-box to capture dragging on the edges. */ public boxScalingTolerance : number ; /** This will prevent the wireframe's center from shifting on scaling. */ public centeredScaling : boolean ; /** Cursors to use in the transformer */ public cursors : ITransformerCursors ; default public lazyMode : boolean ; /** Set this when you want the transformer to redraw when using  Transformer .  public lazyDirty : boolean ; /** Lock aspect ratio when using one of the corner handles. */ public lockAspectRatio : boolean ; /**
     * This is used when the display-object group are rendered through a projection transformation (i.e. are disconnected
     * from the transformer in the scene graph). The transformer project itself into their frame-of-reference using this
     * transform.
     *
     * Specifically, the projection-transform converts points from the group's world space to the transformer's world
     * space. If you are not applying a projection on the transformer itself, this means it is the group's
     * world-to-screen transformation.
     */ public projectionTransform : Matrix ; /** The angles at which rotation should snap. */ public rotationSnaps : number [ ] ; /** The maximum angular difference for snapping rotation. */ public rotationSnapTolerance : number ; /** The distance of skewing handles from the group's center. */ public skewRadius : number ; /** The angles at which both the horizontal & vertical skew handles should snap. */ public skewSnaps : number [ ] ; /**
     * The maximum angular difference for snapping skew handles.
     */ public skewSnapTolerance : number ; /**
     * The root object in your scene in which objects can move.
     *
     * {@code Transformer} will subscribe to this object for `pointermove` events, if provided. This
     * should be used when:
     *
     * *  InteractionManager . moveWhenInside is enabled on the interaction plugin.
     * *  EventBoundary . moveOnAll is not turned off when using the new  EventSystem .
     *
     * Otherwise, the transformer will receive **not** `pointermove` events when the user drags fast enough that
     * the cursor leaves the transformer's bounds.
     *
     * The stage must be fully interactive in the area you want objects to move. Generally, this is the
     * whole canvas:
     *
     * ```ts
     * stage.interactive = true;
     * stage.hitArea = renderer.screen;// or pass custom rect for the canvas dimensions
     * ```
      public stage : Container | null ; default public translateEnabled : boolean ; default public transientGroupTilt : boolean ; /** The last calculated bounds of the whole group being transformed */ protected groupBounds : OrientedBounds ; /** Object mapping handle-names to the handle display-objects. */ protected handles : { [ H in Handle ] ? : TransformerHandle ; } ; internal ignore public handleAnchors : { [ H in Handle ] : Point ; } ; /** Draws the bounding boxes */ protected wireframe : TransformerWireframe ; see Transformer protected _enabledHandles : Handle [ ] ; see Transformer protected _rotateEnabled : boolean ; see Transformer protected _scaleEnabled : boolean ; see Transformer protected _skewEnabled : boolean ; /** The horizontal skew value. Rotating the group by 𝜽 will also change this value by 𝜽. */ protected _skewX : number ; /** The vertical skew value. Rotating the group by 𝜽 will also change this value by 𝜽. */ protected _skewY : number ; /** The currently grabbed handle. This can be used to get the type of transformation. */ protected _transformHandle : Handle ; /** The current type of transform being applied by the user. */ protected _transformType : 'translate' | 'scale' | 'rotate' | 'skew' | 'none' ; /** The style applied on transformer handles */ protected _handleStyle : Partial < ITransformerHandleStyle > ; /** The wireframe style applied on the transformer */ protected _wireframeStyle : Partial < ITransformerStyle > ; private _pointerDown : boolean ; private _pointerDragging : boolean ; private _pointerPosition : Point ; private _pointerMoveTarget : DisplayObject ; /**
     * | Handle                | Type                     | Notes |
     * | --------------------- | ------------------------ | ----- |
     * | rotator               | Rotate                   | |
     * | boxRotateTopLeft      | Rotate                   | Invisible |
     * | boxRotateTopRight     | Rotate                   | Invisible |
     * | boxRotateBottomLeft   | Rotate                   | Invisible |
     * | boxRotateBottomRight  | Rotate                   | Invisible |
     * | topLeft               | Scale                    | |
     * | topCenter             | Scale                    | |
     * | topRight              | Scale                    | |
     * | middleLeft            | Scale                    | |
     * | middleCenter          | Scale                    | This cannot be enabled!                                             |
     * | middleRight           | Scale                    | |
     * | bottomLeft            | Scale                    | |
     * | bottomCenter          | Scale                    | |
     * | bottomRight           | Scale                    | |
     * | skewHorizontal        | Skew                     | Applies vertical shear. Handle segment is horizontal at skew.y = 0! |
     * | skewVertical          | Skew                     | Applied horizontal shear. Handle segment is vertical at skew.x = 0! |
     */ constructor ( options = { } ) { super ( ) ; this . interactive = true ; this . cursors = Object . assign ( { default : 'move' } , options . cursors ) ; this . cursor = this . cursors . default ; this . boundingBoxes = options . boundingBoxes || 'all' ; this . group = options . group || [ ] ; this . boxRotationTolerance = options . boxRotationTolerance || DEFUALT_BOX_ROTATION_TOLERANCE ; this . boxScalingTolerance = options . boxScalingTolerance || DEFAULT_BOX_SCALING_TOLERANCE ; this . centeredScaling = ! ! options . centeredScaling ; this . projectionTransform = new Matrix ( ) ; this . lockAspectRatio = options . lockAspectRatio === true ; this . rotationSnaps = options . rotationSnaps || DEFAULT_ROTATION_SNAPS ; this . rotationSnapTolerance = options . rotationSnapTolerance !== undefined ? options . rotationSnapTolerance : DEFAULT_ROTATION_SNAP_TOLERANCE ; this . skewRadius = options . skewRadius || 64 ; this . skewSnaps = options . skewSnaps || DEFAULT_SKEW_SNAPS ; this . skewSnapTolerance = options . skewSnapTolerance !== undefined ? options . skewSnapTolerance : DEFAULT_SKEW_SNAP_TOLERANCE ; this . boxRotationEnabled = options . boxRotationEnabled === true ; this . boxScalingEnabled = options . boxScalingEnabled === true ; this . _rotateEnabled = options . rotateEnabled !== false ; this . _scaleEnabled = options . scaleEnabled !== false ; this . _skewEnabled = options . skewEnabled === true ; this . translateEnabled = options . translateEnabled !== false ; this . transientGroupTilt = options . transientGroupTilt !== undefined ? options . transientGroupTilt : true ; this . wireframe = this . addChild ( new TransformerWireframe ( this ) ) ; this . wireframe . cursor = 'none' ; this . stage = options . stage || null ; this . _skewX = 0 ; this . _skewY = 0 ; this . _transformType = 'none' ; this . _wireframeStyle = Object . assign ( { } , DEFAULT_WIREFRAME_STYLE , options . wireframeStyle || { } ) ; const HandleConstructor = options . handleConstructor || TransformerHandle ; const handleStyle = options . handleStyle || { } ; this . _handleStyle = handleStyle ; const rotatorHandles = { rotator : this . addChild ( new HandleConstructor ( this , 'rotator' , handleStyle , ( pointerPosition ) => { this . rotateGroup ( 'rotator' , pointerPosition ) ; } , this . commitGroup ) ) , } ; const scaleHandles = SCALE_HANDLES . reduce ( ( scaleHandles , handleKey ) => { const handle = new HandleConstructor ( this , handleKey , handleStyle , null , this . commitGroup , HANDLE_TO_CURSOR [ handleKey ] ) ; handle . onHandleDelta = ( pointerPosition ) : void => { this . scaleGroup ( handle . handle as ScaleHandle , pointerPosition ) ; } ; handle . visible = this . _scaleEnabled ; scaleHandles [ handleKey ] = handle ; this . addChild ( scaleHandles [ handleKey ] ) ; return scaleHandles ; } , { } ) ; const skewHandles = { skewHorizontal : this . addChild ( new HandleConstructor ( this , 'skewHorizontal' , handleStyle , ( pointerPosition ) => { this . skewGroup ( 'skewHorizontal' , pointerPosition ) ; } , this . commitGroup , 'pointer' ) ) , skewVertical : this . addChild ( new HandleConstructor ( this , 'skewVertical' , handleStyle , ( pointerPosition ) => { this . skewGroup ( 'skewVertical' , pointerPosition ) ; } , this . commitGroup , 'pointer' ) ) , } ; this . handles = Object . assign ( { } , scaleHandles , rotatorHandles , skewHandles ) as { [ H in Handle ] ? : TransformerHandle ; } ; this . handles . middleCenter . visible = false ; this . handles . skewHorizontal . visible = this . _skewEnabled ; this . handles . skewVertical . visible = this . _skewEnabled ; this . handleAnchors = { rotator : new Point ( ) , boxRotateTopLeft : new Point ( ) , boxRotateTopRight : new Point ( ) , boxRotateBottomLeft : new Point ( ) , boxRotateBottomRight : new Point ( ) , topLeft : new Point ( ) , topCenter : new Point ( ) , topRight : new Point ( ) , middleLeft : new Point ( ) , middleCenter : new Point ( ) , middleRight : new Point ( ) , bottomLeft : new Point ( ) , bottomCenter : new Point ( ) , bottomRight : new Point ( ) , skewHorizontal : new Point ( ) , skewVertical : new Point ( ) , } ; this . groupBounds = new OrientedBounds ( ) ; this . updateGroupBounds ( ) ; this . _pointerDown = false ; this . _pointerDragging = false ; this . _pointerPosition = new Point ( ) ; this . _pointerMoveTarget = null ; this . on ( 'pointerdown' , this . onPointerDown , this ) ; this . on ( 'pointerup' , this . onPointerUp , this ) ; this . on ( 'pointerupoutside' , this . onPointerUp , this ) ; } /** The list of enabled handles, if applied manually. */ get enabledHandles ( ) : Array < Handle > { return this . _enabledHandles ; } set enabledHandles ( value ) { if ( ! this . _enabledHandles && ! value ) { return ; } this . _enabledHandles = value ; HANDLES . forEach ( ( handleKey ) => { this . handles [ handleKey ] . visible = false ; } ) ; if ( value ) { value . forEach ( ( handleKey ) => { this . handles [ handleKey ] . visible = true ; } ) ; } else { this . handles . rotator . visible = this . _rotateEnabled ; this . handles . skewHorizontal . visible = this . _skewEnabled ; this . handles . skewVertical . visible = this . _skewEnabled ; SCALE_HANDLES . forEach ( ( handleKey ) => { if ( handleKey === 'middleCenter' ) return ; this . handles [ handleKey ] . visible = this . _scaleEnabled ; } ) ; } } /** The currently applied handle style. If you have edited the transformer handles directly, this may be inaccurate. */ get handleStyle ( ) : Partial < ITransformerHandleStyle > { return this . _handleStyle ; } set handleStyle ( value ) { const handles = this . handles ; for ( const handleKey in handles ) { ( handles [ handleKey ] as TransformerHandle ) . style = value ; } this . _handleStyle = value ; } /** This will enable the rotate handles. */ get rotateEnabled ( ) : boolean { return this . _rotateEnabled ; } set rotateEnabled ( value ) { if ( this . _rotateEnabled !== value ) { this . _rotateEnabled = value ; if ( this . _enabledHandles ) { return ; } this . handles . rotator . visible = value ; } } /** This will enable the scale handles. */ get scaleEnabled ( ) : boolean { return this . _scaleEnabled ; } set scaleEnabled ( value ) { if ( this . _scaleEnabled !== value ) { this . _scaleEnabled = value ; if ( this . _enabledHandles ) { return ; } SCALE_HANDLES . forEach ( ( handleKey ) => { if ( handleKey === 'middleCenter' ) { return ; } this . handles [ handleKey ] . visible = value ; } ) ; } } /** This will enable the skew handles. */ get skewEnabled ( ) : boolean { return this . _skewEnabled ; } set skewEnabled ( value ) { if ( this . _skewEnabled !== value ) { this . _skewEnabled = value ; if ( this . _enabledHandles ) { return ; } this . handles . skewHorizontal . visible = value ; this . handles . skewVertical . visible = value ; } } readonly get transformType ( ) : 'translate' | 'scale' | 'rotate' | 'skew' | 'none' { return this . _transformType ; } /** The currently applied wireframe style. */ get wireframeStyle ( ) : Partial < ITransformerStyle > { return this . _wireframeStyle ; } set wireframeStyle ( value ) { this . _wireframeStyle = Object . assign ( { } , DEFAULT_WIREFRAME_STYLE , value ) ; } param forceUpdate returns getGroupBounds ( forceUpdate = false ) { if ( forceUpdate ) { this . updateGroupBounds ( ) ; } return this . groupBounds ; } param delta translateGroup = ( delta ) : void => { this . _transformHandle = null ; this . _transformType = 'translate' ; const matrix = tempMatrix . identity ( ) . translate ( delta . x , delta . y ) ; this . prependTransform ( matrix ) ; } ; param handle param pointerPosition rotateGroup = ( handle , pointerPosition ) : void => { this . _transformHandle = handle ; this . _transformType = 'rotate' ; const bounds = this . groupBounds ; const handlePosition = this . worldTransform . apply ( this . handleAnchors [ handle ] , tempPoint ) ; this . projectionTransform . applyInverse ( handlePosition , handlePosition ) ; pointerPosition = this . projectionTransform . applyInverse ( pointerPosition , tempPointer ) ; const rOrigin = bounds . center ; const orgAngle = Math . atan2 ( handlePosition . y - rOrigin . y , handlePosition . x - rOrigin . x ) ; const dstAngle = Math . atan2 ( pointerPosition . y - rOrigin . y , pointerPosition . x - rOrigin . x ) ; let deltaAngle = dstAngle - orgAngle ; let newRotation = this . groupBounds . rotation + deltaAngle ; newRotation = this . snapAngle ( newRotation , this . rotationSnapTolerance , this . rotationSnaps ) ; deltaAngle = newRotation - this . groupBounds . rotation ; const matrix = tempMatrix . identity ( ) . translate ( - rOrigin . x , - rOrigin . y ) . rotate ( deltaAngle ) . translate ( rOrigin . x , rOrigin . y ) ; this . prependTransform ( matrix , true ) ; this . updateGroupBounds ( newRotation ) ; this . _skewX += deltaAngle ; this . _skewY += deltaAngle ; } ; param handle param pointerPosition scaleGroup = ( handle , pointerPosition ) : void => { this . _transformHandle = handle ; this . _transformType = 'scale' ; const xDir = SCALE_COMPONENTS [ handle ] . x ; const yDir = SCALE_COMPONENTS [ handle ] . y ; const handles = this . handles ; const bounds = this . groupBounds ; const angle = bounds . rotation ; const innerBounds = bounds . innerBounds ; const handlePosition = this . worldTransform . apply ( this . handleAnchors [ handle ] , tempPoint ) ; this . projectionTransform . applyInverse ( handlePosition , handlePosition ) ; pointerPosition = this . projectionTransform . applyInverse ( pointerPosition , tempPointer ) ; const dx = pointerPosition . x - handlePosition . x ; const dy = pointerPosition . y - handlePosition . y ; const uxvec = ( bounds . topRight . x - bounds . topLeft . x ) / innerBounds . width ; const uyvec = ( bounds . topRight . y - bounds . topLeft . y ) / innerBounds . width ; const vxvec = ( bounds . bottomLeft . x - bounds . topLeft . x ) / innerBounds . height ; const vyvec = ( bounds . bottomLeft . y - bounds . topLeft . y ) / innerBounds . height ; const du = ( dx * uxvec ) + ( dy * uyvec ) ; const dv = ( dx * vxvec ) + ( dy * vyvec ) ; let sx = 1 + ( du * xDir / innerBounds . width ) ; let sy = 1 + ( dv * yDir / innerBounds . height ) ; const lockAspectRatio = this . lockAspectRatio && ( handle === 'topLeft' || handle === 'topRight' || handle === 'bottomLeft' || handle === 'bottomRight' ) ; if ( lockAspectRatio ) { if ( sx > sy ) { sy = sx ; } else { sx = sy ; } } const matrix = tempMatrix . identity ( ) ; if ( xDir !== 0 && sx !== 0 ) { const hsOrigin = ! this . centeredScaling ? ( xDir === 1 ? bounds . topLeft : bounds . topRight ) : bounds . center ; matrix . translate ( - hsOrigin . x , - hsOrigin . y ) . rotate ( - angle ) . scale ( sx , 1 ) . rotate ( angle ) . translate ( hsOrigin . x , hsOrigin . y ) ; } if ( yDir !== 0 && sy !== 0 ) { const vsOrigin = ! this . centeredScaling ? ( yDir === 1 ? bounds . topLeft : bounds . bottomLeft ) : bounds . center ; matrix . translate ( - vsOrigin . x , - vsOrigin . y ) . rotate ( - angle ) . scale ( 1 , sy ) . rotate ( angle ) . translate ( vsOrigin . x , vsOrigin . y ) ; } if ( ( sy < 0 || sx < 0 ) && ! ( sy < 0 && sx < 0 ) ) { switch ( handle ) { case 'topLeft' : case 'bottomLeft' : this . swapHandles ( handles . topLeft , handles . bottomLeft ) ; break ; case 'topCenter' : case 'bottomCenter' : this . swapHandles ( handles . topCenter , handles . bottomCenter ) ; break ; case 'topRight' : case 'bottomRight' : this . swapHandles ( handles . topRight , handles . bottomRight ) ; break ; } } this . prependTransform ( matrix ) ; } ; param handle param pointerPosition skewGroup = ( handle , pointerPosition ) : void => { this . _transformHandle = handle ; this . _transformType = 'skew' ; const bounds = this . groupBounds ; const dst = tempPoint . copyFrom ( pointerPosition ) ; this . projectionTransform . applyInverse ( dst , dst ) ; const sOrigin = bounds . center ; const matrix = tempMatrix . identity ( ) . translate ( - sOrigin . x , - sOrigin . y ) ; let rotation = this . groupBounds . rotation ; if ( handle === 'skewHorizontal' ) { const oldSkew = this . _skewX ; this . _skewX = Math . atan2 ( dst . y - sOrigin . y , dst . x - sOrigin . x ) ; this . _skewX = this . snapAngle ( this . _skewX , this . skewSnapTolerance , this . skewSnaps ) ; matrix . prepend ( createVerticalSkew ( - oldSkew ) ) ; matrix . prepend ( createVerticalSkew ( this . _skewX ) ) ; } else { const oldSkew = this . _skewY ; const newSkew = Math . atan2 ( dst . y - sOrigin . y , dst . x - sOrigin . x ) - ( Math . PI / 2 ) ; this . _skewY = newSkew ; this . _skewY = this . snapAngle ( this . _skewY , this . skewSnapTolerance , this . skewSnaps ) ; matrix . prepend ( createHorizontalSkew ( oldSkew ) ) ; matrix . prepend ( createHorizontalSkew ( - this . _skewY ) ) ; rotation -= this . _skewY - oldSkew ; } matrix . translate ( sOrigin . x , sOrigin . y ) ; this . prependTransform ( matrix , true ) ; this . updateGroupBounds ( rotation ) ; } ; /**
     * This is called after the user finishes dragging a handle. If  this . transientGroupTilt is enabled, it will
     * reset the rotation of this group (if more than one display-object is grouped).
      commitGroup = ( ) : void => { this . _transformHandle = null ; this . _transformType = 'none' ; if ( this . transientGroupTilt !== false && this . group . length > 1 ) { this . updateGroupBounds ( 0 ) ; } this . emit ( 'transformcommit' ) ; } ; @override
     *  param renderer render ( renderer ) { if ( this . renderable && this . visible && ( ! this . lazyMode || this . lazyDirty ) ) { this . draw ( ) ; } super . render ( renderer ) ; } /** Recalculates the transformer's geometry. This is called on each render. */ protected draw ( ) { const targets = this . group ; const { color , thickness } = this . _wireframeStyle ; this . wireframe . clear ( ) ; if ( this . boundingBoxes !== 'none' ) { this . wireframe . lineStyle ( thickness , color ) ; } if ( this . translateEnabled ) { this . wireframe . beginFill ( 0xffffff , 1e-4 ) ; } for ( let i = 0 , j = targets . length ; i < j && this . boundingBoxes === 'all' ; i ++ ) { this . wireframe . drawBounds ( Transformer . calculateOrientedBounds ( targets [ i ] , tempBounds ) ) ; } const groupBounds = targets . length !== 1 ? Transformer . calculateGroupOrientedBounds ( targets , this . groupBounds . rotation , tempBounds , true ) : Transformer . calculateOrientedBounds ( targets [ 0 ] , tempBounds ) ; this . wireframe . drawBounds ( groupBounds ) ; this . drawHandles ( groupBounds ) ; this . groupBounds . copyFrom ( groupBounds ) ; if ( this . boxRotationEnabled ) { this . wireframe . closePath ( ) . beginFill ( 0xffffff , 1e-4 ) . lineStyle ( ) ; this . wireframe . drawBoxRotationTolerance ( ) ; } if ( this . boxScalingEnabled ) { this . wireframe . closePath ( ) . beginFill ( 0xfff0ff , 1e-4 ) . lineStyle ( ) ; this . wireframe . drawBoxScalingTolerance ( groupBounds ) ; } this . lazyDirty = false ; } param groupBounds protected drawHandles ( groupBounds ) { const handles = this . handles ; const handleAnchors = this . handleAnchors ; const { topLeft : worldTopLeft , topRight : worldTopRight , bottomLeft : worldBottomLeft , bottomRight : worldBottomRight , center : worldCenter , } = groupBounds ; const [ topLeft , topRight , bottomLeft , bottomRight ] = tempHull ; const center = tempPoint ; this . projectToLocal ( worldTopLeft , topLeft ) ; this . projectToLocal ( worldTopRight , topRight ) ; this . projectToLocal ( worldBottomLeft , bottomLeft ) ; this . projectToLocal ( worldBottomRight , bottomRight ) ; this . projectToLocal ( worldCenter , center ) ; handleAnchors . topLeft . copyFrom ( topLeft ) ; handleAnchors . topCenter . set ( ( topLeft . x + topRight . x ) / 2 , ( topLeft . y + topRight . y ) / 2 ) ; handleAnchors . topRight . copyFrom ( topRight ) ; handleAnchors . middleLeft . set ( ( topLeft . x + bottomLeft . x ) / 2 , ( topLeft . y + bottomLeft . y ) / 2 ) ; handleAnchors . middleCenter . set ( ( topLeft . x + bottomRight . x ) / 2 , ( topLeft . y + bottomRight . y ) / 2 ) ; handleAnchors . middleRight . set ( ( topRight . x + bottomRight . x ) / 2 , ( topRight . y + bottomRight . y ) / 2 ) ; handleAnchors . bottomLeft . copyFrom ( bottomLeft ) ; handleAnchors . bottomCenter . set ( ( bottomLeft . x + bottomRight . x ) / 2 , ( bottomLeft . y + bottomRight . y ) / 2 ) ; handleAnchors . bottomRight . copyFrom ( bottomRight ) ; if ( this . boxRotationEnabled ) { handleAnchors . boxRotateTopLeft . copyFrom ( handleAnchors . topLeft ) ; handleAnchors . boxRotateTopRight . copyFrom ( handleAnchors . topRight ) ; handleAnchors . boxRotateBottomLeft . copyFrom ( handleAnchors . bottomLeft ) ; handleAnchors . boxRotateBottomRight . copyFrom ( handleAnchors . bottomRight ) ; } if ( this . _rotateEnabled ) { const bx = ( topLeft . x + topRight . x ) / 2 ; const by = ( topLeft . y + topRight . y ) / 2 ; let px = - ( topLeft . y - topRight . y ) ; let py = ( topLeft . x - topRight . x ) ; const pl = Math . sqrt ( ( px * px ) + ( py * py ) ) ; px *= 32 / pl ; py *= 32 / pl ; handles . rotator . position . x = bx + px ; handles . rotator . position . y = by + py ; this . wireframe . moveTo ( bx , by ) . lineTo ( handles . rotator . position . x , handles . rotator . position . y ) ; this . handleAnchors . rotator . copyFrom ( handles . rotator . position ) ; } if ( this . _skewEnabled ) { const cx = center . x ; const cy = center . y ; this . worldTransform . apply ( center , center ) ; handleAnchors . skewHorizontal . set ( center . x + ( Math . cos ( this . _skewX ) * this . skewRadius ) , center . y + ( Math . sin ( this . _skewX ) * this . skewRadius ) ) ; handleAnchors . skewVertical . set ( center . x + ( - Math . sin ( this . _skewY ) * this . skewRadius ) , center . y + ( Math . cos ( this . _skewY ) * this . skewRadius ) ) ; this . worldTransform . applyInverse ( handleAnchors . skewHorizontal , handleAnchors . skewHorizontal ) ; this . worldTransform . applyInverse ( handleAnchors . skewVertical , handleAnchors . skewVertical ) ; center . set ( cx , cy ) ; this . wireframe . beginFill ( this . wireframeStyle . color ) . drawCircle ( center . x , center . y , this . wireframeStyle . thickness * 2 ) . endFill ( ) ; this . wireframe . moveTo ( center . x , center . y ) . lineTo ( handles . skewHorizontal . x , handles . skewHorizontal . y ) . moveTo ( center . x , center . y ) . lineTo ( handles . skewVertical . x , handles . skewVertical . y ) ; } for ( const handleName in handles ) { let rotation = this . groupBounds . rotation ; if ( handleName === 'skewHorizontal' ) { rotation = this . _skewX ; } else if ( handleName === 'skewVertical' ) { rotation = this . _skewY ; } const handle = handles [ handleName ] ; handle . rotation = rotation ; handle . position . copyFrom ( handleAnchors [ handleName ] ) ; handle . getBounds ( false , tempRect ) ; } } /** Called on the `pointerdown` event. You must call the super implementation. */ protected onPointerDown ( e ) { this . _pointerDown = true ; this . _pointerDragging = false ; e . stopPropagation ( ) ; if ( this . _pointerMoveTarget ) { this . _pointerMoveTarget . off ( 'pointermove' , this . onPointerMove , this ) ; this . _pointerMoveTarget = null ; } this . _pointerMoveTarget = this . stage || this ; this . _pointerMoveTarget . on ( 'pointermove' , this . onPointerMove , this ) ; } /** Called on the `pointermove` event. You must call the super implementation. */ protected onPointerMove ( e ) { const lastPointerPosition = this . _pointerPosition ; const currentPointerPosition = pointPool . allocate ( ) . copyFrom ( e . data . global ) ; const hoveredHandle = this . wireframe . hitHandleType ( this . groupBounds , this . projectionTransform , currentPointerPosition ) ; if ( ! this . _pointerDown ) { this . setCursorFromHoveredHandle ( hoveredHandle ) ; } else { const cx = currentPointerPosition . x ; const cy = currentPointerPosition . y ; if ( this . _pointerDragging ) { switch ( this . _transformHandle ) { case 'boxRotateTopLeft' : case 'boxRotateTopRight' : case 'boxRotateBottomLeft' : case 'boxRotateBottomRight' : this . rotateGroup ( this . _transformHandle , currentPointerPosition ) ; break ; case 'topCenter' : case 'middleLeft' : case 'middleRight' : case 'bottomCenter' : this . scaleGroup ( this . _transformHandle , currentPointerPosition ) ; break ; default : { if ( this . translateEnabled ) { const [ worldOrigin , worldDestination , worldDelta ] = tempHull ; worldOrigin . set ( 0 , 0 ) ; worldDestination . set ( currentPointerPosition . x - lastPointerPosition . x , currentPointerPosition . y - lastPointerPosition . y ) ; this . projectionTransform . applyInverse ( worldOrigin , worldOrigin ) ; this . projectionTransform . applyInverse ( worldDestination , worldDestination ) ; worldDelta . set ( worldDestination . x - worldOrigin . x , worldDestination . y - worldOrigin . y ) ; this . translateGroup ( worldDelta ) ; } } } } else { this . _transformHandle = this . wireframe . hitHandleType ( this . groupBounds , this . projectionTransform , currentPointerPosition ) ; this . setCursorFromHoveredHandle ( hoveredHandle ) ; } this . _pointerPosition . x = cx ; this . _pointerPosition . y = cy ; this . _pointerDragging = true ; e . stopPropagation ( ) ; } pointPool . release ( currentPointerPosition ) ; } /** Called on the `pointerup` and `pointerupoutside` events. You must call the super implementation. */ protected onPointerUp ( e ) { this . _pointerDragging = false ; this . _pointerDown = false ; this . commitGroup ( ) ; e . stopPropagation ( ) ; if ( this . _pointerMoveTarget ) { this . _pointerMoveTarget . off ( 'pointermove' , this . onPointerMove , this ) ; this . _pointerMoveTarget = null ; } } param delta param skipUpdate private prependTransform ( delta , skipUpdate = false ) { const group = this . group ; for ( let i = 0 , j = group . length ; i < j ; i ++ ) { multiplyTransform ( group [ i ] , delta , false ) ; } emitMatrix . copyFrom ( delta ) ; if ( ! skipUpdate ) { this . updateGroupBounds ( ) ; } this . emit ( 'transformchange' , emitMatrix ) ; } param rotation private updateGroupBounds ( rotation = this . groupBounds . rotation ) { Transformer . calculateGroupOrientedBounds ( this . group , rotation , this . groupBounds ) ; this . drawHandles ( this . groupBounds ) ; this . lazyDirty = true ; } param angle param snapTolerance param snaps returns private snapAngle ( angle , snapTolerance , snaps ? ) { angle = angle % ( Math . PI * 2 ) ; if ( ! snaps || snaps . length === 1 || ! snapTolerance ) { return angle ; } for ( let i = 0 , j = snaps . length ; i < j ; i ++ ) { if ( Math . abs ( angle - snaps [ i ] ) <= snapTolerance ) { return snaps [ i ] ; } } return angle ; } param handle0 param handle1 private swapHandles ( handle0 , handle1 ) { const key0 = handle0 . handle ; const key1 = handle1 . handle ; const cursor0 = handle0 . cursor ; const cursor1 = handle1 . cursor ; const x0 = handle0 . x ; const x1 = handle1 . x ; const y0 = handle0 . y ; const y1 = handle1 . y ; handle0 . handle = key1 ; handle1 . handle = key0 ; handle0 . position . set ( x1 , y1 ) ; handle1 . position . set ( x0 , y0 ) ; handle0 . cursor = cursor1 ; handle1 . cursor = cursor0 ; this . handles [ key0 ] = handle1 ; this . handles [ key1 ] = handle0 ; if ( this . _transformHandle === key0 ) { this . _transformHandle = key1 ; } else if ( this . _transformHandle === key1 ) { this . _transformHandle = key0 ; } } /** Set cursor from {@code this.cursors} based on the handle hovered currently. */ private setCursorFromHoveredHandle ( hoveredHandle ) { if ( hoveredHandle && hoveredHandle . startsWith ( 'boxRotate' ) ) { this . cursor = this . cursors . boxRotate || this . cursors . default ; } else if ( hoveredHandle ) { switch ( hoveredHandle ) { case 'topCenter' : this . cursor = this . cursors . boxScaleTop || this . cursors . default ; break ; case 'middleLeft' : this . cursor = this . cursors . boxScaleLeft || this . cursors . default ; break ; case 'middleRight' : this . cursor = this . cursors . boxScaleRight || this . cursors . default ; break ; case 'bottomCenter' : this . cursor = this . cursors . boxScaleBottom || this . cursors . default ; break ; } } else { this . cursor = this . cursors . translate || this . cursors . default ; } } param input param output returns projectToLocal ( input , output ? ) { if ( ! output ) { output = new Point ( ) ; } this . projectionTransform . apply ( input , output ) ; this . worldTransform . applyInverse ( output , output ) ; return output ; } param displayObject param transform param corners param index returns static calculateTransformedCorners ( displayObject , transform = displayObject . worldTransform , corners ? , index = 0 ) { const localBounds = displayObject . getLocalBounds ( ) ; displayObject . getBounds ( ) ; corners = corners || [ new Point ( ) , new Point ( ) , new Point ( ) , new Point ( ) ] ; corners [ index ] . set ( localBounds . x , localBounds . y ) ; corners [ index + 1 ] . set ( localBounds . x + localBounds . width , localBounds . y ) ; corners [ index + 2 ] . set ( localBounds . x + localBounds . width , localBounds . y + localBounds . height ) ; corners [ index + 3 ] . set ( localBounds . x , localBounds . y + localBounds . height ) ; transform . apply ( corners [ index ] , corners [ index ] ) ; transform . apply ( corners [ index + 1 ] , corners [ index + 1 ] ) ; transform . apply ( corners [ index + 2 ] , corners [ index + 2 ] ) ; transform . apply ( corners [ index + 3 ] , corners [ index + 3 ] ) ; return corners ; } param displayObject param bounds static calculateOrientedBounds ( displayObject , bounds ? ) { const parent = ! displayObject . parent ? displayObject . enableTempParent ( ) : displayObject . parent ; displayObject . updateTransform ( ) ; displayObject . disableTempParent ( parent ) ; decomposeTransform ( tempTransform , displayObject . worldTransform ) ; tempTransform . updateLocalTransform ( ) ; const angle = tempTransform . rotation ; const corners = Transformer . calculateTransformedCorners ( displayObject , displayObject . worldTransform , tempCorners ) ; const cx = ( corners [ 0 ] . x + corners [ 1 ] . x + corners [ 2 ] . x + corners [ 3 ] . x ) / 4 ; const cy = ( corners [ 0 ] . y + corners [ 1 ] . y + corners [ 2 ] . y + corners [ 3 ] . y ) / 4 ; const matrix = tempMatrix . identity ( ) . translate ( - cx , - cy ) . rotate ( - tempTransform . rotation ) . translate ( cx , cy ) ; matrix . apply ( corners [ 0 ] , corners [ 0 ] ) ; matrix . apply ( corners [ 1 ] , corners [ 1 ] ) ; matrix . apply ( corners [ 2 ] , corners [ 2 ] ) ; matrix . apply ( corners [ 3 ] , corners [ 3 ] ) ; bounds = bounds || new OrientedBounds ( ) ; bounds . rotation = angle ; bounds . innerBounds . x = Math . min ( corners [ 0 ] . x , corners [ 1 ] . x , corners [ 2 ] . x , corners [ 3 ] . x ) ; bounds . innerBounds . y = Math . min ( corners [ 0 ] . y , corners [ 1 ] . y , corners [ 2 ] . y , corners [ 3 ] . y ) ; bounds . innerBounds . width = Math . max ( corners [ 0 ] . x , corners [ 1 ] . x , corners [ 2 ] . x , corners [ 3 ] . x ) - bounds . innerBounds . x ; bounds . innerBounds . height = Math . max ( corners [ 0 ] . y , corners [ 1 ] . y , corners [ 2 ] . y , corners [ 3 ] . y ) - bounds . innerBounds . y ; return bounds ; } param group param rotation param bounds param skipUpdate static calculateGroupOrientedBounds ( group , rotation , bounds ? , skipUpdate = false ) { const groupLength = group . length ; const frames = pointPool . allocateArray ( groupLength * 4 ) ; for ( let i = 0 ; i < groupLength ; i ++ ) { const displayObject = group [ i ] ; if ( ! skipUpdate ) { const parent = ! displayObject . parent ? displayObject . enableTempParent ( ) : displayObject . parent ; displayObject . updateTransform ( ) ; displayObject . disableTempParent ( parent ) ; } Transformer . calculateTransformedCorners ( displayObject , displayObject . worldTransform , frames , i * 4 ) ; } const matrix = tempMatrix . identity ( ) . rotate ( - rotation ) ; let minX = Number . MAX_VALUE ; let minY = Number . MAX_VALUE ; let maxX = - Number . MAX_VALUE ; let maxY = - Number . MAX_VALUE ; for ( let i = 0 , j = frames . length ; i < j ; i ++ ) { const point = frames [ i ] ; matrix . apply ( point , point ) ; const x = point . x ; const y = point . y ; minX = x < minX ? x : minX ; minY = y < minY ? y : minY ; maxX = x > maxX ? x : maxX ; maxY = y > maxY ? y : maxY ; } pointPool . releaseArray ( frames ) ; bounds = bounds || new OrientedBounds ( ) ; bounds . innerBounds . x = minX ; bounds . innerBounds . y = minY ; bounds . innerBounds . width = maxX - minX ; bounds . innerBounds . height = maxY - minY ; bounds . rotation = rotation ; matrix . applyInverse ( bounds . center , tempPoint ) ; bounds . center . copyFrom ( tempPoint ) ; return bounds ; } } event type { Matrix } event