git @ Cat's Eye Technologies Wierd / master dialect / wierd-jnc / impl / wierd-jnc.js / src / yoob / playfield-canvas-view.js
master

Tree @master (Download .tar.gz)

playfield-canvas-view.js @masterraw · history · blame

/*
 * This file is part of yoob.js version 0.8
 * Available from https://github.com/catseye/yoob.js/
 * This file is in the public domain.  See http://unlicense.org/ for details.
 */
if (window.yoob === undefined) yoob = {};

/*
 * A view (in the MVC sense) for depicting a yoob.Playfield (-compatible)
 * object on an HTML5 <canvas> element (or compatible object).
 *
 * TODO: don't necesarily resize canvas each time?
 * TODO: option to stretch content rendering to fill a fixed-size canvas
 */
yoob.PlayfieldCanvasView = function() {
    this.init = function(pf, canvas) {
        this.pf = pf;
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.cursors = [];
        this.fixedPosition = false;
        this.fixedSizeCanvas = false;
        this.drawCursorsFirst = true;
        this.setCellDimensions(8, 8);
        return this;
    };

    /*** Chainable setters ***/

    /*
     * Set the list of cursors to the given list of yoob.Cursor (or compatible)
     * objects.
     */
    this.setCursors = function(cursors) {
        this.cursors = cursors;
        return this;
    };

    this.setPlayfield = function(pf) {
        this.pf = pf;
        return this;
    };

    /*
     * Set the displayed dimensions of every cell.
     * cellWidth and cellHeight are canvas units of measure for each cell.
     * If cellWidth is undefined, the width of a character in the monospace
     * font of cellHeight pixels is used.
     */
    this.setCellDimensions = function(cellWidth, cellHeight) {
        this.ctx.textBaseline = "top";
        this.ctx.font = cellHeight + "px monospace";

        if (cellWidth === undefined) {
            cellWidth = this.ctx.measureText("@").width;
        }

        this.cellWidth = cellWidth;
        this.cellHeight = cellHeight;
        return this;
    };

    /*
     * Return the requested bounds of the occupied portion of the playfield.
     * "Occupation" in this sense includes all cursors.
     *
     * These may return 'undefined' if there is nothing in the playfield.
     *
     * Override these if you want to draw some portion of the
     * playfield which is not the whole playfield.
     */
    this.getLowerX = function() {
        var minX = this.pf.getMinX();
        for (var i = 0; i < this.cursors.length; i++) {
            if (minX === undefined || this.cursors[i].x < minX) {
                minX = this.cursors[i].x;
            }
        }
        return minX;
    };
    this.getUpperX = function() {
        var maxX = this.pf.getMaxX();
        for (var i = 0; i < this.cursors.length; i++) {
            if (maxX === undefined || this.cursors[i].x > maxX) {
                maxX = this.cursors[i].x;
            }
        }
        return maxX;
    };
    this.getLowerY = function() {
        var minY = this.pf.getMinY();
        for (var i = 0; i < this.cursors.length; i++) {
            if (minY === undefined || this.cursors[i].y < minY) {
                minY = this.cursors[i].y;
            }
        }
        return minY;
    };
    this.getUpperY = function() {
        var maxY = this.pf.getMaxY();
        for (var i = 0; i < this.cursors.length; i++) {
            if (maxY === undefined || this.cursors[i].y > maxY) {
                maxY = this.cursors[i].y;
            }
        }
        return maxY;
    };

    /*
     * Returns the number of occupied cells in the x direction.
     * "Occupation" in this sense includes all cursors.
     */
    this.getExtentX = function() {
        if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
            return 0;
        } else {
            return this.getUpperX() - this.getLowerX() + 1;
        }
    };

    /*
     * Returns the number of occupied cells in the y direction.
     * "Occupation" in this sense includes all cursors.
     */
    this.getExtentY = function() {
        if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
            return 0;
        } else {
            return this.getUpperY() - this.getLowerY() + 1;
        }
    };

    /*
     * Draws cells of the Playfield in a drawing context.
     * cellWidth and cellHeight are canvas units of measure.
     *
     * The default implementation tries to call a .draw() method on the cell's
     * value, if one exists, and just renders it as text, in black, if not.
     *
     * Override if you wish to draw elements in some other way.
     */
    this.drawCell = function(ctx, value, playfieldX, playfieldY,
                             canvasX, canvasY, cellWidth, cellHeight) {
        if (value.draw !== undefined) {
            value.draw(ctx, playfieldX, playfieldY, canvasX, canvasY,
                       cellWidth, cellHeight);
        } else {
            ctx.fillStyle = "black";
            ctx.fillText(value.toString(), canvasX, canvasY);
        }
    };

    /*
     * Draws the Playfield in a drawing context.
     * cellWidth and cellHeight are canvas units of measure for each cell.
     * offsetX and offsetY are canvas units of measure for the top-left
     *   of the entire playfield.
     */
    this.drawContext = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
        var self = this;
        this.pf.foreach(function (x, y, value) {
            self.drawCell(ctx, value, x, y,
                          offsetX + x * cellWidth, offsetY + y * cellHeight,
                          cellWidth, cellHeight);
        });
    };

    this.drawCursors = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
        var cursors = this.cursors;
        for (var i = 0; i < cursors.length; i++) {
            cursors[i].drawContext(
              ctx,
              offsetX + cursors[i].x * cellWidth,
              offsetY + cursors[i].y * cellHeight,
              cellWidth, cellHeight
            );
        }
    };

    /*
     * Draw the Playfield, and its set of Cursors, on the canvas element.
     * Resizes the canvas to the needed dimensions first.
     */
    this.draw = function() {
        var canvas = this.canvas;
        var ctx = canvas.getContext('2d');
        var cellWidth = this.cellWidth;
        var cellHeight = this.cellHeight;
        var cursors = this.cursors;

        var width = this.getExtentX();
        var height = this.getExtentY();

        canvas.width = width * cellWidth;
        canvas.height = height * cellHeight;

        this.ctx.textBaseline = "top";
        this.ctx.font = cellHeight + "px monospace";

        var offsetX = 0;
        var offsetY = 0;

        if (!this.fixedPosition) {
            offsetX = (this.getLowerX() || 0) * cellWidth * -1;
            offsetY = (this.getLowerY() || 0) * cellHeight * -1;
        }

        if (this.drawCursorsFirst) {
            this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
        }

        this.drawContext(ctx, offsetX, offsetY, cellWidth, cellHeight);
        
        if (!this.drawCursorsFirst) {
            this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
        }
    };

};