blockbench/js/texturing/layers.js

660 lines
19 KiB
JavaScript
Raw Normal View History

2023-10-21 05:35:22 +08:00
class TextureLayer {
constructor(data, texture = Texture.selected, uuid) {
this.uuid = (uuid && isUUID(uuid)) ? uuid : guid();
this.texture = texture;
this.canvas = document.createElement('canvas');
2023-11-06 03:16:41 +08:00
this.ctx = this.canvas.getContext('2d', {willReadFrequently: true});
2023-10-27 19:02:28 +08:00
this.in_limbo = false;
2023-10-21 05:35:22 +08:00
this.img = new Image();
this.img.onload = () => {
this.canvas.width = this.img.naturalWidth;
this.canvas.height = this.img.naturalHeight;
this.ctx.drawImage(this.img, 0, 0);
}
for (var key in TextureLayer.properties) {
TextureLayer.properties[key].reset(this);
}
if (data) this.extend(data);
}
get width() {
return this.canvas.width;
}
get height() {
2023-11-06 03:16:41 +08:00
return this.canvas.height;
2023-10-21 05:35:22 +08:00
}
2023-11-07 07:28:26 +08:00
get scaled_width() {
return this.canvas.width * this.scale[0];
}
get scaled_height() {
return this.canvas.height * this.scale[1];
}
2023-10-21 05:35:22 +08:00
get size() {
return [this.canvas.width, this.canvas.height];
}
2023-10-21 21:30:06 +08:00
get selected() {
return this.texture.selected_layer == this;
}
2023-10-21 05:35:22 +08:00
extend(data) {
for (var key in TextureLayer.properties) {
TextureLayer.properties[key].merge(this, data)
}
if (data.image_data) {
this.canvas.width = data.width || 16;
this.canvas.height = data.height || 16;
this.ctx.putImageData(data.image_data, 0, 0);
} else if (data.data_url) {
this.canvas.width = data.width || 16;
this.canvas.height = data.height || 16;
this.img.src = data.data_url;
}
}
select() {
this.texture.selected_layer = this;
2023-11-06 06:54:21 +08:00
UVEditor.vue.layer = this;
2023-10-21 21:30:06 +08:00
BarItems.layer_opacity.update();
2023-10-21 05:35:22 +08:00
}
showContextMenu(event) {
if (!this.selected) this.select();
this.menu.open(event, this);
}
2023-10-21 21:30:06 +08:00
remove(undo) {
if (undo) {
Undo.initEdit({textures: [this.texture], bitmap: true});
}
2023-11-03 02:21:58 +08:00
let index = this.texture.layers.indexOf(this);
this.texture.layers.splice(index, 1);
2023-11-07 07:28:26 +08:00
if (this.texture.selected_layer == this) {
let select_next = this.texture.layers[index-1] || this.texture.layers[index];
if (select_next) select_next.select();
}
2023-10-21 21:30:06 +08:00
if (undo) {
this.texture.updateLayerChanges(true);
this.texture.saved = false;
2023-10-21 21:30:06 +08:00
Undo.finishEdit('Remove layer');
}
2023-10-21 05:35:22 +08:00
}
2023-10-21 21:30:06 +08:00
getUndoCopy(image_data) {
2023-10-21 05:35:22 +08:00
let copy = {};
2023-10-21 21:30:06 +08:00
copy.texture = this.texture.uuid;
2023-11-06 06:54:21 +08:00
copy.uuid = this.uuid;
2023-10-21 05:35:22 +08:00
for (var key in TextureLayer.properties) {
TextureLayer.properties[key].copy(this, copy);
}
copy.width = this.width;
copy.height = this.height;
2023-10-21 21:30:06 +08:00
if (image_data) {
copy.image_data = this.ctx.getImageData(0, 0, this.width, this.height);
}
2023-10-21 05:35:22 +08:00
return copy;
}
getSaveCopy() {
let copy = {};
for (var key in TextureLayer.properties) {
TextureLayer.properties[key].copy(this, copy);
}
2023-11-06 06:54:21 +08:00
delete copy.in_limbo;
2023-10-21 05:35:22 +08:00
copy.width = this.width;
copy.height = this.height;
copy.data_url = this.canvas.toDataURL();
return copy;
}
2023-10-27 19:02:28 +08:00
setLimbo() {
2023-11-06 03:16:41 +08:00
this.texture.layers.forEach(layer => layer.in_limbo = false);
2023-10-27 19:02:28 +08:00
this.in_limbo = true;
}
2023-11-06 03:16:41 +08:00
resolveLimbo(keep_separate) {
if (keep_separate) {
2023-11-07 07:28:26 +08:00
if (this.scale[0] != 1 || this.scale[1] != 1) {
let temp_canvas = this.canvas.cloneNode();
let temp_canvas_ctx = temp_canvas.getContext('2d');
temp_canvas_ctx.drawImage(this.canvas, 0, 0);
Undo.initEdit({layers: [this], bitmap: true});
2023-11-07 07:28:26 +08:00
this.canvas.width = Math.round(this.canvas.width * this.scale[0]);
this.canvas.height = Math.round(this.canvas.height * this.scale[1]);
this.ctx.imageSmoothingEnabled = false;
2023-11-07 07:28:26 +08:00
this.ctx.drawImage(temp_canvas, 0, 0, this.canvas.width, this.canvas.height);
this.scale.V2_set(1, 1);
this.texture.updateLayerChanges(true);
2023-11-07 07:28:26 +08:00
TextureLayer.selected.in_limbo = false;
Undo.finishEdit('Place selection as layer');
} else {
TextureLayer.selected.in_limbo = false;
}
2023-11-06 03:16:41 +08:00
} else {
TextureLayer.selected.mergeDown(true);
}
2023-11-06 06:54:21 +08:00
UVEditor.vue.$forceUpdate();
2023-11-06 03:16:41 +08:00
Texture.selected.selection.clear();
UVEditor.updateSelectionOutline();
}
2023-10-21 05:35:22 +08:00
setSize(width, height) {
this.canvas.width = width;
this.canvas.height = height;
}
toggleVisibility() {
2023-11-06 03:16:41 +08:00
Undo.initEdit({layers: [this]});
2023-10-21 05:35:22 +08:00
this.visible = !this.visible;
this.texture.updateLayerChanges(true);
this.texture.saved = false;
2023-10-21 05:35:22 +08:00
Undo.finishEdit('Toggle layer visibility');
}
2023-11-06 03:16:41 +08:00
mergeDown(undo = true) {
let down_layer = this.texture.layers[this.texture.layers.indexOf(this) - 1];
if (!down_layer) {
this.in_limbo = false;
return;
}
if (undo) {
Undo.initEdit({textures: [this.texture], bitmap: true});
}
down_layer.expandTo(this.offset, this.offset.slice().V2_add(this.width, this.height));
2023-11-07 07:28:26 +08:00
down_layer.ctx.imageSmoothingEnabled = false;
down_layer.ctx.drawImage(this.canvas, this.offset[0] - down_layer.offset[0], this.offset[1] - down_layer.offset[1], this.scaled_width, this.scaled_height);
2023-11-06 03:16:41 +08:00
let index = this.texture.layers.indexOf(this);
this.texture.layers.splice(index, 1);
2023-11-07 07:28:26 +08:00
if (this.texture.selected_layer == this) {
let select_next = this.texture.layers[index-1] || this.texture.layers[index];
if (select_next) select_next.select();
}
2023-11-06 03:16:41 +08:00
if (undo) {
this.texture.updateLayerChanges(true);
this.texture.saved = false;
2023-11-06 03:16:41 +08:00
Undo.finishEdit('Merge layers');
}
}
expandTo(...points) {
let min = this.offset.slice();
let max = this.offset.slice().V2_add(this.width, this.height);
points.forEach(point => {
point = [
Math.clamp(point[0], 0, this.texture.width),
Math.clamp(point[1], 0, this.texture.height),
]
min[0] = Math.min(min[0], point[0]);
min[1] = Math.min(min[1], point[1]);
max[0] = Math.max(max[0], point[0]);
max[1] = Math.max(max[1], point[1]);
});
if (min[0] < this.offset[0] || min[1] < this.offset[1] || max[0] > this.offset[0]+this.width || max[1] > this.offset[1]+this.height) {
let copy_canvas = Painter.copyCanvas(this.canvas);
this.canvas.width = max[0] - min[0];
this.canvas.height = max[1] - min[1];
this.ctx.drawImage(copy_canvas, this.offset[0] - min[0], this.offset[1] - min[1]);
this.offset.replace(min);
}
}
2023-11-06 03:16:41 +08:00
flip(axis = 0, undo) {
let temp_canvas = this.canvas.cloneNode();
let temp_canvas_ctx = temp_canvas.getContext('2d');
temp_canvas_ctx.drawImage(this.canvas, 0, 0);
if (undo) Undo.initEdit({layers: [this], bitmap: true});
2023-11-06 03:16:41 +08:00
this.ctx.save();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (axis == 0) {
2023-11-07 07:28:26 +08:00
this.ctx.translate(this.canvas.width, 0);
2023-11-06 03:16:41 +08:00
this.ctx.scale(-1, 1);
this.ctx.drawImage(temp_canvas, this.canvas.width, 0, -this.canvas.width, this.canvas.height);
} else {
2023-11-07 07:28:26 +08:00
this.ctx.translate(0, this.canvas.height);
2023-11-06 03:16:41 +08:00
this.ctx.scale(1, -1);
2023-11-07 07:28:26 +08:00
this.ctx.drawImage(temp_canvas, 0, this.canvas.height, this.canvas.width, -this.canvas.height);
2023-11-06 03:16:41 +08:00
}
this.ctx.restore();
this.texture.updateLayerChanges(undo);
this.texture.saved = false;
2023-11-06 03:16:41 +08:00
if (undo) Undo.finishEdit('Flip layer');
}
rotate(angle = 90, undo) {
let temp_canvas = this.canvas.cloneNode();
let temp_canvas_ctx = temp_canvas.getContext('2d');
temp_canvas_ctx.drawImage(this.canvas, 0, 0);
if (undo) Undo.initEdit({layers: [this], bitmap: true});
2023-11-06 03:16:41 +08:00
[this.canvas.width, this.canvas.height] = [this.canvas.height, this.canvas.width];
this.ctx.save();
this.ctx.translate(this.canvas.width/2,this.canvas.height/2);
this.ctx.rotate(Math.degToRad(angle));
this.ctx.drawImage(temp_canvas,-temp_canvas.width/2,-temp_canvas.height/2);
this.ctx.restore();
this.texture.updateLayerChanges(undo);
this.texture.saved = false;
2023-11-07 07:28:26 +08:00
UVEditor.vue.$forceUpdate();
2023-11-06 03:16:41 +08:00
if (undo) Undo.finishEdit('Rotate layer');
}
2023-11-07 07:28:26 +08:00
center() {
this.offset[0] = Math.round(Math.max(0, this.texture.width - this.width ) / 2);
this.offset[1] = Math.round(Math.max(0, this.texture.height - this.height) / 2);
this.texture.updateLayerChanges();
}
2023-10-21 05:35:22 +08:00
propertiesDialog() {
2023-10-21 21:30:06 +08:00
let dialog = new Dialog({
id: 'layer_properties',
title: this.name,
form: {
name: {label: 'generic.name', value: this.name},
opacity: {label: 'Opacity', type: 'range', value: this.opacity},
},
onConfirm: form_data => {
dialog.hide().delete();
if (
form_data.name != this.name
|| form_data.opacity != this.opacity
) {
Undo.initEdit({layers: [this]});
this.extend(form_data)
Blockbench.dispatchEvent('edit_layer_properties', {layer: this})
Undo.finishEdit('Edit layer properties');
}
},
onCancel() {
dialog.hide().delete();
}
})
dialog.show();
2023-10-21 05:35:22 +08:00
}
}
TextureLayer.prototype.menu = new Menu([
new MenuSeparator('edit'),
{id: 'transform', name: 'menu.transform', icon: 'transform', children: [
'flip_texture_x',
'flip_texture_y',
'rotate_texture_cw',
'rotate_texture_ccw',
]},
'layer_to_texture_size',
'merge_layer_down',
2023-10-21 05:35:22 +08:00
new MenuSeparator('copypaste'),
'copy',
'duplicate',
'delete',
2023-10-21 21:30:06 +08:00
new MenuSeparator('properties'),
{
icon: 'list',
name: 'menu.texture.properties',
click(layer) { layer.propertiesDialog()}
}
2023-10-21 05:35:22 +08:00
/**
* Merge
* Copy
* Duplicate
* Delete
*/
])
new Property(TextureLayer, 'string', 'name', {default: 'layer'});
new Property(TextureLayer, 'vector2', 'offset');
2023-11-07 07:28:26 +08:00
new Property(TextureLayer, 'vector2', 'scale', {default: [1, 1]});
2023-10-21 05:35:22 +08:00
new Property(TextureLayer, 'number', 'opacity', {default: 100});
new Property(TextureLayer, 'boolean', 'visible', {default: true});
2023-11-06 06:54:21 +08:00
new Property(TextureLayer, 'boolean', 'in_limbo', {default: false});
2023-10-21 05:35:22 +08:00
2023-10-21 21:30:06 +08:00
Object.defineProperty(TextureLayer, 'all', {
get() {
2023-11-05 21:55:16 +08:00
return Texture.selected?.layers_enabled ? Texture.selected.layers : [];
2023-10-21 21:30:06 +08:00
}
})
Object.defineProperty(TextureLayer, 'selected', {
get() {
2023-11-05 21:55:16 +08:00
return Texture.selected?.selected_layer;
2023-10-21 21:30:06 +08:00
}
})
2023-10-21 05:35:22 +08:00
2023-10-23 01:20:24 +08:00
SharedActions.add('delete', {
2023-11-06 03:16:41 +08:00
subject: 'layer',
2023-10-23 01:20:24 +08:00
condition: () => Prop.active_panel == 'layers' && Texture.selected?.selected_layer,
run() {
if (Texture.selected.layers.length >= 2) {
Texture.selected?.selected_layer.remove(true);
}
}
})
2023-11-06 03:16:41 +08:00
SharedActions.add('delete', {
subject: 'layer_priority',
2023-11-07 07:28:26 +08:00
condition: () => Texture.selected?.selected_layer?.in_limbo,
2023-11-06 03:16:41 +08:00
priority: 2,
run() {
if (Texture.selected.layers.length >= 2) {
Texture.selected?.selected_layer.remove(true);
}
Texture.selected.selection.clear()
UVEditor.updateSelectionOutline()
}
})
2023-10-23 01:20:24 +08:00
SharedActions.add('duplicate', {
2023-11-06 03:16:41 +08:00
subject: 'layer',
2023-10-23 01:20:24 +08:00
condition: () => Prop.active_panel == 'layers' && Texture.selected?.selected_layer,
run() {
let texture = Texture.selected;
let original = texture.getActiveLayer();
let copy = original.getUndoCopy(true);
copy.name += '-copy';
Undo.initEdit({textures: [texture]});
let layer = new TextureLayer(copy, texture);
texture.layers.push(layer);
layer.select();
Undo.finishEdit('Duplicate layer');
}
})
2023-10-21 05:35:22 +08:00
BARS.defineActions(() => {
new Action('create_empty_layer', {
2023-10-23 02:44:45 +08:00
icon: 'new_window',
2023-10-21 05:35:22 +08:00
category: 'layers',
condition: () => Modes.paint && Texture.selected && Texture.selected.layers_enabled,
click() {
let texture = Texture.selected;
2023-10-21 21:30:06 +08:00
Undo.initEdit({textures: [texture], bitmap: true});
2023-10-21 05:35:22 +08:00
let layer = new TextureLayer({
2023-10-21 21:30:06 +08:00
name: `layer #${texture.layers.length+1}`
2023-10-21 05:35:22 +08:00
}, texture);
layer.setSize(texture.width, texture.height);
texture.layers.push(layer);
layer.select();
2023-10-21 21:30:06 +08:00
Undo.finishEdit('Create empty layer');
2023-10-21 05:35:22 +08:00
BARS.updateConditions();
}
})
new Action('enable_texture_layers', {
icon: 'library_add_check',
category: 'layers',
2023-10-21 21:30:06 +08:00
condition: () => Texture.selected && !Texture.selected.layers_enabled,
2023-10-21 05:35:22 +08:00
click() {
2023-10-21 21:30:06 +08:00
if (!Modes.paint) {
Modes.options.paint.select();
}
2023-10-21 05:35:22 +08:00
let texture = Texture.selected;
2023-10-27 19:02:28 +08:00
texture.activateLayers(true);
2023-10-21 05:35:22 +08:00
}
})
2023-11-07 07:28:26 +08:00
new Action('disable_texture_layers', {
icon: 'layers_clear',
category: 'layers',
condition: () => Texture.selected && Texture.selected.layers_enabled,
click() {
let texture = Texture.selected;
Undo.initEdit({textures: [texture], bitmap: true});
texture.layers_enabled = false;
texture.selected_layer = null;
texture.layers.empty();
2023-11-07 07:28:26 +08:00
Undo.finishEdit('Disable layers on texture');
UVEditor.vue.layer = null;
2023-11-07 07:28:26 +08:00
updateInterfacePanels();
BARS.updateConditions();
}
})
2023-10-21 05:35:22 +08:00
new NumSlider('layer_opacity', {
category: 'layers',
2023-10-21 21:30:06 +08:00
condition: () => Modes.paint && Texture.selected && Texture.selected.layers_enabled && Texture.selected.getActiveLayer(),
2023-10-22 19:52:17 +08:00
settings: {
min: 0, max: 100, default: 100,
show_bar: true
},
2023-10-21 05:35:22 +08:00
getInterval(event) {
return 1;
},
get() {
2023-10-21 21:30:06 +08:00
return Texture.selected.getActiveLayer().opacity;
2023-10-21 05:35:22 +08:00
},
change(modify) {
2023-10-21 21:30:06 +08:00
let layer = Texture.selected.getActiveLayer();
2023-10-21 05:35:22 +08:00
layer.opacity = Math.clamp(modify(layer.opacity), 0, 100);
2023-10-21 21:30:06 +08:00
Texture.selected.updateLayerChanges();
2023-10-21 05:35:22 +08:00
},
onBefore() {
2023-10-21 21:30:06 +08:00
Undo.initEdit({layers: [Texture.selected.getActiveLayer()]});
2023-10-21 05:35:22 +08:00
},
onAfter() {
Undo.finishEdit('Change layer opacity');
2023-10-21 21:30:06 +08:00
Texture.selected.updateLayerChanges(true);
Texture.selected.saved = false;
2023-10-21 05:35:22 +08:00
}
})
new Action('layer_to_texture_size', {
icon: 'fit_screen',
category: 'layers',
condition: () => TextureLayer.selected,
click() {
let layer = TextureLayer.selected;
Undo.initEdit({layers: [layer], bitmap: true});
let copy = Painter.copyCanvas(layer.canvas);
layer.canvas.width = layer.texture.width;
layer.canvas.height = layer.texture.height;
layer.ctx.drawImage(copy, layer.offset[0], layer.offset[1]);
console.log(copy, layer.offset[0], layer.offset[1])
layer.offset.V2_set(0, 0);
Undo.finishEdit('Expand layer to texture size');
layer.texture.updateLayerChanges(true);
}
})
new Action('merge_layer_down', {
icon: 'fa-caret-square-down',
category: 'layers',
condition: () => TextureLayer.selected,
click() {
TextureLayer.selected.mergeDown(true);
}
})
2023-10-21 05:35:22 +08:00
})
Interface.definePanels(function() {
Vue.component('texture-layer-icon', {
props: {
layer: TextureLayer
},
template: '<div class="layer_icon_wrapper"></div>',
mounted() {
this.$el.append(this.layer.canvas);
}
})
2023-10-21 21:30:06 +08:00
function eventTargetToLayer(target, texture) {
let target_node = target;
let i = 0;
while (target_node && target_node.classList && !target_node.classList.contains('texture_layer')) {
if (i < 3 && target_node) {
target_node = target_node.parentNode;
i++;
} else {
return [];
}
}
return [texture.layers.find(layer => layer.uuid == target_node.attributes.layer_id.value), target_node];
}
function getOrder(loc, obj) {
if (!obj) {
return;
} else {
if (loc < 16) return -1;
return 1;
}
}
2023-10-21 05:35:22 +08:00
new Panel('layers', {
icon: 'layers',
growable: true,
condition: () => Modes.paint && ((Texture.selected && Texture.selected.layers_enabled) || Format.image_editor),
2023-10-21 05:35:22 +08:00
default_position: {
slot: 'left_bar',
float_position: [0, 0],
float_size: [300, 300],
height: 300
},
toolbars: [
new Toolbar('layers', {
children: [
'create_empty_layer',
'enable_texture_layers',
]
})
],
component: {
name: 'panel-layers',
data() { return {
layers: [],
}},
methods: {
openMenu(event) {
Interface.Panels.layers.menu.show(event)
2023-10-21 21:30:06 +08:00
},
dragLayer(e1) {
if (getFocusedTextInput()) return;
if (e1.button == 1 || e1.button == 2) return;
convertTouchEvent(e1);
let texture = Texture.selected;
if (!texture) return;
let [layer] = eventTargetToLayer(e1.target, texture);
if (!layer || layer.locked) return;
let active = false;
let helper;
let timeout;
let drop_target, drop_target_node, order;
let last_event = e1;
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('layers_list').scrollTop += last_event.clientY - e2.clientY;
}
} else if (distance > 6) {
active = true;
}
} else {
if (e2) e2.preventDefault();
if (Menu.open) Menu.open.hide();
if (!helper) {
helper = document.createElement('div');
helper.id = 'animation_drag_helper';
let icon = document.createElement('i'); icon.className = 'material-icons'; icon.innerText = 'image'; helper.append(icon);
let span = document.createElement('span'); span.innerText = layer.name; helper.append(span);
document.body.append(helper);
}
helper.style.left = `${e2.clientX}px`;
helper.style.top = `${e2.clientY}px`;
// drag
$('.drag_hover').removeClass('drag_hover');
$('.texture_layer[order]').attr('order', null);
let target = document.elementFromPoint(e2.clientX, e2.clientY);
[drop_target, drop_target_node] = eventTargetToLayer(target, texture);
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');
}
}
last_event = e2;
}
function off(e2) {
if (helper) helper.remove();
removeEventListeners(document, 'mousemove touchmove', move);
removeEventListeners(document, 'mouseup touchend', off);
$('.drag_hover').removeClass('drag_hover');
$('.texture_layer[order]').attr('order', null);
if (Blockbench.isTouch) clearTimeout(timeout);
if (active && !open_menu) {
convertTouchEvent(e2);
let target = document.elementFromPoint(e2.clientX, e2.clientY);
[target_layer] = eventTargetToLayer(target, texture);
if (!target_layer || target_layer == layer ) return;
let index = texture.layers.indexOf(target_layer);
if (index == -1) return;
if (texture.layers.indexOf(layer) < index) index--;
if (order == -1) index++;
if (texture.layers[index] == layer) return;
Undo.initEdit({textures: [texture]});
texture.layers.remove(layer);
texture.layers.splice(index, 0, layer);
texture.updateLayerChanges(true);
texture.saved = false;
2023-10-21 21:30:06 +08:00
Undo.finishEdit('Reorder layers');
}
}
if (Blockbench.isTouch) {
timeout = setTimeout(() => {
active = true;
move(e1);
}, 320)
}
addEventListeners(document, 'mousemove touchmove', move, {passive: false});
addEventListeners(document, 'mouseup touchend', off, {passive: false});
2023-10-21 05:35:22 +08:00
}
},
template: `
<ul
id="layers_list"
class="list mobile_scrollbar"
@contextmenu.stop.prevent="openMenu($event)"
2023-10-21 21:30:06 +08:00
@mousedown="dragLayer($event)"
@touchstart="dragLayer($event)"
2023-10-21 05:35:22 +08:00
>
<li
v-for="layer in layers"
2023-11-06 03:16:41 +08:00
:class="{ selected: layer.selected, in_limbo: layer.in_limbo }"
2023-10-21 05:35:22 +08:00
:key="layer.uuid"
2023-10-21 21:30:06 +08:00
:layer_id="layer.uuid"
2023-10-21 05:35:22 +08:00
class="texture_layer"
@click.stop="layer.select()"
@dblclick.stop="layer.propertiesDialog()"
@contextmenu.prevent.stop="layer.showContextMenu($event)"
>
<texture-layer-icon :layer="layer" />
<label>
{{ layer.name }}
</label>
<div class="in_list_button" @click.stop="layer.toggleVisibility()">
<i v-if="layer.visible" class="material-icons icon">visibility</i>
<i v-else class="material-icons icon toggle_disabled">visibility_off</i>
2023-10-21 05:35:22 +08:00
</div>
</li>
</ul>
`
},
menu: new Menu([
'create_empty_layer',
])
})
})