diff --git a/css/general.css b/css/general.css index cd0fca93..e5396540 100644 --- a/css/general.css +++ b/css/general.css @@ -47,8 +47,10 @@ font-weight: normal; display: inline-block; } - .code { + code, .code { font-family: var(--font-code); + } + .code { font-size: 16px; } .small_text { diff --git a/css/panels.css b/css/panels.css index d459209e..33d4c687 100644 --- a/css/panels.css +++ b/css/panels.css @@ -471,8 +471,7 @@ bottom: 4px; width: 4px; margin-left: 10px; - border-left: 2px solid var(--color-text); - opacity: 0.2; + border-left: 2px solid var(--color-guidelines); pointer-events: none; } .drag_hover[order]::before { @@ -639,6 +638,21 @@ z-index: 100; border: 2px solid var(--color-accent); box-shadow: 0 0 16px black; + height: 48px; + width: 48px; + position: absolute; + pointer-events: none; + } + .texture_group_drag_helper { + position: absolute; + z-index: 100; + min-height: 24px; + min-width: 120px; + padding: 4px; + border: 2px solid var(--color-accent); + background-color: var(--color-ui); + box-shadow: 0 0 16px black; + pointer-events: none; } .icon_placeholder { width: 48px; @@ -710,6 +724,63 @@ border-top-right-radius: 4px; border-bottom-right-radius: 4px; } + + .texture_group { + padding-bottom: 4px; + } + .texture_group_head { + height: 32px; + padding: 4px; + padding-right: 8px; + display: flex; + gap: 5px; + color: var(--color-subtle_text); + } + .texture_group_head:hover { + color: var(--color-text); + } + .texture_group_head > .icon-open-state { + text-align: center; + width: 21px; + margin-top: 4px; + flex-shrink: 0; + } + .texture_group_head > label { + flex-shrink: 1; + overflow: hidden; + white-space: nowrap; + } + .texture_group_head.folded > label { + max-width: calc(60% - 50px); + min-width: 30px; + } + .texture_group_head > .in_list_button { + margin-left: auto; + } + .texture_group_mini_icon_list { + display: flex; + gap: 2px; + margin-right: auto; + margin-left: 4px; + max-width: 36%; + overflow: hidden; + } + .texture_group_mini_icon_list > .texture_mini_icon { + width: 24px; + height: 24px; + border-radius: 50%; + flex-shrink: 0; + overflow: hidden; + background-color: var(--color-ui); + flex-shrink: 0; + margin-right: -8px; + border: 1px solid var(--color-border); + } + .texture_group_list { + margin-left: 14px; + padding-left: 6px; + border-left: 2px solid var(--color-guidelines); + } #texture_animation_playback { display: flex; diff --git a/css/setup.css b/css/setup.css index 2d65847a..e8c44776 100644 --- a/css/setup.css +++ b/css/setup.css @@ -343,6 +343,7 @@ --color-checkerboard: #1c2026; --color-menu_separator: #b0afba; + --color-guidelines: rgba(136, 150, 157, 0.35); --color-close: #d62e3f; --color-confirm: #90ee90; diff --git a/index.html b/index.html index bb688fde..6905bd31 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + diff --git a/js/interface/interface.js b/js/interface/interface.js index d01cdbea..5fe8aedc 100644 --- a/js/interface/interface.js +++ b/js/interface/interface.js @@ -538,8 +538,6 @@ function setupInterface() { reference.select(); }); - document.getElementById('texture_list').addEventListener('click', e => unselectTextures()); - $(Panels.timeline.node).mousedown((event) => { setActivePanel('timeline'); }) diff --git a/js/io/formats/bbmodel.js b/js/io/formats/bbmodel.js index a3e03e68..45e558b7 100644 --- a/js/io/formats/bbmodel.js +++ b/js/io/formats/bbmodel.js @@ -189,6 +189,11 @@ var codec = new Codec('project', { if (options.absolute_paths == false) delete t.path; model.textures.push(t); }) + for (let texture_group of TextureGroup.all) { + if (!model.texture_groups) model.texture_groups = []; + let copy = texture_group.getSaveCopy(); + model.texture_groups.push(copy); + } if (Animation.all.length) { model.animations = []; @@ -328,6 +333,11 @@ var codec = new Codec('project', { Project.texture_height = model.resolution.height; } + if (model.texture_groups) { + model.texture_groups.forEach(tex_group => { + new TextureGroup(tex_group, tex_group.uuid).add(false); + }) + } if (model.textures) { model.textures.forEach(tex => { var tex_copy = new Texture(tex, tex.uuid).add(false); @@ -533,6 +543,11 @@ var codec = new Codec('project', { } } + if (model.texture_groups) { + model.texture_groups.forEach(tex_group => { + new TextureGroup(tex_group, tex_group.uuid).add(false); + }) + } if (model.textures && (!Format.single_texture || Texture.all.length == 0)) { new_textures.replace(model.textures.map(loadTexture)) } diff --git a/js/io/formats/bedrock.js b/js/io/formats/bedrock.js index e84a7463..7659b0ef 100644 --- a/js/io/formats/bedrock.js +++ b/js/io/formats/bedrock.js @@ -730,7 +730,6 @@ function calculateVisibleBox() { codec.dispatchEvent('parsed', {model: data.object}); - loadTextureDraggable() Canvas.updateAllBones() setProjectTitle() if (isApp && Project.geometry_name) { diff --git a/js/io/formats/bedrock_old.js b/js/io/formats/bedrock_old.js index 819a4dbe..5118a33c 100644 --- a/js/io/formats/bedrock_old.js +++ b/js/io/formats/bedrock_old.js @@ -114,7 +114,6 @@ function parseGeometry(data) { codec.dispatchEvent('parsed', {model: data.object}); - loadTextureDraggable() Canvas.updateAllBones() setProjectTitle() if (isApp && Project.geometry_name && Project.BedrockEntityManager) { diff --git a/js/io/formats/skin.js b/js/io/formats/skin.js index fad4fc83..5a931956 100644 --- a/js/io/formats/skin.js +++ b/js/io/formats/skin.js @@ -149,7 +149,6 @@ const codec = new Codec('skin_model', { if (data.camera_angle) { main_preview.loadAnglePreset(DefaultCameraPresets.find(p => p.id == data.camera_angle)) } - loadTextureDraggable() Canvas.updateAllBones() Canvas.updateVisibility() setProjectTitle() diff --git a/js/io/project.js b/js/io/project.js index bbbd834b..c75ec9b1 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -59,6 +59,7 @@ class ModelProject { this.mesh_selection = {}; this.textures = []; this.selected_texture = null; + this.texture_groups = []; this.outliner = []; this.animations = []; this.animation_controllers = []; @@ -207,6 +208,7 @@ class ModelProject { BarItems.edit_mode_uv_overlay.updateEnabledState(); Panels.textures.inside_vue.textures = Texture.all; + Panels.textures.inside_vue.texture_groups = TextureGroup.all; Panels.layers.inside_vue.layers = Texture.selected ? Texture.selected.layers : []; scene.add(this.model_3d); @@ -279,8 +281,6 @@ class ModelProject { updateProjectResolution(); Validator.validate(); Vue.nextTick(() => { - loadTextureDraggable(); - if (this.on_next_upen instanceof Array) { this.on_next_upen.forEach(callback => callback()); delete this.on_next_upen; @@ -607,6 +607,7 @@ function selectNoProject() { UVEditor.vue.all_elements = []; Interface.Panels.textures.inside_vue.textures = []; + Interface.Panels.textures.inside_vue.texture_groups = []; Panels.animations.inside_vue.animations = []; Panels.animations.inside_vue.animation_controllers = []; diff --git a/js/misc.js b/js/misc.js index 902d2b29..96523b92 100644 --- a/js/misc.js +++ b/js/misc.js @@ -395,10 +395,6 @@ const TickUpdates = { delete TickUpdates.UVEditor; UVEditor.loadData() } - if (TickUpdates.texture_list) { - delete TickUpdates.texture_list; - loadTextureDraggable(); - } if (TickUpdates.keyframe_selection) { delete TickUpdates.keyframe_selection; Vue.nextTick(updateKeyframeSelection) diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index e6c0efca..ab0a758a 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1433,7 +1433,8 @@ Interface.definePanels(function() { depth: Number }, data() {return { - outliner_colors: settings.outliner_colors + outliner_colors: settings.outliner_colors, + markerColors }}, computed: { indentation() { diff --git a/js/texturing/texture_groups.js b/js/texturing/texture_groups.js new file mode 100644 index 00000000..68cf0b31 --- /dev/null +++ b/js/texturing/texture_groups.js @@ -0,0 +1,135 @@ + +class TextureGroup { + constructor(data, uuid) { + this.uuid = uuid ?? guid(); + this.folded = false; + if (data) this.extend(data); + } + extend(data) { + for (let key in TextureGroup.properties) { + TextureGroup.properties[key].merge(this, data) + } + return this; + } + add() { + TextureGroup.all.push(this); + return this; + } + select() { + let textures = this.getTextures(); + if (textures[0]) textures[0].select(); + for (let texture of textures) { + if (!texture.selected) texture.multi_selected = true; + } + return this; + } + remove() { + TextureGroup.all.remove(this); + } + showContextMenu(event) { + Prop.active_panel = 'textures'; + TextureGroup.active_menu_group = this; + this.menu.open(event, this); + } + rename() { + Blockbench.textPrompt('generic.rename', this.name, (name) => { + if (name && name !== this.name) { + Undo.initEdit({texture_groups: [this]}); + this.name = name; + Undo.finishEdit('Rename texture group'); + } + }) + return this; + } + getTextures() { + return Texture.all.filter(texture => texture.group == this.uuid); + } + getUndoCopy() { + let copy = { + uuid: this.uuid, + index: TextureGroup.all.indexOf(this) + }; + for (let key in TextureGroup.properties) { + TextureGroup.properties[key].copy(this, copy) + } + return copy; + } + getSaveCopy() { + let copy = { + uuid: this.uuid + }; + for (let key in TextureGroup.properties) { + TextureGroup.properties[key].copy(this, copy) + } + return copy; + } +} +Object.defineProperty(TextureGroup, 'all', { + get() { + return Project.texture_groups || []; + }, + set(arr) { + Project.texture_groups.replace(arr); + } +}) +new Property(TextureGroup, 'string', 'name', {default: tl('data.texture_group')}); + +TextureGroup.prototype.menu = new Menu('texture_group', [ + new MenuSeparator('manage'), + 'rename', + { + icon: 'fa-leaf', + name: 'menu.texture_group.resolve', + click(texture_group) { + let textures = texture_group.getTextures(); + Undo.initEdit({textures, texture_groups: [texture_group]}); + texture_group.remove(); + textures.forEach(texture => { + texture.group = ''; + }) + Undo.finishEdit('Resolve texture group', {textures, texture_groups: []}); + } + }, +], { + onClose() { + setTimeout(() => { + TextureGroup.active_menu_group = null; + }, 10); + } +}) +/** +ToDo: +- Auto-generate groups +- Grid view? +- Search + */ + +SharedActions.add('rename', { + condition: () => Prop.active_panel == 'textures' && TextureGroup.active_menu_group, + run() { + TextureGroup.active_menu_group.rename(); + } +}) + + +BARS.defineActions(function() { + new Action('create_texture_group', { + icon: 'perm_media', + category: 'textures', + click() { + let texture_group = new TextureGroup(); + texture_group.name = 'Texture Group ' + (TextureGroup.all.length+1); + let textures_to_add = Texture.all.filter(tex => tex.selected || tex.multi_selected); + Undo.initEdit({texture_groups: [], textures: textures_to_add}); + if (textures_to_add.length) { + for (let texture of textures_to_add) { + texture.group = texture_group.uuid; + } + let first = Texture.selected || textures_to_add[0]; + texture_group.name = first.name.replace(/\.\w+$/, '') + ' Group'; + } + texture_group.add(false); + Undo.finishEdit('Add texture group', {texture_groups: [texture_group], textures: textures_to_add}); + } + }) +}); \ No newline at end of file diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 0e806a9f..69e5418b 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -2,9 +2,9 @@ //Textures class Texture { constructor(data, uuid) { - var scope = this; + let scope = this; //Info - for (var key in Texture.properties) { + for (let key in Texture.properties) { Texture.properties[key].reset(this); } //meta @@ -342,6 +342,15 @@ class Texture { case 3: return tl('texture.error.parent'); break; } } + getGroup() { + if (!this.group) return; + let group = TextureGroup.all.find(group => group.uuid == this.group); + if (group) { + return group; + } else { + this.group = ''; + } + } getUndoCopy(bitmap) { var copy = {}; for (var key in Texture.properties) { @@ -900,6 +909,10 @@ class Texture { if (this.layers_enabled && !this.selected_layer && this.layers[0]) { this.layers[0].select(); } + if (this.group) { + let group = this.getGroup(); + if (group) group.folded = false; + } this.scrollTo(); if (this.render_mode == 'layered') { Canvas.updatePixelGrid() @@ -935,7 +948,6 @@ class Texture { Project.textures.push(this) } Blockbench.dispatchEvent( 'add_texture', {texture: this}) - loadTextureDraggable() if ((Format.single_texture || Format.single_texture_default) && Cube.all.length) { Canvas.updateAllFaces() @@ -1400,7 +1412,7 @@ class Texture { return this; } scrollTo() { - var el = $(`#texture_list > li[texid=${this.uuid}]`) + var el = $(`#texture_list li.texture[texid=${this.uuid}]`) if (el.length === 0 || Texture.all.length < 2) return; var outliner_pos = $('#texture_list').offset().top @@ -2052,6 +2064,7 @@ class Texture { new Property(Texture, 'string', 'folder') new Property(Texture, 'string', 'namespace') new Property(Texture, 'string', 'id') + new Property(Texture, 'string', 'group') new Property(Texture, 'number', 'width') new Property(Texture, 'number', 'height') new Property(Texture, 'number', 'uv_width') @@ -2094,142 +2107,7 @@ function saveTextures(lazy = false) { }) } function loadTextureDraggable() { - Vue.nextTick(function() { - setTimeout(function() { - $('li.texture:not(.ui-draggable)').draggable({ - revertDuration: 0, - cursorAt: { left: 2, top: -5 }, - revert: 'invalid', - appendTo: 'body', - zIndex: 19, - distance: 12, - delay: 120, - helper: function(e) { - var t = $(e.target) - if (!t.hasClass('texture')) t = t.parent() - if (!t.hasClass('texture')) t = t.parent() - return t.find('.texture_icon_wrapper').clone().addClass('texture_drag_helper').attr('texid', t.attr('texid')) - }, - drag: function(event, ui) { - - $('.outliner_node[order]').attr('order', null); - $('.drag_hover').removeClass('drag_hover'); - $('.texture[order]').attr('order', null) - if ($('#cubes_list li.outliner_node:hover').length) { - var tar = $('#cubes_list li.outliner_node:hover').last() - tar.addClass('drag_hover').attr('order', '0'); - /* - var element = Outliner.root.findRecursive('uuid', tar.attr('id')) - if (element) { - tar.attr('order', '0') - }*/ - } else if ($('#texture_list li:hover').length) { - let node = $('#texture_list > .texture:hover') - if (node.length) { - var target_tex = Texture.all.findInArray('uuid', node.attr('texid')); - index = Texture.all.indexOf(target_tex); - let offset = event.clientY - node[0].offsetTop; - if (offset > 24) { - node.attr('order', '1') - } else { - node.attr('order', '-1') - } - } - } - }, - stop: function(event, ui) { - setTimeout(function() { - $('.texture[order]').attr('order', null); - $('.outliner_node[order]').attr('order', null); - var tex = Texture.all.findInArray('uuid', ui.helper.attr('texid')); - if (!tex) return; - if ($('.preview:hover').length > 0) { - var data = Canvas.raycast(event) - if (data.element && data.face) { - var elements = data.element.selected ? UVEditor.getMappableElements() : [data.element]; - - if (Format.per_group_texture) { - elements = []; - let groups = Group.selected ? [Group.selected] : []; - Outliner.selected.forEach(el => { - if (el.faces && el.parent instanceof Group) groups.safePush(el.parent); - }); - Undo.initEdit({outliner: true}); - groups.forEach(group => { - group.texture = ''; - group.forEachChild(child => { - if (child.preview_controller?.updateFaces) child.preview_controller.updateFaces(child); - }) - }) - } else { - Undo.initEdit({elements}) - elements.forEach(element => { - element.applyTexture(tex, event.shiftKey || Pressing.overrides.shift || [data.face]) - }) - } - Undo.finishEdit('Apply texture') - } - } else if ($('#texture_list:hover').length > 0) { - let index = Texture.all.length-1 - let node = $('#texture_list > .texture:hover') - if (node.length) { - var target_tex = Texture.all.findInArray('uuid', node.attr('texid')); - index = Texture.all.indexOf(target_tex); - let own_index = Texture.all.indexOf(tex) - if (own_index == index) return; - if (own_index < index) index--; - if (event.clientY - node[0].offsetTop > 24) index++; - } - Undo.initEdit({texture_order: true}) - Texture.all.remove(tex) - Texture.all.splice(index, 0, tex) - Canvas.updateLayeredTextures() - Undo.finishEdit('Reorder textures') - } else if ($('#cubes_list:hover').length) { - - let target_node = $('#cubes_list li.outliner_node.drag_hover').last().get(0); - $('.drag_hover').removeClass('drag_hover'); - if (!target_node) return; - let uuid = target_node.id; - let target = OutlinerNode.uuids[uuid]; - - let array = []; - if (target.type === 'group') { - target.forEachChild((element) => { - array.push(element); - }) - } else { - array = selected.includes(target) ? selected.slice() : [target]; - } - array = array.filter(element => element.applyTexture); - - if (Format.per_group_texture) { - let group = target.type === 'group' ? target : null; - if (!group) group = target.parent; - - array = []; - Undo.initEdit({group}); - group.texture = tex.uuid; - group.forEachChild(child => { - if (child.preview_controller?.updateFaces) child.preview_controller.updateFaces(child); - }) - } else { - Undo.initEdit({elements: array, uv_only: true}) - array.forEach(element => { - element.applyTexture(tex, true); - }); - } - Undo.finishEdit('Apply texture'); - UVEditor.loadData(); - - } else if ($('#uv_viewport:hover').length) { - UVEditor.applyTexture(tex); - } - }, 10) - } - }) - }, 42) - }) + console.warn('loadTextureDraggable no longer exists'); } function unselectTextures() { Texture.all.forEach(function(s) { @@ -2330,15 +2208,15 @@ BARS.defineActions(function() { icon: 'library_add', category: 'textures', keybind: new Keybind({key: 't', ctrl: true}), - click() { - var start_path; + click(event, context) { + let start_path; if (!isApp) {} else if (Texture.all.length > 0) { - var arr = Texture.all[0].path.split(osfs) + let arr = Texture.all[0].path.split(osfs) arr.splice(-1) start_path = arr.join(osfs) } else if (Project.export_path) { - var arr = Project.export_path.split(osfs) + let arr = Project.export_path.split(osfs) arr.splice(-3) arr.push('textures') start_path = arr.join(osfs) @@ -2351,11 +2229,15 @@ BARS.defineActions(function() { multiple: true, startpath: start_path }, function(results) { - var new_textures = [] - Undo.initEdit({textures: new_textures}) + let new_textures = []; + let texture_group = context instanceof TextureGroup ? context : Texture.selected?.getGroup(); + Undo.initEdit({textures: new_textures}); results.forEach(function(f) { - var t = new Texture({name: f.name}).fromFile(f).add(false).fillParticle() - new_textures.push(t) + let t = new Texture({name: f.name}).fromFile(f).add(false).fillParticle(); + new_textures.push(t); + if (texture_group) { + t.group = texture_group.uuid; + } }) Undo.finishEdit('Add texture') }) @@ -2421,6 +2303,298 @@ BARS.defineActions(function() { Interface.definePanels(function() { + let texture_component = Vue.extend({ + props: { + texture: Texture + }, + methods: { + getDescription(texture) { + if (texture.error) { + return texture.getErrorMessage() + } else { + let message = texture.width + ' x ' + texture.height + 'px'; + if (!Format.image_editor) { + let uv_size = texture.width / texture.getUVWidth() * 16; + message += ` (${trimFloatNumber(uv_size, 2)}x)`; + } + if (texture.frameCount > 1) { + message += ` - ${texture.currentFrame+1}/${texture.frameCount}` + } + return message; + } + }, + getTextureIconOffset(texture) { + if (!texture.currentFrame) return; + let val = texture.currentFrame * -48 * (texture.display_height / texture.width); + return `${val}px`; + }, + dragTexture(e1) { + if (e1.button == 1) return; + if (getFocusedTextInput()) return; + convertTouchEvent(e1); + + let texture = this.texture; + let active = false; + let helper; + let timeout; + let last_event = e1; + let vue_scope = this; + + // scrolling + let list = document.getElementById('texture_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('texture_list').scrollTop += last_event.clientY - e2.clientY; + } + } else if (distance > 6) { + active = true; + } + } + if (!active) return; + + if (e2) e2.preventDefault(); + + if (open_menu) open_menu.hide(); + + if (!helper) { + helper = vue_scope.$el.cloneNode(); + helper.classList.add('texture_drag_helper'); + helper.setAttribute('texid', texture.uuid); + + document.body.append(helper); + + scrollIntervalID = setInterval(scrollInterval, 1000/60) + + Blockbench.addFlag('dragging_textures'); + } + helper.style.left = `${e2.clientX}px`; + helper.style.top = `${e2.clientY}px`; + + // drag + $('.outliner_node[order]').attr('order', null); + $('.drag_hover').removeClass('drag_hover'); + $('.texture[order]').attr('order', null) + + let target = $('#cubes_list li.outliner_node:hover').last(); + if (target.length) { + tar.addClass('drag_hover').attr('order', '0'); + return; + } + target = document.querySelector('#texture_list li.texture:hover'); + if (target) { + let offset = e2.clientY - $(target).offset().top; + target.setAttribute('order', offset > 24 ? '1' : '-1'); + return; + } + target = document.querySelector('#texture_list .texture_group_head:hover'); + if (target) { + target.classList.add('drag_hover'); + target.setAttribute('order', '0'); + return; + } + if (document.querySelector('#texture_list:hover')) { + let nodes = document.querySelectorAll('#texture_list > li'); + if (nodes.length) { + let target = nodes[nodes.length-1]; + target.setAttribute('order', '1'); + target.classList.add('drag_hover'); + } + } + last_event = e2; + } + async function off(e2) { + if (helper) helper.remove(); + clearInterval(scrollIntervalID); + removeEventListeners(document, 'mousemove touchmove', move); + removeEventListeners(document, 'mouseup touchend', off); + e2.stopPropagation(); + + $('.outliner_node[order]').attr('order', null); + $('.drag_hover').removeClass('drag_hover'); + $('.texture[order]').attr('order', null) + if (Blockbench.isTouch) clearTimeout(timeout); + + if (!active || Menu.open) return; + + await new Promise(r => setTimeout(r, 10)); + + Blockbench.removeFlag('dragging_textures'); + + if ($('.preview:hover').length > 0) { + var data = Canvas.raycast(e2) + if (data.element && data.face) { + var elements = data.element.selected ? UVEditor.getMappableElements() : [data.element]; + + if (Format.per_group_texture) { + elements = []; + let groups = Group.selected ? [Group.selected] : []; + Outliner.selected.forEach(el => { + if (el.faces && el.parent instanceof Group) groups.safePush(el.parent); + }); + Undo.initEdit({outliner: true}); + groups.forEach(group => { + group.texture = texture.uuid; + group.forEachChild(child => { + if (child.preview_controller?.updateFaces) child.preview_controller.updateFaces(child); + }) + }) + } else { + Undo.initEdit({elements}); + elements.forEach(element => { + element.applyTexture(texture, e2.shiftKey || Pressing.overrides.shift || [data.face]) + }) + } + Undo.finishEdit('Apply texture') + } + } else if ($('#texture_list:hover').length > 0) { + let index = Texture.all.length-1; + let texture_node = document.querySelector('#texture_list li.texture:hover'); + let target_group_head = document.querySelector('#texture_list .texture_group_head:hover'); + let new_group = ''; + if (target_group_head) { + new_group = target_group_head.parentNode.id; + + } else if (texture_node) { + let target_tex = Texture.all.findInArray('uuid', texture_node.getAttribute('texid')); + index = Texture.all.indexOf(target_tex); + let own_index = Texture.all.indexOf(texture) + if (own_index == index) return; + let offset = e2.clientY - $(texture_node).offset().top; + if (own_index < index) index--; + if (offset > 24) index++; + new_group = target_tex.group; + } + Undo.initEdit({texture_order: true, textures: texture.group != new_group ? [texture] : null}); + Texture.all.remove(texture); + Texture.all.splice(index, 0, texture); + texture.group = new_group; + Canvas.updateLayeredTextures(); + Undo.finishEdit('Rearrange textures'); + + } else if ($('#cubes_list:hover').length) { + + let target_node = $('#cubes_list li.outliner_node.drag_hover').last().get(0); + $('.drag_hover').removeClass('drag_hover'); + if (!target_node) return; + let uuid = target_node.id; + let target = OutlinerNode.uuids[uuid]; + + let array = []; + if (target.type === 'group') { + target.forEachChild((element) => { + array.push(element); + }) + } else { + array = selected.includes(target) ? selected.slice() : [target]; + } + array = array.filter(element => element.applyTexture); + + if (Format.per_group_texture) { + let group = target.type === 'group' ? target : null; + if (!group) group = target.parent; + + array = []; + Undo.initEdit({group}); + group.texture = texture.uuid; + group.forEachChild(child => { + if (child.preview_controller?.updateFaces) child.preview_controller.updateFaces(child); + }) + } else { + Undo.initEdit({elements: array, uv_only: true}) + array.forEach(element => { + element.applyTexture(texture, true); + }); + } + Undo.finishEdit('Apply texture'); + UVEditor.loadData(); + + } else if ($('#uv_viewport:hover').length) { + UVEditor.applyTexture(texture); + } + + + /*convertTouchEvent(e2); + let target = document.elementFromPoint(e2.clientX, e2.clientY); + [drop_target] = eventTargetToNode(target); + if (drop_target) { + moveOutlinerSelectionTo(item, drop_target, e2, order); + } else if ($('#texture_list').is(':hover')) { + moveOutlinerSelectionTo(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: ` +
  • +
    + + error_outline + +
    +
    +
    {{ texture.name }}
    +
    {{ getDescription(texture) }}
    +
    + check + +
  • + ` + }) + new Panel('textures', { icon: 'fas.fa-images', growable: true, @@ -2437,6 +2611,7 @@ Interface.definePanels(function() { children: [ 'import_texture', 'create_texture', + 'create_texture_group', 'append_to_template', ] }) @@ -2449,26 +2624,16 @@ Interface.definePanels(function() { name: 'panel-textures', data() { return { textures: Texture.all, + texture_groups: TextureGroup.all, currentFrame: 0, }}, + components: {'Texture': texture_component}, methods: { openMenu(event) { Interface.Panels.textures.menu.show(event) }, - getDescription(texture) { - if (texture.error) { - return texture.getErrorMessage() - } else { - let message = texture.width + ' x ' + texture.height + 'px'; - if (!Format.image_editor) { - let uv_size = texture.width / texture.getUVWidth() * 16; - message += ` (${trimFloatNumber(uv_size, 2)}x)`; - } - if (texture.frameCount > 1) { - message += ` - ${texture.currentFrame+1}/${texture.frameCount}` - } - return message; - } + addTextureToGroup(texture_group) { + BarItems.import_texture.click(0, texture_group); }, slideTimelinePointer(e1) { let scope = this; @@ -2510,52 +2675,182 @@ Interface.definePanels(function() { }); return count; }, - getTextureIconOffset(texture) { - if (!texture.currentFrame) return; - let val = texture.currentFrame * -48 * (texture.display_height / texture.width); - return `${val}px`; + unselect(event) { + if (Blockbench.hasFlag('dragging_textures')) return; + unselectTextures(); + }, + getUngroupedTextures() { + return this.textures.filter(tex => !(tex.group && TextureGroup.all.find(g => g.uuid == tex.group))); + }, + dragTextureGroup(texture_group, e1) { + if (e1.button == 1) return; + convertTouchEvent(e1); + + let active = false; + let helper; + let timeout; + let last_event = e1; + let texture_group_target_node; + let order = 0; + + // scrolling + let list = document.getElementById('texture_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('texture_list').scrollTop += last_event.clientY - e2.clientY; + } + } else if (distance > 6) { + active = true; + } + } + if (!active) return; + + if (e2) e2.preventDefault(); + + if (open_menu) open_menu.hide(); + + if (!helper) { + helper = Interface.createElement('div', {class: 'texture_group_drag_helper'}, texture_group.name); + 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'); + $('.texture_group[order]').attr('order', null); + + let target = document.querySelector('#texture_list .texture_group:hover'); + if (target) { + target.classList.add('drag_hover'); + let offset = e2.clientY - $(target).offset().top; + order = offset > (target.clientHeight/2) ? 1 : -1; + target.setAttribute('order', order.toString()); + texture_group_target_node = target; + + } else if (document.querySelector('#texture_list:hover')) { + let nodes = document.querySelectorAll('#texture_list > li.texture_group'); + if (nodes.length) { + let target = nodes[nodes.length-1]; + order = 1; + target.setAttribute('order', '1'); + target.classList.add('drag_hover'); + texture_group_target_node = target; + } + } + last_event = e2; + } + async function off(e2) { + if (helper) helper.remove(); + clearInterval(scrollIntervalID); + removeEventListeners(document, 'mousemove touchmove', move); + removeEventListeners(document, 'mouseup touchend', off); + e2.stopPropagation(); + + $('.drag_hover').removeClass('drag_hover'); + $('.texture_group[order]').attr('order', null); + if (Blockbench.isTouch) clearTimeout(timeout); + + if (!active || Menu.open) return; + + if (texture_group_target_node) { + let index = TextureGroup.all.length-1; + let texture_group_target = TextureGroup.all.find(tg => tg.uuid == texture_group_target_node.id); + if (texture_group_target) { + index = TextureGroup.all.indexOf(texture_group_target) + let own_index = TextureGroup.all.indexOf(texture_group) + if (own_index == index) return; + if (own_index < index) index--; + if (order == 1) index++; + } + Undo.initEdit({texture_groups: [texture_group]}); + TextureGroup.all.remove(texture_group); + TextureGroup.all.splice(index, 0, texture_group); + Undo.finishEdit('Rearrange texture groups'); + + } + } + + 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: `
    -