Source: ExpressionWidget.js

/*
 *  2016 Maciej Wiecierzewski
 */

/**
 * @constructor
 * @classdesc Widget for displaying expression and manipulating the order of symbols
 */
function ExpressionWidget(images)
{
    this.images = images;
    
    this.symbolsImg = [];
    this.tiles = [];
    this.checkedTile = 0;
    this.maxX = 0;
    this.drawn = false;
    
    this.tileColor = "#fff";
    this.correctAnsColor = "#6f0";
    this.wrongAnsColor = "#e20";
    
    this.container = new createjs.Container();
    
    var backgroundMousedown = function(event)
    {
        this.xPos = event.stageX;
        this.prevX = this.container.x;
    }.bind(this);

    var backgroundPressmove = function(event)
    {
        var x = event.stageX-this.xPos;
        this.container.x = this.prevX+x;
    }.bind(this);

    var backgroundPressup = function(event)
    {
        if(this.container.x > 0) this.container.x = 0
        if(this.container.x < this.maxX) this.container.x = this.maxX;
    }.bind(this);
    
    this.background = new createjs.Shape();
    this.background.addEventListener("mousedown", backgroundMousedown);
    this.background.addEventListener("pressmove", backgroundPressmove);
    this.background.addEventListener("pressup", backgroundPressup);
    
    this.container.addChild(this.background);
}

/**
 * Creates tile's 'Shape' object
 * @param {type} img
 * @param {type} color
 * @returns {ExpressionWidget.createTileShape.shape|createjs.Shape}
 */
ExpressionWidget.createTileShape = function(img, color)
{
    var graphics = new createjs.Graphics();
    graphics.beginFill(color);
    graphics.drawRect(0, 0, 100, 100);
    graphics.endFill();
    graphics.beginBitmapFill(img, undefined, new createjs.Matrix2D(0.6, 0, 0, 0.6));
    graphics.drawRect(0, 0, 100, 100);
            
    var shape = new createjs.Shape(graphics);
    
    shape.setBounds(0, 0, 60, 60);
    
    return shape;
}
/**
 * Returns current state of order of symbols
 * @returns {Array|ExpressionWidget.prototype.getSolution.array}
 */
ExpressionWidget.prototype.getSolution = function()
{
    var array = [];
    
    for(var i = 0; i < this.tiles.length; i++)
    {
        array.push(this.tiles[i].order);
    }
    
    return array;
}

/**
 * Sets new expression
 * @param {Array} expression Expression to be drawn
 * @returns {undefined}
 */
