    /*
     * @class Ext.util.translatable.Abstract
     * @private
     *
     * The abstract class. Sub-classes are expected, at the very least, to implement translation logics inside
     * the 'translate' method
     */
    Ext.define('Ext.util.translatable.Abstract', {
        mixins: ['Ext.mixin.Observable'],

        requires: ['Ext.fx.Easing'],

        config: {
            element: null,

            easing: {},

            easingX: {},

            easingY: {},

            fps: 60
        },

        constructor: function(config) {
            var element;

            this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);

            this.translation = {
                x: 0,
                y: 0
            };

            this.activeEasing = {
                x: null,
                y: null
            };

            this.initialConfig = config;

            if (config && config.element) {
                element = config.element;
                delete config.element;

                this.setElement(element);
            }
        },

        applyElement: function(element) {
            if (!element) {
                return;
            }

            return Ext.get(element);
        },

        updateElement: function(element) {
            this.initConfig(this.initialConfig);
            this.refresh();
        },

        factoryEasing: function(easing) {
            return new Ext.fx.Easing(easing);
        },

        applyEasing: function(easing) {
            easing = this.factoryEasing(easing);

            if (!this.getEasingX()) {
                this.setEasingX(easing);
            }

            if (!this.getEasingY()) {
                this.setEasingY(easing);
            }

            return easing;
        },

        applyEasingX: function(easing) {
            return this.factoryEasing(easing);
        },

        applyEasingY: function(easing) {
            return this.factoryEasing(easing);
        },

        updateFps: function(fps) {
            this.animationInterval = 1000 / fps;
        },

        doTranslate: function(translation) {
            var current = this.translation;

            if ('x' in translation) {
                current.x = translation.x;
            }

            if ('y' in translation) {
                current.y = translation.y;
            }

            return this;
        },

        translate: function(translation, animation) {
            this.stopAnimation();

            if (animation) {
                return this.translateAnimated(translation, animation);
            }

            return this.doTranslate(translation);
        },

        translateAnimated: function(translation, animation) {
            if (!Ext.isObject(animation)) {
                animation = {};
            }

            var easing = animation.easing,
                activeEasing = this.activeEasing,
                current = this.translation,
                now = Ext.Date.now(),
                easingX = ('x' in translation) ? (easing || animation.easingX || this.getEasingX()) : null,
                easingY = ('y' in translation) ? (easing || animation.easingY || this.getEasingY()) : null;

            if (easingX) {
                easingX = this.factoryEasing(easingX);
                easingX.setStartTime(now);
                easingX.setStartValue(current.x);
                easingX.setEndValue(translation.x);

                if ('duration' in animation) {
                    easingX.setDuration(animation.duration);
                }
            }

            if (easingY) {
                easingY = this.factoryEasing(easingY);
                easingY.setStartTime(now);
                easingY.setStartValue(current.y);
                easingY.setEndValue(translation.y);

                if ('duration' in animation) {
                    easingY.setDuration(animation.duration);
                }
            }

            activeEasing.x = easingX;
            activeEasing.y = easingY;

            this.isAnimating = true;
            this.animationTimer = setInterval(this.doAnimationFrame, this.animationInterval, this);

            this.fireEvent('animationstart', this);
        },

        doAnimationFrame: function() {
            if (!this.isAnimating) {
                return;
            }

            var easing = this.activeEasing,
                easingX = easing.x,
                easingY = easing.y,
                isEasingXEnded = easingX === null || easingX.isEnded,
                isEasingYEnded = easingY === null || easingY.isEnded,
                translation = {};

            if (isEasingXEnded && isEasingYEnded) {
                this.stopAnimation();
                return;
            }

            if (!isEasingXEnded) {
                translation.x = Math.round(easingX.getValue());
            }

            if (!isEasingYEnded) {
                translation.y = Math.round(easingY.getValue());
            }

            this.doTranslate(translation);
            this.fireEvent('animationframe', this, translation);
        },

        stopAnimation: function() {
            if (!this.isAnimating) {
                return;
            }

            var activeEasing = this.activeEasing;

            activeEasing.x = null;
            activeEasing.y = null;

            this.isAnimating = false;

            clearInterval(this.animationTimer);
            this.fireEvent('animationend', this);
        },

        refresh: function() {
            this.translate(this.translation);
        }
    });