diff --git a/css/general.css b/css/general.css
index 287d95361..92fd92cdd 100644
--- a/css/general.css
+++ b/css/general.css
@@ -239,7 +239,6 @@
white-space: pre-line;
cursor: default;
pointer-events: none;
- border-radius: 6px;
}
.uv_message_box {
position: absolute;
diff --git a/css/panels.css b/css/panels.css
index 283df774e..670259854 100644
--- a/css/panels.css
+++ b/css/panels.css
@@ -735,6 +735,10 @@
.texture_layer.selected {
background-color: var(--color-selected);
}
+ .texture_layer.in_limbo {
+ border: 2px dashed var(--color-accent);
+ font-style: italic;
+ }
.texture_layer .layer_icon_wrapper {
height: 40px;
width: 40px;
@@ -2441,6 +2445,16 @@ span.controller_state_section_info {
color: var(--color-close);
float: right;
}
+ .uv_layer_limbo_options {
+ position: absolute;
+ bottom: 52px;
+ left: 0;
+ right: 0;
+ margin: auto;
+ background-color: var(--color-ui);
+ padding: 0 4px;
+ width: fit-content;
+ }
/*Chat*/
#panel_chat {
diff --git a/js/interface/shared_actions.js b/js/interface/shared_actions.js
index 61f3582d6..10f876b00 100644
--- a/js/interface/shared_actions.js
+++ b/js/interface/shared_actions.js
@@ -57,6 +57,16 @@ const SharedActions = {
}
return false;
},
+ find(action_id, event, context) {
+ let list = this.actions[action_id];
+ if (!list) return;
+ for (let handler of list) {
+ if (Condition(handler.condition, context)) {
+ return handler;
+ }
+ }
+ return null;
+ },
actions: {}
};
diff --git a/js/texturing/layers.js b/js/texturing/layers.js
index 3be5444af..80e8d9a1b 100644
--- a/js/texturing/layers.js
+++ b/js/texturing/layers.js
@@ -3,7 +3,7 @@ class TextureLayer {
this.uuid = (uuid && isUUID(uuid)) ? uuid : guid();
this.texture = texture;
this.canvas = document.createElement('canvas');
- this.ctx = this.canvas.getContext('2d');
+ this.ctx = this.canvas.getContext('2d', {willReadFrequently: true});
this.in_limbo = false;
this.img = new Image();
@@ -23,7 +23,7 @@ class TextureLayer {
return this.canvas.width;
}
get height() {
- return this.canvas.width;
+ return this.canvas.height;
}
get size() {
return [this.canvas.width, this.canvas.height];
@@ -90,18 +90,89 @@ class TextureLayer {
return copy;
}
setLimbo() {
+ this.texture.layers.forEach(layer => layer.in_limbo = false);
this.in_limbo = true;
}
+ resolveLimbo(keep_separate) {
+ if (keep_separate) {
+ TextureLayer.selected.in_limbo = false;
+ } else {
+ TextureLayer.selected.mergeDown(true);
+ }
+ Texture.selected.selection.clear();
+ UVEditor.updateSelectionOutline();
+ }
setSize(width, height) {
this.canvas.width = width;
this.canvas.height = height;
}
toggleVisibility() {
- Undo.initEdit({textures: [this.texture]});
+ Undo.initEdit({layers: [this]});
this.visible = !this.visible;
this.texture.updateLayerChanges(true);
Undo.finishEdit('Toggle layer visibility');
}
+ 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.ctx.drawImage(this.canvas, this.offset[0], this.offset[1]);
+
+ let index = this.texture.layers.indexOf(this);
+ this.texture.layers.splice(index, 1);
+ if (this.texture.selected_layer == this) this.texture.selected_layer = this.texture.layers[index-1] || this.texture.layers[index];
+ if (undo) {
+ this.texture.updateLayerChanges(true);
+ Undo.finishEdit('Merge layers');
+ }
+ }
+ 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]});
+
+ this.ctx.save();
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ this.ctx.translate(this.canvas.width, 0);
+ if (axis == 0) {
+ this.ctx.scale(-1, 1);
+ this.ctx.drawImage(temp_canvas, this.canvas.width, 0, -this.canvas.width, this.canvas.height);
+ } else {
+ this.ctx.scale(1, -1);
+ this.ctx.drawImage(temp_canvas, this.canvas.width, 0, this.canvas.width, -this.canvas.height);
+ }
+ this.ctx.restore();
+
+ this.texture.updateLayerChanges(undo);
+
+ 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]});
+
+ [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);
+
+ if (undo) Undo.finishEdit('Rotate layer');
+ }
propertiesDialog() {
let dialog = new Dialog({
id: 'layer_properties',
@@ -166,6 +237,7 @@ Object.defineProperty(TextureLayer, 'selected', {
})
SharedActions.add('delete', {
+ subject: 'layer',
condition: () => Prop.active_panel == 'layers' && Texture.selected?.selected_layer,
run() {
if (Texture.selected.layers.length >= 2) {
@@ -173,7 +245,20 @@ SharedActions.add('delete', {
}
}
})
+SharedActions.add('delete', {
+ subject: 'layer_priority',
+ condition: () => Texture.selected?.selected_layer.in_limbo,
+ priority: 2,
+ run() {
+ if (Texture.selected.layers.length >= 2) {
+ Texture.selected?.selected_layer.remove(true);
+ }
+ Texture.selected.selection.clear()
+ UVEditor.updateSelectionOutline()
+ }
+})
SharedActions.add('duplicate', {
+ subject: 'layer',
condition: () => Prop.active_panel == 'layers' && Texture.selected?.selected_layer,
run() {
let texture = Texture.selected;
@@ -420,7 +505,7 @@ Interface.definePanels(function() {
>
{
+ selection.forEachPixel((x, y, val) => {
if (val) {
ctx.clearRect(x, y, 1, 1);
}
@@ -1375,7 +1372,7 @@ class Texture {
new_layer.setLimbo();
texture.updateLayerChanges(true);
- Undo.finishEdit('Texture selection to layer');
+ if (undo) Undo.finishEdit('Texture selection to layer');
updateInterfacePanels();
BARS.updateConditions();
}
diff --git a/js/texturing/uv.js b/js/texturing/uv.js
index b59c2064f..048aa31aa 100644
--- a/js/texturing/uv.js
+++ b/js/texturing/uv.js
@@ -197,11 +197,10 @@ const UVEditor = {
}
this.vue.selection_outline = outline;
},
- addPastingOverlay() {
+ /*addPastingOverlay() {
if (Painter.selection.overlay) return;
let scope = this;
let overlay = $(Interface.createElement('div', {id: 'texture_pasting_overlay'}));
- UVEditor.vue.copy_overlay.state = 'move';
open_interface = {
confirm() {
@@ -241,7 +240,6 @@ const UVEditor = {
},
removePastingOverlay() {
Painter.selection.overlay.detach();
- UVEditor.vue.copy_overlay.state = 'off';
delete Painter.selection.overlay;
open_interface = false;
},
@@ -254,7 +252,7 @@ const UVEditor = {
.css('left', Painter.selection.x * m)
.css('top', (Painter.selection.y%this.texture.display_height) * m);
return this;
- },
+ },*/
focusOnSelection() {
let min_x = UVEditor.getUVWidth();
let min_y = UVEditor.getUVHeight();
@@ -395,7 +393,6 @@ const UVEditor = {
Project.uv_viewport.zoom = this.zoom;
Vue.nextTick(() => {
UVEditor.updateSelectionOutline(false);
- if (Painter.selection.overlay) UVEditor.updatePastingOverlay()
})
return this;
},
@@ -1922,65 +1919,6 @@ Interface.definePanels(function() {
return temp_canvas
}
- let copy_overlay = {
- state: 'off',
- width: 0, height: 0,
-
- doPlace() {
- open_interface.confirm();
- },
- doCancel() {
- open_interface.hide();
- },
- doCut(e) {
- UVEditor.removePastingOverlay()
- UVEditor.texture.edit((canvas) => {
- var ctx = canvas.getContext('2d');
- ctx.clearRect(Painter.selection.x, Painter.selection.y, Painter.selection.canvas.width, Painter.selection.canvas.height);
- })
- },
- doMirror_x(e) {
- let temp_canvas = getCanvasCopy()
-
- let ctx = Painter.selection.canvas.getContext('2d');
- ctx.save();
- ctx.translate(ctx.canvas.width, 0);
- ctx.scale(-1, 1);
-
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
- ctx.drawImage(temp_canvas, ctx.canvas.width, 0, -ctx.canvas.width, ctx.canvas.height);
- ctx.restore();
- UVEditor.updatePastingOverlay()
- },
- doMirror_y(e) {
- let temp_canvas = getCanvasCopy()
-
- let ctx = Painter.selection.canvas.getContext('2d');
- ctx.save();
- ctx.translate(0, ctx.canvas.height);
- ctx.scale(1, -1);
-
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
- ctx.drawImage(temp_canvas, 0, ctx.canvas.height, ctx.canvas.width, -ctx.canvas.height);
- ctx.restore();
- },
- doRotate(e) {
- let temp_canvas = getCanvasCopy()
-
- let ctx = Painter.selection.canvas.getContext('2d');
- [ctx.canvas.width, ctx.canvas.height] = [ctx.canvas.height, ctx.canvas.width]
- ctx.save();
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
-
- ctx.translate(ctx.canvas.width/2,ctx.canvas.height/2);
- ctx.rotate(Math.PI/2);
-
- ctx.drawImage(temp_canvas,-temp_canvas.width/2,-temp_canvas.height/2);
-
- ctx.restore();
- UVEditor.updatePastingOverlay()
- },
- }
UVEditor.panel = new Panel('uv', {
icon: 'photo_size_select_large',
@@ -2055,7 +1993,6 @@ Interface.definePanels(function() {
active: false,
ellipse: false
},
- copy_overlay,
uv_resolution: [16, 16],
elements: [],
@@ -3301,6 +3238,7 @@ Interface.definePanels(function() {
let create_selection = !(op_mode == 'create' && clicked_val) && Toolbox.selected.id == 'selection_tool';
let layer = texture.selected_layer;
let initial_offset = layer ? layer.offset.slice() : [0, 0];
+ console.log('X')
/*if (op_mode == 'create' && clicked_val) {
if (open_interface) {
@@ -3385,6 +3323,7 @@ Interface.definePanels(function() {
}
let last_x, last_y;
+ let started_movement = false;
function drag(e1) {
var {x, y} = UVEditor.getBrushCoordinates(e1, texture);
@@ -3416,28 +3355,36 @@ Interface.definePanels(function() {
selection_rect.width = calcrect.x;
selection_rect.height = calcrect.y;
+ Blockbench.setCursorTooltip(`${selection_rect.width} x ${selection_rect.height}`);
+
} else {
- UVEditor.vue.selection_outline = '';
- if (!layer) {
- texture.activateLayers();
- layer = texture.selected_layer;
- texture.display_canvas = true;
- UVEditor.vue.updateTextureCanvas();
- }
- if (!layer.in_limbo && texture.selection.is_custom && texture.selection.hasSelection()) {
- texture.selectionToLayer();
- layer = texture.selected_layer;
- initial_offset = layer.offset.slice();
+ if (!started_movement) {
+ UVEditor.vue.selection_outline = '';
+ if ((!layer || !layer.in_limbo) && texture.selection.is_custom && texture.selection.hasSelection()) {
+ Undo.initEdit({textures: [texture], bitmap: true});
+ texture.selectionToLayer(false);
+ layer = texture.selected_layer;
+ initial_offset = layer.offset.slice();
+ } else if (!layer) {
+ Undo.initEdit({textures: [texture], bitmap: true});
+ texture.activateLayers(false);
+ layer = texture.selected_layer;
+ } else {
+ Undo.initEdit({layers: [layer], bitmap: true});
+ }
}
layer.offset[0] = initial_offset[0] + x - start_x;
layer.offset[1] = initial_offset[1] + y - start_y;
+ Blockbench.setCursorTooltip(`${x - start_x} x ${y - start_y}`);
texture.updateLayerChanges();
}
+ started_movement = true;
}
function stop() {
removeEventListeners(document, 'pointermove', drag);
removeEventListeners(document, 'pointerup', stop);
selection_rect.active = false;
+ Blockbench.setCursorTooltip();
if (create_selection) {
if (!calcrect || selection_rect.width == 0 || selection_rect.height == 0) {
@@ -3517,26 +3464,10 @@ Interface.definePanels(function() {
UVEditor.updateSelectionOutline();
} else {
texture.updateLayerChanges(true);
- texture.selection.translate(last_x - start_x, last_y - start_y);
+ texture.selection.clear();
UVEditor.updateSelectionOutline();
+ Undo.finishEdit('Move layer');
}
-
-
- /*if (isApp) {
- let image = nativeImage.createFromDataURL(canvas.toDataURL())
- clipboard.writeImage(image)
- }*/
- /*Painter.selection.canvas = canvas;
-
- Painter.selection.move_mode = BarItems.copy_paste_tool_mode.value == 'move';
- if (Painter.selection.move_mode) {
- UVEditor.texture.edit((canvas) => {
- var ctx = canvas.getContext('2d');
- ctx.clearRect(Painter.selection.x, Painter.selection.y, Painter.selection.canvas.width, Painter.selection.canvas.height);
- }, {no_undo_finish: true});
- }
-
- //UVEditor.addPastingOverlay();*/
}
addEventListeners(document, 'pointermove', drag);
addEventListeners(document, 'pointerup', stop);
@@ -3881,25 +3812,25 @@ Interface.definePanels(function() {
${tl('uv_editor.transparent_face')}
+
+
+
+
+
-
diff --git a/lang/en.json b/lang/en.json
index 355bada43..8bb0b1998 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -2104,7 +2104,7 @@
"uv_editor.transparent_face": "Transparent Face",
"uv_editor.copy_paste_tool.place": "Place",
- "uv_editor.copy_paste_tool.cut": "Cut",
+ "uv_editor.copy_paste_tool.to_layer": "Place",
"uv_editor.copy_paste_tool.mirror_x": "Mirror X",
"uv_editor.copy_paste_tool.mirror_y": "Mirror Y",
"uv_editor.copy_paste_tool.rotate": "Rotate 90 Degrees",