mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-01-30 15:42:42 +08:00
2c98c43754
Implement normal transform space Turn name of locked elements gray in outliner Fix face selection issue Improve performance by hiding unselected faces on large meshes Fix issue where imported bbmodels would switch to generic model
1539 lines
42 KiB
JavaScript
1539 lines
42 KiB
JavaScript
const Outliner = {
|
|
root: [],
|
|
get elements() {
|
|
return Project.elements || []
|
|
},
|
|
set elements(val) {
|
|
console.warn('You cannot modify this')
|
|
},
|
|
get selected() {
|
|
return Project.selected_elements || []
|
|
},
|
|
set selected(val) {
|
|
console.warn('You cannot modify this')
|
|
},
|
|
buttons: {
|
|
visibility: {
|
|
id: 'visibility',
|
|
title: tl('switches.visibility'),
|
|
icon: ' fa fa-eye',
|
|
icon_off: ' fa fa-eye-slash',
|
|
advanced_option: false
|
|
},
|
|
locked: {
|
|
id: 'locked',
|
|
title: tl('switches.lock'),
|
|
icon: ' fas fa-lock',
|
|
icon_off: ' fas fa-lock-open',
|
|
advanced_option: true
|
|
},
|
|
export: {
|
|
id: 'export',
|
|
title: tl('switches.export'),
|
|
icon: ' fa fa-camera',
|
|
icon_off: ' far fa-window-close',
|
|
advanced_option: true
|
|
},
|
|
shade: {
|
|
id: 'shade',
|
|
get title() {return Project.box_uv ? tl('switches.mirror') : tl('switches.shade')},
|
|
get icon() {return Project.box_uv ? 'fa fa-star' : 'fa fa-star'},
|
|
get icon_off() {return Project.box_uv ? 'fas fa-star-half-alt' : 'far fa-star'},
|
|
advanced_option: true
|
|
},
|
|
autouv: {
|
|
id: 'autouv',
|
|
title: tl('switches.autouv'),
|
|
icon: ' fa fa-thumbtack',
|
|
icon_off: ' far fa-times-circle',
|
|
icon_alt: ' fa fa-magic',
|
|
advanced_option: true,
|
|
getState(element) {
|
|
if (!element.autouv) {
|
|
return false
|
|
} else if (element.autouv === 1) {
|
|
return true
|
|
} else {
|
|
return 'alt'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Object.defineProperty(window, 'elements', {
|
|
get() {
|
|
return Outliner.elements;
|
|
},
|
|
set(val) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
});
|
|
Object.defineProperty(window, 'selected', {
|
|
get() {
|
|
return Outliner.selected;
|
|
},
|
|
set(val) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
});
|
|
//Colors
|
|
var markerColors = [
|
|
{pastel: "#A2EBFF", standard: "#58C0FF", name: 'light_blue'},
|
|
{pastel: "#FFF899", standard: "#F3D81A", name: 'yellow'},
|
|
{pastel: "#E8BD7B", standard: "#EC9218", name: 'orange'},
|
|
{pastel: "#FFA7A4", standard: "#FA565D", name: 'red'},
|
|
{pastel: "#C5A6E8", standard: "#B55AF8", name: 'purple'},
|
|
{pastel: "#A6C8FF", standard: "#4D89FF", name: 'blue'},
|
|
{pastel: "#7BFFA3", standard: "#00CE71", name: 'green'},
|
|
{pastel: "#BDFFA6", standard: "#AFFF62", name: 'lime'}
|
|
]
|
|
class OutlinerNode {
|
|
constructor(uuid) {
|
|
this.uuid = uuid || guid()
|
|
this.export = true;
|
|
this.locked = false;
|
|
}
|
|
init() {
|
|
OutlinerNode.uuids[this.uuid] = this;
|
|
//this.constructor.all.safePush(this);
|
|
if (!this.parent || (this.parent === 'root' && Outliner.root.indexOf(this) === -1)) {
|
|
this.addTo('root')
|
|
}
|
|
return this;
|
|
}
|
|
get preview_controller() {
|
|
return this.constructor.preview_controller;
|
|
}
|
|
//Sorting
|
|
sortInBefore(element, index_mod) {
|
|
var index = -1;
|
|
index_mod = index_mod || 0;
|
|
|
|
if (element.parent === 'root') {
|
|
index = Outliner.root.indexOf(element)
|
|
var arr = Outliner.root
|
|
this.parent = 'root'
|
|
} else {
|
|
index = element.parent.children.indexOf(element)
|
|
element = element.parent
|
|
var arr = element.children
|
|
this.parent = element
|
|
}
|
|
this.removeFromParent()
|
|
|
|
//Adding
|
|
if (index < 0)
|
|
arr.push(this)
|
|
else {
|
|
arr.splice(index+index_mod, 0, this)
|
|
}
|
|
return this;
|
|
}
|
|
addTo(group, index = -1) {
|
|
//Resolve Group Argument
|
|
if (!group) {
|
|
group = 'root'
|
|
} else if (group !== 'root') {
|
|
if (group.type !== 'group') {
|
|
if (group.parent === 'root') {
|
|
index = Outliner.root.indexOf(group)+1
|
|
group = 'root'
|
|
} else {
|
|
index = group.parent.children.indexOf(group)+1
|
|
group = group.parent
|
|
}
|
|
}
|
|
}
|
|
this.removeFromParent()
|
|
|
|
//Get Array
|
|
if (group === 'root') {
|
|
var arr = Outliner.root
|
|
this.parent = 'root'
|
|
} else {
|
|
var arr = group.children
|
|
this.parent = group
|
|
}
|
|
|
|
//Adding
|
|
if (arr.includes(this)) return this;
|
|
if (index < 0)
|
|
arr.push(this)
|
|
else {
|
|
arr.splice(index, 0, this)
|
|
}
|
|
|
|
return this;
|
|
}
|
|
removeFromParent() {
|
|
this.getParentArray().remove(this);
|
|
return this;
|
|
}
|
|
getParentArray() {
|
|
if (this.parent === 'root') {
|
|
return Outliner.root
|
|
} else if (typeof this.parent === 'object') {
|
|
return this.parent.children
|
|
}
|
|
}
|
|
//Outliner
|
|
showInOutliner() {
|
|
var scope = this;
|
|
if (this.parent !== 'root') {
|
|
this.parent.openUp()
|
|
}
|
|
Vue.nextTick(() => {
|
|
var el = $('#'+scope.uuid)
|
|
if (el.length === 0) return;
|
|
var outliner_pos = $('#outliner').offset().top
|
|
|
|
var el_pos = el.offset().top
|
|
if (el_pos > outliner_pos && el_pos < $('#cubes_list').height() + outliner_pos) return;
|
|
|
|
var multiple = el_pos > outliner_pos ? 0.8 : 0.2
|
|
var scroll_amount = el.offset().top + $('#cubes_list').scrollTop() - outliner_pos - 20
|
|
scroll_amount -= $('#cubes_list').height()*multiple - 15
|
|
|
|
$('#cubes_list').animate({
|
|
scrollTop: scroll_amount
|
|
}, 200);
|
|
})
|
|
}
|
|
updateElement() {
|
|
var scope = this;
|
|
var old_name = this.name;
|
|
scope.name = '_&/3%6-7A';
|
|
scope.name = old_name;
|
|
return this;
|
|
}
|
|
get mesh() {
|
|
return Project.nodes_3d[this.uuid];
|
|
}
|
|
getDepth() {
|
|
var d = 0;
|
|
function it(p) {
|
|
if (p.parent) {
|
|
d++;
|
|
return it(p.parent)
|
|
} else {
|
|
return d-1;
|
|
}
|
|
}
|
|
return it(this)
|
|
}
|
|
remove() {
|
|
if (this.preview_controller) this.preview_controller.remove(this);
|
|
this.constructor.all.remove(this);
|
|
if (OutlinerNode.uuids[this.uuid] == this) delete OutlinerNode.uuids[this.uuid];
|
|
this.removeFromParent();
|
|
}
|
|
rename() {
|
|
this.showInOutliner()
|
|
var obj = $('#'+this.uuid+' > div.outliner_object > input.cube_name')
|
|
obj.attr('disabled', false)
|
|
obj.select()
|
|
obj.focus()
|
|
obj.addClass('renaming')
|
|
Blockbench.addFlag('renaming')
|
|
this.old_name = this.name
|
|
return this;
|
|
}
|
|
saveName(save) {
|
|
var scope = this;
|
|
if (save !== false && scope.name.trim().length > 0 && scope.name != scope.old_name) {
|
|
var name = scope.name.trim();
|
|
scope.name = scope.old_name;
|
|
if (scope.type === 'group') {
|
|
Undo.initEdit({outliner: true})
|
|
} else {
|
|
Undo.initEdit({elements: [scope]})
|
|
}
|
|
scope.name = name
|
|
scope.sanitizeName();
|
|
delete scope.old_name
|
|
if (Condition(scope.needsUniqueName)) {
|
|
scope.createUniqueName()
|
|
}
|
|
Undo.finishEdit('Rename element')
|
|
} else {
|
|
scope.name = scope.old_name
|
|
delete scope.old_name
|
|
}
|
|
return this;
|
|
}
|
|
sanitizeName() {
|
|
var name_regex = typeof this.name_regex == 'function' ? this.name_regex(this) : this.name_regex;
|
|
if (name_regex) {
|
|
var regex = new RegExp(`[^${name_regex}]`, 'g');
|
|
this.name = this.name.replace(regex, c => {
|
|
if (c == '-' && '_'.search(regex) == -1) {
|
|
return '_';
|
|
}
|
|
if (c.toLowerCase().search(regex) == -1) {
|
|
return c.toLowerCase();
|
|
}
|
|
return '';
|
|
});
|
|
}
|
|
}
|
|
createUniqueName(arr) {
|
|
if (!Condition(this.needsUniqueName)) return;
|
|
var scope = this;
|
|
var others = this.constructor.all.slice();
|
|
if (arr && arr.length) {
|
|
arr.forEach(g => {
|
|
others.safePush(g)
|
|
})
|
|
}
|
|
let zero_based = this.name.match(/[^\d]0$/) !== null;
|
|
var name = this.name.replace(/\d+$/, '').replace(/\s+/g, '_');
|
|
function check(n) {
|
|
for (var i = 0; i < others.length; i++) {
|
|
if (others[i] !== scope && others[i].name.toLowerCase() == n.toLowerCase()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (check(this.name)) {
|
|
return this.name;
|
|
}
|
|
for (var num = zero_based ? 1 : 2; num < 8e3; num++) {
|
|
if (check(name+num)) {
|
|
scope.name = name+num;
|
|
return scope.name;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
isIconEnabled(toggle) {
|
|
if (typeof toggle.getState == 'function') {
|
|
return toggle.getState(this);
|
|
} else if (this[toggle.id] !== undefined) {
|
|
return this[toggle.id];
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
isChildOf(group, max_levels) {
|
|
function iterate(obj, level) {
|
|
if (!obj || obj === 'root') {
|
|
return false;
|
|
} else if (obj === group) {
|
|
return true;
|
|
} else if (!max_levels || level < max_levels-1) {
|
|
return iterate(obj.parent, level+1)
|
|
}
|
|
return false;
|
|
}
|
|
return iterate(this.parent, 0)
|
|
}
|
|
get mirror_uv() {
|
|
return !this.shade;
|
|
}
|
|
set mirror_uv(val) {
|
|
this.shade = !val;
|
|
}
|
|
}
|
|
class OutlinerElement extends OutlinerNode {
|
|
constructor(data, uuid) {
|
|
super(uuid);
|
|
this.parent = 'root';
|
|
this.selected = false;
|
|
}
|
|
init() {
|
|
super.init();
|
|
Project.elements.safePush(this);
|
|
if (!this.mesh || !this.mesh.parent) {
|
|
this.preview_controller.setup(this);
|
|
}
|
|
return this;
|
|
}
|
|
remove() {
|
|
super.remove()
|
|
selected.remove(this);
|
|
elements.remove(this);
|
|
this.constructor.selected.remove(this);
|
|
return this;
|
|
}
|
|
showContextMenu(event) {
|
|
Prop.active_panel = 'outliner'
|
|
if (this.locked) return this;
|
|
if (!this.selected) {
|
|
this.select()
|
|
}
|
|
this.menu.open(event, this)
|
|
return this;
|
|
}
|
|
forSelected(fc, undo_tag) {
|
|
if (this.constructor.selected.length <= 1 || !this.constructor.selected.includes(this)) {
|
|
var edited = [this]
|
|
} else {
|
|
var edited = this.constructor.selected
|
|
}
|
|
if (typeof fc === 'function') {
|
|
if (undo_tag) {
|
|
Undo.initEdit({elements: edited})
|
|
}
|
|
for (var i = 0; i < edited.length; i++) {
|
|
fc(edited[i])
|
|
}
|
|
if (undo_tag) {
|
|
Undo.finishEdit(undo_tag)
|
|
}
|
|
}
|
|
return edited;
|
|
}
|
|
duplicate() {
|
|
var copy = new this.constructor(this);
|
|
//Numberation
|
|
var number = copy.name.match(/[0-9]+$/)
|
|
if (number) {
|
|
number = parseInt(number[0])
|
|
copy.name = copy.name.split(number).join(number+1)
|
|
}
|
|
//Rest
|
|
let last_selected = this.getParentArray().filter(el => el.selected || el == this).last();
|
|
copy.sortInBefore(last_selected, 1).init();
|
|
var index = selected.indexOf(this)
|
|
if (index >= 0) {
|
|
selected[index] = copy
|
|
} else {
|
|
selected.push(copy)
|
|
}
|
|
if (Condition(copy.needsUniqueName)) {
|
|
copy.createUniqueName()
|
|
}
|
|
TickUpdates.selection = true;
|
|
return copy;
|
|
}
|
|
select(event, isOutlinerClick) {
|
|
if (Modes.animate && this.constructor != NullObject) return false;
|
|
//Shiftv
|
|
var just_selected = []
|
|
if (event && (event.shiftKey === true || Pressing.overrides.shift) && this.getParentArray().includes(selected[selected.length-1]) && !Modes.paint && isOutlinerClick) {
|
|
var starting_point;
|
|
var last_selected = selected[selected.length-1]
|
|
this.getParentArray().forEach((s, i) => {
|
|
if (s === last_selected || s === this) {
|
|
if (starting_point) {
|
|
starting_point = false
|
|
} else {
|
|
starting_point = true
|
|
}
|
|
if (s.type !== 'group') {
|
|
if (!selected.includes(s)) {
|
|
s.selectLow()
|
|
just_selected.push(s)
|
|
}
|
|
} else {
|
|
s.selectLow()
|
|
}
|
|
} else if (starting_point) {
|
|
if (s.type !== 'group') {
|
|
if (!selected.includes(s)) {
|
|
s.selectLow()
|
|
just_selected.push(s)
|
|
}
|
|
} else {
|
|
s.selectLow()
|
|
}
|
|
}
|
|
})
|
|
|
|
//Control
|
|
} else if (event && !Modes.paint && (event.ctrlOrCmd || event.shiftKey || Pressing.overrides.ctrl || Pressing.overrides.shift)) {
|
|
if (selected.includes(this)) {
|
|
selected.replace(selected.filter((e) => {
|
|
return e !== this
|
|
}))
|
|
} else {
|
|
this.selectLow()
|
|
just_selected.push(this)
|
|
}
|
|
|
|
//Normal
|
|
} else {
|
|
selected.forEachReverse(obj => obj.unselect())
|
|
if (Group.selected) Group.selected.unselect()
|
|
this.selectLow()
|
|
just_selected.push(this)
|
|
this.showInOutliner()
|
|
}
|
|
if (Group.selected) {
|
|
Group.selected.unselect()
|
|
}
|
|
Group.all.forEach(function(s) {
|
|
s.selected = false;
|
|
})
|
|
Blockbench.dispatchEvent('added_to_selection', {added: just_selected})
|
|
TickUpdates.selection = true;
|
|
return this;
|
|
}
|
|
selectLow() {
|
|
Outliner.selected.safePush(this);
|
|
this.constructor.selected.safePush(this)
|
|
this.selected = true;
|
|
TickUpdates.selection = true;
|
|
return this;
|
|
}
|
|
unselect() {
|
|
selected.remove(this);
|
|
this.selected = false;
|
|
this.constructor.selected.remove(this);
|
|
TickUpdates.selection = true;
|
|
return this;
|
|
}
|
|
}
|
|
OutlinerElement.prototype.isParent = false;
|
|
OutlinerElement.fromSave = function(obj, keep_uuid) {
|
|
let Type = OutlinerElement.types[obj.type] || Cube;
|
|
if (Type) {
|
|
return new Type(obj, keep_uuid ? obj.uuid : 0).init()
|
|
}
|
|
}
|
|
OutlinerElement.isTypePermitted = function(type) {
|
|
return !(
|
|
(type == 'locator' && !Format.locators) ||
|
|
(type == 'mesh' && !Format.meshes)
|
|
)
|
|
}
|
|
Object.defineProperty(OutlinerElement, 'all', {
|
|
get() {
|
|
return Project.elements ? Project.elements : [];
|
|
},
|
|
set(arr) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
})
|
|
Object.defineProperty(OutlinerElement, 'selected', {
|
|
get() {
|
|
return Project.selected_elements ? Project.selected_elements : [];
|
|
},
|
|
set(group) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
})
|
|
OutlinerElement.types = {};
|
|
|
|
|
|
class NodePreviewController {
|
|
constructor(type, data = {}) {
|
|
this.type = type;
|
|
type.preview_controller = this;
|
|
|
|
this.updateGeometry = null;
|
|
this.updateUV = null;
|
|
this.updateFaces = null;
|
|
this.updatePaintingGrid = null;
|
|
this.updateHighlight = null;
|
|
|
|
Object.assign(this, data);
|
|
}
|
|
setup(element) {
|
|
var mesh = new THREE.Object3D();
|
|
Project.nodes_3d[element.uuid] = mesh;
|
|
mesh.name = element.uuid;
|
|
mesh.type = element.type;
|
|
mesh.isElement = true;
|
|
mesh.visible = element.visibility;
|
|
mesh.rotation.order = 'ZYX';
|
|
}
|
|
remove(element) {
|
|
let {mesh} = element;
|
|
if (mesh.parent) mesh.parent.remove(mesh);
|
|
if (mesh.geometry) mesh.geometry.dispose();
|
|
if (mesh.outline && mesh.outline.geometry) {
|
|
mesh.outline.geometry.dispose();
|
|
if (Transformer.dragging) {
|
|
Canvas.outlines.remove(Canvas.outlines.getObjectByName(this.uuid+'_ghost_outline'))
|
|
}
|
|
}
|
|
delete Project.nodes_3d[element.uuid];
|
|
}
|
|
updateAll(element) {
|
|
this.updateTransform(element);
|
|
this.updateVisibility(element);
|
|
if (this.updateGeometry) this.updateGeometry(element);
|
|
if (this.updateUV) this.updateUV(element);
|
|
if (this.updateFaces) this.updateFaces(element);
|
|
if (this.updatePaintingGrid) this.updatePaintingGrid(element);
|
|
}
|
|
updateTransform(element) {
|
|
let mesh = element.mesh;
|
|
|
|
if (element.movable) {
|
|
mesh.position.set(element.origin[0], element.origin[1], element.origin[2])
|
|
}
|
|
|
|
if (element.rotatable) {
|
|
mesh.rotation.x = Math.degToRad(element.rotation[0]);
|
|
mesh.rotation.y = Math.degToRad(element.rotation[1]);
|
|
mesh.rotation.z = Math.degToRad(element.rotation[2]);
|
|
} else {
|
|
mesh.rotation.set(0, 0, 0);
|
|
}
|
|
|
|
if (element.scalable) {
|
|
mesh.scale.x = element.scale[0] || 1e-7;
|
|
mesh.scale.y = element.scale[1] || 1e-7;
|
|
mesh.scale.z = element.scale[2] || 1e-7;
|
|
} else {
|
|
mesh.scale.set(1, 1, 1);
|
|
}
|
|
|
|
if (Format.bone_rig) {
|
|
if (element.parent instanceof Group) {
|
|
element.parent.mesh.add(mesh);
|
|
mesh.position.x -= element.parent.origin[0]
|
|
mesh.position.y -= element.parent.origin[1]
|
|
mesh.position.z -= element.parent.origin[2]
|
|
} else if (mesh.parent !== Project.model_3d) {
|
|
Project.model_3d.add(mesh)
|
|
}
|
|
} else if (mesh.parent !== Project.model_3d) {
|
|
Project.model_3d.add(mesh)
|
|
}
|
|
|
|
mesh.updateMatrixWorld();
|
|
}
|
|
updateVisibility(element) {
|
|
element.mesh.visible = element.visibility;
|
|
}
|
|
updateSelection(element) {
|
|
let {mesh} = element;
|
|
if (mesh && mesh.outline) {
|
|
mesh.outline.visible = element.selected
|
|
}
|
|
}
|
|
}
|
|
|
|
OutlinerElement.registerType = function(constructor, id) {
|
|
OutlinerElement.types[id] = constructor;
|
|
Object.defineProperty(constructor, 'all', {
|
|
get() {
|
|
return Project.elements ? Project.elements.filter(element => element instanceof constructor) : [];
|
|
},
|
|
set(arr) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
})
|
|
Object.defineProperty(constructor, 'selected', {
|
|
get() {
|
|
return Project.selected_elements ? Project.selected_elements.filter(element => element instanceof constructor) : [];
|
|
},
|
|
set(group) {
|
|
console.warn('You cannot modify this')
|
|
}
|
|
})
|
|
}
|
|
|
|
Array.prototype.findRecursive = function(key1, val) {
|
|
var i = 0
|
|
while (i < this.length) {
|
|
if (this[i][key1] === val) {
|
|
return this[i];
|
|
} else if (this[i].children && this[i].children.length > 0) {
|
|
let inner = this[i].children.findRecursive(key1, val)
|
|
if (inner !== undefined) {
|
|
return inner;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function compileGroups(undo, lut) {
|
|
var result = []
|
|
function iterate(array, save_array) {
|
|
var i = 0;
|
|
for (var element of array) {
|
|
if (element.type === 'group') {
|
|
|
|
if (lut === undefined || element.export === true) {
|
|
|
|
var obj = element.compile(undo)
|
|
|
|
if (element.children.length > 0) {
|
|
iterate(element.children, obj.children)
|
|
}
|
|
save_array.push(obj)
|
|
}
|
|
} else {
|
|
if (undo) {
|
|
save_array.push(element.uuid)
|
|
} else {
|
|
if (lut) {
|
|
var index = lut[elements.indexOf(element)]
|
|
} else {
|
|
var index = elements.indexOf(element)
|
|
}
|
|
if (index >= 0) {
|
|
save_array.push(index)
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
iterate(Outliner.root, result)
|
|
return result;
|
|
}
|
|
function parseGroups(array, import_reference, startIndex) {
|
|
function iterate(array, save_array, addGroup) {
|
|
var i = 0;
|
|
while (i < array.length) {
|
|
if (typeof array[i] === 'number' || typeof array[i] === 'string') {
|
|
|
|
if (typeof array[i] === 'number') {
|
|
var obj = elements[array[i] + (startIndex ? startIndex : 0) ]
|
|
} else {
|
|
var obj = OutlinerNode.uuids[array[i]];
|
|
}
|
|
if (obj) {
|
|
obj.removeFromParent()
|
|
save_array.push(obj)
|
|
obj.parent = addGroup
|
|
}
|
|
} else {
|
|
if (OutlinerNode.uuids[array[i].uuid] instanceof Group) {
|
|
OutlinerNode.uuids[array[i].uuid].removeFromParent();
|
|
delete OutlinerNode.uuids[array[i].uuid];
|
|
}
|
|
var obj = new Group(array[i], array[i].uuid)
|
|
obj.parent = addGroup
|
|
obj.isOpen = !!array[i].isOpen
|
|
if (array[i].uuid) {
|
|
obj.uuid = array[i].uuid
|
|
}
|
|
save_array.push(obj)
|
|
obj.init()
|
|
if (array[i].children && array[i].children.length > 0) {
|
|
iterate(array[i].children, obj.children, obj)
|
|
}
|
|
if (array[i].content && array[i].content.length > 0) {
|
|
iterate(array[i].content, obj.children, obj)
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
if (import_reference instanceof Group && startIndex !== undefined) {
|
|
iterate(array, import_reference.children, import_reference)
|
|
} else {
|
|
if (!import_reference) {
|
|
Group.all.forEach(group => {
|
|
group.removeFromParent();
|
|
})
|
|
Group.all.empty();
|
|
}
|
|
iterate(array, Outliner.root, 'root');
|
|
}
|
|
}
|
|
|
|
// Dropping
|
|
function dropOutlinerObjects(item, target, event, order) {
|
|
if (item.type === 'group' && target && target.parent) {
|
|
var is_parent = false;
|
|
function iterate(g) {
|
|
if (!(is_parent = g === item) && g.parent.type === 'group') {
|
|
iterate(g.parent)
|
|
}
|
|
}
|
|
iterate(target)
|
|
if (is_parent) return;
|
|
}
|
|
if (item instanceof OutlinerElement && selected.includes( item )) {
|
|
var items = selected.slice();
|
|
} else {
|
|
var items = [item];
|
|
}
|
|
if (event.altKey || Pressing.overrides.alt) {
|
|
Undo.initEdit({elements: [], outliner: true, selection: true})
|
|
Outliner.selected.empty();
|
|
} else {
|
|
Undo.initEdit({outliner: true, selection: true})
|
|
var updatePosRecursive = function(item) {
|
|
if (item.type == 'group') {
|
|
if (item.children && item.children.length) {
|
|
item.children.forEach(updatePosRecursive)
|
|
}
|
|
} else {
|
|
item.preview_controller.updateTransform(item);
|
|
}
|
|
}
|
|
}
|
|
if (order) {
|
|
var parent = target.parent
|
|
if (!parent || parent === 'root') {
|
|
parent = {children: Outliner.root};
|
|
}
|
|
}
|
|
function place(obj) {
|
|
if (!order) {
|
|
obj.addTo(target)
|
|
} else {
|
|
obj.removeFromParent()
|
|
var position = parent.children.indexOf(target)
|
|
if (order === 1) position++;
|
|
parent.children.splice(position, 0, obj)
|
|
obj.parent = parent.type ? parent : 'root';
|
|
}
|
|
}
|
|
items.forEach(function(item) {
|
|
if (item && item !== target) {
|
|
if (event.altKey || Pressing.overrides.alt) {
|
|
if (item instanceof Group) {
|
|
var dupl = item.duplicate()
|
|
place(dupl)
|
|
dupl.select()
|
|
} else {
|
|
var cube = item.duplicate()
|
|
place(cube)
|
|
selected.safePush(cube)
|
|
}
|
|
} else {
|
|
place(item)
|
|
if (Format.bone_rig) {
|
|
updatePosRecursive(item)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
if (Format.bone_rig) {
|
|
Canvas.updateAllBones()
|
|
}
|
|
if (event.altKey || Pressing.overrides.alt) {
|
|
updateSelection()
|
|
Undo.finishEdit('Duplicate selection', {elements: selected, outliner: true, selection: true})
|
|
} else {
|
|
Undo.finishEdit('Drag elements in outliner')
|
|
}
|
|
}
|
|
|
|
//Misc
|
|
function renameOutliner(element) {
|
|
stopRenameOutliner()
|
|
|
|
if (Group.selected && !element && !Project.EditSession) {
|
|
Group.selected.rename()
|
|
|
|
} else if (selected.length === 1 && !Project.EditSession) {
|
|
selected[0].rename()
|
|
|
|
} else {
|
|
|
|
if (Group.selected && !element) {
|
|
Blockbench.textPrompt('generic.rename', Group.selected.name, function (name) {
|
|
name = name.trim();
|
|
if (name) {
|
|
Undo.initEdit({group: Group.selected})
|
|
Group.selected.name = name
|
|
if (Format.bone_rig) {
|
|
Group.selected.createUniqueName()
|
|
}
|
|
Undo.finishEdit('Rename group')
|
|
}
|
|
})
|
|
} else if (selected.length) {
|
|
Blockbench.textPrompt('generic.rename', selected[0].name, function (name) {
|
|
name = name.trim();
|
|
if (name) {
|
|
Undo.initEdit({elements: selected})
|
|
selected.forEach(function(obj, i) {
|
|
obj.name = name.replace(/%/g, obj.index).replace(/\$/g, i)
|
|
})
|
|
Undo.finishEdit('Rename')
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
function stopRenameOutliner(save) {
|
|
if (Blockbench.hasFlag('renaming')) {
|
|
var uuid = $('.outliner_object input.renaming').parent().parent().attr('id')
|
|
var element = Outliner.root.findRecursive('uuid', uuid)
|
|
if (element) {
|
|
element.saveName(save)
|
|
}
|
|
$('.outliner_object input.renaming').attr('disabled', true).removeClass('renaming')
|
|
$('body').focus()
|
|
if (window.getSelection) {
|
|
window.getSelection().removeAllRanges()
|
|
} else if (document.selection) {
|
|
document.selection.empty()
|
|
}
|
|
Blockbench.removeFlag('renaming')
|
|
}
|
|
}
|
|
function toggleCubeProperty(key) {
|
|
let affected = selected.filter(element => element[key] != undefined);
|
|
if (!affected.length) return;
|
|
var state = affected[0][key];
|
|
if (typeof state === 'number') {
|
|
state = (state+1) % 3;
|
|
} else {
|
|
state = !state
|
|
}
|
|
Undo.initEdit({elements: affected})
|
|
affected.forEach(element => {
|
|
if (element[key] != undefined) {
|
|
element[key] = state;
|
|
}
|
|
})
|
|
if (key === 'visibility') {
|
|
Canvas.updateVisibility()
|
|
}
|
|
if (key === 'shade' && Project.box_uv) {
|
|
Canvas.updateUVs();
|
|
}
|
|
Undo.finishEdit('Toggle ' + key)
|
|
}
|
|
|
|
StateMemory.init('advanced_outliner_toggles', 'boolean')
|
|
|
|
BARS.defineActions(function() {
|
|
new Toggle('outliner_toggle', {
|
|
icon: 'dns',
|
|
category: 'edit',
|
|
keybind: new Keybind({key: 115}),
|
|
default: StateMemory.advanced_outliner_toggles,
|
|
onChange: function (value) {
|
|
Outliner.vue.options.show_advanced_toggles = value;
|
|
StateMemory.advanced_outliner_toggles = value;
|
|
StateMemory.save('advanced_outliner_toggles');
|
|
}
|
|
})
|
|
new BarText('cube_counter', {
|
|
right: true,
|
|
click: function() {
|
|
|
|
var face_count = 0;
|
|
let vertex_count = 0;
|
|
Outliner.elements.forEach(element => {
|
|
if (element instanceof Cube) {
|
|
for (var face in element.faces) {
|
|
if (element.faces[face].texture !== null) face_count++;
|
|
}
|
|
vertex_count += 8;
|
|
} else if (element.faces) {
|
|
face_count += Object.keys(element.faces).length;
|
|
}
|
|
if (element instanceof Mesh) {
|
|
vertex_count += Object.keys(element.vertices).length;
|
|
}
|
|
})
|
|
var dialog = new Dialog({
|
|
id: 'model_stats',
|
|
title: 'dialog.model_stats.title',
|
|
width: 300,
|
|
singleButton: true,
|
|
form: {
|
|
cubes: {type: 'info', label: tl('dialog.model_stats.cubes'), text: stringifyLargeInt(Cube.all.length) },
|
|
meshes: {type: 'info', label: tl('dialog.model_stats.meshes'), text: stringifyLargeInt(Mesh.all.length), condition: Format.meshes },
|
|
locators: {type: 'info', label: tl('dialog.model_stats.locators'), text: stringifyLargeInt(Locator.all.length), condition: Format.locators },
|
|
groups: {type: 'info', label: tl('dialog.model_stats.groups'), text: stringifyLargeInt(Group.all.length) },
|
|
vertices: {type: 'info', label: tl('dialog.model_stats.vertices'), text: stringifyLargeInt(vertex_count) },
|
|
faces: {type: 'info', label: tl('dialog.model_stats.faces'), text: stringifyLargeInt(face_count) },
|
|
}
|
|
})
|
|
dialog.show()
|
|
|
|
},
|
|
onUpdate: function() {
|
|
if (Animator.open) {
|
|
var sel = 0;
|
|
if (Group.selected) {
|
|
Group.selected.forEachChild(_ => sel++, Group, true)
|
|
}
|
|
this.set(stringifyLargeInt(sel)+' / '+stringifyLargeInt(Group.all.length));
|
|
} else {
|
|
this.set(stringifyLargeInt(Outliner.selected.length)+' / '+stringifyLargeInt(Outliner.elements.length));
|
|
}
|
|
}
|
|
})
|
|
|
|
new Action('sort_outliner', {
|
|
icon: 'sort_by_alpha',
|
|
category: 'edit',
|
|
click: function () {
|
|
Undo.initEdit({outliner: true});
|
|
if (Outliner.root.length < 1) return;
|
|
Outliner.root.sort(function(a,b) {
|
|
return sort_collator.compare(a.name, b.name)
|
|
});
|
|
Undo.finishEdit('Sort outliner')
|
|
}
|
|
})
|
|
new Action('unlock_everything', {
|
|
icon: 'fas.fa-key',
|
|
category: 'edit',
|
|
click: function () {
|
|
let locked = Outliner.elements.filter(el => el.locked);
|
|
let locked_groups = Group.all.filter(group => group.locked)
|
|
if (locked.length + locked_groups.length == 0) return;
|
|
|
|
Undo.initEdit({outliner: locked_groups.length > 0, elements: locked});
|
|
[...locked, ...locked_groups].forEach(el => {
|
|
el.locked = false;
|
|
})
|
|
Undo.finishEdit('Unlock everything')
|
|
}
|
|
})
|
|
new Toggle('element_colors', {
|
|
category: 'edit',
|
|
icon: 'palette',
|
|
linked_setting: 'outliner_colors'
|
|
})
|
|
new Action('select_window', {
|
|
icon: 'filter_list',
|
|
category: 'edit',
|
|
keybind: new Keybind({key: 'f', ctrl: true}),
|
|
condition: () => Modes.edit || Modes.paivnt,
|
|
click: function () {
|
|
let color_options = {
|
|
'-1': 'generic.all'
|
|
}
|
|
markerColors.forEach((color, i) => {
|
|
color_options[i] = 'cube.color.' + color.name;
|
|
})
|
|
let dialog = new Dialog({
|
|
id: 'selection_creator',
|
|
title: 'dialog.select.title',
|
|
form_first: true,
|
|
form: {
|
|
new: {label: 'dialog.select.new', type: 'checkbox', value: true},
|
|
group: {label: 'dialog.select.group', type: 'checkbox'},
|
|
name: {label: 'dialog.select.name', type: 'text'},
|
|
texture: {label: 'data.texture', type: 'text', list: Texture.all.map(tex => tex.name)},
|
|
color: {label: 'menu.cube.color', type: 'select', value: '-1', options: color_options}
|
|
},
|
|
lines: [
|
|
`<div class="dialog_bar form_bar">
|
|
<label class="name_space_left">${tl('dialog.select.random')}</label>
|
|
<input type="range" min="0" max="100" step="1" value="100" class="tool half" style="width: 100%;" id="selgen_random">
|
|
</div>`
|
|
],
|
|
onConfirm(formData) {
|
|
if (formData.new) {
|
|
selected.empty();
|
|
}
|
|
let selected_group = Group.selected;
|
|
if (Group.selected) {
|
|
Group.selected.unselect()
|
|
}
|
|
var name_seg = formData.name.toUpperCase()
|
|
var tex_seg = formData.texture.toLowerCase()
|
|
var rdm = $('#selgen_random').val()/100
|
|
|
|
var array = Outliner.elements;
|
|
if ($('#selgen_group').is(':checked') && selected_group) {
|
|
array = selected_group.children
|
|
}
|
|
|
|
array.forEach(function(obj) {
|
|
if (obj.name.toUpperCase().includes(name_seg) === false) return;
|
|
if (obj instanceof Cube && tex_seg && !Format.single_texture) {
|
|
var has_tex = false;
|
|
for (var key in obj.faces) {
|
|
var tex = obj.faces[key].getTexture();
|
|
if (tex && tex.name.includes(tex_seg)) {
|
|
has_tex = true
|
|
}
|
|
}
|
|
if (!has_tex) return;
|
|
}
|
|
if (formData.color != '-1') {
|
|
if (obj instanceof Cube == false || obj.color.toString() != formData.color) return;
|
|
}
|
|
if (Math.random() > rdm) return;
|
|
selected.safePush(obj)
|
|
})
|
|
updateSelection()
|
|
if (selected.length) {
|
|
selected[0].showInOutliner()
|
|
}
|
|
this.hide()
|
|
}
|
|
}).show()
|
|
$('.dialog#selection_creator .form_bar_name > input').focus()
|
|
}
|
|
})
|
|
new Action('invert_selection', {
|
|
icon: 'swap_vert',
|
|
category: 'edit',
|
|
keybind: new Keybind({key: 'i', ctrl: true}),
|
|
condition: () => Modes.edit || Modes.paint,
|
|
click: function () {
|
|
elements.forEach(function(s) {
|
|
if (s.selected) {
|
|
s.unselect()
|
|
} else {
|
|
s.selectLow()
|
|
}
|
|
})
|
|
if (Group.selected) Group.selected.unselect()
|
|
updateSelection()
|
|
Blockbench.dispatchEvent('invert_selection')
|
|
}
|
|
})
|
|
new Action('select_all', {
|
|
icon: 'select_all',
|
|
category: 'edit',
|
|
condition: () => !Modes.display,
|
|
keybind: new Keybind({key: 'a', ctrl: true}),
|
|
click: function () {selectAll()}
|
|
})
|
|
|
|
let enabled = false;
|
|
let were_hidden_before = [];
|
|
new Action('hide_everything_except_selection', {
|
|
icon: 'fa-glasses',
|
|
category: 'view',
|
|
keybind: new Keybind({key: 'i'}),
|
|
condition: {modes: ['edit', 'paint']},
|
|
click() {
|
|
enabled = !enabled;
|
|
|
|
let affected = Project.elements.filter(el => typeof el.visibility == 'boolean' && (!el.selected || were_hidden_before.includes(el.uuid)));
|
|
Undo.initEdit({elements: affected})
|
|
affected.forEach(el => {
|
|
if (enabled) {
|
|
if (el.visibility) were_hidden_before.push(el.uuid);
|
|
el.visibility = !!el.selected;
|
|
} else {
|
|
el.visibility = were_hidden_before.includes(el.uuid);
|
|
}
|
|
})
|
|
if (!enabled) were_hidden_before.empty();
|
|
Canvas.updateVisibility();
|
|
Undo.finishEdit('Toggle visibility on everything except selection');
|
|
}
|
|
})
|
|
Blockbench.on('unselect_project', () => {
|
|
enabled = false;
|
|
were_hidden_before.empty();
|
|
})
|
|
})
|
|
|
|
Interface.definePanels(function() {
|
|
|
|
var VueTreeItem = Vue.extend({
|
|
template:
|
|
'<li class="outliner_node" v-bind:class="{ parent_li: node.children && node.children.length > 0}" v-bind:id="node.uuid">' +
|
|
`<div
|
|
class="outliner_object"
|
|
v-bind:class="{ cube: node.type === 'cube', group: node.type === 'group', selected: node.selected }"
|
|
v-bind:style="{'padding-left': indentation + 'px'}"
|
|
@contextmenu.prevent.stop="node.showContextMenu($event)"
|
|
@click="node.select($event, true)"
|
|
@touchstart="node.select($event)" :title="node.title"
|
|
@dblclick.stop.self="renameOutliner()"
|
|
>` +
|
|
//Opener
|
|
|
|
'<i v-if="node.children && node.children.length > 0 && (!options.hidden_types.length || node.children.some(node => !options.hidden_types.includes(node.type)))" v-on:click.stop="node.isOpen = !node.isOpen" class="icon-open-state fa" :class=\'{"fa-angle-right": !node.isOpen, "fa-angle-down": node.isOpen}\'></i>' +
|
|
'<i v-else class="outliner_opener_placeholder"></i>' +
|
|
//Main
|
|
'<i :class="node.icon + ((settings.outliner_colors.value && node.color >= 0) ? \' ec_\'+node.color : \'\')" v-on:dblclick.stop="if (node.children && node.children.length) {node.isOpen = !node.isOpen;}"></i>' +
|
|
'<input type="text" class="cube_name tab_target" :class="{locked: node.locked}" v-model="node.name" disabled>' +
|
|
|
|
|
|
`<i v-for="btn in node.buttons"
|
|
v-if="(!btn.advanced_option || options.show_advanced_toggles || (btn.id === \'locked\' && node.isIconEnabled(btn)))"
|
|
class="outliner_toggle"
|
|
:class="getBtnClasses(btn, node)"
|
|
:title="btn.title"
|
|
:toggle="btn.id"
|
|
@click.stop
|
|
></i>` +
|
|
'</div>' +
|
|
//Other Entries
|
|
'<ul v-if="node.isOpen">' +
|
|
'<vue-tree-item v-for="item in visible_children" :node="item" :options="options" v-key="item.uuid"></vue-tree-item>' +
|
|
`<div class="outliner_line_guide" v-if="node == Group.selected" v-bind:style="{left: indentation + 'px'}"></div>` +
|
|
'</ul>' +
|
|
'</li>',
|
|
props: {
|
|
options: Object,
|
|
node: {
|
|
type: Object
|
|
}
|
|
},
|
|
computed: {
|
|
indentation() {
|
|
return this.node.getDepth ? (limitNumber(this.node.getDepth(), 0, (this.width-100) / 16) * 16) : 0;
|
|
},
|
|
visible_children() {
|
|
if (!this.options.hidden_types.length) {
|
|
return this.node.children;
|
|
} else {
|
|
return this.node.children.filter(node => !this.options.hidden_types.includes(node.type));
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
nodeClass: function (node) {
|
|
if (node.isOpen) {
|
|
return node.openedIcon || node.icon;
|
|
} else {
|
|
return node.closedIcon || node.icon;
|
|
}
|
|
},
|
|
getBtnClasses: function (btn, node) {
|
|
let value = node.isIconEnabled(btn);
|
|
if (value === true) {
|
|
return [btn.icon];
|
|
} else if (value === false) {
|
|
return [btn.icon_off, 'icon_off'];
|
|
} else {
|
|
return [btn.icon_alt];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
Vue.component('vue-tree-item', VueTreeItem);
|
|
|
|
function eventTargetToNode(target) {
|
|
if (!target) return [];
|
|
let target_node = target;
|
|
let i = 0;
|
|
while (target_node && target_node.classList && !target_node.classList.contains('outliner_node')) {
|
|
if (i < 4 && target_node) {
|
|
target_node = target_node.parentNode;
|
|
i++;
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
return [OutlinerNode.uuids[target_node.id], target_node];
|
|
}
|
|
function getOrder(loc, obj) {
|
|
if (!obj) {
|
|
return;
|
|
} else if (obj instanceof Group) {
|
|
if (loc < 8) return -1;
|
|
if (loc > 24) return 1;
|
|
} else {
|
|
if (loc < 16) return -1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Interface.Panels.outliner = new Panel({
|
|
id: 'outliner',
|
|
icon: 'list_alt',
|
|
condition: {modes: ['edit', 'paint', 'animate']},
|
|
toolbars: {
|
|
head: Toolbars.outliner
|
|
},
|
|
growable: true,
|
|
onResize() {
|
|
if (this.inside_vue) this.inside_vue.width = this.width;
|
|
},
|
|
component: {
|
|
name: 'panel-outliner',
|
|
data() { return {
|
|
root: Outliner.root,
|
|
options: {
|
|
width: 300,
|
|
show_advanced_toggles: StateMemory.advanced_outliner_toggles,
|
|
hidden_types: []
|
|
}
|
|
}},
|
|
methods: {
|
|
openMenu(event) {
|
|
Interface.Panels.outliner.menu.show(event)
|
|
},
|
|
dragToggle(e1) {
|
|
let [original] = eventTargetToNode(e1.target);
|
|
let affected = [];
|
|
let affected_groups = [];
|
|
let key = e1.target.getAttribute('toggle');
|
|
let previous_values = {};
|
|
let value = original[key];
|
|
value = (typeof value == 'number') ? (value+1) % 3 : !value;
|
|
|
|
function move(e2) {
|
|
convertTouchEvent(e2);
|
|
if (e2.target.classList.contains('outliner_toggle') && e2.target.getAttribute('toggle') == key) {
|
|
let [node] = eventTargetToNode(e2.target);
|
|
if (key == 'visibility' && (e2.altKey || Pressing.overrides.alt) && !affected.length) {
|
|
let new_affected = Outliner.elements.filter(node => !node.selected);
|
|
value = !(new_affected[0] && new_affected[0][key]);
|
|
new_affected.forEach(node => {
|
|
affected.push(node);
|
|
previous_values[node.uuid] = node[key];
|
|
node[key] = value;
|
|
})
|
|
// Update
|
|
Canvas.updateVisibility();
|
|
|
|
} else if (!affected.includes(node) && (!node.locked || key == 'locked' || key == 'visibility')) {
|
|
let new_affected = [node];
|
|
if (node instanceof Group) {
|
|
node.forEachChild(node => new_affected.push(node))
|
|
affected_groups.push(node);
|
|
} else if (node.selected && selected.length > 1) {
|
|
selected.forEach(el => {
|
|
if (node[key] != undefined) new_affected.safePush(el);
|
|
})
|
|
}
|
|
new_affected.forEach(node => {
|
|
affected.push(node);
|
|
previous_values[node.uuid] = node[key];
|
|
node[key] = value;
|
|
if (key == 'shade' && node instanceof Cube) Canvas.updateUV(node);
|
|
})
|
|
// Update
|
|
if (key == 'visibility') Canvas.updateVisibility();
|
|
if (key == 'locked') updateSelection();
|
|
}
|
|
}
|
|
}
|
|
function off(e2) {
|
|
if (affected.length) {
|
|
affected.forEach(node => {
|
|
node[key] = previous_values[node.uuid];
|
|
})
|
|
Undo.initEdit({elements: affected.filter(node => node instanceof OutlinerElement), outliner: affected_groups.length > 0})
|
|
affected.forEach(node => {
|
|
node[key] = value;
|
|
if (key == 'shade') node.updateElement();
|
|
})
|
|
Undo.finishEdit(`toggle ${key} property`)
|
|
}
|
|
removeEventListeners(document, 'mousemove touchmove', move);
|
|
removeEventListeners(document, 'mouseup touchend', off);
|
|
}
|
|
addEventListeners(document, 'mousemove touchmove', move, {passive: false});
|
|
addEventListeners(document, 'mouseup touchend', off, {passive: false});
|
|
|
|
move(e1);
|
|
|
|
e1.preventDefault()
|
|
|
|
},
|
|
dragNode(e1) {
|
|
if (getFocusedTextInput()) return;
|
|
convertTouchEvent(e1);
|
|
|
|
if (e1.target.classList.contains('outliner_toggle')) {
|
|
this.dragToggle(e1);
|
|
return false;
|
|
}
|
|
|
|
let [item] = eventTargetToNode(e1.target);
|
|
if (!item || item.locked) {
|
|
function off(e2) {
|
|
removeEventListeners(document, 'mouseup touchend', off);
|
|
if (e2.target && e2.target.id == 'cubes_list') unselectAll();
|
|
}
|
|
addEventListeners(document, 'mouseup touchend', off);
|
|
return;
|
|
};
|
|
|
|
let active = false;
|
|
let helper;
|
|
let timeout;
|
|
let drop_target, drop_target_node, order;
|
|
let last_event = e1;
|
|
|
|
// scrolling
|
|
let list = document.getElementById('cubes_list');
|
|
let list_offset = $(list).offset();
|
|
let scrollInterval = function() {
|
|
if (!active) return;
|
|
if (mouse_pos.y < list_offset.top) {
|
|
list.scrollTop += (mouse_pos.y - list_offset.top) / 7 - 3;
|
|
} else if (mouse_pos.y > list_offset.top + list.clientHeight) {
|
|
list.scrollTop += (mouse_pos.y - (list_offset.top + list.clientHeight)) / 6 + 3;
|
|
}
|
|
}
|
|
let scrollIntervalID;
|
|
|
|
function move(e2) {
|
|
convertTouchEvent(e2);
|
|
let offset = [
|
|
e2.clientX - e1.clientX,
|
|
e2.clientY - e1.clientY,
|
|
]
|
|
if (!active) {
|
|
let distance = Math.sqrt(Math.pow(offset[0], 2) + Math.pow(offset[1], 2))
|
|
if (Blockbench.isTouch) {
|
|
if (distance > 20 && timeout) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
} else {
|
|
document.getElementById('cubes_list').scrollTop += last_event.clientY - e2.clientY;
|
|
}
|
|
} else if (distance > 6) {
|
|
active = true;
|
|
}
|
|
} else {
|
|
if (e2) e2.preventDefault();
|
|
|
|
if (open_menu) open_menu.hide();
|
|
|
|
if (!helper) {
|
|
helper = document.createElement('div');
|
|
helper.id = 'outliner_drag_helper';
|
|
let icon = document.createElement('i'); icon.className = item.icon; helper.append(icon);
|
|
let span = document.createElement('span'); span.innerText = item.name; helper.append(span);
|
|
|
|
if (item instanceof Group == false && Outliner.selected.length > 1) {
|
|
let counter = document.createElement('div');
|
|
counter.classList.add('outliner_drag_number');
|
|
counter.textContent = Outliner.selected.length.toString();
|
|
helper.append(counter);
|
|
}
|
|
document.body.append(helper);
|
|
|
|
scrollIntervalID = setInterval(scrollInterval, 1000/60)
|
|
}
|
|
helper.style.left = `${e2.clientX}px`;
|
|
helper.style.top = `${e2.clientY}px`;
|
|
|
|
// drag
|
|
$('.drag_hover').removeClass('drag_hover');
|
|
$('.outliner_node[order]').attr('order', null);
|
|
|
|
let target = document.elementFromPoint(e2.clientX, e2.clientY);
|
|
[drop_target, drop_target_node] = eventTargetToNode(target);
|
|
if (drop_target) {
|
|
var location = e2.clientY - $(drop_target_node).offset().top;
|
|
order = getOrder(location, drop_target)
|
|
drop_target_node.setAttribute('order', order)
|
|
drop_target_node.classList.add('drag_hover');
|
|
|
|
} else if ($('#cubes_list').is(':hover')) {
|
|
$('#cubes_list').addClass('drag_hover');
|
|
}
|
|
}
|
|
last_event = e2;
|
|
}
|
|
function off(e2) {
|
|
if (helper) helper.remove();
|
|
clearInterval(scrollIntervalID);
|
|
removeEventListeners(document, 'mousemove touchmove', move);
|
|
removeEventListeners(document, 'mouseup touchend', off);
|
|
$('.drag_hover').removeClass('drag_hover');
|
|
$('.outliner_node[order]').attr('order', null);
|
|
if (Blockbench.isTouch) clearTimeout(timeout);
|
|
|
|
if (active && !open_menu) {
|
|
convertTouchEvent(e2);
|
|
let target = document.elementFromPoint(e2.clientX, e2.clientY);
|
|
[drop_target] = eventTargetToNode(target);
|
|
if (drop_target) {
|
|
dropOutlinerObjects(item, drop_target, e2, order);
|
|
} else if ($('#cubes_list').is(':hover')) {
|
|
dropOutlinerObjects(item, undefined, e2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Blockbench.isTouch) {
|
|
timeout = setTimeout(() => {
|
|
active = true;
|
|
move(e1);
|
|
}, 320)
|
|
}
|
|
|
|
addEventListeners(document, 'mousemove touchmove', move, {passive: false});
|
|
addEventListeners(document, 'mouseup touchend', off, {passive: false});
|
|
}
|
|
},
|
|
template: `
|
|
<div>
|
|
<div class="toolbar_wrapper outliner"></div>
|
|
<ul id="cubes_list"
|
|
class="list mobile_scrollbar"
|
|
@contextmenu.stop.prevent="openMenu($event)"
|
|
@mousedown="dragNode($event)"
|
|
@touchstart="dragNode($event)"
|
|
>
|
|
<vue-tree-item v-for="item in root" :node="item" :options="options" v-key="item.uuid"></vue-tree-item>
|
|
</ul>
|
|
</div>
|
|
`
|
|
},
|
|
menu: new Menu([
|
|
'add_cube',
|
|
'add_mesh',
|
|
'add_texture_mesh',
|
|
'add_group',
|
|
'_',
|
|
'sort_outliner',
|
|
'select_all',
|
|
'collapse_groups',
|
|
'unfold_groups',
|
|
'element_colors',
|
|
'outliner_toggle'
|
|
])
|
|
})
|
|
Outliner.vue = Interface.Panels.outliner.inside_vue;
|
|
})
|
|
|
|
class Face {
|
|
constructor(data) {
|
|
for (var key in this.constructor.properties) {
|
|
this.constructor.properties[key].reset(this);
|
|
}
|
|
}
|
|
extend(data) {
|
|
for (var key in this.constructor.properties) {
|
|
this.constructor.properties[key].merge(this, data)
|
|
}
|
|
if (data.texture === null) {
|
|
this.texture = null;
|
|
} else if (data.texture === false) {
|
|
this.texture = false;
|
|
} else if (Texture.all.includes(data.texture)) {
|
|
this.texture = data.texture.uuid;
|
|
} else if (typeof data.texture === 'string') {
|
|
Merge.string(this, data, 'texture')
|
|
}
|
|
return this;
|
|
}
|
|
getTexture() {
|
|
if (Format.single_texture && this.texture !== null) {
|
|
return Texture.getDefault();
|
|
}
|
|
if (typeof this.texture === 'string') {
|
|
return Texture.all.findInArray('uuid', this.texture)
|
|
} else {
|
|
return this.texture;
|
|
}
|
|
}
|
|
reset() {
|
|
for (var key in Mesh.properties) {
|
|
Mesh.properties[key].reset(this);
|
|
}
|
|
this.texture = false;
|
|
return this;
|
|
}
|
|
getSaveCopy() {
|
|
var copy = new oneLiner({
|
|
uv: this.uv,
|
|
})
|
|
for (var key in this.constructor.properties) {
|
|
if (this[key] != this.constructor.properties[key].default) this.constructor.properties[key].copy(this, copy);
|
|
}
|
|
var tex = this.getTexture()
|
|
if (tex === null) {
|
|
copy.texture = null;
|
|
} else if (tex instanceof Texture) {
|
|
copy.texture = Texture.all.indexOf(tex)
|
|
}
|
|
return copy;
|
|
}
|
|
getUndoCopy() {
|
|
var copy = new CubeFace(this.direction, this);
|
|
delete copy.cube;
|
|
delete copy.mesh;
|
|
delete copy.direction;
|
|
return copy;
|
|
}
|
|
}
|