"use strict"; (function gridLayout_js() { var moduleName = "views/gridLayout"; var defineArray = []; defineArray.push("views/view"); defineArray.push("views/layout"); define(moduleName, defineArray, function gridLayout_module() { var View = require("views/view"); var Layout = require("views/layout"); var GridLayout = Layout.inherit({ "className": "GridLayout", "constructor": function(controller, options) { var base = { }; W.extend(base, options); Layout.fn.constructor.call(this, controller, base); this._rh = []; this._rH = []; this._rw = []; this._rW = []; this._lay = [[[]]]; this._cols = [{}]; this._rows = [{}]; this._sde = []; //size dependent elements }, "append": function(child, row, col, rowSpan, colSpan, aligment) { aligment = aligment || 5; this._addChild(child, aligment); child.on("sizeDependencyChange", this._onChildSizeDependencyChange.bind(this, child)); rowSpan = rowSpan || 1; colSpan = colSpan || 1; var tRow = row + rowSpan; var tCol = col + colSpan; while (this._lay.length < tRow) { this._lay.push([]); } for (var i = 0; i < this._lay.length; ++i) { while (this._lay[i].length < tCol) { this._lay[i].push([]); } } var obj = { child: child, col: col, row: row, colspan: colSpan, rowspan: rowSpan, a: aligment } var item = this._c[this._c.length - 1]; item.col = col; item.row = row; item.colspan = colSpan; item.rowspan = rowSpan; for (i = row; i < tRow; ++i) { for (var j = col; j < tCol; ++j) { this._lay[i][j].push(obj); } } if (child._o.sizeDependency !== Layout.SizeDependency.straight.independent) { this._sde.push(obj); if (this._o.sizeDependency === Layout.SizeDependency.straight.independent) { this.setSizeDependency(child._o.sizeDependency) } else { if (this._o.sizeDependency !== child._o.sizeDependency) { this.setSizeDependency(Layout.SizeDependency.straight.undefined) } } } this._recountLimits(); if (this._w !== undefined && this._h !== undefined) { this.refreshLay(); } }, "_cleanupLay": function() { var i; var rowsC = false; var colsC = false; while (!rowsC) { for (i = 0; i < this._lay[this._lay.length - 1].length; ++i) { if (this._lay[this._lay.length - 1][i].length) { rowsC = true; break; } } if (!rowsC) { this._lay.pop() rowsC = !this._lay.length; colsC = !this._lay.length; } } while (!colsC) { for (i = 0; i < this._lay.length; ++i) { if (this._lay[i][this._lay[i].length - 1].length) { colsC = true; break; } } if (!colsC) { for (i = 0; i < this._lay.length; ++i) { this._lay[i].pop(); } } } }, "clear": function() { Layout.fn.clear.call(this); this._rh = []; this._rH = []; this._rw = []; this._rW = []; this._lay = [[[]]]; this._cols = [{}]; this._rows = [{}]; this._sde = []; }, "_onChildChangeLimits": function() { var notification = this._recountLimits(); if (!notification) { this.refreshLay(); } }, "_positionElements": function() { var shiftW = 0; var shiftH = 0; var positioned = []; for (var i = 0; i < this._lay.length; ++i) { shiftW = 0; for (var j = 0; j < this._lay[i].length; ++j) { for (var k = 0; k < this._lay[i][j].length; ++k) { var e = this._lay[i][j][k]; var child = e.child; if (positioned.indexOf(child) === -1) { var tWidth = 0; var tHeight = 0; var s; for (s = 0; s < e.colspan; ++s) { tWidth += this._cols[j + s].cur; } for (s = 0; s < e.rowspan; ++s) { tHeight += this._rows[i + s].cur; } child.setSize(tWidth, tHeight); switch (e.a) { case Layout.Aligment.LeftTop: child.setTop(shiftH); child.setLeft(shiftW); break; case Layout.Aligment.LeftCenter: child.setTop(shiftH + (tHeight - child._h) / 2); child.setLeft(shiftW); break; case Layout.Aligment.LeftBottom: child.setTop(shiftH + (tHeight - child._h)); child.setLeft(shiftW); break; case Layout.Aligment.CenterTop: child.setTop(shiftH); child.setLeft(shiftW + (tWidth - child._w) / 2); break; case Layout.Aligment.CenterCenter: child.setTop(shiftH + (tHeight - child._h) / 2); child.setLeft(shiftW + (tWidth - child._w) / 2); break; case Layout.Aligment.CenterBottom: child.setTop(shiftH + (tHeight - child._h)); child.setLeft((tWidth - child._w) / 2); break; case Layout.Aligment.RightTop: child.setTop(shiftH); child.setLeft(shiftW + (tWidth - child._h)); break; case Layout.Aligment.RightCenter: child.setTop((tHeight - child._h) / 2); child.setLeft(shiftW + (tWidth - child._h)); break; case Layout.Aligment.RightBottom: child.setTop(shiftH + (tHeight - child._h)); child.setLeft(shiftW + (tWidth - child._h)); break; } positioned.push(child); } } shiftW += this._cols[j].cur; } shiftH += this._rows[i].cur; } }, "_recountLimits": function() { this._rh = []; this._rH = []; this._rw = []; this._rW = []; this._cols = []; this._rows = []; var i, j; var multiCols = []; var multiRows = []; var proccessed = Object.create(null); for (i = 0; i < this._lay.length; ++i) { while (!this._rows[i]) { this._rows.push({ count: 0, min: 0, max: Infinity }); } for (j = 0; j < this._lay[i].length; ++j) { while (!this._cols[j]) { this._cols.push({ count: 0, min: 0, max: Infinity }); } for (var k = 0; k < this._lay[i][j].length; ++k) { var e = this._lay[i][j][k]; ++this._cols[j].count; ++this._rows[i].count; if (proccessed[e.child._id]) { continue; } var colMinW = this._cols[j].min; var rowMinH = this._rows[i].min; var colMaxW = this._cols[j].max; var rowMaxH = this._rows[i].max; if (e.colspan === 1) { this._cols[j].min = Math.max(colMinW, e.child._o.minWidth); this._cols[j].max = Math.min(colMaxW, e.child._o.maxWidth); } else { multiCols.push({ p: j, e: e }); } if (e.rowspan === 1) { this._rows[i].min = Math.max(rowMinH, e.child._o.minHeight); this._rows[i].max = Math.min(rowMaxH, e.child._o.maxHeight); } else { multiRows.push({ p: i, e: e }); } proccessed[e.child._id] = true; } } } multiLimits(this._cols, multiCols, true, this._rw, this._rW); multiLimits(this._rows, multiRows, false, this._rh, this._rH); for (i = 0; i < this._sde.length; ++i) { var dep = this._sde[i]; var cols = []; var rows = []; var tdwMax = 0; var tdwMin = 0; var tdhMax = 0; var tdhMin = 0; for (var c = 0; c < dep.colspan; ++c) { var col = this._cols[dep.col + c]; tdwMax += col.max; tdwMin += col.min; if (col.min < col.max) { cols.push(col); } } for (var r = 0; r < dep.rowspan; ++r) { var row = this._rows[dep.row + r]; tdhMax += row.max; tdhMin += row.min; if (row.min < row.max) { rows.push(row); } } var cMaxWidth, cMinWidth, cMaxHeight, cMinHeight; var opts = dep.child._o; var cmw = true; var cMw = true; var cmh = true; var cMh = true; while (cols.length || rows.length) { switch (opts.sizeDependency) { case Layout.SizeDependency.straight.straight: if (cMh) { cMaxWidth = dep.child.getMaxWidthForHeight(tdhMax); cMh = false; } if (cmh) { cMinWidth = dep.child.getMinWidthForHeight(tdhMin); cmh = false; } if (cMw) { cMaxHeight = dep.child.getMaxHeightForWidth(tdwMax); cMw = false; } if (cmw) { cMinHeight = dep.child.getMinHeightForWidth(tdwMin); cmw = false; } break; case Layout.SizeDependency.straight.reversed: if (cMh) { cMinWidth = dep.child.getMinWidthForHeight(tdhMax); cMh = false; } if (cmh) { cMaxWidth = dep.child.getMaxWidthForHeight(tdhMin); cmh = false; } if (cMw) { cMinHeight = dep.child.getMinHeightForWidth(tdwMax); cMw = false; } if (cmw) { cMaxHeight = dep.child.getMaxHeightForWidth(tdwMin); cmw = false; } break; } var changed = false; if (!changed && cMinWidth > tdwMin) { var wDiff = cMinWidth - tdwMin; var change = wDiff - adjust(wDiff, cols, true); if (change > 0) { changed = true; cmw = true; tdwMin += change; } } if (!changed && cMinHeight > tdhMin) { var hDiff = cMaxHeight - tdhMin; var change = hDiff - adjust(hDiff, rows, true); if (change > 0) { changed = true; cmh = true; tdhMin += change; } } if (!changed && cMaxWidth < tdwMax) { var wDiff = tdwMax - cMaxWidth; if (wDiff < Infinity) { var change = wDiff - adjust(wDiff, cols, false); if (change > 0) { changed = true; cMw = true; tdwMax -= change; } } else { constrain(cMaxWidth, cols, false); changed = true; cMw = true; tdwMax = cMaxWidth; } } if (!changed && cMaxHeight < tdhMax) { var hDiff = tdhMax - cMaxHeight; if (hDiff < Infinity) { var change = hDiff - adjust(hDiff, rows, false); if (change > 0) { changed = true; cMh = true; tdhMax -= change; } } else { constrain(cMaxHeight, rows, false); changed = true; cMh = true; tdhMax = cMaxHeight; } } if (!changed) { break; } } } var tw = total(this._cols, this._rw, this._rW); var th = total(this._rows, this._rh, this._rH); this._storedCols = []; this._storedRows = []; if (this._o.sizeDependency !== Layout.SizeDependency.straight.independent) { W.extend(true, this._storedCols, this._cols); W.extend(true, this._storedRows, this._rows); } return this._setLimits(tw.min, th.min, tw.max, th.max); }, "refreshLay": function() { if (this._o.sizeDependency !== Layout.SizeDependency.straight.independent) { this._cols = []; this._rows = []; W.extend(true, this._cols, this._storedCols); W.extend(true, this._rows, this._storedRows); } var totalMaxW = 0; var totalMaxH = 0; var totalMinW = 0; var totalMinH = 0; var i; for (i = 0; i < this._cols.length; ++i) { totalMaxW += this._cols[i].max; totalMinW += this._cols[i].min } if (this._w <= totalMinW || this._w >= totalMaxW) { var kW; var keyW; if (this._w <= totalMinW) { kW = this._w / totalMinW; keyW = "min"; } else { kW = this._w / totalMaxW; keyW = "max"; } for (i = 0; i < this._cols.length; ++i) { this._cols[i].cur = this._cols[i][keyW] * kW; } } else { distribute(this._w, this._cols, this._rw, this._rW); } for (i = 0; i < this._sde.length; ++i) { var dep = this._sde[i]; var rows = []; var tdw = 0; var tdhMax = 0; var tdhMin = 0; for (var c = 0; c < dep.colspan; ++c) { tdw += this._cols[dep.col + c].cur; } for (var r = 0; r < dep.rowspan; ++r) { var row = this._rows[dep.row + r]; tdhMax += row.max; tdhMin += row.min; if (row.min < row.max) { rows.push(row); } } if (rows.length > 0) { var cMaxHeight = dep.child.getMaxHeightForWidth(tdw); var cMinHeight = dep.child.getMinHeightForWidth(tdw); if (cMaxHeight < tdhMax) { adjust(tdhMax - cMaxHeight, rows, false); } if (cMinHeight > tdhMin) { adjust(cMinHeight - cMinHeight, rows, true); } } } for (i = 0; i < this._rows.length; ++i) { totalMaxH += this._rows[i].max; totalMinH += this._rows[i].min; } if (this._h <= totalMinH || this._h >= totalMaxH) { var kH; var keyH; if (this._h <= totalMinH) { kH = this._h / totalMinH; keyH = "min"; } else { kH = this._h / totalMaxH; keyH = "max"; } for (i = 0; i < this._rows.length; ++i) { this._rows[i].cur = this._rows[i][keyH] * kH; } } else { distribute(this._h, this._rows, this._rh, this._rH); } this._positionElements(); }, "removeChild": function(child) { var obj = Layout.fn.removeChild.call(this, child); var found = obj !== undefined; var layObject; if (found) { child.off("sizeDependencyChange") //assuming a child is supposed to have only one parent that should be okay var cs = obj.colspan; var rs = obj.rowspan; var row = obj.row; var col = obj.col; for (var i = 0; i < rs; ++ i) { for (var j = 0; j < cs; ++j) { var cell = this._lay[row + i][col + j]; for (var k = 0; k < cell.length; ++k) { if (cell[k].child === child) { layObject = cell[k]; cell.splice(k, 1); } } } } var di = this._sde.indexOf(layObject); if (di !== -1) { this._sde.splice(di, 1); this._recalculateSizeDependency(); //TODO need to recalculate the grid, but not sure about the order of triggers. } this._cleanupLay(); this._recountLimits(); this.refreshLay(); } }, "setSize": function(w, h) { View.fn.setSize.call(this, w, h); if (this._c.length) { this.refreshLay(); } }, "_recalculateSizeDependency": function() { if (this._sde.length === 0) { this.setSizeDependency(Layout.SizeDependency.straight.independent); } else { var sd = this._sde[0]._o.sizeDependency; for (var d = 0; d < this._sde.length; ++d) { var csd = this._sde[d]._o.sizeDependency if (sd !== csd) { sd = Layout.SizeDependency.straight.undefined; } if (sd === Layout.SizeDependency.straight.undefined) { break; } } if (sd !== this._o.sizeDependency) { this.setSizeDependency(sd); } } }, "_onChildSizeDependencyChange": function(child, d) { if (d === Layout.SizeDependency.straight.independent) { var index = this._getChildObjectIndex(child); var obj = this._c[index]; //I assume it presents among children var di = this._sde.indexOf(obj); this._sde.splice(di, 1); } this._recalculateSizeDependency(); //TODO need to recalculate the grid, but not sure about the order of triggers. } }); function distribute (size, array, rm, rM) { var i; var cMax = []; for (i = 0; i < array.length; ++i) { array[i].cur = array[i].min; size -= array[i].min; if (array[i].cur < array[i].max) { cMax.push(array[i]); } } for (i = 0; i < rm.length && size > 1; ++i) { var rule = rm[i]; var trm = 0; var mems = []; for (var j = 0; j < rule.el.length; ++j) { var mem = array[rule.el[j]]; trm += mem.cur; mems.push(mem); } var c = rule.val - trm; if (c > 1) { size -= c; mems.sort(GridLayout._candidatesSortMax); c -= deliver(mems, c); if (c > 1) { var p = c / rule.el.length; for (j = 0; j < rule.el.length; ++j) { array[rule.el[j]].cur += p; } } } } cMax.sort(GridLayout._candidatesSortMax); size -= deliver(cMax, size); for (i = 0; i < rM.length; ++i) { var rule = rm[i]; var trm = 0; var mems = []; for (var j = 0; j < rule.el.length; ++j) { var mem = array[rule.el[j]]; trm += mem.cur; mems.push(mem); } var c = trm - rule.val; if (c > 1) { size += c; var p = c / mems.length; for (j = 0; j < mems.length; ++j) { mems.cur -= p; } } } if (size) { var portion = size / array.length; for (i = 0; i < array.length; ++i) { array.cur += portion; } } } function deliver(cMax, size) { var oSize = size; while (cMax.length && size) { var portion = size / cMax.length; var last = cMax[cMax.length -1]; var avail = last.max - last.cur; if (portion >= avail) { size -= avail; last.cur = last.max; cMax.pop(); } else { for (var i = 0; i < cMax.length; ++i) { cMax[i].cur += portion; } size = 0; } } return oSize - size; } function multiLimits(arr, elements, w, rm, rM) { var mind = "minWidth"; var maxd = "maxWidth"; var spand = "colspan"; if (!w) { mind = "minHeight"; maxd = "maxHeight"; spand = "rowspan"; } for (var i = 0; i < elements.length; ++i) { var e = elements[i].e; var pos = elements[i].p; var span = e[spand]; var target = pos + span; var minSize = 0; var maxSize = 0; var flexible = []; var el = []; for (var j = pos; j < target; ++j) { minSize += arr[j].min; maxSize += arr[j].max; el.push(j); if (arr[j].min < arr[j].max) { flexible.push(arr[j]); } } var leftMin = e.child._o[mind] - minSize; var leftMax = maxSize - e.child._o[maxd]; var naMin = adjust(leftMin, flexible, true); var naMax = adjust(leftMax, flexible, false); if (naMin > 0) { rm.push({ el: el, val: e.child._o[mind] }); } if (naMax > 0) { rM.push({ el: el, val: e.child._o[maxd] }); } } } function adjust(space, arr, isMin) { while (space > 0 && arr.length > 0) { var portion = space / arr.length; for (var j = 0; j < arr.length; ++j) { var mem = arr[j]; var lPortion = Math.min(mem.max - mem.min, portion); if (isMin === true) { mem.min += lPortion; } else { mem.max -= lPortion; } space -= lPortion; if (mem.min === mem.max) { arr.splice(j, 1); break; } } if (space < 1) { space = 0; } } if (space < 0) { space = 0; } return space; } function constrain(space, arr, isMin) { while (space > 0 && arr.length > 0) { var portion = space / arr.length; for (var j = 0; j < arr.length; ++j) { var mem = arr[j]; var lPortion if (isMin === true) { lPortion = Math.min(mem.max, portion); mem.min = lPortion; } else { lPortion = Math.max(mem.min, portion); mem.max = lPortion; } space -= lPortion; if (mem.min === mem.max) { arr.splice(j, 1); break; } } if (space < 1) { space = 0; } } } function total(arr, rm, rM) { var min = 0; var max = 0; var am = []; for (var i = 0; i < arr.length; ++i) { var el = arr[i]; if (el.count === 0) { el.min = 0; el.max = 0; } else { min += el.min; el.max = Math.max(el.max, el.min); max += el.max; } am.push({ min: el.min, max: el.max }); } min += shove(rm, am, true); max -= shove(rM, am, false); return { min: min, max: Math.max(max, min) } } function shove(rules, arr, isMin) { var dim = "max"; if (isMin === true) { dim = "min"; } var change = 0; for (var i = 0; i < rules.length; ++i) { var rule = rules[i]; var val = rule.val; var tr = 0; for (var j = 0; j < rule.el.length; ++j) { tr += arr[rule.el[j]][dim]; } var c; if (isMin) { c = val - tr; } else { c = tr - val; } if (c > 0) { var p = c / rule.el.length; for (j = 0; j < rule.el.length; ++j) { if (isMin) { arr[rule.el[j]][dim] += p; } else { arr[rule.el[j]][dim] -= p; //TODO a possibility of going under zero } } change += c; } } return change; } GridLayout._candidatesSortMax = function(a, b) { return b.max - a.max; } return GridLayout; }); })();