"use strict";
/**
* @module gallery-matrix-background
*/
// Based on my ancient JMatrixCtrl MFC widget
/**
* Node plugin to display falling text similar to what was used in the
* credits for The Matrix. If you plug into the body element, then it will
* fill the viewport. Otherwise, you must set a width and height for the
* node.
*
* @main gallery-matrix-background
* @class MatrixBackground
* @constructor
* @param config {Object} Plugin configuration
*/
function MatrixBackground(config)
{
this.timer = {};
this.cell_on = [];
MatrixBackground.superclass.constructor.call(this, config);
}
MatrixBackground.NAME = 'MatrixBackgroundPlugin';
MatrixBackground.NS = 'matrix';
MatrixBackground.ATTRS =
{
/**
* The range of Unicode characters to use for the background noise.
*
* @attribute charRange
* @type {Array}
* @default ['\u30A1', '\u30FA']
*/
charRange:
{
value: ['\u30A1', '\u30FA'],
validator: function(value)
{
return (Y.Lang.isArray(value) && value.length == 2 &&
value[0].length == 1 && value[1].length == 1 &&
value[0] < value[1]);
}
},
/**
* Set to `true` to force a monospace font. This only works if the
* browser can find a monospace version of the character range which
* you are using.
*
* @attribute monospace
* @type {Boolean}
* @default true for IE, false for all other browsers
*/
monospace:
{
value: !!Y.UA.ie,
validator: Y.Lang.isBoolean
},
/**
* If you do not have a monospace font for the charRange, set this to
* `true` to computer the widest character in the range. Note that
* this can take a long time if you have a large charRange!
*
* @attribute computeWidestChar
* @type {Boolean}
* @default false
*/
computeWidestChar:
{
value: false,
validator: Y.Lang.isBoolean
},
/**
* Fraction of total columns that have a spinning character.
*
* @attribute spinFraction
* @type {Number}
* @default 0.2
*/
spinFraction:
{
value: 0.2,
validator: function(value)
{
return (0.0 <= value && value <= 1.0);
}
}
};
var interval =
{
spin: 10,
drop: 80
},
drop_bottom_offset = 3, // minimum number of rows affected by column drop
min_spin_count = 300,
max_spin_count = 800;
function rnd(min, max)
{
return min + Math.floor(Math.random() * (max - min));
}
function getCharacterRange()
{
var c_range = this.get('charRange');
return [ c_range[0].charCodeAt(0), c_range[1].charCodeAt(0) ];
}
function getCell(x,y)
{
return this.table.getDOMNode().firstChild.childNodes[y].childNodes[x];
}
function startTimer(id)
{
stopTimer.call(this, id);
this.timer[id] = Y.later(interval[id], this, function()
{
this.fire(id);
},
null, true);
}
function stopTimer(id)
{
if (this.timer[id])
{
this.timer[id].cancel();
delete this.timer[id];
}
}
function renderTable()
{
if (this.table)
{
this.table.destroy();
}
var c_range = getCharacterRange.call(this),
c = String.fromCharCode(c_range[0]);
this.container.set('innerHTML',
'<table><tr><td>' + c + '</td></tr></table>');
var table = this.container.one('table');
if (this.get('computeWidestChar'))
{
var c_max = c_range[0],
w_max = table.totalWidth(),
c_end = c_range[1],
cell = this.container.getDOMNode().getElementsByTagName('td')[0];
for (c=c_max+1; c<=c_end; c++)
{
cell.innerHTML = String.fromCharCode(c);
var w = table.totalWidth();
if (w > w_max)
{
w_max = w;
c_max = c;
}
}
cell.innerHTML = c = String.fromCharCode(c_max);
}
var w = Math.ceil(this.container.totalWidth() / table.totalWidth()),
h = Math.ceil(this.container.totalHeight() / table.totalHeight());
var row = '<tr>';
for (var x=0; x<w; x++)
{
row += '<td> </td>';
}
row += '</tr>';
var s = '';
for (var y=0; y<h; y++)
{
s += row;
}
// force column widths with row outside bounds ( forces row heights)
s += '<tr>';
for (var x=0; x<w; x++)
{
s += '<td>' + c + '</td>';
}
s += '</tr>';
table.setContent('<tbody>' + s + '</tbody>');
this.table = table;
this.row_count = h;
this.col_count = w;
this.drop_active = 0;
this.drop_col = [];
for (var i=0; i<w; i++)
{
this.drop_col.push({ active: false });
}
startTimer.call(this, 'drop');
initSpin.call(this);
startTimer.call(this, 'spin');
}
function initSpin()
{
this.spin_count = Math.floor(this.col_count * this.get('spinFraction'));
this.spin_active = 0;
this.spin = [];
for (var i=0; i<this.spin_count; i++)
{
this.spin.push({ active: false });
}
}
function initDropColumn(x)
{
var col = this.drop_col[x];
col.active = true;
col.y = Math.random() < 0.5 ? rnd(0, this.row_count - drop_bottom_offset) : 0;
if (Math.random() < 0.5)
{
col.y_max = col.y + rnd(drop_bottom_offset, this.row_count - col.y - 1)
}
else
{
col.y_max = this.row_count - 1;
}
if (Math.random() < 0.2)
{
col.c = ' ';
}
else
{
var c_range = getCharacterRange.call(this);
col.c = String.fromCharCode(rnd(c_range[0], c_range[1]+1));
}
}
function updateDrop()
{
// increment all active columns
var count = this.col_count,
c_range = getCharacterRange.call(this);
for (var i=0; i<count; i++)
{
var col = this.drop_col[i];
if (col.active && col.y >= col.y_max)
{
col.active = false;
this.drop_active--;
}
else
{
if (col.c != ' ')
{
col.c = String.fromCharCode(rnd(c_range[0], c_range[1]+1));
}
col.y++;
}
}
// activate another column
if (this.drop_active < this.col_count)
{
var safety = 0;
do
{
var i = rnd(0, this.col_count);
safety++;
}
while (this.drop_col[i].active && safety < this.col_count);
if (!this.drop_col[i].active)
{
initDropColumn.call(this, i);
this.drop_active++;
}
}
update.call(this);
}
function updateSpin()
{
// activate another spinner
if (this.spin_active < this.spin_count && rnd(0,100) === 0)
{
var safety = 0;
do
{
var i = rnd(0, this.spin_count);
safety++;
}
while (this.spin[i].active && safety < this.spin_count);
if (!this.spin[i].active)
{
var x = rnd(0, this.col_count),
y = rnd(0, this.row_count);
this.spin[i] =
{
active: true,
counter: rnd(min_spin_count, max_spin_count),
cell: getCell.call(this, x, y)
};
this.spin_active++;
}
}
// increment all active spinners
var count = this.spin_count,
c_range = getCharacterRange.call(this);
for (var i=0; i<count; i++)
{
var spin = this.spin[i];
if (spin.active && spin.counter <= 0)
{
spin.active = false;
spin.cell = null;
this.spin_active--;
}
else if (spin.active)
{
spin.c = String.fromCharCode(rnd(c_range[0], c_range[1]+1));
spin.counter--;
}
}
update.call(this);
}
function update()
{
var count = this.cell_on.length;
for (var i=0; i<count; i++)
{
Y.DOM.removeClass(this.cell_on[i], 'on');
}
this.cell_on = [];
var count = this.col_count;
for (var i=0; i<count; i++)
{
var col = this.drop_col[i];
if (col.active)
{
var cell = getCell.call(this, i, col.y);
Y.DOM.addClass(cell, 'on');
this.cell_on.push(cell);
cell.innerHTML = col.c;
}
}
var count = this.spin_count;
for (var i=0; i<count; i++)
{
var spin = this.spin[i];
if (spin.active)
{
Y.DOM.addClass(spin.cell, 'on');
this.cell_on.push(spin.cell);
spin.cell.innerHTML = spin.c;
}
}
}
function updateMonospace()
{
if (this.get('monospace'))
{
this.container.addClass('monospace');
}
else
{
this.container.removeClass('monospace');
}
}
function resize()
{
if (this.is_body)
{
this.container.setStyles(
{
width: Y.DOM.winWidth() + 'px',
height: Y.DOM.winHeight() + 'px'
});
}
else
{
var host = this.get('host');
this.container.setStyles(
{
width: host.getStyle('width'),
height: host.getStyle('height')
});
}
renderTable.call(this);
}
Y.extend(MatrixBackground, Y.Plugin.Base,
{
initializer: function(config)
{
var host = this.get('host');
host.removeClass('yui3-matrixbkgd-loading');
this.container = Y.Node.create('<div class="yui3-matrixbkgd"></div>');
host.append(this.container);
updateMonospace.call(this);
this.after('charRangeChange', renderTable);
this.after('monospaceChange', updateMonospace);
this.after('spinFractionChange', initSpin);
this.on('drop', updateDrop);
this.on('spin', updateSpin);
if (host == Y.one('body'))
{
this.is_body = true;
Y.on('windowresize', resize, host, this);
}
this.afterHostMethod('setStyle', function(name, value)
{
if (name == 'width' || name == 'height')
{
resize.call(this);
}
});
this.afterHostMethod('setStyles', function(map)
{
if (map.width || map.height)
{
resize.call(this);
}
});
this.afterHostMethod('addClass', resize);
this.afterHostMethod('removeClass', resize);
this.afterHostMethod('replaceClass', resize);
resize.call(this);
},
destructor: function()
{
stopTimer.call(this, 'drop');
stopTimer.call(this, 'spin');
if (this.table)
{
this.table.destroy();
}
}
});
Y.namespace("Plugin");
Y.Plugin.MatrixBackground = MatrixBackground;
// for use by gallery-matrix-credits
Y.mix(Y.Plugin.MatrixBackground,
{
rnd: rnd,
startTimer: startTimer,
stopTimer: stopTimer,
getCharacterRange: getCharacterRange
});