git @ Cat's Eye Technologies Gemooy / master 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.12
 * 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).
 *
 * drawCursorsFirst defaults to true.  This produces the pleasing visual
 * effect of the cursor being behind the cell values, but only if the cell values
 * themselves have transparent areas (e.g. if they're glyphs in some font.)
 * If the cell values are solid and fill the entire cell, drawCursorsFirst: false
 * may be in order.
 *
 * resizeCanvas defaults to true.  If set to false, the canvas element will
 * not be resized before each draw.  You may wish to do this yourself in your
 * code which calls playfieldCanvasView.draw().
 *
 */
yoob.PlayfieldCanvasView = function() {
    this.init = function(cfg) {
        this.pf = cfg.playfield;
        this.canvas = cfg.canvas;
        this.ctx = this.canvas.getContext('2d');
        this.fixedPosition = !!cfg.fixedPosition;
        this.fixedSizeCanvas = !!cfg.fixedSizeCanvas;
        this.drawCursorsFirst = (cfg.drawCursorsFirst === undefined) ? true : !!cfg.drawCursorsFirst;
        this.setCellDimensions(cfg.cellWidth || 8, cfg.cellHeight || 8);
        this.resizeCanvas = cfg.resizeCanvas === false ? false : true;
        return this;
    };

    /*** Chainable setters ***/

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

    this.setCanvas = function(element) {
        this.canvas = element;
        this.ctx = this.canvas.getContext('2d');
        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;
    };

    /*
     * 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 $this = this;
        this.pf.foreach(function(x, y, value) {
            $this.drawCell(ctx, value, x, y,
                           offsetX + x * cellWidth, offsetY + y * cellHeight,
                           cellWidth, cellHeight);
        });
    };

    /*
     * Override if you like.
     */
    this.drawCursor = function(ctx, cursor, canvasX, canvasY, cellWidth, cellHeight) {
        ctx.fillStyle = this.cursorFillStyle || "#50ff50";
        ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
    };

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

    /*
     * Draw the Playfield, and its set of Cursors, on the canvas element.
     * Optionally resizes the canvas to the needed dimensions first.
     */
    this.draw = function() {
        var canvas = this.canvas;
        var cellWidth = this.cellWidth;
        var cellHeight = this.cellHeight;

        var width = this.pf.getCursoredExtentX();
        var height = this.pf.getCursoredExtentY();

        if (this.resizeCanvas) {
            canvas.width = width * cellWidth;
            canvas.height = height * cellHeight;
        }
        var ctx = this.ctx;

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

        var offsetX = 0;
        var offsetY = 0;

        if (!this.fixedPosition) {
            offsetX = (this.pf.getLowerX() || 0) * cellWidth * -1;
            offsetY = (this.pf.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);
        }
    };

};