CircuitOnline/public/js/layout.js

416 lines
13 KiB
JavaScript

// Layout.js - all subcircuit layout related code is here
// Function to toggle between layoutMode and normal Mode
function toggleLayoutMode() {
if (layoutMode) {
layoutMode = false;
temp_buffer = undefined;
$("#layoutDialog").fadeOut();
globalScope.centerFocus(false);
dots();
} else {
layoutMode = true;
$("#layoutDialog").fadeIn();
globalScope.ox = 0;
globalScope.oy = 0;
globalScope.scale = DPR * 1.3;
dots();
temp_buffer = new layout_buffer();
$("#toggleLayoutTitle")[0].checked=temp_buffer.layout.titleEnabled;
}
hideProperties();
update(globalScope, true)
scheduleUpdate();
}
// Update UI, positions of inputs and outputs
function layoutUpdate(scope = globalScope) {
if (!layoutMode) return;
willBeUpdated = false;
for (var i = 0; i < temp_buffer.Input.length; i++) {
temp_buffer.Input[i].update()
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
temp_buffer.Output[i].update()
}
paneLayout(scope);
renderLayout(scope);
}
function paneLayout(scope = globalScope){
if (!simulationArea.selected && simulationArea.mouseDown) {
simulationArea.selected = true;
simulationArea.lastSelected = scope.root;
simulationArea.hover = scope.root;
} else if (simulationArea.lastSelected == scope.root && simulationArea.mouseDown) {
//pane canvas
if (!objectSelection) {
globalScope.ox = (simulationArea.mouseRawX - simulationArea.mouseDownRawX) + simulationArea.oldx;
globalScope.oy = (simulationArea.mouseRawY - simulationArea.mouseDownRawY) + simulationArea.oldy;
globalScope.ox = Math.round(globalScope.ox);
globalScope.oy = Math.round(globalScope.oy);
gridUpdate = true;
if (!embed && !lightMode) miniMapArea.setup();
} else {
}
} else if (simulationArea.lastSelected == scope.root) {
// Select multiple objects
simulationArea.lastSelected = undefined;
simulationArea.selected = false;
simulationArea.hover = undefined;
}
}
// Buffer object to store changes
function layout_buffer(scope = globalScope) {
// Position of screen in layoutMode -- needs to be deprecated, reset screen position instead
var x = -Math.round(globalScope.ox / 10) * 10;
var y = -Math.round(globalScope.oy / 10) * 10;
var w = Math.round((width / globalScope.scale) * 0.01) * 10; // 10% width of screen in layoutMode
var h = Math.round((height / globalScope.scale) * 0.01) * 10; // 10% height of screen in layoutMode
var xx = x + w;
var yy = y + h;
// Position of subcircuit
this.xx = xx;
this.yy = yy;
// Assign layout if exist or create new one
this.layout = Object.assign({}, scope.layout); //Object.create(scope.layout);
// Push Input Nodes
this.Input = [];
for (var i = 0; i < scope.Input.length; i++)
this.Input.push(new layoutNode(scope.Input[i].layoutProperties.x, scope.Input[i].layoutProperties.y, scope.Input[i].layoutProperties.id, scope.Input[i].label, xx, yy, scope.Input[i].type, scope.Input[i]))
// Push Output Nodes
this.Output = [];
for (var i = 0; i < scope.Output.length; i++)
this.Output.push(new layoutNode(scope.Output[i].layoutProperties.x, scope.Output[i].layoutProperties.y, scope.Output[i].layoutProperties.id, scope.Output[i].label, xx, yy, scope.Output[i].type, scope.Output[i]))
}
// Check if position is on the boundaries of subcircuit
layout_buffer.prototype.isAllowed = function(x, y) {
if (x < 0 || x > this.layout.width || y < 0 || y > this.layout.height) return false;
if (x > 0 && x < this.layout.width && y > 0 && y < this.layout.height) return false;
if ((x == 0 && y == 0) || (x == 0 && y == this.layout.height) || (x == this.layout.width && y == 0) || (x == this.layout.width && y == this.layout.height)) return false;
return true;
}
// Check if node is already at a position
layout_buffer.prototype.isNodeAt = function(x, y) {
for (var i = 0; i < this.Input.length; i++)
if (this.Input[i].x == x && this.Input[i].y == y) return true;
for (var i = 0; i < this.Output.length; i++)
if (this.Output[i].x == x && this.Output[i].y == y) return true;
return false;
}
// Function to render layout on screen
function renderLayout(scope = globalScope) {
if (!layoutMode) return;
var xx = temp_buffer.xx;
var yy = temp_buffer.yy;
var ctx = simulationArea.context;
simulationArea.clear();
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.lineWidth = correctWidth(3);
// Draw base rectangle
ctx.beginPath();
rect2(ctx, 0, 0, temp_buffer.layout.width, temp_buffer.layout.height, xx, yy, "RIGHT");
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.textAlign = "center";
ctx.fillStyle = "black";
if(temp_buffer.layout.titleEnabled){
fillText(ctx, scope.name, temp_buffer.layout.title_x + xx, yy + temp_buffer.layout.title_y, 11);
}
// Draw labels
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (!temp_buffer.Input[i].label) continue;
var info = determine_label(temp_buffer.Input[i].x, temp_buffer.Input[i].y, scope);
ctx.textAlign = info[0];
fillText(ctx, temp_buffer.Input[i].label, temp_buffer.Input[i].x + info[1] + xx, yy + temp_buffer.Input[i].y + info[2], 12);
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (!temp_buffer.Output[i].label) continue;
var info = determine_label(temp_buffer.Output[i].x, temp_buffer.Output[i].y, scope);
ctx.textAlign = info[0];
fillText(ctx, temp_buffer.Output[i].label, temp_buffer.Output[i].x + info[1] + xx, yy + temp_buffer.Output[i].y + info[2], 12);
}
ctx.fill();
// Draw points
for (var i = 0; i < temp_buffer.Input.length; i++) {
temp_buffer.Input[i].draw()
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
temp_buffer.Output[i].draw()
}
if (gridUpdate) {
gridUpdate = false;
dots();
}
}
// Helper function to reset all nodes to original default positions
function layoutResetNodes() {
temp_buffer.layout.width = 100;
temp_buffer.layout.height = Math.max(temp_buffer.Input.length, temp_buffer.Output.length) * 20 + 20;
for (var i = 0; i < temp_buffer.Input.length; i++) {
temp_buffer.Input[i].x = 0;
temp_buffer.Input[i].y = i * 20 + 20;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
temp_buffer.Output[i].x = temp_buffer.layout.width;
temp_buffer.Output[i].y = i * 20 + 20;
}
}
// Increase width, and move all nodes
function increaseLayoutWidth() {
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].x == temp_buffer.layout.width)
temp_buffer.Input[i].x += 10;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].x == temp_buffer.layout.width)
temp_buffer.Output[i].x += 10;
}
temp_buffer.layout.width += 10;
}
// Increase Height, and move all nodes
function increaseLayoutHeight() {
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].y == temp_buffer.layout.height)
temp_buffer.Input[i].y += 10;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].y == temp_buffer.layout.height)
temp_buffer.Output[i].y += 10;
}
temp_buffer.layout.height += 10;
}
// Decrease Width, and move all nodes, check if space is there
function decreaseLayoutWidth() {
if (temp_buffer.layout.width < 30) return;
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].x == temp_buffer.layout.width - 10) {
showMessage("No space. Move or delete some nodes to make space.");
return;
}
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].x == temp_buffer.layout.width - 10) {
showMessage("No space. Move or delete some nodes to make space.");
return;
}
}
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].x == temp_buffer.layout.width)
temp_buffer.Input[i].x -= 10;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].x == temp_buffer.layout.width)
temp_buffer.Output[i].x -= 10;
}
temp_buffer.layout.width -= 10;
}
// Decrease Height, and move all nodes, check if space is there
function decreaseLayoutHeight() {
if (temp_buffer.layout.height < 30) return;
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].y == temp_buffer.layout.height - 10) {
showMessage("No space. Move or delete some nodes to make space.");
return;
}
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].y == temp_buffer.layout.height - 10) {
showMessage("No space. Move or delete some nodes to make space.");
return;
}
}
for (var i = 0; i < temp_buffer.Input.length; i++) {
if (temp_buffer.Input[i].y == temp_buffer.layout.height)
temp_buffer.Input[i].y -= 10;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
if (temp_buffer.Output[i].y == temp_buffer.layout.height)
temp_buffer.Output[i].y -= 10;
}
temp_buffer.layout.height -= 10;
}
// Helper functions to move the titles
function layoutTitleUp() {
temp_buffer.layout.title_y -= 5;
}
function layoutTitleDown() {
temp_buffer.layout.title_y += 5;
}
function layoutTitleRight() {
temp_buffer.layout.title_x += 5;
}
function layoutTitleLeft() {
temp_buffer.layout.title_x -= 5;
}
function toggleLayoutTitle(){
temp_buffer.layout.titleEnabled=!temp_buffer.layout.titleEnabled;
}
function layoutNode(x, y, id, label = "", xx, yy, type, parent) {
this.type = type;
this.id = id
this.xx = xx; // Position of parent
this.yy = yy; // Position of parent
this.label = label;
this.prevx = undefined;
this.prevy = undefined;
this.x = x; // Position of node wrt to parent
this.y = y; // Position of node wrt to parent
this.radius = 5;
this.clicked = false;
this.hover = false;
this.wasClicked = false;
this.prev = 'a';
this.count = 0;
this.parent = parent;
}
layoutNode.prototype.absX = function() {
return this.x + this.xx;
}
layoutNode.prototype.absY = function() {
return this.y + this.yy
}
layoutNode.prototype.update = function() {
// Code copied from node.update() - Some code is redundant - needs to be removed
if (this == simulationArea.hover) simulationArea.hover = undefined;
this.hover = this.isHover();
if (!simulationArea.mouseDown) {
if (this.absX() != this.prevx || this.absY() != this.prevy) {
// Store position before clicked
this.prevx = this.absX();
this.prevy = this.absY();
}
}
if (this.hover) {
simulationArea.hover = this;
}
if (simulationArea.mouseDown && ((this.hover && !simulationArea.selected) || simulationArea.lastSelected == this)) {
simulationArea.selected = true;
simulationArea.lastSelected = this;
this.clicked = true;
} else {
this.clicked = false;
}
if (!this.wasClicked && this.clicked) {
this.wasClicked = true;
this.prev = 'a';
simulationArea.lastSelected = this;
} else if (this.wasClicked && this.clicked) {
// Check if valid position and update accordingly
if (temp_buffer.isAllowed(simulationArea.mouseX - this.xx, simulationArea.mouseY - this.yy) && !temp_buffer.isNodeAt(simulationArea.mouseX - this.xx, simulationArea.mouseY - this.yy)) {
this.x = simulationArea.mouseX - this.xx;
this.y = simulationArea.mouseY - this.yy;
}
}
}
layoutNode.prototype.draw = function() {
var ctx = simulationArea.context;
drawCircle(ctx, this.absX(), this.absY(), 3, ["green", "red"][+(simulationArea.lastSelected == this)]);
}
layoutNode.prototype.isHover = function() {
return this.absX() == simulationArea.mouseX && this.absY() == simulationArea.mouseY;
}
// Helper function to determine alignment and position of nodes for rendering
function determine_label(x, y) {
if (x == 0) return ["left", 5, 5]
if (x == temp_buffer.layout.width) return ["right", -5, 5]
if (y == 0) return ["center", 0, 13]
return ["center", 0, -6]
}
function cancelLayout() {
if (layoutMode) {
toggleLayoutMode();
}
}
// Store all data into layout and exit
function saveLayout() {
if (layoutMode) {
for (var i = 0; i < temp_buffer.Input.length; i++) {
temp_buffer.Input[i].parent.layoutProperties.x = temp_buffer.Input[i].x;
temp_buffer.Input[i].parent.layoutProperties.y = temp_buffer.Input[i].y;
}
for (var i = 0; i < temp_buffer.Output.length; i++) {
temp_buffer.Output[i].parent.layoutProperties.x = temp_buffer.Output[i].x;
temp_buffer.Output[i].parent.layoutProperties.y = temp_buffer.Output[i].y;
}
globalScope.layout = Object.assign({}, temp_buffer.layout);
toggleLayoutMode();
}
}