ExpressionWidget.prototype.draw = function(expression)
{
    this.container.removeAllChildren();
    this.container.addChild(this.background);
    this.checkedTile = 0;
    this.tiles = [];
    
    // Creating tiles
    for(var i = 0; i < expression.string.length; i++)
    {
        // Building the spriteSheet
        
        var symbol = expression.string.charAt(i);
        
        var builder = new createjs.SpriteSheetBuilder();
        
        var tileFrame = ExpressionWidget.createTileShape(this.symbolsImg[symbol], this.tileColor);
        var correctAnsFrame = ExpressionWidget.createTileShape(this.symbolsImg[symbol], this.correctAnsColor);
        var wrongAnsFrame = ExpressionWidget.createTileShape(this.symbolsImg[symbol], this.wrongAnsColor);
        builder.addFrame(tileFrame);
        builder.addFrame(correctAnsFrame);
        builder.addFrame(wrongAnsFrame);
        
        var spriteSheet = builder.build();
        
        // tile's sprite
        var tile = new createjs.Sprite(spriteSheet);
        tile.id = 's'+i;
        this.container.addChild(tile);
        
        var tileMousedown = function(event)
        {
            this.yPos = event.stageY
            this.timeStamp = event.timeStamp;
            var id = parseInt(event.target.id.substring(1));
            this.checkTile(id);
            Dialog.actions['showDescription'](id);
        }.bind(this);
        
        // Tile's event listeners
        var tilePressmove = function(event)
        {
            var y = event.stageY-this.yPos;
            this.moveTile(y);
        }.bind(this);
        
        var tilePressup = function(event)
        {
            var y = event.stageY-this.yPos;
            var hold = (event.timeStamp - this.timeStamp) > 300;
            if(y > 30)
                this.tileDown(hold);
            else if(y < -30)
                this.tileUp(hold);
            
            this.putBackTile();
        }.bind(this);
        
        tile.addEventListener("mousedown", tileMousedown);
        tile.addEventListener("pressmove", tilePressmove);
        tile.addEventListener("pressup", tilePressup);
        
        // Tile's text
        var text = new createjs.Text("0", "26px Arial", "#000");
        this.container.addChild(text);
        
        // The tile object
        this.tiles[i] = { tileObj: tile, text: text, symbol: symbol, order: 0, correctOrder: null };
    }
    
    // Creates up arrow
    {
        // Event listeners
        var arrowMousedown = function(event)
        {
            this.timeStamp = event.timeStamp;
        }.bind(this);
        
        var arrowPressup = function(event)
        {
            var hold = (event.timeStamp - this.timeStamp) > 400;
            this.tileUp(hold);
        }.bind(this);

        // 'Graphics' object
        var graphics = new createjs.Graphics();
        graphics.beginBitmapFill(this.images['uparrow']);
        graphics.drawRect(0, 0, 100, 60);

        // Creating the object
        this.uparrow = new createjs.Shape(graphics);
        
        var hitArea = new createjs.Shape();
        hitArea.graphics.beginFill("#000").drawRect(0, 0, 100, 60);
        
        this.uparrow.hitArea = hitArea;
        this.uparrow.scaleX = 0.6;
        this.uparrow.scaleY = 0.6;
        this.uparrow.addEventListener('mousedown', arrowMousedown);
        this.uparrow.addEventListener('pressup', arrowPressup);
        
        // Appending to the container
        this.container.addChild(this.uparrow);
    }
    
    // Creates down arrow
    {
        // Event listeners
        var arrowMousedown = function(event)
        {
            this.timeStamp = event.timeStamp;
        }.bind(this);
        
        var arrowPressup = function(event)
        {
            var hold = (event.timeStamp - this.timeStamp) > 400;
            this.tileDown(hold);
        }.bind(this);
        
        // 'Graphics' object
        var graphics = new createjs.Graphics();
        graphics.beginBitmapFill(this.images['downarrow']);
        graphics.drawRect(0, 0, 100, 60);

        // Creating the object
        this.downarrow = new createjs.Shape(graphics);
        
        var hitArea = new createjs.Shape();
        hitArea.graphics.beginFill("#000").drawRect(0, 0, 100, 60);
        
        this.downarrow.hitArea = hitArea;
        this.downarrow.scaleX = 0.6;
        this.downarrow.scaleY = 0.6;
        this.downarrow.addEventListener('mousedown', arrowMousedown);
        this.downarrow.addEventListener('pressup', arrowPressup);
        
        // Appending to the container
        this.container.addChild(this.downarrow);
    }
    
    this.drawn = true;
}

ExpressionWidget.prototype.setCheckResult = function(result)
{
    for(var i = 0; i < result.length; i++)
    {
        if(result[i] === 1)
        {
            this.tiles[i].correctOrder = this.tiles[i].order;
            this.tiles[i].wrongOrder = null;
        }
        else if(result[i] === 2)
        {
            this.tiles[i].correctOrder = null;
            this.tiles[i].wrongOrder = this.tiles[i].order;
        }
        else
        {
            this.tiles[i].correctOrder = null;
            this.tiles[i].wrongOrder = null
        }
    }
    
    this.updateTilesState();
}

/**
 * Resizes the widget
 * @param {number} x
 * @param {number} y
 * @param {number} w
 * @param {number} h
 */
ExpressionWidget.prototype.resize = function(x, y, w, h)
{
    this.container.x = x;
    this.container.y = y;
    
    this.maxX = Math.min(w-(this.tiles.length*65+50), 0);
            
    this.background.graphics.clear();
    this.background.graphics.beginFill('#eee').drawRect(0, 0, this.tiles.length*65+50, h);
    
    if(!this.drawn)
        return;
    
    this.shapeY = 0.5*h;
    for(var i = 0; i < this.tiles.length; i++)
    {
        var tile = this.tiles[i].tileObj;
        var text = this.tiles[i].text;

        tile.x = 10+65*i;
        tile.y = this.shapeY;
        
        text.x = 35+65*i;
        text.y = this.shapeY-80;
    }
    
    this.uparrow.y = this.shapeY-40;
    this.downarrow.y = this.shapeY+65;
    
    this.showArrows();
}

