From 49e251cc39eebb6499251189035d183bfeec6f0b Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Nov 2023 20:16:41 +0100 Subject: [PATCH] Layer improvements --- css/general.css | 1 - css/panels.css | 14 ++++ js/interface/shared_actions.js | 10 +++ js/texturing/layers.js | 93 +++++++++++++++++++++- js/texturing/painter.js | 2 +- js/texturing/textures.js | 23 +++--- js/texturing/uv.js | 139 +++++++++------------------------ lang/en.json | 2 +- 8 files changed, 160 insertions(+), 124 deletions(-) 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')}
    +
    + + +
    +
    -
    -
    ${tl('uv_editor.copy_paste_tool.cut')}
    -
    ${tl('uv_editor.copy_paste_tool.mirror_x')}
    -
    ${tl('uv_editor.copy_paste_tool.mirror_y')}
    -
    ${tl('uv_editor.copy_paste_tool.rotate')}
    rotate_right
    - -
    ${tl('dialog.cancel')}
    clear
    -
    ${tl('uv_editor.copy_paste_tool.place')}
    check
    +
    +
    ${tl('uv_editor.copy_paste_tool.mirror_x')}
    +
    ${tl('uv_editor.copy_paste_tool.mirror_y')}
    +
    ${tl('uv_editor.copy_paste_tool.rotate')}
    rotate_right
    -
    +
    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",