kopia lustrzana https://github.com/c9/core
1850 wiersze
70 KiB
JavaScript
1850 wiersze
70 KiB
JavaScript
|
define(function(require, exports, module) {
|
||
|
main.consumes = ["Plugin", "util", "layout"];
|
||
|
main.provides = ["DockableLayout", "DockableWidget", "DockableAbsoluteRenderer"];
|
||
|
return main;
|
||
|
|
||
|
function main(options, imports, register) {
|
||
|
var Plugin = imports.Plugin;
|
||
|
var util = imports.util;
|
||
|
var layout = imports.layout;
|
||
|
var event = require("ace/lib/event");
|
||
|
|
||
|
/***** Initialization *****/
|
||
|
|
||
|
var counter = 0;
|
||
|
|
||
|
/*
|
||
|
@todo mix flex/percentage/fixed
|
||
|
- resizeTo - use split
|
||
|
- hsplit/vsplit - always leave existing flex col
|
||
|
(where widget is) and create new fixed col.
|
||
|
- [DONE] clean - use merge
|
||
|
|
||
|
@todo [Harutyun] Support fixed width widgets - preserving their width/height when dragging/dropping
|
||
|
@todo Add constraints such as min-width/max-width min-height/max-height, container size
|
||
|
@todo Move to cell should give an as big an area as possible (multiple cells)
|
||
|
|
||
|
BUGS:
|
||
|
@todo What does this mean? http://screencast.com/t/vi8WMm6nc20z
|
||
|
@todo Moving filters to split values in the bottom doesnt resize values
|
||
|
@todo Off by padding when resizing
|
||
|
@todo Steps:
|
||
|
- Resize grid
|
||
|
- Move values to below grid
|
||
|
- Move Columns to right side of values
|
||
|
*/
|
||
|
|
||
|
function DockableLayout(developer, deps, options) {
|
||
|
var plugin = new Plugin(developer, deps);
|
||
|
var emit = plugin.getEmitter();
|
||
|
|
||
|
var parent = options.parent;
|
||
|
var renderer = options.renderer;
|
||
|
|
||
|
var model = [];
|
||
|
var renderId = 0;
|
||
|
var widgets = [];
|
||
|
var edge = [0, 0, 0, 0];
|
||
|
var paused = false;
|
||
|
|
||
|
var container, columns, rows, changes, pending, padding;
|
||
|
var parentLayout;
|
||
|
|
||
|
var CHANGE_INIT = 1;
|
||
|
var CHANGE_COLUMNS = 2;
|
||
|
var CHANGE_ROWS = 4;
|
||
|
var CHANGE_REDRAW = 7;
|
||
|
var CHANGE_RESIZE = 6;
|
||
|
|
||
|
var loaded = false;
|
||
|
function load() {
|
||
|
if (loaded) return false;
|
||
|
loaded = true;
|
||
|
|
||
|
if (parent)
|
||
|
attachToParent(parent);
|
||
|
}
|
||
|
|
||
|
function attachToParent(parent) {
|
||
|
container = document.createElement("div");
|
||
|
parent.appendChild(container);
|
||
|
|
||
|
container.style.position = "absolute";
|
||
|
container.style.left = "0";
|
||
|
container.style.top = "0";
|
||
|
container.style.right = "0";
|
||
|
container.style.bottom = "0";
|
||
|
|
||
|
// Start Render Loop
|
||
|
schedule(CHANGE_REDRAW);
|
||
|
|
||
|
// Hook resize
|
||
|
layout.on("resize", resize, plugin);
|
||
|
}
|
||
|
|
||
|
function applyChanges(){
|
||
|
if (changes && !paused) {
|
||
|
var changeset = calculate(changes);
|
||
|
render(changeset);
|
||
|
changes = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var inited = false;
|
||
|
function schedule(change) {
|
||
|
changes = changes | change;
|
||
|
|
||
|
if (paused || pending) return;
|
||
|
|
||
|
pending = true;
|
||
|
util.nextFrame(function(){
|
||
|
if (inited)
|
||
|
emit("change");
|
||
|
|
||
|
inited = true;
|
||
|
pending = false;
|
||
|
applyChanges();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/***** Methods *****/
|
||
|
|
||
|
function initModel(){
|
||
|
model = [];
|
||
|
if (rows && rows.length && columns && columns.length) {
|
||
|
for (var i = 0; i < columns.length; i++) {
|
||
|
model[i] = [];
|
||
|
for (var j = 0; j < rows.length; j++) {
|
||
|
model[i][j] = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getWidgetAtPoint(x, y) {
|
||
|
for (var i = 0; i < widgets.length; i++) {
|
||
|
var widget = widgets[i];
|
||
|
var computed = widget.computed;
|
||
|
if (computed.top < y && computed.top + computed.height > y
|
||
|
&& computed.left < x && computed.left + computed.width > x) {
|
||
|
return widget;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var rect = container.getBoundingClientRect();
|
||
|
// Make sure x/y are in the layout
|
||
|
if (x < rect.width && y < rect.height) {
|
||
|
var total = edge[3];
|
||
|
var pos = {
|
||
|
widget: { empty: true, rowspan: 1, colspan: 1 },
|
||
|
layout: plugin,
|
||
|
insertionPoint: "full"
|
||
|
};
|
||
|
|
||
|
// Find row/col
|
||
|
columns.every(function(c, i) {
|
||
|
if (total + c.computed > x) {
|
||
|
pos.left = total;
|
||
|
pos.width = c.computed;
|
||
|
pos.widget.col = i;
|
||
|
return false;
|
||
|
}
|
||
|
else {
|
||
|
total += c.computed + padding;
|
||
|
return true;
|
||
|
}
|
||
|
});
|
||
|
total = edge[0];
|
||
|
rows.every(function(c, i) {
|
||
|
if (total + c.computed > y) {
|
||
|
pos.top = total;
|
||
|
pos.height = c.computed;
|
||
|
pos.widget.row = i;
|
||
|
return false;
|
||
|
}
|
||
|
else {
|
||
|
total += c.computed + padding;
|
||
|
return true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (model[pos.widget.col] && model[pos.widget.col][pos.widget.row])
|
||
|
return model[pos.widget.col][pos.widget.row];
|
||
|
|
||
|
return fill(pos);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function fill(pos) {
|
||
|
// Expand to empty space
|
||
|
var start, passed, end;
|
||
|
var col = pos.widget.col;
|
||
|
var row = pos.widget.row;
|
||
|
for (var i = 0; i < model.length; i++) {
|
||
|
if (!model[i][row]) {
|
||
|
if (start === undefined)
|
||
|
start = i;
|
||
|
if (i == col)
|
||
|
passed = true;
|
||
|
}
|
||
|
else {
|
||
|
if (!passed)
|
||
|
start = undefined;
|
||
|
else {
|
||
|
end = i - 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!end) end = i - 1;
|
||
|
|
||
|
function searchRows(i) {
|
||
|
var rstart, rend;
|
||
|
// Before Row
|
||
|
for (var j = row; j >= 0; j--) {
|
||
|
if (!model[i][j])
|
||
|
rstart = j;
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
// After Row
|
||
|
for (var j = row; j < rows.length; j++) {
|
||
|
if (!model[i][j])
|
||
|
rend = j;
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return { start: rstart, end: rend };
|
||
|
}
|
||
|
|
||
|
var info, rstart = 0, rend = 900;
|
||
|
for (var i = start; i <= end; i++) {
|
||
|
info = searchRows(i);
|
||
|
rstart = Math.max(info.start, rstart);
|
||
|
rend = Math.min(info.end, rend);
|
||
|
}
|
||
|
|
||
|
pos.widget.col = start;
|
||
|
pos.widget.colspan = end - start + 1;
|
||
|
pos.widget.row = rstart;
|
||
|
pos.widget.rowspan = rend - rstart + 1;
|
||
|
|
||
|
var left = edge[3], width = 0;
|
||
|
for (var i = 0; i <= end; i++) {
|
||
|
if (i < start)
|
||
|
left += columns[i].computed + padding;
|
||
|
else
|
||
|
width += columns[i].computed + padding;
|
||
|
}
|
||
|
width -= padding;
|
||
|
|
||
|
var top = edge[0], height = 0;
|
||
|
for (var i = 0; i <= rend; i++) {
|
||
|
if (i < rstart)
|
||
|
top += rows[i].computed + padding;
|
||
|
else
|
||
|
height += rows[i].computed + padding;
|
||
|
}
|
||
|
height -= padding;
|
||
|
|
||
|
pos.left = left;
|
||
|
pos.top = top;
|
||
|
pos.width = width;
|
||
|
pos.height = height;
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is now a simple algorithm that simply allows a widget to be
|
||
|
* inserted at a side of the widget that it is hovering above.
|
||
|
* In the future this should be expanded to a more complex algo
|
||
|
* where the full spectrum of insertion points is addressed.
|
||
|
* @todo make this pluggable
|
||
|
*/
|
||
|
function getInsertionPoint(widget, x, y) {
|
||
|
// Expand into parent layout
|
||
|
if (x < 0 || y < 0) { // @todo beyond max size
|
||
|
if (parentLayout) {
|
||
|
var r1 = plugin.container.getBoundingClientRect();
|
||
|
var r2 = parentLayout.container.getBoundingClientRect();
|
||
|
|
||
|
return parentLayout.getInsertionPoint(widget,
|
||
|
x + (r1.left - r2.left), y + (r1.top - r2.top));
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Find widget in this layout
|
||
|
var targetWidget = getWidgetAtPoint(x, y);
|
||
|
if (!targetWidget)
|
||
|
return false;
|
||
|
if (targetWidget.widget)
|
||
|
return targetWidget; // Which is actually pos
|
||
|
|
||
|
var target = targetWidget.computed;
|
||
|
var pos, widgetSize;
|
||
|
|
||
|
// Dive into child layout
|
||
|
if (targetWidget.innerLayout && targetWidget != widget) {
|
||
|
pos = targetWidget.innerLayout.getInsertionPoint(widget,
|
||
|
x - target.left, y - target.top);
|
||
|
if (pos)
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
pos = { widget: targetWidget, layout: plugin };
|
||
|
|
||
|
var xdiff = x - target.left;
|
||
|
var ydiff = y - target.top;
|
||
|
var xfar = target.width / 2 < xdiff ? target.width - xdiff : false;
|
||
|
var yfar = target.height / 2 < ydiff ? target.height - ydiff : false;
|
||
|
var xshort = (xfar !== false ? xfar : xdiff) / target.width;
|
||
|
var yshort = (yfar !== false ? yfar : ydiff) / target.height;
|
||
|
|
||
|
if (xshort < yshort) {
|
||
|
pos.width = (target.width - padding) / 2; //widget.computed.width; //
|
||
|
pos.height = target.height;
|
||
|
pos.width = clip(pos.width, widget.minWidth, widget.maxWidth);
|
||
|
pos.height = clip(pos.height, widget.minHeight, widget.maxHeight);
|
||
|
pos.isVertical = true;
|
||
|
widgetSize = widget.getPreferedSize(pos);
|
||
|
if (widgetSize) {
|
||
|
pos.width = widgetSize.width || pos.width;
|
||
|
pos.height = widgetSize.height || pos.height;
|
||
|
}
|
||
|
|
||
|
pos.left = xfar === false ? target.left :
|
||
|
target.left + (target.width - pos.width);
|
||
|
pos.top = target.top;
|
||
|
pos.insertionPoint = xfar === false ? "x0" : "x1";
|
||
|
}
|
||
|
else {
|
||
|
pos.width = target.width;
|
||
|
pos.height = (target.height - padding) / 2; //widget.computed.height; //
|
||
|
pos.width = clip(pos.width, widget.minWidth, widget.maxWidth);
|
||
|
pos.height = clip(pos.height, widget.minHeight, widget.maxHeight);
|
||
|
pos.isVertical = false;
|
||
|
widgetSize = widget.getPreferedSize(pos);
|
||
|
if (widgetSize) {
|
||
|
pos.width = widgetSize.width || pos.width;
|
||
|
pos.height = widgetSize.height || pos.height;
|
||
|
}
|
||
|
|
||
|
pos.left = target.left;
|
||
|
pos.top = yfar === false ? target.top :
|
||
|
target.top + (target.height - pos.height);
|
||
|
pos.insertionPoint = yfar === false ? "y0" : "y1";
|
||
|
}
|
||
|
|
||
|
widget.computed.lastDragPosition = pos;
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
function insertWidget(widget, pos) {
|
||
|
// Remove widget from layout
|
||
|
if (widget.layout) {
|
||
|
var other = widget.layout != plugin && widget.layout;
|
||
|
widget.layout.remove(widget);
|
||
|
if (other)
|
||
|
other.clean();
|
||
|
}
|
||
|
|
||
|
var args, info;
|
||
|
var point = pos.insertionPoint;
|
||
|
|
||
|
if (point == "x0") {
|
||
|
// Add widget to this column
|
||
|
args = [widget, pos.widget.col,
|
||
|
pos.widget.row, 1, pos.widget.rowspan];
|
||
|
|
||
|
info = hsplit(pos.widget, pos.width);
|
||
|
args[1] = info.col;
|
||
|
args[3] = info.colspan;
|
||
|
}
|
||
|
else if (point == "x1") {
|
||
|
// Add widget to this column
|
||
|
args = [widget, pos.widget.col + pos.widget.colspan,
|
||
|
pos.widget.row, 1, pos.widget.rowspan];
|
||
|
|
||
|
info = hsplit(pos.widget, pos.width, true);
|
||
|
args[1] = info.col;
|
||
|
args[3] = info.colspan;
|
||
|
}
|
||
|
else if (point == "y0") {
|
||
|
// Add widget to this row
|
||
|
args = [widget, pos.widget.col,
|
||
|
pos.widget.row, pos.widget.colspan, 1];
|
||
|
|
||
|
info = vsplit(pos.widget, pos.height);
|
||
|
args[2] = info.row;
|
||
|
args[4] = info.rowspan;
|
||
|
}
|
||
|
else if (point == "y1") {
|
||
|
// Add widget to this row
|
||
|
args = [widget, pos.widget.col, pos.widget.row
|
||
|
+ pos.widget.rowspan, pos.widget.colspan, 1];
|
||
|
|
||
|
info = vsplit(pos.widget, pos.height, true);
|
||
|
args[2] = info.row;
|
||
|
args[4] = info.rowspan;
|
||
|
}
|
||
|
else if (point == "full") {
|
||
|
// Add widget to this row
|
||
|
args = [widget, pos.widget.col, pos.widget.row,
|
||
|
pos.widget.colspan, pos.widget.rowspan];
|
||
|
}
|
||
|
|
||
|
// Move dragged widget to it's new location
|
||
|
moveTo.apply(this, args);
|
||
|
|
||
|
// Remove Unused Columns & Rows
|
||
|
clean();
|
||
|
|
||
|
schedule(CHANGE_REDRAW);
|
||
|
}
|
||
|
|
||
|
function vsplit(widget, height, far) {
|
||
|
var result, start, i;
|
||
|
|
||
|
function innerloop(){
|
||
|
if (rows[i].pixels < height) {
|
||
|
height -= rows[i].pixels;
|
||
|
}
|
||
|
else if (rows[i].pixels == height) {
|
||
|
if (widget.rowspan == 1) throw new Error();
|
||
|
|
||
|
// Move split widget
|
||
|
moveTo(widget, widget.col, widget.row + (far ? 0 : 1),
|
||
|
widget.colspan, i - widget.row + (far ? -1 : 1));
|
||
|
|
||
|
return { row: i, rowspan: start - i + 1 };
|
||
|
}
|
||
|
else {
|
||
|
// Create a row
|
||
|
insertRow(height, i + (far ? 1 : 0), far);
|
||
|
|
||
|
// Move split widget
|
||
|
moveTo(widget, widget.col, widget.row + (far ? 0 : 1),
|
||
|
widget.colspan, i - widget.row + 1);
|
||
|
|
||
|
return { row: i + (far ? 1 : 0), rowspan: start - i + 1 };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (far) {
|
||
|
// Find Row number and height
|
||
|
start = widget.row + widget.rowspan - 1;
|
||
|
for (i = start; i >= widget.row; i--) {
|
||
|
result = innerloop();
|
||
|
if (result) return result;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Find Row number and height
|
||
|
start = widget.row;
|
||
|
for (i = start; i < widget.row + widget.rowspan; i++) {
|
||
|
result = innerloop();
|
||
|
if (result) return result;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function hsplit(widget, width, far) {
|
||
|
var result, start, i;
|
||
|
|
||
|
function innerloop(){
|
||
|
if (columns[i].pixels < width) {
|
||
|
width -= columns[i].pixels;
|
||
|
}
|
||
|
else if (columns[i].pixels == width) {
|
||
|
if (widget.colspan == 1) throw new Error();
|
||
|
|
||
|
// Move split widget
|
||
|
moveTo(widget, widget.col + (far ? 0 : 1), widget.row,
|
||
|
i - widget.col + (far ? -1 : 1), widget.rowspan);
|
||
|
|
||
|
return { col: i, colspan: start - i + 1 };
|
||
|
}
|
||
|
else {
|
||
|
// Create a col
|
||
|
insertColumn(width, i + (far ? 1 : 0), far);
|
||
|
|
||
|
// Move split widget
|
||
|
moveTo(widget, widget.col + (far ? 0 : 1), widget.row,
|
||
|
i - widget.col + 1, widget.rowspan);
|
||
|
|
||
|
return { col: i + (far ? 1 : 0), colspan: start - i + 1 };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (far) {
|
||
|
// Find Column number and height
|
||
|
start = widget.col + widget.colspan - 1;
|
||
|
for (i = start; i >= widget.col; i--) {
|
||
|
result = innerloop();
|
||
|
if (result) return result;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Find Column number and height
|
||
|
start = widget.col;
|
||
|
for (i = start; i < widget.col + widget.colspan; i++) {
|
||
|
result = innerloop();
|
||
|
if (result) return result;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function insertColumn(width, index, copyFromLeft) {
|
||
|
// Add to columns
|
||
|
columns.splice(index, 0, getRowColEntry(width));
|
||
|
|
||
|
// Move & Resize items
|
||
|
if (model[index]) {
|
||
|
var newcol = [], done = {};
|
||
|
model[index - (copyFromLeft ? 1 : 0)].forEach(function(widget) {
|
||
|
newcol.push(widget);
|
||
|
if (widget && !done[widget.name]) {
|
||
|
done[widget.name] = true;
|
||
|
widget.colspan++;
|
||
|
}
|
||
|
});
|
||
|
model.splice(index, 0, newcol);
|
||
|
|
||
|
// Move all subsequent widgets to column + 1
|
||
|
for (var i = index + (copyFromLeft ? 1 : 2); i < model.length; i++) {
|
||
|
model[i].forEach(function(widget) {
|
||
|
if (widget && !done["s" + widget.name] && widget.col == i - 1) {
|
||
|
widget.col++;
|
||
|
done["s" + widget.name] = true;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
model[index] = [];
|
||
|
rows.forEach(function(n, i) {
|
||
|
model[index][i] = null;
|
||
|
});
|
||
|
// @todo expand fill?
|
||
|
}
|
||
|
|
||
|
schedule(CHANGE_COLUMNS);
|
||
|
}
|
||
|
|
||
|
function removeColumn(index) {
|
||
|
var col = model[index];
|
||
|
|
||
|
// Move & Resize items
|
||
|
if (col.length) {
|
||
|
var done = {};
|
||
|
col.forEach(function(widget) {
|
||
|
if (!widget || done[widget.name]) return;
|
||
|
if (widget.colspan == 1)
|
||
|
remove(widget);
|
||
|
else
|
||
|
widget.colspan--;
|
||
|
done[widget.name] = true;
|
||
|
});
|
||
|
|
||
|
// Move all subsequent widgets to column - 1
|
||
|
for (var i = index + 1; i < model.length; i++) {
|
||
|
model[i].forEach(function(widget) {
|
||
|
if (widget && !done["s" + widget.name] && widget.col == i) {
|
||
|
widget.col--;
|
||
|
done["s" + widget.name] = true;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove from model
|
||
|
model.splice(index, 1);
|
||
|
|
||
|
// Remove from columns
|
||
|
columns.splice(index, 1);
|
||
|
|
||
|
schedule(CHANGE_COLUMNS);
|
||
|
}
|
||
|
|
||
|
function insertRow(height, index, copyFromTop) {
|
||
|
// Add to rows
|
||
|
rows.splice(index, 0, getRowColEntry(height));
|
||
|
|
||
|
// Move & Resize items
|
||
|
var done = {};
|
||
|
model.forEach(function(col) {
|
||
|
var widget = col[index - (copyFromTop ? 1 : 0)];
|
||
|
col.splice(index, 0, widget || null);
|
||
|
if (widget) {
|
||
|
if (!done[widget.name]) {
|
||
|
done[widget.name] = true;
|
||
|
widget.rowspan++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move all subsequent widgets to row + 1
|
||
|
for (var i = index + (copyFromTop ? 1 : 2); i < col.length; i++) {
|
||
|
widget = col[i];
|
||
|
if (widget && !done["s" + widget.name] && widget.row == i - 1) {
|
||
|
widget.row++;
|
||
|
done["s" + widget.name] = true;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
schedule(CHANGE_ROWS);
|
||
|
}
|
||
|
|
||
|
function removeRow(index) {
|
||
|
// Move & Resize items
|
||
|
var done = {};
|
||
|
model.forEach(function(col) {
|
||
|
var widget = col[index];
|
||
|
if (widget) {
|
||
|
if (!done[widget.name]){
|
||
|
if (widget.rowspan == 1)
|
||
|
remove(widget);
|
||
|
else
|
||
|
widget.rowspan--;
|
||
|
done[widget.name] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move all subsequent widgets to row + 1
|
||
|
for (var i = index + 1; i < col.length; i++) {
|
||
|
widget = col[i];
|
||
|
if (widget && !done["s" + widget.name] && widget.row == i) {
|
||
|
widget.row--;
|
||
|
done["s" + widget.name] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove from model
|
||
|
col.splice(index, 1);
|
||
|
});
|
||
|
|
||
|
// Remove from rows
|
||
|
rows.splice(index, 1);
|
||
|
|
||
|
schedule(CHANGE_ROWS);
|
||
|
}
|
||
|
|
||
|
function merge(name, indexFrom, indexTo) {
|
||
|
var defSet = name == "columns" ? columns : rows;
|
||
|
|
||
|
var from = defSet[indexFrom];
|
||
|
var to = defSet[indexTo];
|
||
|
|
||
|
// Keep pixels
|
||
|
if (to.pixels && from.pixels) {
|
||
|
to.pixels += from.pixels + padding;
|
||
|
}
|
||
|
// Keep flex
|
||
|
else if (to.flex || from.flex) {
|
||
|
// Simple case, both have flex
|
||
|
if (to.flex && from.flex)
|
||
|
to.flex += from.flex;
|
||
|
else {
|
||
|
var flex = defSet.filter(function(n){ return n.flex });
|
||
|
|
||
|
if (!to.flex) {
|
||
|
delete to.pixels;
|
||
|
delete to.percentage;
|
||
|
}
|
||
|
|
||
|
// There is only 1 flex item (so missing px/%
|
||
|
// will be moved to this one automatically)
|
||
|
if (flex.length === 1) {
|
||
|
if (!to.flex)
|
||
|
to.flex = from.flex;
|
||
|
}
|
||
|
// Lets convert the px/% into flex units
|
||
|
else {
|
||
|
var flextotal = 0;
|
||
|
flex.forEach(function(n) {
|
||
|
if (n != to && n != from)
|
||
|
flextotal += n.flex;
|
||
|
});
|
||
|
// 200,200,200,200: 1,1,1,1
|
||
|
// 300,166,166,166: 1.5,0.83,0.83,0.83
|
||
|
|
||
|
var f, p, a;
|
||
|
if (to.flex)
|
||
|
f = to.flex, p = to.computed, a = from.computed;
|
||
|
else
|
||
|
f = from.flex, p = from.computed, a = to.computed;
|
||
|
|
||
|
var newflex = (f / p) * (p + a);
|
||
|
var delta = (newflex - f) / flextotal;
|
||
|
|
||
|
flex.forEach(function(n) {
|
||
|
if (n != to && n != from)
|
||
|
n.flex -= delta;
|
||
|
});
|
||
|
|
||
|
to.flex = f + delta;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Keep percentage
|
||
|
else if (to.percentage || from.percentage) {
|
||
|
if (to.percentage && from.percentage) {
|
||
|
to.percentage += from.percentage;
|
||
|
}
|
||
|
else {
|
||
|
// @todo unused right now
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (name == "columns")
|
||
|
removeColumn(indexFrom);
|
||
|
else
|
||
|
removeRow(indexFrom);
|
||
|
}
|
||
|
|
||
|
function clean(){
|
||
|
var empty = rows.map(function(){ return true; });
|
||
|
|
||
|
for (var i = model.length - 1; i >= 0; i--) {
|
||
|
var col = model[i];
|
||
|
var prev = model[i - 1];
|
||
|
|
||
|
// Unused columns
|
||
|
var unused = prev && col.every(function(n, j) {
|
||
|
// return !n && !prev[j] || n && n.colspan > 1 && n.col != i;
|
||
|
return n == prev[j];
|
||
|
});
|
||
|
if (unused)
|
||
|
merge("columns", i, i - 1);
|
||
|
|
||
|
// Unused rows
|
||
|
col.forEach(function(n, j) {
|
||
|
if (!empty[j]) return;
|
||
|
|
||
|
// if (n && (n.rowspan == 1 || n.row == j))
|
||
|
if (empty[j] && col[j - 1] != n)
|
||
|
empty[j] = false;
|
||
|
// if (!n && !col[j - 1])
|
||
|
// empty[j] = false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Unused rows
|
||
|
for (var i = empty.length; i > 0; i--) {
|
||
|
if (empty[i])
|
||
|
merge("rows", i, i - 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function serializeRowCol(m) {
|
||
|
if (m.pixels)
|
||
|
return m.pixels + "px";
|
||
|
if (m.flex)
|
||
|
return m.flex;
|
||
|
if (m.percentage)
|
||
|
return m.percentage + "%";
|
||
|
}
|
||
|
|
||
|
function getRowColEntry(m) {
|
||
|
if (typeof m == "number") {
|
||
|
// if (m < 0) throw new Error("Invalid column size");
|
||
|
return { pixels: m };
|
||
|
}
|
||
|
else if (parseFloat(m) == m) // Flex
|
||
|
return { flex: parseFloat(m) };
|
||
|
else if (m.indexOf("%") > -1) // Percentage
|
||
|
return { percentage: parseFloat(m) / 100 };
|
||
|
else if (m.indexOf("px") > -1) // Pixels
|
||
|
return { pixels: parseInt(m, 10) };
|
||
|
else throw new Error("Invalid Row/Column Configuration");
|
||
|
}
|
||
|
|
||
|
function calculate(changes) {
|
||
|
renderId++;
|
||
|
if (renderId > 30000)
|
||
|
renderId = 0;
|
||
|
|
||
|
// Get width/height of container
|
||
|
var width = container.offsetWidth
|
||
|
- edge[1] - edge[3] - ((columns.length - 1) * padding);
|
||
|
var height = container.offsetHeight
|
||
|
- edge[2] - edge[0] - ((rows.length - 1) * padding);
|
||
|
|
||
|
if (!width || !height) {
|
||
|
console.warn("Invalid size of dockable layout");
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
var flexCols = [], totalCols = 0, totalColFlex = 0;
|
||
|
var flexRows = [], totalRows = 0, totalRowFlex = 0;
|
||
|
|
||
|
// Calc width of each col
|
||
|
var col;
|
||
|
for (var i = 0; i < columns.length; i++) {
|
||
|
col = columns[i];
|
||
|
if (col.flex) {
|
||
|
flexCols.push(col);
|
||
|
totalColFlex += col.flex;
|
||
|
}
|
||
|
else {
|
||
|
col.computed = col.pixels || col.percentage * width;
|
||
|
totalCols += col.computed;
|
||
|
}
|
||
|
}
|
||
|
flexCols.forEach(function(col) {
|
||
|
col.computed = (width - totalCols) / totalColFlex * col.flex;
|
||
|
});
|
||
|
|
||
|
// Calc height of each row
|
||
|
var row;
|
||
|
for (var i = 0; i < rows.length; i++) {
|
||
|
row = rows[i];
|
||
|
if (row.flex) {
|
||
|
flexRows.push(row);
|
||
|
totalRowFlex += row.flex;
|
||
|
}
|
||
|
else {
|
||
|
row.computed = row.pixels || row.percentage * height;
|
||
|
totalRows += row.computed;
|
||
|
}
|
||
|
}
|
||
|
flexRows.forEach(function(row) {
|
||
|
row.computed = (height - totalRows) / totalRowFlex * row.flex;
|
||
|
});
|
||
|
|
||
|
var changed = [];
|
||
|
|
||
|
// Loop over elements and calc new width/height, left/top
|
||
|
var curwidth = edge[3];
|
||
|
var widget, computed, curheight, wchanged, hasChanges;
|
||
|
for (var i = 0, li = model.length; i < li; i++) {
|
||
|
col = model[i];
|
||
|
curheight = edge[0];
|
||
|
if (i > 0)
|
||
|
curwidth += padding;
|
||
|
|
||
|
for (var k, j = 0, lj = col.length; j < lj; j++) {
|
||
|
widget = col[j];
|
||
|
computed = widget && widget.computed;
|
||
|
|
||
|
if (!computed || computed.renderId == renderId) {
|
||
|
curheight += (computed
|
||
|
? computed.height
|
||
|
: rows[j].computed) + padding;
|
||
|
continue;
|
||
|
}
|
||
|
computed.renderId = renderId;
|
||
|
|
||
|
if (changes & CHANGE_INIT) {
|
||
|
computed.left =
|
||
|
computed.top =
|
||
|
computed.width =
|
||
|
computed.height = null;
|
||
|
}
|
||
|
|
||
|
wchanged = { widget: widget };
|
||
|
hasChanges = false;
|
||
|
|
||
|
if (computed.left != curwidth) {
|
||
|
wchanged.left = computed.left = curwidth;
|
||
|
hasChanges = true;
|
||
|
}
|
||
|
|
||
|
if (computed.top != curheight) {
|
||
|
wchanged.top = computed.top = curheight;
|
||
|
hasChanges = true;
|
||
|
}
|
||
|
|
||
|
if (changes & CHANGE_COLUMNS) {
|
||
|
width = 0;
|
||
|
for (k = 0; k < widget.colspan; k++) {
|
||
|
width += columns[i + k].computed
|
||
|
+ (k > 0 ? padding : 0);
|
||
|
}
|
||
|
|
||
|
if (computed.width != width) {
|
||
|
wchanged.width = computed.width = width;
|
||
|
hasChanges = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
height = 0;
|
||
|
for (k = 0; k < widget.rowspan; k++) {
|
||
|
height += rows[j + k].computed
|
||
|
+ (k > 0 ? padding : 0);
|
||
|
}
|
||
|
j += k - 1; //Lets skip the next row items
|
||
|
|
||
|
if (changes & CHANGE_ROWS) {
|
||
|
if (computed.height != height) {
|
||
|
wchanged.height = computed.height = height;
|
||
|
hasChanges = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hasChanges)
|
||
|
changed.push(wchanged);
|
||
|
|
||
|
curheight += height + padding;
|
||
|
}
|
||
|
|
||
|
curwidth += columns[i].computed;
|
||
|
}
|
||
|
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
function render(changeset) {
|
||
|
renderer.render(changeset, plugin);
|
||
|
}
|
||
|
|
||
|
function add(widget, col, row, colspan, rowspan) {
|
||
|
widget.row = row;
|
||
|
widget.col = col;
|
||
|
widget.rowspan = rowspan || (rowspan = 1);
|
||
|
widget.colspan = colspan || (colspan = 1);
|
||
|
widget.layout = plugin;
|
||
|
|
||
|
if (widgets.indexOf(widget) == -1)
|
||
|
widgets.push(widget);
|
||
|
|
||
|
var rowindex, colindex;
|
||
|
|
||
|
for (var i = 0; i < colspan; i++) {
|
||
|
colindex = col + i;
|
||
|
|
||
|
var colset = model[colindex];
|
||
|
if (!colset)
|
||
|
colset = model[colindex] = [];
|
||
|
|
||
|
for (var j = 0; j < rowspan; j++) {
|
||
|
rowindex = row + j;
|
||
|
|
||
|
if (colset[rowindex]) {
|
||
|
var w = colset[rowindex];
|
||
|
throw new Error("Conflict occurred adding widget to col: "
|
||
|
+ colindex + " and row: " + rowindex
|
||
|
+ ". The following widget is already there, col: " + w.col
|
||
|
+ " row: " + w.row);
|
||
|
}
|
||
|
|
||
|
colset[rowindex] = widget;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
schedule(CHANGE_REDRAW);
|
||
|
}
|
||
|
|
||
|
function moveTo(widget, col, row, colspan, rowspan) {
|
||
|
if (widget.layout) {
|
||
|
if (widget.layout != plugin)
|
||
|
throw new Error("Moving a widget that is not first added to this layout.");
|
||
|
remove(widget);
|
||
|
}
|
||
|
|
||
|
add(widget, col, row, colspan, rowspan);
|
||
|
}
|
||
|
|
||
|
function isEmptyCol(col, widget) {
|
||
|
var c = model[col];
|
||
|
for (var i = widget.row; i < widget.row + widget.rowspan; i++) {
|
||
|
if (c[i] && c[i] != widget) return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function isEmptyRow(row, widget) {
|
||
|
for (var i = widget.col; i < widget.col + widget.colspan; i++) {
|
||
|
if (model[i][row] && model[i][row] != widget) return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function resizeTo(widget, width, fromLeft, height, fromTop) {
|
||
|
var delta, shrink, shrinkOther, insertIndex,copyFromTop;
|
||
|
var copyFromLeft, snap;
|
||
|
|
||
|
width = getRowColEntry(width);
|
||
|
height = getRowColEntry(height);
|
||
|
|
||
|
width.pixels = clip(width.pixels, widget.minWidth, widget.maxWidth);
|
||
|
height.pixels = clip(height.pixels, widget.minHeight, widget.maxHeight);
|
||
|
|
||
|
// Find the right column / size
|
||
|
var i, l, size, total = 0, collission = false;
|
||
|
if (fromLeft) {
|
||
|
for (i = widget.col + widget.colspan - 1; i >= 0; i--) {
|
||
|
size = columns[i].computed;
|
||
|
|
||
|
if (total + size > width.pixels
|
||
|
|| (collission = !isEmptyCol(i, widget)))
|
||
|
break;
|
||
|
|
||
|
total += size + padding;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for (i = widget.col, l = columns.length; i < l; i++) {
|
||
|
size = columns[i].computed;
|
||
|
|
||
|
if (total + size > width.pixels
|
||
|
|| (collission = !isEmptyCol(i, widget)))
|
||
|
break;
|
||
|
|
||
|
total += size + padding;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move to column
|
||
|
delta = width.pixels - total + padding;
|
||
|
snap = !delta;
|
||
|
shrinkOther = fromLeft && widget.computed.width > width.pixels;
|
||
|
|
||
|
var col, colspan;
|
||
|
if (collission) {
|
||
|
// Resize current row
|
||
|
curcol = columns[i - (fromTop ? -1 : 1)];
|
||
|
if (curcol && curcol.pixels)
|
||
|
curcol.pixels += delta;
|
||
|
else {
|
||
|
// @todo
|
||
|
}
|
||
|
}
|
||
|
else if (!snap) {
|
||
|
var curcol = columns[i];
|
||
|
if (curcol && curcol.pixels)
|
||
|
curcol.pixels -= delta;
|
||
|
|
||
|
insertIndex = i + (fromLeft || i > 0 && shrink ? 1 : 0);
|
||
|
copyFromLeft = fromLeft && width.pixels < widget.computed.width
|
||
|
|| !fromLeft && width.pixels > widget.computed.width;
|
||
|
insertColumn(width.pixels - total, insertIndex, copyFromLeft);
|
||
|
|
||
|
col = fromLeft ? i + 1 : widget.col;
|
||
|
colspan = fromLeft
|
||
|
? widget.col + widget.colspan - i - 1
|
||
|
: i - widget.col + 1;
|
||
|
moveTo(widget, col, widget.row, colspan, widget.rowspan);
|
||
|
}
|
||
|
// Snap
|
||
|
else {
|
||
|
col = fromLeft ? i + 1 : widget.col;
|
||
|
colspan = fromLeft
|
||
|
? widget.col + widget.colspan - i - 1
|
||
|
: i - col;
|
||
|
moveTo(widget, col, widget.row, colspan, widget.rowspan);
|
||
|
}
|
||
|
|
||
|
// Find the right row / size
|
||
|
total = 0;
|
||
|
collission = false;
|
||
|
if (fromTop) {
|
||
|
for (i = widget.row + widget.rowspan - 1; i >= 0; i--) {
|
||
|
size = rows[i].computed;
|
||
|
|
||
|
if (total + size > height.pixels
|
||
|
|| (collission = !isEmptyRow(i, widget)))
|
||
|
break;
|
||
|
|
||
|
total += size + padding;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for (i = widget.row, l = rows.length; i < l; i++) {
|
||
|
size = rows[i].computed;
|
||
|
|
||
|
if (total + size > height.pixels
|
||
|
|| (collission = !isEmptyRow(i, widget)))
|
||
|
break;
|
||
|
|
||
|
total += size + padding;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move to row
|
||
|
delta = height.pixels - total + padding;
|
||
|
shrinkOther = fromTop && widget.computed.height > height.pixels;
|
||
|
|
||
|
var row, rowspan;
|
||
|
if (collission) {
|
||
|
// Resize current row
|
||
|
currow = rows[i - (fromTop ? -1 : 1)];
|
||
|
if (currow && currow.pixels)
|
||
|
currow.pixels += delta;
|
||
|
else {
|
||
|
// @todo
|
||
|
}
|
||
|
}
|
||
|
else if (delta) {
|
||
|
var currow = rows[i];
|
||
|
if (currow && currow.pixels)
|
||
|
currow.pixels -= delta;
|
||
|
|
||
|
insertIndex = i + (fromTop || i > 0 && shrink ? 1 : 0);
|
||
|
copyFromTop = fromTop && height.pixels < widget.computed.height
|
||
|
|| !fromTop && height.pixels > widget.computed.height;
|
||
|
insertRow(height.pixels - total, insertIndex, copyFromTop);
|
||
|
|
||
|
row = fromTop ? i + 1 : widget.row;
|
||
|
rowspan = fromTop
|
||
|
? widget.row + widget.rowspan - i - 1
|
||
|
: i - widget.row + 1;
|
||
|
moveTo(widget, widget.col, row, widget.colspan, rowspan);
|
||
|
}
|
||
|
// Snap
|
||
|
else {
|
||
|
row = fromTop ? i + 1 : widget.row;
|
||
|
rowspan = fromTop
|
||
|
? widget.row + widget.rowspan - i - 1
|
||
|
: i - row;
|
||
|
moveTo(widget, widget.col, row, widget.colspan, rowspan);
|
||
|
}
|
||
|
|
||
|
clean();
|
||
|
}
|
||
|
|
||
|
function remove(widget, deep) {
|
||
|
var index = widgets.indexOf(widget);
|
||
|
if (index === -1)
|
||
|
return;
|
||
|
widgets.splice(index, 1);
|
||
|
|
||
|
var col = widget.col;
|
||
|
var row = widget.row;
|
||
|
var colspan = widget.colspan;
|
||
|
var rowspan = widget.rowspan;
|
||
|
|
||
|
for (var i = 0; i < colspan; i++) {
|
||
|
var colset = model[col + i];
|
||
|
for (var j = rowspan - 1; j >= 0; j--) {
|
||
|
colset[row + j] = null;
|
||
|
}
|
||
|
// if (deep && colset.length === 0)
|
||
|
// removeColumn(col + i);
|
||
|
}
|
||
|
|
||
|
widget.computed = {
|
||
|
width: widget.computed.width,
|
||
|
height: widget.computed.height
|
||
|
};
|
||
|
widget.layout = null;
|
||
|
|
||
|
schedule(CHANGE_REDRAW);
|
||
|
}
|
||
|
|
||
|
function getState(){
|
||
|
var state = {};
|
||
|
widgets.forEach(function(w) {
|
||
|
state[w.name] = w.getState();
|
||
|
});
|
||
|
|
||
|
state.columns = columns.map(serializeRowCol).join(",");
|
||
|
state.rows = rows.map(serializeRowCol).join(",");
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
function setState(state) {
|
||
|
plugin.columns = state.columns;
|
||
|
plugin.rows = state.rows;
|
||
|
|
||
|
widgets.forEach(function(w) {
|
||
|
if (state[w.name]) {
|
||
|
w.setState(state[w.name]);
|
||
|
add(w, w.col, w.row, w.colspan, w.rowspan);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
schedule(CHANGE_REDRAW);
|
||
|
}
|
||
|
|
||
|
function getCoords(col, row) {
|
||
|
if (col > columns.length)
|
||
|
col = columns.length - 1;
|
||
|
if (row > rows.length)
|
||
|
row = rows.length - 1;
|
||
|
|
||
|
var y = edge[3];
|
||
|
for (var i = 0; i < row; i++) y += rows[i].computed;
|
||
|
y += row * padding;
|
||
|
y += 1;
|
||
|
|
||
|
var x = edge[0];
|
||
|
for (var i = 0; i < col; i++) x += columns[i].computed;
|
||
|
x += row * padding;
|
||
|
x += 1;
|
||
|
|
||
|
return { x: x, y: y };
|
||
|
}
|
||
|
|
||
|
function resize(){
|
||
|
changes = CHANGE_RESIZE;
|
||
|
applyChanges();
|
||
|
}
|
||
|
|
||
|
function clip(w, min, max) {
|
||
|
if (w < min) return min;
|
||
|
if (w > max) return max;
|
||
|
return w;
|
||
|
}
|
||
|
|
||
|
function pause(){
|
||
|
paused = true;
|
||
|
}
|
||
|
|
||
|
function resume(){
|
||
|
paused = false;
|
||
|
resize();
|
||
|
}
|
||
|
|
||
|
/***** Lifecycle *****/
|
||
|
|
||
|
plugin.on("load", function() {
|
||
|
load();
|
||
|
});
|
||
|
plugin.on("enable", function() {
|
||
|
|
||
|
});
|
||
|
plugin.on("disable", function() {
|
||
|
|
||
|
});
|
||
|
plugin.on("unload", function() {
|
||
|
loaded = false;
|
||
|
});
|
||
|
|
||
|
/***** Register and define API *****/
|
||
|
|
||
|
// This is a baseclass
|
||
|
plugin.freezePublicAPI.baseclass();
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
**/
|
||
|
plugin.freezePublicAPI({
|
||
|
get container(){ return container;},
|
||
|
get paused(){ return paused;},
|
||
|
|
||
|
get children(){ return widgets;},
|
||
|
|
||
|
get columns(){ return columns/* && columns.join(", ")*/; },
|
||
|
set columns(v) {
|
||
|
columns = String(v).split(/\s*,\s*/).map(function(m) {
|
||
|
return getRowColEntry(m);
|
||
|
});
|
||
|
initModel();
|
||
|
},
|
||
|
|
||
|
get rows(){ return rows /*&& rows.join(", ")*/; },
|
||
|
set rows(v) {
|
||
|
rows = String(v).split(/\s*,\s*/).map(function(m) {
|
||
|
return getRowColEntry(m);
|
||
|
});
|
||
|
initModel();
|
||
|
},
|
||
|
|
||
|
get padding(){ return padding; },
|
||
|
set padding(v){ padding = parseInt(v, 10); },
|
||
|
|
||
|
get edge(){ return edge; },
|
||
|
set edge(v){ edge = util.getBox(v); },
|
||
|
|
||
|
get parentLayout(){ return parentLayout; },
|
||
|
set parentLayout(el){ parentLayout = el; },
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getWidgetAtPoint: getWidgetAtPoint,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
resize: resize,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
attachToParent: attachToParent,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
clean: clean,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
fill: fill,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getCoords: getCoords,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getState: getState,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
setState: setState,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
add: add,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
moveTo: moveTo,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
resizeTo: resizeTo,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
remove: remove,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
insertWidget: insertWidget,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getInsertionPoint: getInsertionPoint,
|
||
|
|
||
|
pause: pause,
|
||
|
|
||
|
resume: resume
|
||
|
});
|
||
|
|
||
|
return plugin;
|
||
|
}
|
||
|
|
||
|
function DockableAbsoluteRenderer(){
|
||
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
||
|
// var emit = plugin.getEmitter();
|
||
|
|
||
|
var inited = {};
|
||
|
|
||
|
function init(widget, dockLayout) {
|
||
|
widget.load("plugin" + counter++); // @todo remove after testing
|
||
|
widget.container.style.position = "absolute";
|
||
|
dockLayout.container.appendChild(widget.container);
|
||
|
inited[widget.name] = dockLayout;
|
||
|
}
|
||
|
|
||
|
function render(changeset, dockLayout) {
|
||
|
var change, widget, html;
|
||
|
for (var i = 0; i < changeset.length; i++) {
|
||
|
change = changeset[i];
|
||
|
widget = change.widget;
|
||
|
html = widget.container;
|
||
|
|
||
|
if (!inited[widget.name]
|
||
|
|| dockLayout.container != widget.container.parentNode) // Detect layout moving
|
||
|
init(widget, dockLayout);
|
||
|
|
||
|
if (change.left !== undefined)
|
||
|
html.style.left = change.left + "px";
|
||
|
if (change.top !== undefined)
|
||
|
html.style.top = change.top + "px";
|
||
|
if (change.width !== undefined)
|
||
|
html.style.width = change.width + "px";
|
||
|
if (change.height !== undefined)
|
||
|
html.style.height = change.height + "px";
|
||
|
|
||
|
widget.resize(change);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
**/
|
||
|
plugin.freezePublicAPI({
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
render: render
|
||
|
});
|
||
|
|
||
|
plugin.load("DockRenderer" + counter++);
|
||
|
|
||
|
return plugin;
|
||
|
}
|
||
|
|
||
|
var CURSOR = {
|
||
|
"s" : "ns-resize",
|
||
|
"n" : "ns-resize",
|
||
|
"w" : "ew-resize",
|
||
|
"e" : "ew-resize",
|
||
|
"ne" : "nesw-resize",
|
||
|
"nw" : "nwse-resize",
|
||
|
"se" : "nwse-resize",
|
||
|
"sw" : "nesw-resize"
|
||
|
}
|
||
|
|
||
|
function DockableWidget(developer, deps) {
|
||
|
var plugin = new Plugin(developer, deps);
|
||
|
var emit = plugin.getEmitter();
|
||
|
|
||
|
var computed = {};
|
||
|
var row, col, rowspan, colspan, container, handle, layout;
|
||
|
var innerLayout;
|
||
|
|
||
|
var draggable = true;
|
||
|
var resizable = true;
|
||
|
|
||
|
var EDGE_SIZE = 10;
|
||
|
|
||
|
var loaded = false;
|
||
|
function load() {
|
||
|
if (loaded) return false;
|
||
|
loaded = true;
|
||
|
}
|
||
|
|
||
|
function setDragHandle(c, h) {
|
||
|
if (c)
|
||
|
container = c;
|
||
|
else {
|
||
|
container = document.createElement("div");
|
||
|
container.style.boxSizing = "border-box";
|
||
|
}
|
||
|
|
||
|
if (typeof h == "string")
|
||
|
h = c.querySelector(h);
|
||
|
|
||
|
handle = h || container;
|
||
|
handle.decorated = plugin;
|
||
|
|
||
|
event.addListener(container, "mousedown", function(e) {
|
||
|
if (layout.paused) return;
|
||
|
|
||
|
var edge = detectEdge(e);
|
||
|
if (edge && resizable)
|
||
|
startResize(e, edge);
|
||
|
event.stopEvent(e);
|
||
|
});
|
||
|
|
||
|
event.addListener(handle, "mousedown", function(e) {
|
||
|
if (layout.paused) return;
|
||
|
|
||
|
var edge = detectEdge(e);
|
||
|
if ((!edge || !resizable) && draggable)
|
||
|
startDragWatch(e);
|
||
|
event.stopEvent(e);
|
||
|
});
|
||
|
|
||
|
event.addListener(container, "mousemove", function(e) {
|
||
|
if (layout.paused) return;
|
||
|
|
||
|
if (!plugin.innerLayout) {
|
||
|
var edge = detectEdge(e);
|
||
|
var cursor = edge ? CURSOR[edge] : "default";
|
||
|
container.style.cursor = cursor;
|
||
|
|
||
|
setGlobalCursor(edge ? cursor : false);
|
||
|
}
|
||
|
});
|
||
|
event.addListener(container, "mouseout", function(e) {
|
||
|
if (layout.paused) return;
|
||
|
|
||
|
if (e.currentTarget == container && !plugin.innerLayout) {
|
||
|
setGlobalCursor(false);
|
||
|
container.style.cursor = "";
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/***** Methods *****/
|
||
|
|
||
|
function resize(e) {
|
||
|
emit("resize", e);
|
||
|
}
|
||
|
|
||
|
function getPosition() {
|
||
|
return {row: row, col: col, rowspan: rowspan, colspan: colspan};
|
||
|
}
|
||
|
|
||
|
function getPreferedSize(availablePos) {
|
||
|
if (this.computePreferredSize)
|
||
|
return this.computePreferredSize(availablePos);
|
||
|
}
|
||
|
|
||
|
function isEdge(value){ return value >= 0 && value < EDGE_SIZE; }
|
||
|
function detectEdge(e) {
|
||
|
var rect = container.getBoundingClientRect();
|
||
|
var hor = "", ver = "";
|
||
|
|
||
|
if (isEdge(e.clientX - rect.left))
|
||
|
hor = "w";
|
||
|
else if (isEdge(rect.left + rect.width - e.clientX))
|
||
|
hor = "e";
|
||
|
if (isEdge(e.clientY - rect.top))
|
||
|
ver = "n";
|
||
|
else if (isEdge(rect.top + rect.height - e.clientY))
|
||
|
ver = "s";
|
||
|
|
||
|
return hor || ver ? ver + hor : false;
|
||
|
}
|
||
|
|
||
|
function startResize(e, edge) {
|
||
|
var el = container;
|
||
|
var drag = getDragOverlay();
|
||
|
|
||
|
// Set Top
|
||
|
drag.style.zIndex = 1000000;
|
||
|
drag.className = "drag resize";
|
||
|
|
||
|
var offsetX = e.clientX - (parseInt(container.style.left, 10) || 0);
|
||
|
var offsetY = e.clientY - (parseInt(container.style.top, 10) || 0);
|
||
|
var moved = false;
|
||
|
var startX = e.clientX - offsetX;
|
||
|
var startY = e.clientY - offsetY;
|
||
|
|
||
|
var rect = layout.container.getBoundingClientRect();
|
||
|
var parentLeft = rect.left;
|
||
|
var parentTop = rect.top;
|
||
|
var startWidth = container.offsetWidth;
|
||
|
var startHeight = container.offsetHeight;
|
||
|
|
||
|
drag.style.left = (startX + parentLeft) + "px";
|
||
|
drag.style.top = (startY + parentTop) + "px";
|
||
|
drag.style.width = startWidth + "px";
|
||
|
drag.style.height = startHeight + "px";
|
||
|
|
||
|
var sizes = { v: layout.rows, h: layout.columns };
|
||
|
var margin = layout.edge;
|
||
|
var padding = layout.padding;
|
||
|
var SNAP_DIST = 20;
|
||
|
|
||
|
function findSnapPos(pos, dir, side) {
|
||
|
var snapPos = margin[dir === "v" ? 0 : 3];
|
||
|
var rows = sizes[dir];
|
||
|
for (var i = 0; i <= rows.length; i++) {
|
||
|
if (side > 0 && i > 1)
|
||
|
snapPos += padding;
|
||
|
if (Math.abs(pos - snapPos) < SNAP_DIST)
|
||
|
return snapPos;
|
||
|
snapPos += rows[i] && rows[i].computed;
|
||
|
if (side < 0)
|
||
|
snapPos += padding;
|
||
|
}
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
event.capture(el, function(e) {
|
||
|
var snapToGrid = !e.ctrlKey && !e.altKey && !e.metaKey;
|
||
|
var x = e.clientX - offsetX;
|
||
|
var y = e.clientY - offsetY;
|
||
|
|
||
|
if (!moved && Math.abs(x - startX) + Math.abs(y - startY) > 5)
|
||
|
moved = true;
|
||
|
|
||
|
if (edge.indexOf("w") > -1) {
|
||
|
if (snapToGrid)
|
||
|
x = findSnapPos(x, "h", -1);
|
||
|
drag.style.left = (x + parentLeft) + "px";
|
||
|
drag.style.width = (startWidth + (startX - x)) + "px";
|
||
|
}
|
||
|
else if (edge.indexOf("e") > -1) {
|
||
|
var left = startX + parentLeft;
|
||
|
var w = startWidth + (x - startX);
|
||
|
if (snapToGrid)
|
||
|
w = findSnapPos(w + startX, "h", 1) - startX;
|
||
|
drag.style.left = left + "px";
|
||
|
drag.style.width = w + "px";
|
||
|
}
|
||
|
|
||
|
if (edge.indexOf("n") > -1) {
|
||
|
if (snapToGrid)
|
||
|
y = findSnapPos(y, "v", -1);
|
||
|
drag.style.top = (y + parentTop) + "px";
|
||
|
drag.style.height = (startHeight + (startY - y)) + "px";
|
||
|
}
|
||
|
else if (edge.indexOf("s") > -1) {
|
||
|
var top = startY + parentTop;
|
||
|
var h = startHeight + (y - startY);
|
||
|
if (snapToGrid)
|
||
|
h = findSnapPos(h + startY, "v", 1) - startY;
|
||
|
drag.style.top = top + "px";
|
||
|
drag.style.height = h + "px";
|
||
|
}
|
||
|
|
||
|
drag.style.display = "block";
|
||
|
}, function() {
|
||
|
if (moved)
|
||
|
layout.resizeTo(plugin,
|
||
|
drag.offsetWidth, edge.indexOf("w") > -1,
|
||
|
drag.offsetHeight, edge.indexOf("n") > -1);
|
||
|
|
||
|
|
||
|
drag.style.zIndex = "";
|
||
|
drag.style.display = "none";
|
||
|
});
|
||
|
|
||
|
event.stopEvent(e);
|
||
|
}
|
||
|
|
||
|
function startDragWatch(e) {
|
||
|
var el = handle || container;
|
||
|
var drag = getDragOverlay();
|
||
|
|
||
|
// Set Top
|
||
|
drag.style.zIndex = 1000000;
|
||
|
|
||
|
var lastLayout = layout;
|
||
|
var rect = lastLayout.container.getBoundingClientRect();
|
||
|
var baseX = rect.left;
|
||
|
var baseY = rect.top;
|
||
|
|
||
|
var parentLeft = rect.left;
|
||
|
var parentTop = rect.top;
|
||
|
|
||
|
var offsetX = e.clientX - (parseInt(container.style.left, 10) || 0);
|
||
|
var offsetY = e.clientY - (parseInt(container.style.top, 10) || 0);
|
||
|
var moved = false;
|
||
|
var startX = e.clientX - offsetX;
|
||
|
var startY = e.clientY - offsetY;
|
||
|
|
||
|
var startWidth = container.offsetWidth;
|
||
|
var startHeight = container.offsetHeight;
|
||
|
|
||
|
var lastPos;
|
||
|
event.capture(el, function(e) {
|
||
|
var x = e.clientX - offsetX;
|
||
|
var y = e.clientY - offsetY;
|
||
|
|
||
|
if (!moved && Math.abs(x - startX) + Math.abs(y - startY) > 5)
|
||
|
moved = true;
|
||
|
|
||
|
var pos = lastLayout.getInsertionPoint(plugin,
|
||
|
e.clientX - baseX, e.clientY - baseY) || lastPos;
|
||
|
lastPos = pos;
|
||
|
|
||
|
if (!pos) return;
|
||
|
|
||
|
if (pos.widget == plugin) {
|
||
|
drag.className = "drag";
|
||
|
drag.style.left = (x + parentLeft) + "px";
|
||
|
drag.style.top = (y + parentTop) + "px";
|
||
|
drag.style.width = startWidth + "px";
|
||
|
drag.style.height = startHeight + "px";
|
||
|
}
|
||
|
else {
|
||
|
if (lastLayout != pos.layout) {
|
||
|
lastLayout = pos.layout;
|
||
|
var rect = lastLayout.container.getBoundingClientRect();
|
||
|
baseX = rect.left;
|
||
|
baseY = rect.top;
|
||
|
}
|
||
|
|
||
|
drag.className = "drag " + pos.insertionPoint;
|
||
|
drag.style.left = (baseX + pos.left) + "px";
|
||
|
drag.style.top = (baseY + pos.top) + "px";
|
||
|
drag.style.width = pos.width + "px";
|
||
|
drag.style.height = pos.height + "px";
|
||
|
}
|
||
|
|
||
|
drag.style.display = "block";
|
||
|
}, function() {
|
||
|
if (moved && lastPos.widget != plugin)
|
||
|
lastPos.layout.insertWidget(plugin, lastPos);
|
||
|
|
||
|
drag.style.zIndex = "";
|
||
|
drag.style.display = "none";
|
||
|
});
|
||
|
|
||
|
event.stopEvent(e);
|
||
|
}
|
||
|
|
||
|
var dragOverlay;
|
||
|
function getDragOverlay(){
|
||
|
if (!dragOverlay) {
|
||
|
var el = document.createElement("div");
|
||
|
el.className = "drag";
|
||
|
document.body.appendChild(el);
|
||
|
|
||
|
dragOverlay = el;
|
||
|
}
|
||
|
|
||
|
dragOverlay.style.width = container.offsetWidth + "px";
|
||
|
dragOverlay.style.height = container.offsetHeight + "px";
|
||
|
dragOverlay.style.display = "none";
|
||
|
|
||
|
return dragOverlay;
|
||
|
}
|
||
|
|
||
|
function getState(){
|
||
|
return {
|
||
|
col: col,
|
||
|
row: row,
|
||
|
colspan: colspan,
|
||
|
rowspan: rowspan
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setState(state) {
|
||
|
col = state.col;
|
||
|
row = state.row;
|
||
|
colspan = state.colspan;
|
||
|
rowspan = state.rowspan;
|
||
|
}
|
||
|
|
||
|
/***** Lifecycle *****/
|
||
|
|
||
|
plugin.on("load", function() {
|
||
|
load();
|
||
|
});
|
||
|
plugin.on("enable", function() {
|
||
|
|
||
|
});
|
||
|
plugin.on("disable", function() {
|
||
|
|
||
|
});
|
||
|
plugin.on("unload", function() {
|
||
|
loaded = false;
|
||
|
});
|
||
|
|
||
|
/***** Register and define API *****/
|
||
|
|
||
|
// This is a baseclass
|
||
|
plugin.freezePublicAPI.baseclass();
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
**/
|
||
|
plugin.freezePublicAPI({
|
||
|
get layout(){ return layout; },
|
||
|
set layout(el) {
|
||
|
layout = el;
|
||
|
if (innerLayout)
|
||
|
innerLayout.parentLayout = layout;
|
||
|
},
|
||
|
|
||
|
get innerLayout(){ return innerLayout; },
|
||
|
set innerLayout(el) {
|
||
|
innerLayout = el;
|
||
|
innerLayout.parentLayout = layout;
|
||
|
},
|
||
|
|
||
|
get container(){ return container; },
|
||
|
get handle(){ return handle || container; },
|
||
|
|
||
|
get row(){ return row; },
|
||
|
set row(v){ row = v; },
|
||
|
|
||
|
get col(){ return col; },
|
||
|
set col(v){ col = v; },
|
||
|
|
||
|
get rowspan(){ return rowspan; },
|
||
|
set rowspan(v){ rowspan = v; },
|
||
|
|
||
|
get colspan(){ return colspan; },
|
||
|
set colspan(v){ colspan = v; },
|
||
|
|
||
|
get computed(){ return computed; },
|
||
|
set computed(v){ computed = v; },
|
||
|
|
||
|
get draggable(){ return draggable; },
|
||
|
set draggable(v){ draggable = v; },
|
||
|
|
||
|
get resizable(){ return resizable; },
|
||
|
set resizable(v){ resizable = v; },
|
||
|
|
||
|
_events: [
|
||
|
/**
|
||
|
* @event draw
|
||
|
*/
|
||
|
"draw",
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
"resize"
|
||
|
],
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getState: getState,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
setState: setState,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
startDragWatch: startDragWatch,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
setDragHandle: setDragHandle,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getPosition: getPosition,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
getPreferedSize: getPreferedSize,
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
resize: resize
|
||
|
});
|
||
|
|
||
|
return plugin;
|
||
|
}
|
||
|
|
||
|
var setGlobalCursor = (function(){
|
||
|
var done = {};
|
||
|
var sheet;
|
||
|
|
||
|
return function(cursor) {
|
||
|
if (!sheet) {
|
||
|
var style = document.createElement("style");
|
||
|
style.appendChild(document.createTextNode(""));
|
||
|
document.head.appendChild(style);
|
||
|
sheet = style.sheet;
|
||
|
}
|
||
|
|
||
|
if (cursor) {
|
||
|
if (!done[cursor]) {
|
||
|
sheet.addRule("." + cursor + " *", "cursor:" + cursor + " !important;", 0);
|
||
|
done[cursor] = true;
|
||
|
}
|
||
|
|
||
|
document.body.className = cursor;
|
||
|
}
|
||
|
else {
|
||
|
document.body.className = "";
|
||
|
}
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
register(null, {
|
||
|
DockableLayout: DockableLayout,
|
||
|
DockableWidget: DockableWidget,
|
||
|
DockableAbsoluteRenderer: DockableAbsoluteRenderer
|
||
|
});
|
||
|
}
|
||
|
});
|