/**
 * Sets tile to be checked
 * @param {Number} tile number
 */
ExpressionWidget.prototype.checkTile = function(i)
{
    this.putBackTile();
    this.checkedTile = i;
    this.showArrows();
}

/** Sets arrows' position */
ExpressionWidget.prototype.showArrows = function()
{
    var i = this.checkedTile;
    this.uparrow.x = 10+65*i;
    this.downarrow.x = 10+65*i;
}

/*
 * Changes operators' order
 * @param {type} hold
 * @returns {undefined}
 */
ExpressionWidget.prototype.tileUp = function(hold)
{
    var i = this.checkedTile;
    
    if(hold === true)
    {
        var max = 0;
        for(var j = 0; j < this.tiles.length; j++)
        {
            if(this.tiles[j].order > this.tiles[max].order)
            {
                max = j;
            }
        }
        
        if(this.tiles[max].order < this.tiles.length)
        {
            this.tiles[i].order = this.tiles[max].order+1;
        }
        
        this.updateTilesState();
        return;
    }
    else if(this.tiles[i].order === 0)
    {
        var minValue = this.tiles[i].order;
        var maxValue = this.tiles[i].order;
        var end = false;
        
        while(!end)
        {
            end = true;
            for(var j = 0; j < this.tiles.length; j++)
            {
                if(this.tiles[j].order === maxValue)
                {
                    maxValue++;
                    end = false;
                    break;
                }
            }
        }
        
        for(var j = 0; j < this.tiles.length; j++)
        {
            if(this.tiles[j].order > minValue && this.tiles[j].order < maxValue)
                this.tiles[j].order++;
        }
    }
    if(this.tiles[i].order === this.tiles.length)
    {
        this.tiles[i].order = 0;
    }
    else
    {
        var newValue = this.tiles[i].order+1;

        for(var j = 0; j < this.tiles.length; j++)
        {
            if(this.tiles[j].order === newValue)
            {
                this.tiles[j].order = this.tiles[i].order;
                break;
            }
        }

        this.tiles[i].order++;
    }
    
    this.updateTilesState();
}

/*
 * Changes operators' order
 * @param {type} hold
 * @returns {undefined}
 */
ExpressionWidget.prototype.tileDown = function(hold)
{
    var i = this.checkedTile;
    
    if(hold === true)
    {
        this.tiles[i].order = 0;
    }
    else if(this.tiles[i].order === 0)
    {
        var newValue = this.tiles.length;
        var end = false;
        while(!end && newValue > 0)
        {
            end = true;
            
            for(var j = 0; j < this.tiles.length; j++)
            {
                if(this.tiles[j].order === newValue)
                {
                    end = false;
                    newValue--;
                    break;
                }
            }
        }
        
        this.tiles[i].order = newValue;
    }
    else if(this.tiles[i].order === 1)
    {
        this.tiles[i].order--;
    }
    else
    {
        var newValue = this.tiles[i].order-1;

        for(var j = 0; j < this.tiles.length; j++)
        {
            if(this.tiles[j].order === newValue)
            {
                this.tiles[j].order = this.tiles[i].order;
                break;
            }
        }

        this.tiles[i].order--;
    }
    
    this.updateTilesState();
}

/** Updates tile's color and displayed number */
ExpressionWidget.prototype.updateTilesState = function()
{
    for(var i = 0; i < this.tiles.length; i++)
    {
        this.tiles[i].text.text = this.tiles[i].order;
        
        if(this.tiles[i].order === this.tiles[i].correctOrder)
            this.tiles[i].tileObj.gotoAndStop(1);
        else if(this.tiles[i].order === this.tiles[i].wrongOrder)
            this.tiles[i].tileObj.gotoAndStop(2);
        else
            this.tiles[i].tileObj.gotoAndStop(0);
    }
}

/**
 * Tilts the checked tile in y axes
 * @param {type} y
 */
ExpressionWidget.prototype.moveTile = function(y)
{
    var shape = this.tiles[this.checkedTile].tileObj;
    shape.y = this.shapeY+y*0.2;
}

/** Puts the checked tile's y axes in its original position */
ExpressionWidget.prototype.putBackTile = function()
{
    var shape = this.tiles[this.checkedTile].tileObj;
    shape.y = this.shapeY;
}