UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

1,837 lines (1,582 loc) 59.2 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ define.class("$ui/view", function(require, $ui$, view, label, textbox, icon, checkbox, treeview, button, tabbar, $widgets$, palette, propviewer, jseditor, $server$, astio){ // The DreemGL Visual Toolkit allows for visual manipulation of a running composition // <br/><a href="/examples/usingtoolkit">examples &raquo;</a> this.name = "toolkit"; this.clearcolor = "#565656"; this.bgcolor = "#565656"; this.flex = 1; this.flexdirection = "column"; this.alignitems = "stretch"; this.tooltarget = false; this.position = "absolute"; this.width = 400; this.height = 800; this.opacity = 0.7; this.visible = false; this.borderradius = 7; this.bordercolor = vec4(0.3,0.6,0.8,0.4); this.borderwidth = 1; this.defaultcomponents = { Views:[ { label:"View", icon:"sticky-note", desc:"A rectangular view", classname:"view", classdir:"$ui$", params:{ height:150, width:200, pickalpha:-1, bgcolor:'white' } }, { label:"Text", text:"Aa", desc:"A text label", classname:"label", classdir:"$ui$", params:{ fontsize:44, pickalpha:-1, bgcolor:"transparent", fgcolor:'#999', text:'Label' } }, //{ // label:"Check Button", // icon:"check-square-o", // desc:"A check button", // classname:"checkbox", // classdir:"$ui$", // params:{ // tooldragroot:true, // fontsize:24, // bgcolor:"transparent", // buttoncolor1:"transparent", // buttoncolor2:"transparent", // hovercolor1:"transparent", // hovercolor2:"transparent", // pressedcolor1:"transparent", // pressedcolor2:"transparent", // pickalpha:-1, // fgcolor:'white' // } //}, //{ // label:"Button", // icon:"stop", // desc:"A basic button", // classname:"button", // classdir:"$ui$", // params:{ // tooldragroot:true, // fontsize:24, // pickalpha:-1, // text:'Button' // } //}, { label:"Icon", icon:"flag", desc:"A Fontawesome icon", classname:"icon", classdir:"$ui$", params:{ fgcolor:'#e22', bgcolor:'transparent', pickalpha:-1, icon:'flag', fontsize:80 } }, //{ // label:"Input", // icon:"italic", // desc:"An input box", // classname:"textbox", // classdir:"$ui$", // params:{ // value:"Input Text" // } //} ], //Behaviors:[ // { // label:"Hover Border", // icon:"square", // desc:"Adds a hover event that turns on and off a border", // behaviors:{ // pointerhover:function(ev,v,o) { // o.borderwidth = 3; // o.bordercolor = "yellow"; // }, // pointerout:function(ev,v,o) { // o.borderwidth = 0; // o.bordercolor = NaN; // } // } // } //] }; this.attributes = { // The target for the property inspector inspect:Config({type:Object}), // Components available to be dragged into compositions. components:this.defaultcomponents, // When in 'design' mode buttons in compositions no longer become clickable, text fields become immutable, // and views can be resized and manipulated. In 'live' mode views lock into place the composition regains // it's active behaviors mode:Config({type:Enum('design','live'), value:'design'}), // Should views be dropped as absolute or relative children dropmode:Config({type:Enum('absolute','relative'), value:'absolute'}), // The size of the reticle hot corners inside of a view reticlesize: 9, // When dragging multiple selections, `groupdrag:true` will result in all selected views dragging together // whereas `groupdrag:false` will only move the view under the cursor groupdrag:true, // When dropping a multiple selection into a view, should all views be reparented into the view that the // mouse is over, or should they drop exactly where they are physically locate don the canvas. groupreparent:false, // Show or hide the rules when selecting and dragging rulers:true, // Show or hide the rotatation handle handles:true, // Show guide bars guides:true, // Snap to guides snap:true, // Show guidelines when moving movelines:true, // Always center guideline crosshairs on the mouse cursor hoverlines:false, // internal clipboard:Config({persist:true, value:[], meta:"hidden"}), // internal selection:Config({value:[], meta:"hidden"}), // internal selected:Config({persist:true, value:[], meta:"hidden"}) }; this.setupFileDrop = function () { var doc = document.documentElement; doc.ondragover = function (e) { return !this.visible; }.bind(this); doc.ondragend = function (e) { return !this.visible; }.bind(this); doc.ondrop = function (e) { if (!this.visible) { return; } e.preventDefault && e.preventDefault(); var files = e.dataTransfer.files; var formData = new FormData(); var imagename; for (var i = 0; i < files.length; i++) { var file = files[i]; if (file.type && file.type.indexOf("image/") === 0) { imagename = file.name } formData.append('file', file); } var xhr = new XMLHttpRequest(); xhr.open('POST', window.location.pathname, true); xhr.onload = function() { if (xhr.status === 200) { if (imagename) { this.screen.device.doPick(function(v) { if (v) { var compfile = v.screen.composition.constructor.module.filename; var compdir = compfile.substring(0, compfile.lastIndexOf('/')); var filename = compdir + "/" + imagename; this.setASTObjectProperty(v, "bgimage", filename); this.commit(); } }.bind(this)) } } else { console.log('Oops, upload failed', xhr, files); } }.bind(this); xhr.send(formData); return false; }.bind(this); }; this.init = function() { this.sourcefile = astio(this.screen.composition.constructor); this.sourcefile.onchange = this.onchange.bind(this); this.onselected(null, this.selected, this); this.ensureDeps(); this.screen.globalpointerstart = this.globalpointerstart.bind(this); this.screen.globalpointermove = this.globalpointermove.bind(this); this.screen.globalpointerend = this.globalpointerend.bind(this); this.screen.globalpointerhover = this.globalpointerhover.bind(this); this.screen.globalpointerout = this.globalpointerout.bind(this); this.screen.globalkeydown = this.globalkeydown.bind(this); this.setupFileDrop(); }; this.onchange = function(ev,src,o) { var xhr = new XMLHttpRequest(); var formData = new FormData(); var fullpath = this.screen.composition.constructor.module.filename; var filename = fullpath.substring(fullpath.lastIndexOf('/') + 1); var source = 'define.class("$server/composition",' + src + ');' var blob = new Blob([source], {type: 'plain/text'}); formData.append('file', blob, filename); xhr.open("POST", window.location.pathname, true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log("[COMMIT]");//, src); } }; xhr.send(formData); }; this.onselected = function(ev,v,o) { var selection = []; if (v && v.length && this.sourcefile) { var find = function(a, b) { if (a === this.sourcefile.nodeFor(b)) return b; if (b.children) { for (var i = 0;i < b.children.length;i++) { var c = find(a, b.children[i]); if (c) return c; } } }.bind(this); for (var i=0;i< v.length;i++) { var astpath = JSON.parse(v[i]); this.sourcefile.reset(); var node = this.sourcefile.nodeForPath(astpath); var found = find(node, this.screen); if (found !== this.screen) { selection.push(found) } } } this.selection = selection; }; this.onselection = function(ev,v,o) { var i; var inspector = this.find('inspector'); if (this.__selrects) { for (i = 0; i < this.__selrects.length; i++) { var selrect = this.__selrects[i]; selrect.closeOverlay(); } } if (this.selection) { this.__selrects = []; if (inspector) { if (this.selection.length <= 1) { var selected = this.selection[0]; if (selected && inspector.target != selected) { inspector.astarget = JSON.stringify(this.sourcefile.nodePathFor(selected)); } } else { inspector.target = null; } } for (i=0;i<this.selection.length;i++) { var target = this.selection[i]; if (target.toolrect !== false && this.testView(target)) { var selectrect = this.screen.openOverlay(this.selectedrect); selectrect.target = target; this.__selrects.push(selectrect); } } } else { inspector.target = null; } var tree = this.find("structure"); if (tree && tree.reload) { tree.reload(); } }; this.globalpointerstart = function(ev) { if (!this.visible) { return; } if (this.__ruler) { this.__ruler.rulermarkstart = ev.pointer.position; } if (ev.view == this) { this.__startpos = ev.view.globalToLocal(ev.pointer.position); this.__originalpos = { x:ev.view.x, y:ev.view.y }; this.__originalsize = { w:ev.view.width, h:ev.view.height }; this.__resizecorner = this.resetCursor(ev); if (!this.__resizecorner) { var inspector = this.find('inspector'); if (inspector) { inspector.astarget = JSON.stringify(this.sourcefile.nodePathFor(this)); } } } else if (this.testView(ev.view)) { var astpath = JSON.stringify(this.sourcefile.nodePathFor(ev.view)); if (!this.selected || this.selected.indexOf(astpath) < 0) { if (this.selected && (ev.pointer.meta || ev.pointer.ctrl)) { this.selected = this.selected.concat([astpath]); } else { this.selected = [astpath]; } } var dragview = ev.view; if (!dragview.tooldragroot) { var p = dragview; while (p = p.parent) { if (p.tooldragroot) { dragview = p; break; } } } if (dragview.toolmove === false){ dragview.cursor = "crosshair"; this.__startrect = ev.pointer.position; } else { // This may be a drag this.__startpos = dragview.globalToLocal(ev.pointer.position); this.__originalpos = { x:dragview.x, y:dragview.y }; this.__originalsize = { w:dragview.width, h:dragview.height }; this.__resizecorner = this.resetCursor(ev, dragview); this.screen.pointer.cursor = "move"; dragview.cursor = "move"; dragview.drawtarget = "color"; ev.pointer.pickview = true; } } }; this.globalpointermove = function(ev) { if (!this.visible) { return; } if (this.__ruler) { if (ev.pointer.pick && this.testView(ev.pointer.pick) && this.__ruler.target !== ev.pointer.pick) { this.__ruler.target = ev.pointer.pick; } if (this.__ruler.target == ev.view) { this.__ruler.target = ev.view.parent; } this.__ruler.rulermarkstart = ev.view.pos; this.__ruler.rulermarkend = vec3(ev.view._layout.left + ev.view._layout.width, ev.view._layout.top + ev.view._layout.height,0); this.__ruler.guides = this.guides; } var dragview = ev.view; if (!dragview.tooldragroot) { var p = dragview; while (p = p.parent) { if (p.tooldragroot) { dragview = p; break; } } } if (this.__resizecorner) { // Resize if (this.__resizecorner === "bottom-right") { dragview.width = this.__originalsize.w + ev.pointer.delta.x; dragview.height = this.__originalsize.h + ev.pointer.delta.y; } else if (this.__resizecorner === "bottom") { dragview.height = this.__originalsize.h + ev.pointer.delta.y; } else if (this.__resizecorner === "right") { dragview.width = this.__originalsize.w + ev.pointer.delta.x; } else if (this.__resizecorner === "top-left") { dragview.x = ev.pointer.position.x - this.__startpos.x; dragview.y = ev.pointer.position.y - this.__startpos.y; dragview.width = this.__originalsize.w - ev.pointer.delta.x; dragview.height = this.__originalsize.h - ev.pointer.delta.y; } else if (this.__resizecorner === "left") { dragview.x = ev.pointer.position.x - this.__startpos.x; dragview.width = this.__originalsize.w - ev.pointer.delta.x; } else if (this.__resizecorner === "top") { dragview.y = ev.pointer.position.y - this.__startpos.y; dragview.height = this.__originalsize.h - ev.pointer.delta.y; } else if (this.__resizecorner === "bottom-left") { dragview.x = ev.pointer.position.x - this.__startpos.x; dragview.width = this.__originalsize.w - ev.pointer.delta.x; dragview.height = this.__originalsize.h + ev.pointer.delta.y; } else if (this.__resizecorner === "top-right") { dragview.y = ev.pointer.position.y - this.__startpos.y; dragview.height = this.__originalsize.h - ev.pointer.delta.y; dragview.width = this.__originalsize.w + ev.pointer.delta.x; } } else if (this.__startpos && this.testView(ev.view) && ev.view.toolmove !== false) { // Move this.screen.pointer.cursor = "move"; ev.view.cursor = "move"; if (dragview.parent) { if (dragview.position != "absolute") { dragview.position = "absolute"; } } var ax,ay,bx,by; if (this.selection) { ev.view.pos = vec3(ev.view.pos.x + ev.pointer.movement.x, ev.view.pos.y + ev.pointer.movement.y,0); ax = ev.view.pos[0]; ay = ev.view.pos[1]; bx = ax + ev.view._layout.width; by = ay + ev.view._layout.height; if (this.__ruler && this.__ruler.target && this.movelines !== false) { this.__ruler.lines = vec4(ax,ay,bx,by) } if (this.groupdrag) { for (var i=0;i<this.selection.length;i++) { var selected = this.selection[i]; if (this.__input) { if (this.__input.target === selected) { this.__input.pos = vec3(this.__input.pos.x + ev.pointer.movement.x, this.__input.pos.y + ev.pointer.movement.y,0); } } if (selected === ev.view) { continue; } selected.pos = vec3(selected.pos.x + ev.pointer.movement.x, selected.pos.y + ev.pointer.movement.y,0); } } } this.__lastpick = ev.pointer.pick; } else if (this.__startrect) { //select rect var select = this.__selectrect || this.find('selectorrect'); if (!select) { select = this.__selectrect = this.screen.openOverlay(this.selectorrect); this.__selectrect.pos = this.__startrect; } var pos = ev.pointer.position; var a = this.__startrect; var b = pos; if (a.x < b.x && a.y < b.y) { //normal select.pos = a; select.size = vec3(b.x - a.x, b.y - a.y,0); } else if (b.x < a.x && a.y < b.y) { // b lower left, a upper right select.pos = vec3(b.x, a.y,0); select.size = vec3(a.x - b.x, b.y - a.y); } else if (a.x < b.x && b.y < a.y) { // a lower left, b upper right select.pos = vec3(a.x, b.y,0); select.size = vec3(b.x - a.x, a.y - b.y,0); } else { select.pos = vec3(b.x, b.y); select.size = vec3(a.x - b.x, a.y - b.y,0); } } }; this.globalpointerend = function(ev) { if (ev.view.drawtarget != "both") { ev.view.drawtarget = "both"; } if (!this.visible) { return; } if (this.__ruler && this.__ruler.target !== ev.view && this.testView(ev.view)) { this.__ruler.lines = vec4(0,0,0,0); this.__ruler.target = ev.view; this.__ruler.guides = false; } if (this.__handle && this.__handle.target !== ev.view && this.testView(ev.view)) { this.__handle.target = ev.view; } var evview = ev.view; evview.cursor = 'arrow'; var commit = false; if (this.__resizecorner && (evview == this || this.testView(evview)) && evview.toolresize !== false) { // Resize if (this.__resizecorner === "top-left") { evview.x = ev.pointer.position.x - this.__startpos.x; evview.y = ev.pointer.position.y - this.__startpos.y; } else if (this.__resizecorner === "top") { evview.y = ev.pointer.position.y - this.__startpos.y; } else if (this.__resizecorner === "left") { evview.x = ev.pointer.position.x - this.__startpos.x; } else if (this.__resizecorner === "bottom-left") { evview.x = ev.pointer.position.x - this.__startpos.x; } this.setASTObjectProperty(evview, "position", "absolute"); this.setASTObjectProperty(evview, "x", evview._layout.absx); this.setASTObjectProperty(evview, "y", evview._layout.absy); this.setASTObjectProperty(evview, "width", evview._layout.width); this.setASTObjectProperty(evview, "height", evview._layout.height); commit = (Math.abs(evview.x - this.__originalpos.x) > 0.5) || (Math.abs(evview.y - this.__originalpos.y) > 0.5) || (Math.abs(evview._layout.width - this.__originalsize.w) > 0.5) || (Math.abs(evview._layout.height - this.__originalsize.h) > 0.5); } else if (this.__startpos && this.testView(evview) && evview.toolmove !== false) { // Move view var pos = ev.pointer.position; if (evview.parent) { //if (evview.position != "absolute") { // evview.position = "absolute"; //} pos = evview.parent.globalToLocal(ev.pointer.position) } var nx = pos.x - this.__startpos.x; var dx = Math.abs(evview.pos.x - this.__originalpos.x); if (dx > 0.5) { commit = true; } var ny = pos.y - this.__startpos.y; var dy = Math.abs(evview.pos.y - this.__originalpos.y); if (dy > 0.5) { commit = true; } if (commit && this.selection) { if (this.testView(evview) && evview.toolmove !== false && evview.position === "absolute") { nx = evview.pos.x + ev.pointer.movement.x; this.setASTObjectProperty(evview, "x", nx); ny = evview.pos.y + ev.pointer.movement.y; this.setASTObjectProperty(evview, "y", ny); } if (this.groupdrag) { for (var i=0;i<this.selection.length;i++) { var selected = this.selection[i]; if (selected === evview) { continue; } if (this.testView(selected) && selected.toolmove !== false && selected.position === "absolute") { this.setASTObjectProperty(selected, "x", selected.pos.x + ev.pointer.movement.x); this.setASTObjectProperty(selected, "y", selected.pos.y + ev.pointer.movement.y); } } } } if (this.__lastpick && this.__lastpick !== evview.parent && this.testView(this.__lastpick) && this.__lastpick.tooldrop !== false) { // Reparent because we dropped into a new view pos = this.__lastpick.globalToLocal(ev.pointer.position); nx = pos.x - this.__startpos.x; ny = pos.y - this.__startpos.y; this.setASTObjectProperty(evview, "x", nx, false); this.setASTObjectProperty(evview, "y", ny, false); this.appendASTNodeOn(this.__lastpick, evview); this.removeASTNodeFor(evview); if (this.selection && this.groupdrag && this.groupreparent) { for (var j=0;j<this.selection.length;j++) { var sel = this.selection[j]; if (sel === evview) { continue; } this.appendASTNodeOn(this.__lastpick, sel); this.removeASTNodeFor(selected) } } commit = true; } if (!commit) { // Just a click ending, let's target what we clicked var inspector = this.find('inspector'); if (inspector) { inspector.astarget = JSON.stringify(this.sourcefile.nodePathFor(evview)); } } } else if (this.__startrect) { // Selection rectangle var pos = ev.pointer.position; var a = this.__startrect; var b = pos; var rect = vec4(); if (a.x < b.x && a.y < b.y) { //normal rect.x = a.x; rect.y = a.y; rect.w = b.x - a.x; rect.z = b.y - a.y; } else if (b.x < a.x && a.y < b.y) { // b lower left, a upper right rect.x = b.x; rect.y = a.y; rect.w = a.x - b.x; rect.z = b.y - a.y; } else if (a.x < b.x && b.y < a.y) { // a lower left, b upper right rect.x = a.x; rect.y = b.y; rect.w = b.x - a.x; rect.z = a.y - b.y; } else { rect.x = b.x; rect.y = b.y; rect.w = a.x - b.x; rect.z = a.y - b.y; } var select = this.__selectrect || this.find('selectorrect'); if (select) { select.closeOverlay(); this.__selectrect = undefined; } if (rect.w > 0.5 || rect.z > 0.5) { var selection = this.screen.childrenInRect(rect, [select]); var selctedinrect = []; for (var i=0;i<selection.length;i++) { var selected = selection[i]; if (selected !== this && this.testView(selected) && this.toolselect !== false) { var astpath = JSON.stringify(this.sourcefile.nodePathFor(selected)); selctedinrect.push(astpath); } } if (this.selected && (ev.pointer.meta || ev.pointer.ctrl)) { this.selected = this.selected.concat(selctedinrect); } else { this.selected = selctedinrect; } } } if (commit) { this.ensureDeps(); this.commit(); } this.__lastpick = this.__startrect = this.__startpos = this.__originalpos = this.__resizecorner = this.__originalsize = undefined; }; this.globalpointerout = function(ev) { if (ev.view.emit_block_set) { ev.view.emit_block_set = null; } }; this.globalpointerhover = function(ev) { if (!this.visible) { return; } var pointer = ev.pointer; this.__lasthover = pointer.position; this.__lastover = ev.view; if (this.mode === "design" && this.testView(ev.view)) { if (ev.view.constructor.name === "button" || ev.view.constructor.module.factory.baseclass === "/ui/button" || ev.view.constructor.name === "checkbox" || ev.view.constructor.module.factory.baseclass === "/ui/checkbox") { ev.view.emit_block_set = ["pointerhover", "pointerover", "pointerstart", "pointerend", "pointerout"] } } var text = ev.view.constructor.name; if (ev.view.name) { text = ev.view.name + " (" + text + ")" } var pos = ev.view.globalToLocal(pointer.position); if (this.__ruler && this.__ruler.target) { this.__ruler.rulermarkstart = this.__ruler.target.globalToLocal(pointer.position); if (this.hoverlines !== false) { var rpos = this.__ruler.target.globalToLocal(pointer.position) this.__ruler.lines = vec4(rpos.x,rpos.y,0,0) } } text = text + " @ " + ev.pointer.position.x.toFixed(0) + ", " + ev.pointer.position.y.toFixed(0); text = text + " <" + pos.x.toFixed(0) + ", " + pos.y.toFixed(0) + ">"; this.find("pointer").text = text; this.resetCursor(ev); if (this.__selectrect) { var m = this.__selectrect; this.__selectrect = undefined; m.closeOverlay(); } }; this.globalkeydown = function(ev) { if (ev.name === "t" && ev.ctrl && ev.shift) { this.setASTObjectProperty(this, "visible", !this.visible); this.ensureDeps(); this.commit(); return; } if (!this.visible) { return; } if (ev.name === "z" && (ev.ctrl || ev.meta) && ev.shift) { this.selected = []; this.sourcefile.redo(); } else if (ev.name === "z" && (ev.ctrl || ev.meta)) { this.selected = []; this.sourcefile.undo(); } else if (ev.name === "backspace" && this.selection && this.selection.length) { var candelete = !this.screen.focus_view || (["textbox", "jseditor"].indexOf(this.screen.focus_view.constructor.name) === -1 && this.screen.focus_view.constructor.name !== "input"); if (candelete) { this.deleteselection(); } } else if (ev.name === "x" && (ev.ctrl || ev.meta)) { this.copyselection(); var candelete = !this.screen.focus_view || (["textbox", "jseditor"].indexOf(this.screen.focus_view.constructor.name) === -1 && this.screen.focus_view.constructor.name !== "input"); if (candelete) { this.deleteselection(); } } else if (ev.name === "c" && (ev.ctrl || ev.meta)) { this.copyselection(); } else if (ev.name === "v" && (ev.ctrl || ev.meta)) { var commit = false; if (this.clipboard && this.clipboard.length) { var pastetargets = this.selection || []; if (this.__lastover && !pastetargets.length) { pastetargets.push(this.__lastover) } for (var i=0;i<pastetargets.length;i++) { var v = pastetargets[i]; if (this.testView(v)) { for (var j=0;j<this.clipboard.length;j++) { var ast = JSON.parse(this.clipboard[j]); if (this.dropmode === "absolute") { this.sourcefile.setCallNodeValue(ast, "position", "absolute"); var indent = j * 10; if (this.__lasthover) { var pos = v.globalToLocal(this.__lasthover); this.sourcefile.setCallNodeValue(ast, "x", pos[0] + indent); this.sourcefile.setCallNodeValue(ast, "y", pos[1] + indent) } else { this.sourcefile.setCallNodeValue(ast, "x", indent); this.sourcefile.setCallNodeValue(ast, "y", indent) } } else { this.sourcefile.deleteCallNodeKey(ast, "position"); this.sourcefile.deleteCallNodeKey(ast, "x"); this.sourcefile.deleteCallNodeKey(ast, "y"); } this.appendASTArgOn(v, ast); commit = true; } } } } if (commit) { this.ensureDeps(); this.commit(); } } }; this.deleteselection = function() { var commit = false; for (var i=this.selection.length - 1; i>=0; i--) { var v = this.selection[i]; if (this.testView(v) && v.toolremove !== false) { this.removeASTNodeFor(v); commit = true; } } if (commit) { this.ensureDeps(); this.commit(); } }; this.copyselection = function() { var copied = []; for (var i=this.selection.length - 1; i>=0; i--) { var v = this.selection[i]; if (this.testView(v)) { copied.push(JSON.stringify(this.sourcefile.nodeFor(v))); } } this.clipboard = copied; }; this.ensureDeps = function() { var at = ""; var arglist = []; var plist = {}; var main = this.sourcefile.nodeFor(this.screen.composition); // console.log('AST', main); if (main && main.params) { for (var i=0;i<main.params.length;i++) { var param = main.params[i]; if (param && param.id && param.id.name) { var name = param.id.name; if (name[0] === '$' && name[name.length - 1] === '$') { at = name; } else { if (!plist[at]) { plist[at] = []; } plist[at].push(name) } arglist.push(name); } } if (this.components) { var missing = {}; if (Array.isArray(this.components)) { for (var i=0;i<this.components.length;i++) { var compdef = this.components[i]; var classname = compdef.classname; if (classname) { var cdir = compdef.classdir || "$$"; var included = plist[cdir]; if (!included) { included = []; } if (included.indexOf(classname) < 0) { if (!missing[cdir]) { missing[cdir] = [] } missing[cdir].push(classname) } } } } else { for (var key in this.components) { if (this.components.hasOwnProperty(key)) { var section = this.components[key]; for (var s=0;s<section.length;s++) { var compdef = section[s]; var classname = compdef.classname; if (classname) { var cdir = compdef.classdir || "$$"; var included = plist[cdir]; if (!included) { included = []; } if (included.indexOf(classname) < 0) { if (!missing[cdir]) { missing[cdir] = [] } missing[cdir].push(classname) } } } } } } for (var dir in missing) { if (missing.hasOwnProperty(dir)) { var position = arglist.indexOf(dir); if (position < 0) { position = arglist.length; arglist.push(dir); this.spliceASTParam(dir) } var missed = missing[dir]; for (var m = 0; m < missed.length; m++) { var item = missed[m]; arglist.splice(position + 1, 0, item); this.spliceASTParam(item, position + 1) } } } } } }; this.resetCursor = function (ev, useview) { var resize = false; var vw = useview || ev.view; if (vw === this || (this.testView(vw) && ev.view.toolmove !== false && ev.view.toolresize !== false)) { var pos = vw.globalToLocal(ev.pointer.position); var edge = this.reticlesize; if (vw._layout.width < edge * 2) { edge = Math.max(3, vw._layout.width * 0.25) } if (vw._layout.height < edge * 2) { edge = Math.max(3, vw._layout.height * 0.25) } vw.cursor = 'arrow'; if (pos.x < edge && pos.y < edge) { resize = "top-left"; vw.cursor = 'nwse-resize' } else if (pos.x > vw.width - edge && pos.y < edge) { resize = "top-right"; vw.cursor = 'nesw-resize' } else if (pos.x < edge && pos.y > vw.height - edge) { resize = "bottom-left"; vw.cursor = 'nesw-resize' } else if (pos.x > vw.width - edge && pos.y > vw.height - edge) { resize = "bottom-right"; vw.cursor = 'nwse-resize' } else if (pos.x < edge) { resize = "left"; vw.cursor = 'ew-resize' } else if (pos.y < edge) { resize = "top"; vw.cursor = 'ns-resize' } else if (pos.x > vw.width - edge) { resize = "right"; vw.cursor = 'ew-resize' } else if (pos.y > vw.height - edge) { resize = "bottom"; vw.cursor = 'ns-resize' } if (!resize) { vw.cursor = 'arrow'; } } else if (vw.toolallow === false) { vw.cursor = 'not-allowed'; } else { vw.cursor = 'arrow'; } return resize; }; this.testView = function(v) { var ok = v != this.screen; var p = v; while (p && ok) { ok = p.tooltarget !== false; p = p.parent; } return ok; }; this.pointerstart = function(ev,v,o) { var at = this.globalToLocal(ev.position); var lowest; for (var i=0;i<this.children.length;i++) { var child = this.children[i]; var layout = child.layout; var childbottom = layout.top + layout.height; if (childbottom < at.y) { if (child.title !== "Cursor" && (!lowest || lowest._layout.top + lowest._layout.height < childbottom)) { lowest = child; } } } this.__movepanel = lowest; }; this.pointermove = function(ev,v,o) { if (this.__movepanel) { this.__movepanel.flex = 0; this.__movepanel.height = this.__movepanel._layout.height + ev.movement.y; // this.height = this._layout.height + ev.movement.y; } }; this.pointerend = function() { this.__movepanel = undefined; }; this.paletteDropTest = function(ev, v, item, orig, dv) { return v !== this && this.testView(v); }; this.paletteDrop = function(ev, v, item, orig, dv) { if (!v) { v = this.screen } if (item.behaviors) { for (var o in item.behaviors) { if (item.behaviors.hasOwnProperty(o)) { var behave = item.behaviors[o]; this.setASTObjectProperty(v, o, behave, true); } } } if (item.classname && item.params) { var params = JSON.parse(JSON.stringify(item.params)); if (v != this.screen) { var pos = v.globalToLocal(ev.position); params.position = this.dropmode; if (this.dropmode === 'absolute') { params.x = pos.x; params.y = pos.y; } } this.createASTNodeOn(v, {classname:item.classname, params:params}) } this.ensureDeps(); this.commit(); //TODO(mason) set propviewer to inspect new object on reload? }; this.render = function() { var views = []; var vertical = this.flexdirection === "column"; if (vertical) { views = [ view({ justifycontent:'space-between', bgcolor:"white", hardrect:{pickonly:true}, pointerstart:function(p) { this.__grabpos = p.view.globalToLocal(p.position); }, pointermove:function(p) { if (this.parent.position === "absolute") { this.screen.pointer.cursor = "move"; // TODO(mason) Figure out why this fixes the bug (comment the following and drag toolkit to see the bug) this.parent.find("components").pos = vec3(0,0,0); this.parent.find("structure").pos = vec3(0,0,0); this.parent.find("inspector").pos = vec3(0,0,0); this.parent.find("code").pos = vec3(0,0,0); if (this.__grabpos) { this.parent.pos = vec3(p.position.x - this.__grabpos.x, p.position.y - this.__grabpos.y,0) } } }, pointerend:function(p) { var parent = this.parent; if (parent.testView && parent.toolmove !== false && parent.position === "absolute") { parent.pos = vec3(p.position.x - this.__grabpos.x, p.position.y - this.__grabpos.y, 0); parent.setASTObjectProperty(parent, "position", "absolute"); parent.setASTObjectProperty(parent, "x", parent._layout.absx); parent.setASTObjectProperty(parent, "y", parent._layout.absy); parent.setASTObjectProperty(parent, "width", parent._layout.width); parent.setASTObjectProperty(parent, "height", parent._layout.height); parent.ensureDeps(); parent.commit(); } this.screen.pointer.cursor = "arrow"; this.__grabpos = undefined; } }, icon({ icon:"gears", fgcolor:vec4(0.8,0.8,0.8,0.6), marginleft:5, padding:5, drawtarget:'color' }), label({ name:"title", text:"DreemGL Visual Toolkit", bgcolor:NaN, fgcolor:vec4(0.8,0.8,0.8,0.8), padding:5, drawtarget:'color' }), button({ fontsize:16, icon:"times", pickalpha:-1, bgcolor:"transparent", borderwidth:0, margintop:0, marginright:7, textactivecolor:"white", textcolor:vec4(0.8,0.8,0.8,0.8), buttoncolor1:"transparent", buttoncolor2:"transparent", hovercolor1:"transparent", hovercolor2:"transparent", pressedcolor1:"transparent", pressedcolor2:"transparent", click:function(ev,v,o) { this.setASTObjectProperty(this, "visible", false); this.ensureDeps(); this.commit(); }.bind(this) })) ] } views.push(this.panel({flex:1.0}, palette({ name:"components", flex:1, bgcolor:"#4e4e4e", items:this.components, dropTest:this.paletteDropTest.bind(this), drop:this.paletteDrop.bind(this) }) )); views.push(this.panel({flex:vertical ? 0 : 2}, label({name:"pointer", text:"", padding:5, paddingleft:10, bgcolor:"#4e4e4e"}) )); views.push(this.panel({flex:1}, treeview({ flex:1, name:"structure", bgcolor:"#4e4e4e", style:{ $:{ buttoncolor1:"transparent", buttoncolor2:"transparent", borderwidth:0, bgcolor:"#4e4e4e" }, treeline:{ bgcolor:"#4e4e4e" } }, reload:function() { var swalk = function (v) { if (v.tooltarget !== false) { var children = []; for (var i = 0; i < v.children.length; i++) { var child = swalk(v.children[i]); if (child) { children.push(child); } } var name = v.constructor.name; if (v.name) { name = v.name + " (" + name + ")" } var selected = (!!(this.selection) && this.selection.indexOf(v) > -1); var itemview = view({ bgcolor:NaN, padding:0, margin:0, height:25, alignitems:"center" }, checkbox({ icon:"lock", pickalpha:-1, textactivecolor:vec4(0.1,0.6,0.8,1), textcolor:"#666", bgcolor:"transparent", fontsize:14, borderwidth:0, padding:0, value: v.toolmove === false, click:function() { v.toolmove = !this.value; } }), button({ text:name, bgcolor:"#4e4e4e", buttoncolor1:"transparent", buttoncolor2:"transparent", hovercolor1:"transparent", hovercolor2:"transparent", pressedcolor1:"transparent", pressedcolor2:"transparent", textcolor: "#ddd", textactivecolor:"#fff", borderwidth:0, click:function(ev, val, o) { var astpath = JSON.stringify(this.sourcefile.nodePathFor(v)); if (!this.selected || this.selected.indexOf(astpath) < 0) { this.selected = [astpath] } //o.state = "selected" }.bind(this), margintop:5, padding:5, fontsize:14, pickalpha:-1 }), checkbox({ icon:"eye-slash", pickalpha:-1, fontsize:14, textactivecolor:vec4(1,0.5,0.5,1), textcolor:"#666", bgcolor:"transparent", borderwidth:0, padding:0, value: !v.visible, click:function() { v.visible = !v.visible; } }), checkbox({ icon:"warning", pickalpha:-1, textactivecolor:"yellow", textcolor:"#444", bgcolor:"transparent", fontsize:12, //margintop:7, marginleft:3, borderwidth:0, padding:0, value: v.tooltarget === false, click:function() { v.tooltarget = !this.value; } }) ); return { itemview: itemview, children: children, selected: selected, collapsed:false, view:v } } }.bind(this.parent.outer); this.data = swalk(this.screen); }, init:function() { this.reload(); } }) )); views.push(this.panel( {flex:1.7}, propviewer({ name:"inspector", target:this.inspect, flex:1, overflow:"scroll", bgcolor:"#4e4e4e", callback:function(val, editor, commit) { if (editor && editor.target && editor.propertyname) { var t = editor.target; if (typeof(t) === 'string') { t = editor.find(t); } if (t && (t == this || this.testView(t)) && t.tooledit !== false) { if (commit === "file") { var formData = new FormData(); var fileobjname; for (var i = 0; i < val.length; i++) { var file = val[i]; fileobjname = file.name; formData.append('file', file); } if (fileobjname) { var xhr = new XMLHttpRequest(); xhr.open('POST', window.location.pathname, true); xhr.onload = function() { if (xhr.status === 200) { var compfile = t.screen.composition.constructor.module.filename; var compdir = compfile.substring(0, compfile.lastIndexOf('/')); var filename = compdir + "/" + fileobjname; this.setASTObjectProperty(t, editor.propertyname, filename); this.ensureDeps(); this.commit(); } else { console.log('Oops, upload failed', xhr, val); } }.bind(this); xhr.send(formData); } } else { this.setASTObjectProperty(t, editor.propertyname, val); this.__needscommit = true; } } } if (commit && this.__needscommit) { this.__needscommit = false; this.ensureDeps(); this.commit(); } }.bind(this), ontarget:function(ev,v,o) { if (v) { if (this.__ruler) { this.__ruler.closeOverlay(); } if (this.rulers && this.testView(v)) { this.__ruler = this.screen.openOverlay(this.ruler); this.__ruler.target = v; } if (this.__input) { if (this.__input.target) { this.__input.target.opacity = 1.0 * this.__input.target.__toolkitopacity; } this.__input.closeOverlay(); } if (this.testView(v) && v.tooltextedit !== false && (v.constructor.name === "label" || v.constructor.module.factory.baseclass === "/ui/label")) { this.__input = this.screen.openOverlay(this.input); this.__input.target = v; } if (this.__handle) { this.__handle.closeOverlay(); } if (this.handles && this.testView(v) && v.toolrotate !== false) { this.__handle = this.screen.openOverlay(this.handle); this.__handle.target = v; } if (v === this || this.testView(v)) { var editor = this.find("code"); if (editor) { // todo(mason) remove this ugly hack when jseditor events exist editor.__lasttarget = editor.__target; editor.__target = v; editor.ast = this.sourcefile.nodeFor(v); } } } }.bind(this), astarget:Config({type:String, persist:true}), onastarget:function(ev,v,o) { if (v) { var find = function(a, b) { if (a === this.sourcefile.nodeFor(b)) return b; if (b.children) { for (var i = 0;i < b.children.length;i++) { var c = find(a, b.children[i]); if (c) return c; } } }.bind(this); var astpath = JSON.parse(v); this.sourcefile.reset(); var node = this.sourcefile.nodeForPath(astpath); o.target = find(node, this.screen); } }.bind(this) }), jseditor({ name:"code", flex:1, overflow:'scroll', margin:vec4(2,7,2,2), fontsize:12, boldness:0.1, wrap:true, format_options: { force_newlines_array:false, force_newlines_object:true }, onfocus:function(ev,v,o){ if (!v && o._value && o.__lasttarget) { var newsource = o._value; var newast = this.sourcefile.parse(newsource); if (newast) { // TODO(mason) make this less of a terrible hack this.sourcefile.fork(function(src) { var currentnode = src.nodeFor(o.__lasttarget); var parentnode = src.nodeFor(o.__lasttarget.parent); var index = parentnode.args.indexOf(currentnode); var node = newast.steps[0]; parentnode.args.splice(index, 1, node); }) } } }.bind(this) }) )); return views; }; this.spliceASTParam = function(param, pos) { if (!this.__changes) { this.__changes = []; } var change = { param:param }; if (pos) { change.pos = pos } this.__changes.push(change); }; this.createASTNodeOn = function(parent, params) { if (!this.__changes) { this.__changes = []; } this.__changes.push({ parent:parent, params:params }); }; this.appendASTNodeOn = function(parent, child) { if (!this.__changes) { this.__changes = []; } this.__changes.push({ parent:parent, child:child }); }; this.appendASTArgOn = function(parent, arg) { if (!this.__changes) { this.__changes = []; } this.__changes.push({ parent:parent, arg:arg }); }; this.removeASTNodeFor = function(v) { if (!this.__changes) { this.__changes = []; } this.__changes.push({ remove:v }); }; this.setASTObjectProperty = function(v, name, value, setval) { if (v == this.screen || v.constructor.name === "screen") { console.error("how did a screen get selected to be edited?") return; } if (setval !== false) { v[name] = value; } if (!this.__changes) { this.__changes = []; } var changes; for (var i=0;i<this.__changes.length;i++) { var ch = this.__changes[i]; if (ch.view === v) { changes = ch; break; } } if (!changes) { changes = { view:v, changes:[] }; this.__changes.push(changes) } changes.changes.push({key:name, value:value}); }; this.commit = function() { if (this.__changes && this.__changes.length) { this.sourcefile.fork(function(src) { while (this.__changes.length) { var changes = this.__changes; this.__changes = []; for (var i=0;i<changes.length;i++){ var changeset = changes[i]; src.reset(); if (changeset.changes) { for (var j=0;j<changeset.changes.length;j++) { var change = changeset.changes[j]; src.reset(); src.seekNodeFor(changeset.view); src.setArgValue(change.key, change.value); } } else if (changeset.remove) { var v = changeset.remove; var node = src.nodeFor(v); src.seekNodeFor(v.parent); src.removeArgNode(node); } else if (changeset.param) { var param = changeset.param; var def = src.build.Def(src.build.Id(param)); var main = this.sourcefile.nodeFor(this.screen.composition); if (changeset.pos) { main.params.splice(changeset.pos, 0, def) } else { main.params.push(def) } } else if (changeset.parent) { src.seekNodeFor(changeset.parent); if (changeset.child) { src.pushArg(src.nodeFor(changeset.child)); } if (changeset.params) { var item = changeset.params; // console.log("build", item.params) var newo = src.createASTNode(item.params) // console.log("build2", newo) var obj = src.build.Call(src.build.Id(item.classname),[newo]); // console.log("build3", obj) src.pushArg(obj); } if (changeset.arg) { src.pushArg(changeset.arg); } } else { console.log("bad change?", changeset) } } } }.bind(this)); } }; define.class(this, "selectorrect", view, function() { this.name = "selectorrect"; this.drawtarget = "color"; this.bordercolorfn = function(pos) { var speed = time * 27.0; var size = 0.0008; var slices = 3.5; var v = int(mod(size * (gl_FragCoord.x - gl_FragCoord.y + speed), slices)); return vec4((v + 0.45) * vec3(0.5, 0.9, 0.9), 0.8); } this.borderwidth = 1; this.bgcolor = vec4(0.7,0.7,0.7,0.07); this.borderradius = 7; this.position = "absolute"; this.tooltarget = false; }); define.class(this, "selectedrect", view, function() { this.visible = wire('this.outer.visible'); this.drawtarget = "color"; this.attributes = { borderseed:Math.random() + 17.0, target:Config({persist:true, type:Object}) }; this.bordercolorfn = function(pos) { var size = 0.02; var slices = 2.0; var v = int(mod(size * (gl_FragCoord.x - gl_FragCoord.y + this.borderseed), slices)); return vec4((v + 1) * vec3(0.9, 0.5, 0.8), 0.8); }; this.minimumborderradius = 3; this.borderradius = this.minimumborderradius; this.onborderradius = function(ev,v,o) { if (v) { for (var i = 0; i < v.length; i++) { if (!v[i]) { v[i] = this.minimumborderradius; } } } }; this.borderwidth = 1.5; this.bgcolor = NaN; this.position = "absolute"; this.tooltarget = false; this.ontarget = function(ev,v,o) { this.pos = vec3(v._layout.absx - this.borderwidth[0] / 2.0, v._layout.absy - this.borderwidth[0] / 2.0, 0); this.size = vec3(v._layout.width + this.borderwidth[0], v._layout.height + this.borderwidth[0], 0); this.rotate = v.rotate; var p = v; while (p = p.parent) { for (var i=0;i<this.rotate.length;i++) { this.rotate[i] += p.rotate[i] } } if (v.borderradius && v.borderradius[0] + v.borderradius[1] + v.borderradius[2] + v.borderradius[3]) { this.borderradius = v.borderradius; } //TODO (mason) these events maybe need to be cleaned up later, not sure yet v.onsize = function(ev,v,o) { this.size = vec3(v.x + this.borderwidth[0], v.y + this.borderwidth[0], v.z) }.bind(this); v.onpos = function(ev,v,o) { var x = v.x - (this.borderwidth[0] + this.borderwidth[1]) / 2.0; var y = v.y - (this.borderwidth[2] + this.borderwidth[3]) / 2.0; var p = o; while (p = p.parent) { x = x + p.x; y = y + p.y; } this.pos = vec3(x, y, v.z); }.bind(this); v.onfontsize = function(ev,fs,o) { var v = o.layout; this.size = vec3(v.width + this.borderwidth[0], v.height + this.borderwidth[0], 0) var x = v.left - (this.borderwidth[0] + this.borderwidth[1]) / 2.0; var y = v.top - (this.borderwidth[2] + this.borderwidth[3]) / 2.0; var p = o; while (p = p.parent) { x = x + p.x; y = y + p.y; } this.pos = vec3(x, y, 0); }.bind(this); v.onrotate = function(ev,v,o) { this.rotate = v; var p = o; while (p = p.parent) { for (var i=0;i<this.rotate.length;i++) { this.rotate[i] += p.rotate[i] } } }.bind(this); v.onborderradius = function(ev,v,o) { this.borderradius = v; }.bind(this); } }); define.class(this, "ruler", view, function() { this.visible = wire('this.outer.visible'); this.drawtarget = "color"; this.bgcolor = "transparent"; this.position = "absolute"; this.tooltarget = false; this.borderwidth = vec4(5.0,5.0,5.0,5.0); this.attributes = { target:Config({type:Object}), rulertickwidth:1, rulertickspacing:10.0, rulermajorevery:10, rulermajorcolor:vec4("#F9F6F4"), rulerminorcolor:vec4("#B0C4DE"), rulermarkstartcolor:vec4("#00CCDD"), rulermarkstart:vec3(0,0,0), rulermarkendcolor:vec4("#DD00BB"), rulermarkend:vec3(0,0,0), linecolor:vec4("#00CCDD"), lines:vec4(0,0,0,0), linedotspacing:10.0, guidecolor:vec4("#FFDD00"), edges:vec4(0,0,0,0), guides:false, centertrigger:100.0 }; this.calculateEdges = function() { }; this.bgcolorfn = function(p) { var px = width * p.x; var py = height * p.y; if (guides) { if (abs(layout.width * 0.5 - px) < 0.5 && abs(layout.width * 0.5 - (lines[0] + ((lines[2] - lines[0]) * 0.5))) < 1.0 || abs(layout.height * 0.5 - py) < 0.5 && abs(layout.height * 0.5 - (lines[1] + ((lines[3] - lines[1]) * 0.5))) < 1.0) { return guidecolor; } if (abs(layout.width * 0.5 - px) < 0.5 && abs(layout.width * 0.5 - (lines[0] + ((lines[2] - lines[0]) * 0.5))) < centertrigger) { return vec4(guidecolor.rgb, 0.5 + (((1.0 - abs(layout.width * 0.5 - (lines[0] + ((lines[2] - lines[0]) * 0.5)))) / centertrig