Search code examples
flashapache-flexactionscript-3actionscriptdraggable

canvas moved on startDrag() without calling set x/y or move() - how does startDrag() exactly work in as3/flex?


i have an .as class that extends mx.containers.Canvas (it's a draggable border of a resizable component), it becomes draggable on MOUSE_DOWN and stops being draggable on MOUSE_UP, MOUSE_OUT and ROLL_OUT.
before calling startDrag() i create a Rectangle to define the drag area, i also have a _dragging: Boolean variable to control if it's draggable at the moment.

the problem is that when i click this border it jumps to a negative coordinate without calling startDrag or switching _dragging to true.

i've overriden get x, set x, get y, set y and move() methods in order to solve it but the only thing i got was the fact that position changes without calling coords setters or move(), but at the moment it's changed a getter is called and returns new (negative) value

so my question is what happens on startDrag() and how to filter unwanted incoming coords values?


Solution

  • startDrag's process doesn't call the x and y setters, so adjustment of the transform or transform.matrix must be occurring. If transform is being changed, you can override the property and watch for the change. On the other hand, if the transform.matrix subproperty is being changed, then you would not be able to detect that by overriding the transform property. The only thing you could possibly do is either not use startDrag at all and use mouse events to move the object, or you could subclass the Transform class. If you subclass the Transform class and implement an override for the matrix property that notifies the parent when it has changed, then you would have to also override the transform property of whatever you're dragging to covert any Transform instances to the subclassed type and associate it with the transformed object for notification of the matrix property change. It would be must easier to just use mouse down/move/up events for the movement instead of startDrag.

    And that's all fine in theory, except it doesn't seem to work when implemented. For example:

    Subclass the Transform class:

    package
    {
        import flash.geom.Transform;
        import flash.geom.Matrix;
        import flash.geom.Matrix3D;
        import flash.display.DisplayObject;
    
        public class TransformNotify extends Transform
        {
            public var owner:DisplayObject;
    
            public function TransformNotify( owner:DisplayObject )
            {
                super( owner );
                this.owner = owner;
            }
    
            public override function get matrix():Matrix
            {
                trace( "acquiring matrix" );
                return super.matrix;
            }
    
            public override function set matrix( m:Matrix ):void
            {
                trace( "matrix changing: " + m.tx + "," + m.ty );
                super.matrix = m;
                //Here you could actually alter some property of the owner to notify it that the matrix has changed
                //if (owner != null)
                //{
                //  owner.x = m.tx;
                //  owner.y = m.ty;
                //}
            }
        }   
    }
    

    Subclass a MovieClip which will use the subclassed Transform:

    package
    {
        import flash.display.MovieClip;
        import flash.geom.Transform;
    
        public dynamic class MovieClipTransformed extends MovieClip
        {
            public function MovieClipTransformed()
            {
                super();
                transform = convertToTransformNotify( super.transform );
            }
    
            public override function get transform( ):Transform
            {
                trace("acquiring transform" );
                if (!(super.transform is TransformNotify))
                {
                    var tn:TransformNotify = convertToTransformNotify( super.transform );
                    super.transform = tn;
                    return tn;
                }
                else
                    return super.transform;
            }
    
            public override function set transform( tf:Transform ):void
            {
                trace( "assigning new transform" );
                if (!(tf is TransformNotify))
                    tf = convertToTransformNotify( tf );
                else
                    (tf as TransformNotify).owner = this; //take ownership of transform
                super.transform = tf;
            }
    
            function convertToTransformNotify( tf:Transform ):TransformNotify
            {
                var tfn:TransformNotify = new TransformNotify( this );
                tfn.colorTransform = tf.colorTransform;
                tfn.matrix = tf.matrix;
                tfn.matrix3D = tf.matrix3D;
                tfn.perspectiveProjection = tf.perspectiveProjection;
                return tfn;
            }
        }   
    }
    

    And attempt to use the class:

    //Main timeline code
    import flash.display.Graphics;
    import flash.events.MouseEvent;
    
    var mct:MovieClipTransformed = new MovieClipTransformed();
    var g:Graphics = mct.graphics;
    g.moveTo( 0, 0 );
    g.beginFill( 0xff0000 );
    g.drawRect( 0, 0, 100, 100 );
    g.endFill();
    
    addChild( mct );
    //Make sure override system is functioning properly; should trace that a change has occurred when transform.matrix is modified like so:
    mct.transform.matrix = new Matrix( 1,0, 0, 1, 300, 10 ); //seems to function correctly
    
    mct.addEventListener( flash.events.MouseEvent.MOUSE_DOWN, md, false, 0, true );
    
    function md( m:MouseEvent ):void
    {
        mct.startDrag( false );
        stage.addEventListener( flash.events.MouseEvent.MOUSE_UP, stageMouseUp, true );
    }
    
    function stageMouseUp(e:MouseEvent ):void
    {
        stage.removeEventListener( flash.events.MouseEvent.MOUSE_UP, stageMouseUp, true );
        mct.stopDrag();
    }
    

    When you drag the red square around the screen, notice that nothing is traced, so the transform property is not being updated, and neither is the transform instance's matrix property changing.

    The manual setting of transform.matrix traces "matrix changing: 300,10" as expected, although it also traces "matrix changing: 0,0" first, suggesting it's being set twice after it is acquired, for no apparent reason. I even dupllicated the line with a different value, and again it is set twice, so it's not just some initialization step. It seems that every time you set the transform.matrix property, it sets itself once to its current value, and then sets itself again to the new value. Strange.

    Also interesting is that if I try to override the matrix3D property, it gives me an "incompatible override" error, even though I'm sure the override signature for it matches exactly. It must be some kind of bug or special restriction.

    Something is very, very wrong with the way startDrag works. There is not way to passively detect the changes as they are occuring; they can only be observed actively via a timer or checking for changes after the fact. What a terrible thing startDrag is.

    Related question, still doesn't solve the problem despite a claim to the contrary. How to override the transform.matrix setter