diff --git a/editor/css/light.css b/editor/css/light.css index 621de54e19f70f72ba665fb03a166103e75d3335..9eaa6cfa7a3e2f719720f73b50b2a372f8575c6b 100644 --- a/editor/css/light.css +++ b/editor/css/light.css @@ -17,7 +17,7 @@ } .Outliner .option.active { - background-color: #f8f8f8; + background-color: rgba(0,0,0,0.02); } input.Number { diff --git a/editor/css/main.css b/editor/css/main.css index a1fb1f3b3778522cdff15cd8e6a60f73847533e1..6c969be9037b51f4b0e220b2dc36549797ee32b5 100644 --- a/editor/css/main.css +++ b/editor/css/main.css @@ -94,6 +94,29 @@ textarea, input { outline: none; } /* osx */ /* outliner */ +#outliner .option { + + border: 1px solid transparent; +} + +#outliner .option.drag { + + border: 1px dashed #999; + +} + +#outliner .option.dragTop { + + border-top: 1px dashed #999; + +} + +#outliner .option.dragBottom { + + border-bottom: 1px dashed #999; + +} + #outliner .type { position:relative; top:-2px; diff --git a/editor/index.html b/editor/index.html index 2409f9021ea9813c7c43fccbc607e4e9307ae6c9..fbb2c2f3893e6a78e52520349712480cc38ebe2f 100644 --- a/editor/index.html +++ b/editor/index.html @@ -73,7 +73,6 @@ - diff --git a/editor/js/libs/sortable.min.js b/editor/js/libs/sortable.min.js deleted file mode 100644 index 56d114ba9679be520744027cdda1dc6b2a2e9cbc..0000000000000000000000000000000000000000 --- a/editor/js/libs/sortable.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! Sortable 1.0.1 - MIT | git://github.com/rubaxa/Sortable.git */ -!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():"undefined"!=typeof Package?Sortable=a():window.Sortable=a()}(function(){"use strict";function a(a,b){this.el=a,this.options=b=b||{};var d={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1};for(var e in d)!(e in b)&&(b[e]=d[e]);var g=b.group;g&&"object"==typeof g||(g=b.group={name:g}),["pull","put"].forEach(function(a){a in g||(g[a]=!0)}),L.forEach(function(d){b[d]=c(this,b[d]||M),f(a,d.substr(2).toLowerCase(),b[d])},this),a[E]=g.name+" "+(g.put.join?g.put.join(" "):"");for(var h in this)"_"===h.charAt(0)&&(this[h]=c(this,this[h]));f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),I&&f(a,"selectstart",this._onTapStart),f(a,"dragover",this._onDragOver),f(a,"dragenter",this._onDragOver),P.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){s&&s.state!==a&&(i(s,"display",a?"none":""),!a&&s.state&&t.insertBefore(s,q),s.state=a)}function c(a,b){var c=O.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(O.call(arguments)))}}function d(a,b,c){if(a){c=c||G,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function e(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,!1)}function g(a,b,c){a.removeEventListener(b,c,!1)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(/\s+/g," ").replace(" "+b+" ","");a.className=d+(c?" "+b:"")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return G.defaultView&&G.defaultView.getComputedStyle?c=G.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function k(a){a.draggable=!1}function l(){J=!1}function m(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return b.clientY-(d.top+d.height)>5&&c}function n(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function o(a){for(var b=0;a&&(a=a.previousElementSibling)&&"TEMPLATE"!==a.nodeName.toUpperCase();)b++;return b}function p(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}var q,r,s,t,u,v,w,x,y,z,A,B,C,D={},E="Sortable"+(new Date).getTime(),F=window,G=F.document,H=F.parseInt,I=!!G.createElement("div").dragDrop,J=!1,K=function(a,b,c,d,e,f){var g=G.createEvent("Event");g.initEvent(b,!0,!0),g.item=c||a,g.from=d||a,g.clone=s,g.oldIndex=e,g.newIndex=f,a.dispatchEvent(g)},L="onAdd onUpdate onRemove onStart onEnd onFilter onSort".split(" "),M=function(){},N=Math.abs,O=[].slice,P=[];return a.prototype={constructor:a,_dragStarted:function(){h(q,this.options.ghostClass,!0),a.active=this,K(t,"start",q,t,y)},_onTapStart:function(a){var b=a.type,c=a.touches&&a.touches[0],e=(c||a).target,g=e,h=this.options,i=this.el,l=h.filter;if(!("mousedown"===b&&0!==a.button||h.disabled)){if(h.handle&&(e=d(e,h.handle,i)),e=d(e,h.draggable,i),y=o(e),"function"==typeof l){if(l.call(this,a,e,this))return K(g,"filter",e,i,y),void a.preventDefault()}else if(l&&(l=l.split(",").some(function(a){return a=d(g,a.trim(),i),a?(K(a,"filter",e,i,y),!0):void 0})))return void a.preventDefault();if(e&&!q&&e.parentNode===i){"selectstart"===b&&e.dragDrop(),B=a,t=this.el,q=e,v=q.nextSibling,A=this.options.group,q.draggable=!0,h.ignore.split(",").forEach(function(a){j(e,a.trim(),k)}),c&&(B={target:e,clientX:c.clientX,clientY:c.clientY},this._onDragStart(B,!0),a.preventDefault()),f(G,"mouseup",this._onDrop),f(G,"touchend",this._onDrop),f(G,"touchcancel",this._onDrop),f(q,"dragend",this),f(t,"dragstart",this._onDragStart),f(G,"dragover",this);try{G.selection?G.selection.empty():window.getSelection().removeAllRanges()}catch(m){}}}},_emulateDragOver:function(){if(C){i(r,"display","none");var a=G.elementFromPoint(C.clientX,C.clientY),b=a,c=this.options.group.name,d=P.length;if(b)do{if((" "+b[E]+" ").indexOf(c)>-1){for(;d--;)P[d]({clientX:C.clientX,clientY:C.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);i(r,"display","")}},_onTouchMove:function(a){if(B){var b=a.touches[0],c=b.clientX-B.clientX,d=b.clientY-B.clientY,e="translate3d("+c+"px,"+d+"px,0)";C=b,i(r,"webkitTransform",e),i(r,"mozTransform",e),i(r,"msTransform",e),i(r,"transform",e),this._onDrag(b),a.preventDefault()}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;if(this._offUpEvents(),"clone"==A.pull&&(s=q.cloneNode(!0),i(s,"display","none"),t.insertBefore(s,q)),b){var e,g=q.getBoundingClientRect(),h=i(q);r=q.cloneNode(!0),i(r,"top",g.top-H(h.marginTop,10)),i(r,"left",g.left-H(h.marginLeft,10)),i(r,"width",g.width),i(r,"height",g.height),i(r,"opacity","0.8"),i(r,"position","fixed"),i(r,"zIndex","100000"),t.appendChild(r),e=r.getBoundingClientRect(),i(r,"width",2*g.width-e.width),i(r,"height",2*g.height-e.height),f(G,"touchmove",this._onTouchMove),f(G,"touchend",this._onDrop),f(G,"touchcancel",this._onDrop),this._loopId=setInterval(this._emulateDragOver,150)}else c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,q)),f(G,"drop",this);if(u=d.scroll,u===!0){u=t;do if(u.offsetWidth=i-g)-(e>=g),l=(e>=j-h)-(e>=h);k||l?b=F:u&&(b=u,c=u.getBoundingClientRect(),k=(N(c.right-g)<=e)-(N(c.left-g)<=e),l=(N(c.bottom-h)<=e)-(N(c.top-h)<=e)),(D.vx!==k||D.vy!==l||D.el!==b)&&(D.el=b,D.vx=k,D.vy=l,clearInterval(D.pid),b&&(D.pid=setInterval(function(){b===F?F.scrollTo(F.scrollX+k*f,F.scrollY+l*f):(l&&(b.scrollTop+=l*f),k&&(b.scrollLeft+=k*f))},24)))}},30),_onDragOver:function(a){var c,e,f,g=this.el,h=this.options,j=h.group,k=j.put,n=A===j,o=h.sort;if(void 0!==a.preventDefault&&(a.preventDefault(),!h.dragoverBubble&&a.stopPropagation()),!J&&A&&(n?o||(f=!t.contains(q)):A.pull&&k&&(A.name===j.name||k.indexOf&&~k.indexOf(A.name)))&&(void 0===a.rootEl||a.rootEl===this.el)){if(c=d(a.target,h.draggable,g),e=q.getBoundingClientRect(),f)return b(!0),void(s||v?t.insertBefore(q,s||v):o||t.appendChild(q));if(0===g.children.length||g.children[0]===r||g===a.target&&(c=m(g,a))){if(c){if(c.animated)return;u=c.getBoundingClientRect()}b(n),g.appendChild(q),this._animate(e,q),c&&this._animate(u,c)}else if(c&&!c.animated&&c!==q&&void 0!==c.parentNode[E]){w!==c&&(w=c,x=i(c));var p,u=c.getBoundingClientRect(),y=u.right-u.left,z=u.bottom-u.top,B=/left|right|inline/.test(x.cssFloat+x.display),C=c.offsetWidth>q.offsetWidth,D=c.offsetHeight>q.offsetHeight,F=(B?(a.clientX-u.left)/y:(a.clientY-u.top)/z)>.5,G=c.nextElementSibling;J=!0,setTimeout(l,30),b(n),p=B?c.previousElementSibling===q&&!C||F&&C:G!==q&&!D||F&&D,p&&!G?g.appendChild(q):c.parentNode.insertBefore(q,p?G:c),this._animate(e,q),this._animate(u,c)}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),b.animated=!1},c)}},_offUpEvents:function(){g(G,"mouseup",this._onDrop),g(G,"touchmove",this._onTouchMove),g(G,"touchend",this._onDrop),g(G,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(D.pid),g(G,"drop",this),g(G,"dragover",this),g(c,"dragstart",this._onDragStart),this._offUpEvents(),b&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation(),r&&r.parentNode.removeChild(r),q&&(g(q,"dragend",this),k(q),h(q,this.options.ghostClass,!1),t!==q.parentNode?(z=o(q),K(q.parentNode,"sort",q,t,y,z),K(t,"sort",q,t,y,z),K(q,"add",q,t,y,z),K(t,"remove",q,t,y,z)):(s&&s.parentNode.removeChild(s),q.nextSibling!==v&&(z=o(q),K(t,"update",q,t,y,z),K(t,"sort",q,t,y,z))),a.active&&K(t,"end",q,t,y,z)),t=q=r=v=s=B=C=w=x=A=a.active=null,this.save())},handleEvent:function(a){var b=a.type;"dragover"===b?(this._onDrag(a),e(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],c=this.el.children,e=0,f=c.length;f>e;e++)a=c[e],d(a,this.options.draggable,this.el)&&b.push(a.getAttribute("data-id")||n(a));return b},sort:function(a){var b={},c=this.el;this.toArray().forEach(function(a,e){var f=c.children[e];d(f,this.options.draggable,c)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(c.removeChild(b[a]),c.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return d(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:void(c[a]=b)},destroy:function(){var a=this.el,b=this.options;L.forEach(function(c){g(a,c.substr(2).toLowerCase(),b[c])}),g(a,"mousedown",this._onTapStart),g(a,"touchstart",this._onTapStart),g(a,"selectstart",this._onTapStart),g(a,"dragover",this._onDragOver),g(a,"dragenter",this._onDragOver),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),P.splice(P.indexOf(this._onDragOver),1),this._onDrop(),this.el=null}},a.utils={on:f,off:g,css:i,find:j,bind:c,is:function(a,b){return!!d(a,b,a)},throttle:p,closest:d,toggleClass:h,dispatchEvent:K,index:o},a.version="1.0.1",a.create=function(b,c){return new a(b,c)},a}); \ No newline at end of file diff --git a/editor/js/libs/ui.three.js b/editor/js/libs/ui.three.js index ec6fd6b15d5f51b6171926127244261098cd17e3..bfec4a43b17fe46e92625937d0b5d141f69e8b1f 100644 --- a/editor/js/libs/ui.three.js +++ b/editor/js/libs/ui.three.js @@ -146,33 +146,8 @@ UI.Outliner = function ( editor ) { dom.className = 'Outliner'; dom.tabIndex = 0; // keyup event is ignored without setting tabIndex - var scene = editor.scene; - - var sortable = Sortable.create( dom, { - draggable: '.draggable', - onUpdate: function ( event ) { - - var item = event.item; - - var object = scene.getObjectById( item.value ); - - if ( item.nextSibling === null ) { - - editor.execute( new MoveObjectCommand( object, editor.scene ) ); - - } else { - - var nextObject = scene.getObjectById( item.nextSibling.value ); - editor.execute( new MoveObjectCommand( object, nextObject.parent, nextObject ) ); - - } - - } - } ); - - // Broadcast for object selection after arrow navigation - var changeEvent = document.createEvent( 'HTMLEvents' ); - changeEvent.initEvent( 'change', true, true ); + // hack + this.scene = editor.scene; // Prevent native scroll behavior dom.addEventListener( 'keydown', function ( event ) { @@ -190,26 +165,12 @@ UI.Outliner = function ( editor ) { // Keybindings to support arrow navigation dom.addEventListener( 'keyup', function ( event ) { - function select( index ) { - - if ( index >= 0 && index < scope.options.length ) { - - scope.selectedIndex = index; - - // Highlight selected dom elem and scroll parent if needed - scope.setValue( scope.options[ index ].value ); - scope.dom.dispatchEvent( changeEvent ); - - } - - } - switch ( event.keyCode ) { case 38: // up - select( scope.selectedIndex - 1 ); + scope.selectIndex( scope.selectedIndex - 1 ); break; case 40: // down - select( scope.selectedIndex + 1 ); + scope.selectIndex( scope.selectedIndex + 1 ); break; } @@ -228,19 +189,140 @@ UI.Outliner = function ( editor ) { UI.Outliner.prototype = Object.create( UI.Element.prototype ); UI.Outliner.prototype.constructor = UI.Outliner; +UI.Outliner.prototype.selectIndex = function ( index ) { + + if ( index >= 0 && index < this.options.length ) { + + this.setValue( this.options[ index ].value ); + + var changeEvent = document.createEvent( 'HTMLEvents' ); + changeEvent.initEvent( 'change', true, true ); + this.dom.dispatchEvent( changeEvent ); + + } + +}; + UI.Outliner.prototype.setOptions = function ( options ) { var scope = this; - var changeEvent = document.createEvent( 'HTMLEvents' ); - changeEvent.initEvent( 'change', true, true ); - while ( scope.dom.children.length > 0 ) { scope.dom.removeChild( scope.dom.firstChild ); } + function onClick() { + + scope.setValue( this.value ); + + var changeEvent = document.createEvent( 'HTMLEvents' ); + changeEvent.initEvent( 'change', true, true ); + scope.dom.dispatchEvent( changeEvent ); + + } + + // Drag + + var currentDrag; + + function onDrag( event ) { + + currentDrag = this; + + } + + function onDragStart( event ) { + + event.dataTransfer.setData( 'text', 'foo' ); + + } + + function onDragOver( event ) { + + if ( this === currentDrag ) return; + + var area = event.offsetY / this.clientHeight; + + if ( area < 0.25 ) { + + this.className = 'option dragTop'; + + } else if ( area > 0.75 ) { + + this.className = 'option dragBottom'; + + } else { + + this.className = 'option drag'; + + } + + } + + function onDragLeave() { + + if ( this === currentDrag ) return; + + this.className = 'option'; + + } + + function onDrop( event ) { + + if ( this === currentDrag ) return; + + this.className = 'option'; + + var scene = scope.scene; + var object = scene.getObjectById( currentDrag.value ); + + var area = event.offsetY / this.clientHeight; + + if ( area < 0.25 ) { + + var nextObject = scene.getObjectById( this.value ); + moveObject( object, nextObject.parent, nextObject ); + + } else if ( area > 0.75 ) { + + var nextObject = scene.getObjectById( this.nextSibling.value ); + moveObject( object, nextObject.parent, nextObject ); + + } else { + + var parentObject = scene.getObjectById( this.value ); + moveObject( object, parentObject ); + + } + + } + + function moveObject( object, newParent, nextObject ) { + + if ( nextObject === null ) nextObject = undefined; + + var newParentIsChild = false; + + object.traverse( function ( child ) { + + if ( child === newParent ) newParentIsChild = true; + + } ); + + if ( newParentIsChild ) return; + + editor.execute( new MoveObjectCommand( object, newParent, nextObject ) ); + + var changeEvent = document.createEvent( 'HTMLEvents' ); + changeEvent.initEvent( 'change', true, true ); + scope.dom.dispatchEvent( changeEvent ); + + } + + // + scope.options = []; for ( var i = 0; i < options.length; i ++ ) { @@ -248,19 +330,28 @@ UI.Outliner.prototype.setOptions = function ( options ) { var option = options[ i ]; var div = document.createElement( 'div' ); - div.className = 'option ' + ( option.static === true ? '' : 'draggable' ); + div.className = 'option'; div.innerHTML = option.html; div.value = option.value; scope.dom.appendChild( div ); scope.options.push( div ); - div.addEventListener( 'click', function ( event ) { + div.addEventListener( 'click', onClick, false ); + + if ( option.static !== true ) { + + div.draggable = true; - scope.setValue( this.value ); - scope.dom.dispatchEvent( changeEvent ); + div.addEventListener( 'drag', onDrag, false ); + div.addEventListener( 'dragstart', onDragStart, false ); // Firefox needs this + + div.addEventListener( 'dragover', onDragOver, false ); + div.addEventListener( 'dragleave', onDragLeave, false ); + div.addEventListener( 'drop', onDrop, false ); + + } - }, false ); }