blob: 25b1c8857919ff99f668cda6b6d3d75f22e66bde [file] [log] [blame]
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!--
@group Polymer Core Elements
@element core-layout-grid
@status beta
@homepage github.io
TODO
-->
<link rel="import" href="../polymer/polymer.html">
<polymer-element name="core-layout-grid" attributes="nodes layout auto">
<script>
(function() {
Polymer('core-layout-grid', {
nodes: null,
layout: null,
auto: false,
created: function() {
this.layout = [];
},
nodesChanged: function() {
this.invalidate();
},
layoutChanged: function() {
this.invalidate();
},
autoNodes: function() {
this.nodes = this.parentNode.children.array().filter(
function(node) {
switch(node.localName) {
case 'core-layout-grid':
case 'style':
return false;
}
return true;
}
);
},
invalidate: function() {
if (this.layout && this.layout.length) {
// job debounces layout, only letting it occur every N ms
this.layoutJob = this.job(this.layoutJob, this.relayout);
}
},
relayout: function() {
if (!this.nodes || this.auto) {
this.autoNodes();
}
layout(this.layout, this.nodes);
this.asyncFire('core-layout');
}
});
//
var lineParent;
function line(axis, p, d) {
var l = document.createElement('line');
var extent = (axis === 'left' ? 'width' :
(axis === 'top' ? 'height' : axis));
l.setAttribute('extent', extent);
if (d < 0) {
axis = (axis === 'left' ? 'right' :
(axis === 'top' ? 'bottom' : axis));
}
p = Math.abs(p);
l.style[axis] = p + 'px';
l.style[extent] = '0px';
lineParent.appendChild(l);
}
var colCount, colOwners, rowCount, rowOwners;
function matrixillate(matrix) {
// mesaure the matrix, must be rectangular
rowCount = matrix.length;
colCount = rowCount && matrix[0].length || 0;
// transpose matrix
var transpose = [];
for (var i=0; i<colCount; i++) {
var c = [];
for (var j=0; j<rowCount; j++) {
c.push(matrix[j][i]);
}
transpose.push(c);
}
// assign sizing control
colOwners = findOwners(matrix);
rowOwners = findOwners(transpose);
//console.log('colOwners', colOwners);
//console.log('rowOwners', rowOwners);
}
function findOwners(matrix) {
var majCount = matrix.length;
var minCount = majCount && matrix[0].length || 0;
var owners = [];
// for each column (e.g.)
for (var i=0; i<minCount; i++) {
// array of contained areas
var contained = {};
// look at each row to find a containing area
for (var j=0; j<majCount; j++) {
// get the row vector
var vector = matrix[j]
// node index at [i,j]
var nodei = vector[i];
// if a node is there
if (nodei) {
// determine if it bounds this column
var owns = false;
if (i === 0) {
owns = (i === minCount-1) || (nodei !== vector[i+1]);
} else if (i === minCount - 1) {
owns = (i === 0) || (nodei !== vector[i-1]);
} else {
owns = nodei !== vector[i-1] && nodei !== vector[i+1];
}
if (owns) {
contained[nodei] = 1;
}
}
// store the owners for this column
owners[i] = contained;
}
}
return owners;
}
var nodes;
function colWidth(i) {
for (var col in colOwners[i]) {
col = Number(col);
if (col === 0) {
return 96;
}
var node = nodes[col - 1];
if (node.hasAttribute('h-flex') || node.hasAttribute('flex')) {
return -1;
}
var w = node.offsetWidth;
//console.log('colWidth(' + i + ') ==', w);
return w;
}
return -1;
}
function rowHeight(i) {
for (var row in rowOwners[i]) {
row = Number(row);
if (row === 0) {
return 96;
}
var node = nodes[row - 1];
if (node.hasAttribute('v-flex') || node.hasAttribute('flex')) {
return -1;
}
var h = node.offsetHeight;
//console.log('rowHeight(' + i + ') ==', h);
return h;
}
return -1;
}
var m = 0;
function railize(count, sizeFn) {
//
// create rails for `count` tracks using
// sizing function `sizeFn(trackNo)`
//
// for n tracks there are (n+1) rails
//
// |track|track|track|
// 0|->sz0|->sz1|<-sz2|0
//
// |track|track|track|
// 0|->sz0| |<-sz2|0
//
// there can be one elastic track per set
//
// |track|track|track|track|
// 0|-->s0|-->s1|<--s1|<--s2|0
//
// sz1 spans multiple tracks which makes
// it elastic (it's underconstrained)
//
var rails = [];
var a = 0;
for (var i=0, x; i<count; i++) {
rails[i] = {p: a, s: 1};
x = sizeFn(i) + m + m;
if (x == -1) {
break;
}
a += x;
}
if (i === count) {
rails[i] = {p: 0, s: -1};
}
var b = 0;
for (var ii=count, x; ii>i; ii--) {
rails[ii] = {p: b, s: -1};
x = sizeFn(ii - 1) + m + m;
if (x !== -1) {
b += x;
}
}
return rails;
}
// TODO(sjmiles): this code tries to preserve actual position,
// so 'unposition' is really 'naturalize' or something
function unposition(box) {
var style = box.style;
//style.right = style.bottom = style.width = style.height = '';
style.position = 'absolute';
style.display = 'inline-block';
style.boxSizing = style.mozBoxSizing = 'border-box';
}
function _position(style, maj, min, ext, a, b) {
style[maj] = style[min] = '';
style[ext] = 'auto';
if (a.s < 0 && b.s < 0) {
var siz = a.p - b.p - m - m;
style[ext] = siz + 'px';
var c = 'calc(100% - ' + (b.p + siz + m) + 'px' + ')';
style[maj] = '-webkit-' + c;
style[maj] = c;
} else if (b.s < 0) {
style[maj] = a.p + m + 'px';
style[min] = b.p + m + 'px';
} else {
style[maj] = a.p + m + 'px';
style[ext] = b.p - a.p - m - m + 'px';
}
}
function position(elt, left, right, top, bottom) {
_position(elt.style, 'top', 'bottom', 'height', rows[top],
rows[bottom]);
_position(elt.style, 'left', 'right', 'width', columns[left],
columns[right]);
}
function layout(matrix, anodes, alineParent) {
//console.group('layout');
lineParent = alineParent;
nodes = anodes;
matrixillate(matrix);
nodes.forEach(unposition);
columns = railize(colCount, colWidth);
rows = railize(rowCount, rowHeight);
if (alineParent) {
//console.group('column rails');
columns.forEach(function(c) {
//console.log(c.p, c.s);
line('left', c.p, c.s);
});
//console.groupEnd();
//console.group('row rails');
rows.forEach(function(r) {
//console.log(r.p, r.s);
line('top', r.p, r.s);
});
//console.groupEnd();
}
//console.group('rail boundaries');
nodes.forEach(function(node, i) {
// node indices are 1-based
var n = i + 1;
// boundary rails
var l, r, t = 1e10, b = -1e10;
matrix.forEach(function(vector, i) {
var f = vector.indexOf(n);
if (f > -1) {
l = f;
r = vector.lastIndexOf(n) + 1;
t = Math.min(t, i);
b = Math.max(b, i) + 1;
}
});
if (l == undefined) {
//console.log('unused');
node.style.position = 'absolute';
var offscreen = node.getAttribute('offscreen');
switch (offscreen) {
case 'basement':
node.style.zIndex = 0;
break;
case 'left':
case 'top':
node.style[offscreen] = node.offsetWidth * -2 + 'px';
break;
case 'right':
node.style.left = node.offsetParent.offsetWidth
+ node.offsetWidth + 'px';
break;
case 'bottom':
node.style.top = node.parentNode.offsetHeight
+ node.offsetHeight + 'px';
break;
default:
node.style[Math.random() >= 0.5 ? 'left' : 'top'] = '-110%';
}
//node.style.opacity = 0;
node.style.pointerEvents = 'none';
} else {
node.style.pointerEvents = '';
//node.style.opacity = '';
//console.log(l, r, t, b);
position(node, l, r, t, b);
}
});
//console.groupEnd();
//console.groupEnd();
}
})();
</script>
</polymer-element>