Added split method to matrix, fixed caching a bit, first iteration of fix for correct...
authorDmitry Baranovskiy <Dmitry@Baranovskiy.com>
Wed, 13 Apr 2011 00:05:45 +0000 (10:05 +1000)
committerDmitry Baranovskiy <Dmitry@Baranovskiy.com>
Wed, 13 Apr 2011 00:05:45 +0000 (10:05 +1000)
raphael.js

index 0a1ee2c..1a66634 100644 (file)
     R._path2string = function () {
         return this.join(",").replace(p2s, "$1");
     };
+    function repush(array, item) {
+        for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+            return array.push(array.splice(i, 1)[0]);
+        }
+    }
     function cacher(f, scope, postprocessor) {
         function newf() {
             var arg = Array.prototype.slice.call(arguments, 0),
                 cache = newf.cache = newf.cache || {},
                 count = newf.count = newf.count || [];
             if (cache[has](args)) {
+                repush(count, args);
                 return postprocessor ? postprocessor(cache[args]) : cache[args];
             }
             count.length >= 1e3 && delete cache[count.shift()];
             }
         },
         equaliseTransform = function (t1, t2) {
+            t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
             t1 = R.parseTransformString(t1) || [];
             t2 = R.parseTransformString(t2) || [];
             var maxlength = mmax(t1.length, t2.length),
             for (; i < maxlength; i++) {
                 tt1 = t1[i] || getEmpty(t2[i]);
                 tt2 = t2[i] || getEmpty(tt1);
-                if (    (tt1[0] != tt2[0]) ||
-                        (tt1[0] == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
-                        (tt1[0] == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                if ((tt1[0] != tt2[0]) ||
+                    (tt1[0] == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                    (tt1[0] == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
                     ) {
                     return;
                 }
             this.f = 0;
         }
     }
-    var matrixproto = Matrix.prototype;
-    matrixproto.add = function (a, b, c, d, e, f) {
-        var out = [[], [], []],
-            m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
-            matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
-            x, y, z, res;
+    (function (matrixproto) {
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
 
-        if (a && a instanceof Matrix) {
-            matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
-        }
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
 
-        for (x = 0; x < 3; x++) {
-            for (y = 0; y < 3; y++) {
-                res = 0;
-                for (z = 0; z < 3; z++) {
-                    res += m[x][z] * matrix[z][y];
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
                 }
-                out[x][y] = res;
             }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+        };
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        matrixproto.translate = function (x, y) {
+            this.add(1, 0, 0, 1, x, y);
+        };
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            this.add(1, 0, 0, 1, -cx, -cy);
+        };
+        matrixproto.rotate = function (a, x, y) {
+            a = R.rad(a);
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            this.add(1, 0, 0, 1, -x, -y);
+        };
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return R.svg ?
+                "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
+                [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
+        };
+        matrixproto.toFilter = function () {
+            return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
+                ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
+                ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
         }
-        this.a = out[0][0];
-        this.b = out[1][0];
-        this.c = out[0][1];
-        this.d = out[1][1];
-        this.e = out[0][2];
-        this.f = out[1][2];
-    };
-    matrixproto.invert = function () {
-        var me = this,
-            x = me.a * me.d - me.b * me.c;
-        return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
-    };
-    matrixproto.clone = function () {
-        return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
-    };
-    matrixproto.translate = function (x, y) {
-        this.add(1, 0, 0, 1, x, y);
-    };
-    matrixproto.scale = function (x, y, cx, cy) {
-        y == null && (y = x);
-        this.add(1, 0, 0, 1, cx, cy);
-        this.add(x, 0, 0, y, 0, 0);
-        this.add(1, 0, 0, 1, -cx, -cy);
-    };
-    matrixproto.rotate = function (a, x, y) {
-        a = R.rad(a);
-        var cos = +math.cos(a).toFixed(9),
-            sin = +math.sin(a).toFixed(9);
-        this.add(cos, sin, -sin, cos, x, y);
-        this.add(1, 0, 0, 1, -x, -y);
-    };
-    matrixproto.x = function (x, y) {
-        return x * this.a + y * this.c + this.e;
-    };
-    matrixproto.y = function (x, y) {
-        return x * this.b + y * this.d + this.f;
-    };
-    matrixproto.get = function (i) {
-        return +this[Str.fromCharCode(97 + i)].toFixed(4);
-    };
-    matrixproto.toString = function () {
-        return R.svg ?
-            "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
-            [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
-    };
-    matrixproto.toFilter = function () {
-        return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
-            ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
-            ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmedthod='auto expand')";
-    };
-    matrixproto.offset = function () {
-        return [this.e.toFixed(4), this.f.toFixed(4)];
-    };
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] /= mag;
+            a[1] /= mag;
+        }
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+            this.e = this.f = 0;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
 
+            // rotation
+            out.rotate = R.deg(math.asin(-row[0][1]));
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+
+            return out;
+        };
+        matrixproto.toTransformString = function () {
+            var s = this.split();
+            if (s.isSimple) {
+                return "t" + [s.dx, s.dy] + "s" + [s.scalex, s.scaley, 0, 0] + "r" + [s.rotate, 0, 0];
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    
     // SVG
     if (R.svg) {
         var xlink = "http://www.w3.org/1999/xlink",
             extractTransform(this, tstr);
 
             this.clip && $(this.clip, {transform: this.matrix.invert()});
-            // this.gradient && $(this.gradient, {gradientTransform: this.matrix.invert()});
             this.pattern && updatePosition(this);
             this.node && $(this.node, {transform: this.matrix});
             
                     dy: m.y(dx, dy)
                 };
             },
-            setCoords = function (p) {
+            setCoords = function (p, sx, sy, dx, dy, deg) {
                 var _ = p._,
-                    sx = _.sx,
-                    sy = _.sy,
-                    deg = _.deg,
-                    dx = _.dx,
-                    dy = _.dy,
+                    m = p.matrix,
+                    // sp = m.split(),
+                    // sx = sp.scalex,
+                    // sy = sp.scaley,
+                    // deg = sp.rotate,
+                    // dx = sp.dx,
+                    // dy = sp.dy,
                     fillpos = _.fillpos,
                     o = p.node,
                     s = o.style,
                     y = 1,
-                    m = p.matrix,
                     flip = "",
                     dxdy,
                     kx = zoom / sx,
                     fill.on = false;
                 }
                 if (fill.on && params.fill) {
-                    var isURL = params.fill.match(ISURL);
+                    var isURL = Str(params.fill).match(ISURL);
                     if (isURL) {
                         fill.parentNode == node && node.removeChild(fill);
                         fill.rotate = true;
             extractTransform(this, tstr);
             var matrix = this.matrix.clone(),
                 vbs = this.paper._viewBoxShift,
-                skew = this.skew;
+                skew = this.skew,
+                o = this.node;
             matrix.translate(-.5, -.5);
             if (vbs) {
                 matrix.scale(vbs.scale, vbs.scale, -1, -1);
-                matrix.add(1, 0, 0, 1, vbs.dx, vbs.dy);
-            }
-            if (this.type == "image") {
-                if (Str(tstr).indexOf("m") + 1) {
-                    this.node.style.filter = matrix.toFilter();
+                matrix.translate(vbs.dx, vbs.dy);
+            }
+            if (Str(this.attrs.fill).indexOf("-") + 1 || this.type == "image") {
+                skew.matrix = "1 0 0 1";
+                skew.offset = "0 0";
+                var split = matrix.split();
+                if (!split.isSimple) {
+                    o.style.filter = matrix.toFilter();
                     var bb = this.getBBox(),
                         bbt = this.getBBox(1),
-                        im = matrix.invert(),
-                        dx = im.x(bb.x, bb.y) - im.x(bbt.x, bbt.y),
-                        dy = im.y(bb.x, bb.y) - im.y(bbt.x, bbt.y);
-                    // skew.offset = dx + S + dy;
-                    // this.node.getElementsByTagName(fillString)[0].position = skew.offset;
+                        dx = bb.x - bbt.x,
+                        dy = bb.y - bbt.y;
+                    o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
+                    setCoords(this, 1, 1, dx, dy, 0);
                 } else {
-                    this.node.style.filter = E;
-                    setCoords(this);
+                    o.style.filter = E;
+                    alert("clean");
+                    setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
                 }
             } else {
-                    // o = this.node,
-                    // _ = this._,
-                    // fillpos = _.fillpos,
-                    // deg,
-                    // matrix = this.matrix;
-                    // fill = o.getElementsByTagName(fillString)[0],
-                    // angle = fill.angle;
-
-                this.node.style.filter = E;
+                o.style.filter = E;
                 skew.matrix = Str(matrix);
                 skew.offset = matrix.offset();
-                
-                // if (0&&angle) {
-                //     angle = R.rad(270 - angle);
-                //     var dx = 100 * math.cos(angle),
-                //         dy = 100 * math.sin(angle),
-                //         zx = matrix.x(0, 0),
-                //         zy = matrix.y(0, 0),
-                //         mx = matrix.x(dx, dy),
-                //         my = matrix.y(dx, dy);
-                //     angle = R.angle(zx, zy, mx, my);
-                //     fill.angle = (270 - angle) % 360;
-                // }
             }
             return this;
         };
                     res[i] = this.attrs[i];
                 }
                 res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+                res.transform = this._.transform;
                 return res;
             }
             if (value == null && R.is(name, "string")) {
             res._.fillpos = [x, y];
             res._.fillsize = [w, h];
             node.appendChild(fill);
-            setCoords(res);
+            setCoords(res, 1, 1, 0, 0, 0);
             return res;
         };
         theText = function (vml, x, y, text) {
             path.textpathok = true;
             o.string = Str(text);
             o.on = true;
-            el.style.cssText = "position:absolute;left:0;top:0;width:1;height:1";
+            el.style.cssText = "position:absolute;left:0;top:0;width:1px;height:1px";
             el.coordsize = zoom + S + zoom;
             el.coordorigin = "0 0";
             var p = new Element(el, vml),
                             R.is(f, "function") && f.call(el);
                         });
                     })(e.callback, that, e.anim);
-                    if (--e.repeat) {
-                        that.attr(e.origin);
-                        e.start = Now;
-                    } else {
-                        that.attr(to);
-                        animationElements.splice(l--, 1);
+                    that.attr(to);
+                    animationElements.splice(l--, 1);
+                    if (e.repeat > 1 && !e.next) {
+                        runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
                     }
                     if (e.next && !e.stop) {
-                        runAnimation(e.anim, e.el, e.next, null, e.totalOrigin);
+                        runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
                     }
                 }
             }
             animationElements.length && requestAnimFrame(animation);
         },
         upto255 = function (color) {
-            return mmax(mmin(color, 255), 0);
+            return color > 255 ? 255 : color < 0 ? 0 : color;
         };
     /*\
      * Element.animateWith
         a.times = math.floor(mmax(times, 0)) || 1;
         return a;
     };
-    function runAnimation(anim, element, percent, status, totalOrigin) {
+    function runAnimation(anim, element, percent, status, totalOrigin, times) {
         percent = toFloat(percent);
         var params,
             isInAnim,
                                     }
                                 }
                             } else {
-                                var m = (element.matrix || new Matrix).m,
+                                var m = (element.matrix || new Matrix),
                                     to2 = {_:{transform: _.transform}, getBBox: function () { return element.getBBox(); }};
                                 from[attr] = [
-                                    m[0][0],
-                                    m[1][0],
-                                    m[0][1],
-                                    m[1][1],
-                                    m[0][2],
-                                    m[1][2]
+                                    m.a,
+                                    m.b,
+                                    m.c,
+                                    m.d,
+                                    m.e,
+                                    m.f
                                 ];
                                 extractTransform(to2, to[attr]);
                                 to[attr] = to2._.transform;
                                 diff[attr] = [
-                                    (to2.matrix.m[0][0] - m[0][0]) / ms,
-                                    (to2.matrix.m[1][0] - m[1][0]) / ms,
-                                    (to2.matrix.m[0][1] - m[0][1]) / ms,
-                                    (to2.matrix.m[1][1] - m[1][1]) / ms,
-                                    (to2.matrix.m[0][2] - m[0][2]) / ms,
-                                    (to2.matrix.m[1][2] - m[1][2]) / ms
+                                    (to2.matrix.a - m.a) / ms,
+                                    (to2.matrix.b - m.b) / ms,
+                                    (to2.matrix.c - m.c) / ms,
+                                    (to2.matrix.d - m.d) / ms,
+                                    (to2.matrix.e - m.e) / ms,
+                                    (to2.matrix.e - m.f) / ms
                                 ];
                                 // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
                                 // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
                 callback: params.callback,
                 prev: prev,
                 next: next,
-                repeat: anim.times,
+                repeat: times || anim.times,
                 origin: element.attr(),
                 totalOrigin: totalOrigin
             };
     oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
 
     /*
-     * Eve 0.2.1 - JavaScript Events Library
+     * Eve 0.2.3 - JavaScript Events Library
      *
      * Copyright (c) 2010 Dmitry Baranovskiy (http://dmitry.baranovskiy.com/)
      * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
      */
 
     var eve = R.eve = (function () {
-        var version = "0.2.1",
+        var version = "0.2.3",
             has = "hasOwnProperty",
             separator = /[\.\/]/,
             wildcard = "*",
+            fun = function () {},
+            numsort = function (a, b) {
+                return a - b;
+            },
+            current_event,
             events = {n: {}},
+        /*\
+         * eve
+         [ method ]
+         **
+         * Fires event with given `name`, given scope and other parameters.
+         **
+         > Arguments
+         **
+         - name (string) name of the event, dot (`.`) or slash (`/`) separated
+         - scope (object) context for the event handlers
+         - varargs (...) the rest of arguments will be sent to event handlers
+         **
+         = (array) array of errors, if any. Each element of the array is in format:
+         o {
+         o     error (string) error message
+         o     func (function) handler that caused error
+         o }
+        \*/
             eve = function (name, scope) {
                 var e = events,
                     args = Array.prototype.slice.call(arguments, 2),
                     listeners = eve.listeners(name),
+                    z = 0,
+                    f = false,
+                    l,
+                    indexed = [],
+                    queue = {},
                     errors = [];
-                for (var i = 0, ii = listeners.length; i < ii; i++) {
-                    try {
-                        listeners[i].apply(scope, args);
-                    } catch (ex) {
-                        errors.push({error: ex && ex.message || ex, func: listeners[i]});
+                current_event = name;
+                for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                    indexed.push(listeners[i].zIndex);
+                    if (listeners[i].zIndex < 0) {
+                        queue[listeners[i].zIndex] = listeners[i];
+                    }
+                }
+                indexed.sort(numsort);
+                while (indexed[z] < 0) {
+                    l = queue[indexed[z++]];
+                    if (l.apply(scope, args) === f) {
+                        return f;
                     }
                 }
-                if (errors.length) {
-                    return errors;
+                for (i = 0; i < ii; i++) {
+                    l = listeners[i];
+                    if ("zIndex" in l) {
+                        if (l.zIndex == indexed[z]) {
+                            if (l.apply(scope, args) === f) {
+                                return f;
+                            }
+                            do {
+                                z++;
+                                l = queue[indexed[z]];
+                                if (l) {
+                                    if (l.apply(scope, args) === f) {
+                                        return f;
+                                    }
+                                }
+                            } while (l)
+                        } else {
+                            queue[l.zIndex] = l;
+                        }
+                    } else {
+                        if (l.apply(scope, args) === f) {
+                            return f;
+                        }
+                    }
                 }
             };
+        /*\
+         * eve.listeners
+         [ method ]
+         **
+         * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+         **
+         > Arguments
+         **
+         - name (string) name of the event, dot (`.`) or slash (`/`) separated
+         **
+         = (array) array of event handlers
+        \*/
         eve.listeners = function (name) {
             var names = name.split(separator),
                 e = events,
             }
             return out;
         };
+    
+        /*\
+         * eve.on
+         [ method ]
+         **
+         * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+         | eve.on("*.under.*", f);
+         | eve("mouse.under.floor"); // triggers f
+         * Use @eve to trigger the listener.
+         **
+         > Arguments
+         **
+         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+         - f (function) event handler function
+         **
+         = (function) returned function accept one number parameter that represents z-index of the handler. It is optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
+         > Example:
+         | eve.on("mouse", eat)(2);
+         | eve.on("mouse", scream);
+         | eve.on("mouse", catch)(1);
+         * This will ensure that `catch` function will be called before `eat`.
+         * If you want to put you hadler before not indexed handlers specify negative value.
+         * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+        \*/
         eve.on = function (name, f) {
             var names = name.split(separator),
                 e = events;
             }
             e.f = e.f || [];
             for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
-                return false;
+                return fun;
             }
             e.f.push(f);
+            return function (zIndex) {
+                if (+zIndex == +zIndex) {
+                    f.zIndex = +zIndex;
+                }
+            };
         };
+        /*\
+         * eve.nt
+         [ method ]
+         **
+         * Could be used inside event handler to figure out actual name of the event.
+         **
+         > Arguments
+         **
+         - subname (string) #optional subname of the event
+         **
+         = (string) name of the event, if `subname` is not specified
+         * or
+         = (boolean) `true`, if current event’s name contains `subname`
+        \*/
+        eve.nt = function (subname) {
+            if (subname) {
+                return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+            }
+            return current_event;
+        };
+        /*\
+         * eve.unbind
+         [ method ]
+         **
+         * Removes given function from the list of event listeners assigned to given name.
+         **
+         > Arguments
+         **
+         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+         - f (function) event handler function
+        \*/
         eve.unbind = function (name, f) {
             var names = name.split(separator),
                 e,
                     e = e.n;
                 }
             }
-            return true;
         };
+        /*\
+         * eve.version
+         [ property (string) ]
+         **
+         * Current version of the library.
+        \*/
         eve.version = version;
         eve.toString = function () {
             return "You are running Eve " + version;