diff --git a/js/copy_paste.js b/js/copy_paste.js index 70db854f..31422596 100644 --- a/js/copy_paste.js +++ b/js/copy_paste.js @@ -127,6 +127,9 @@ const Clipbench = { } }, copy(event, cut) { + let match = SharedActions.run('copy', event, cut); + if (match) return; + let copy_type = Clipbench.getCopyType(1); Clipbench.last_copied = copy_type; switch (copy_type) { @@ -175,6 +178,9 @@ const Clipbench = { } }, async paste(event) { + let match = SharedActions.run('paste', event); + if (match) return; + switch (await Clipbench.getPasteType()) { case 'text': Clipbench.setText(window.getSelection()+''); @@ -417,7 +423,7 @@ BARS.defineActions(function() { icon: 'fa-copy', category: 'edit', work_in_dialog: true, - condition: () => Clipbench.getCopyType(1, true), + condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'), keybind: new Keybind({key: 'c', ctrl: true, shift: null}), click(event) { Clipbench.copy(event) @@ -427,7 +433,7 @@ BARS.defineActions(function() { icon: 'fa-cut', category: 'edit', work_in_dialog: true, - condition: () => Clipbench.getCopyType(1, true), + condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'), keybind: new Keybind({key: 'x', ctrl: true, shift: null}), click(event) { Clipbench.copy(event, true) @@ -437,7 +443,7 @@ BARS.defineActions(function() { icon: 'fa-clipboard', category: 'edit', work_in_dialog: true, - condition: () => Clipbench.getCopyType(2, true), + condition: () => Clipbench.getCopyType(2, true) || SharedActions.condition('paste'), keybind: new Keybind({key: 'v', ctrl: true, shift: null}), click(event) { Clipbench.paste(event) diff --git a/js/interface/actions.js b/js/interface/actions.js index ab0efafc..1f457f2c 100644 --- a/js/interface/actions.js +++ b/js/interface/actions.js @@ -434,6 +434,7 @@ class Tool extends Action { } } this.onCanvasClick = data.onCanvasClick; + this.onTextureEditorClick = data.onTextureEditorClick; this.onSelect = data.onSelect; this.onUnselect = data.onUnselect; this.node.onclick = () => { @@ -2044,6 +2045,7 @@ const BARS = { 'draw_shape_tool', 'gradient_tool', 'selection_tool', + 'move_layer_tool', ], vertical: Blockbench.isMobile == true, default_place: true diff --git a/js/interface/shared_actions.js b/js/interface/shared_actions.js index 836eade0..de8349ea 100644 --- a/js/interface/shared_actions.js +++ b/js/interface/shared_actions.js @@ -4,7 +4,10 @@ const SharedActions = { * @param {('delete'|'rename'|'duplicate'|'select_all'|'unselect_all')} action_id * @param {Object} handler Case handler * @param {*} handler.condition Condition - * @param {number} handler.priority Condition + * @param {number} handler.priority Handler priority. + * Unset or 0 is typically used for relatively specific handlers, like those that check for a specific active panel. + * Higher priorities are used for even more specific conditions, like when multiple handlers work in a panel. + * Lower priorities are used for more fallback-like handlers, like deleting elements in edit mode, regardless of active panel. * @param {function} handler.run * @returns */ @@ -33,6 +36,17 @@ const SharedActions = { } return false; }, + runSpecific(action_id, subject, event, context) { + let list = this.actions[action_id]; + if (!list) return; + for (let handler of list) { + if (handler.subject == subject && Condition(handler.condition)) { + handler.run(event, context); + return true; + } + } + return false; + }, condition(action_id) { let list = this.actions[action_id]; if (!list) return; diff --git a/js/texturing/layers.js b/js/texturing/layers.js index 5c3400e5..e0395f02 100644 --- a/js/texturing/layers.js +++ b/js/texturing/layers.js @@ -4,6 +4,7 @@ class TextureLayer { this.texture = texture; this.canvas = document.createElement('canvas'); this.ctx = this.canvas.getContext('2d'); + this.in_limbo = false; this.img = new Image(); this.img.onload = () => { @@ -87,6 +88,9 @@ class TextureLayer { copy.data_url = this.canvas.toDataURL(); return copy; } + setLimbo() { + this.in_limbo = true; + } setSize(width, height) { this.canvas.width = width; this.canvas.height = height; @@ -210,20 +214,7 @@ BARS.defineActions(() => { Modes.options.paint.select(); } let texture = Texture.selected; - Undo.initEdit({textures: [texture], bitmap: true}); - texture.layers_enabled = true; - if (!texture.layers.length) { - let layer = new TextureLayer({ - }, texture); - let image_data = texture.ctx.getImageData(0, 0, texture.width, texture.height); - layer.setSize(texture.width, texture.height); - layer.ctx.putImageData(image_data, 0, 0); - texture.layers.push(layer); - layer.select(); - } - Undo.finishEdit('Enable layers on texture'); - updateInterfacePanels(); - BARS.updateConditions(); + texture.activateLayers(true); } }) new NumSlider('layer_opacity', { diff --git a/js/texturing/painter.js b/js/texturing/painter.js index 93b76c06..fc0dd991 100644 --- a/js/texturing/painter.js +++ b/js/texturing/painter.js @@ -263,12 +263,14 @@ const Painter = { delete Painter.paint_stroke_canceled; return; } + let texture = Texture.selected; if (Toolbox.selected.brush && Toolbox.selected.brush.onStrokeEnd) { let result = Toolbox.selected.brush.onStrokeEnd({texture, x, y, uv, event, raycast_data: data}); if (result == false) return; } if (Painter.brushChanges) { + texture.updateLayerChanges(true); Undo.finishEdit('Paint texture'); Painter.brushChanges = false; } @@ -1643,6 +1645,9 @@ class IntMatrix { this.array = null; this.override = false; } + get is_custom() { + return this.override === null; + } /** * The array does not exist by default to save memory, this activates it. */ @@ -1685,6 +1690,27 @@ class IntMatrix { getDirect(x, y) { return this.array[y * this.width + x]; } + getBoundingRect() { + let rect = new Rectangle(); + if (this.override == true) { + rect.width = this.width; + rect.height = this.height; + } else if (this.override == null) { + let min_x = Infinity; + let min_y = Infinity; + let max_x = -Infinity; + let max_y = -Infinity; + this.forEachPixel((x, y, value) => { + if (!value) return; + min_x = Math.min(min_x, x); + min_y = Math.min(min_y, y); + max_x = Math.max(max_x, x+1); + max_y = Math.max(max_y, y+1); + }) + rect.fromCoords(min_x, min_y, max_x, max_y); + } + return rect; + } /** * Set the value at a specified pixel * @param {number} x @@ -1751,9 +1777,118 @@ class IntMatrix { } } -SharedActions.add('delete', { +SharedActions.add('copy', { + subject: 'image_content', condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(), - run() { + run(event, cut) { + let texture = Texture.getDefault(); + let selection = texture.selection; + + let {canvas, ctx} = texture.getActiveCanvas(); + let layer = texture.selected_layer; + let offset = layer ? layer.offset : [0, 0]; + + if (selection.override != null) { + Clipbench.image = { + x: offset[0], y: offset[1], + data: canvas.toDataURL(), + } + } else { + let rect = selection.getBoundingRect(); + let copy_canvas = document.createElement('canvas'); + let copy_ctx = copy_canvas.getContext('2d'); + copy_canvas.width = rect.width; + copy_canvas.height = rect.height; + + copy_ctx.beginPath() + selection.forEachPixel((x, y, val) => { + if (!val) return; + copy_ctx.rect( + x - rect.start_x - offset[0], + y - rect.start_y - offset[1], + 1, 1 + ); + }) + copy_ctx.closePath(); + copy_ctx.clip(); + copy_ctx.drawImage(canvas, -rect.start_x, -rect.start_y); + + Clipbench.image = { + x: rect.start_x, + y: rect.start_y, + data: copy_canvas.toDataURL() + } + canvas = copy_canvas; + } + + + if (isApp) { + let img = nativeImage.createFromDataURL(Clipbench.image.data); + clipboard.writeImage(img); + } else { + canvas.toBlob(blob => { + navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob, + }), + ]); + }); + } + + if (cut) { + SharedActions.runSpecific('delete', 'image_content', {message: 'Cut texture selection'}); + } + } +}) +SharedActions.add('paste', { + subject: 'image_content', + condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(), + run(event) { + let texture = Texture.getDefault(); + + async function loadFromDataUrl(data_url) { + let frame = new CanvasFrame(); + await frame.loadFromURL(data_url); + + Undo.initEdit({textures: [texture], bitmap: true}); + if (!texture.layers_enabled) { + texture.activateLayers(false); + } + let layer = new TextureLayer({name: 'pasted', offset: [0, 0]}, texture); + let image_data = frame.ctx.getImageData(0, 0, frame.width, frame.height); + layer.setSize(frame.width, frame.height); + layer.ctx.putImageData(image_data, 0, 0); + texture.layers.push(layer); + layer.select(); + layer.setLimbo(); + texture.updateLayerChanges(true); + + Undo.finishEdit('Paste into texture'); + updateInterfacePanels(); + BARS.updateConditions(); + } + + + if (isApp) { + var image = clipboard.readImage().toDataURL(); + loadFromDataUrl(image); + } else { + navigator.clipboard.read().then(content => { + if (content && content[0] && content[0].types.includes('image/png')) { + content[0].getType('image/png').then(blob => { + let url = URL.createObjectURL(blob); + loadFromDataUrl(url); + }) + } + }).catch(() => {}) + } + + } +}) +SharedActions.add('delete', { + subject: 'image_content', + condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(), + run(event, context = 0) { let texture = Texture.getDefault(); if (texture.selection.override == false) return; @@ -1765,7 +1900,7 @@ SharedActions.add('delete', { ctx.clearRect(x, y, 1, 1); } }) - }, {edit_name: 'Delete texture section'}); + }, {edit_name: context.message || 'Delete texture section'}); } }) @@ -2074,6 +2209,12 @@ BARS.defineActions(function() { onCanvasClick: function(data) { Painter.startPaintToolCanvas(data, data.event) }, + onTextureEditorClick(texture, x, y, event) { + if (texture) { + Painter.startPaintTool(texture, x, y, undefined, event); + } + return false; + }, onSelect: function() { Painter.updateNslideValues() } @@ -2191,12 +2332,47 @@ BARS.defineActions(function() { Blockbench.showQuickMessage('message.copy_paste_tool_viewport') } }, + onTextureEditorClick(texture, x, y, event) { + if (texture) { + UVEditor.vue.startTextureSelection(x, y, event); + } + return false; + }, onSelect() { } }) selection_tool.mode = 'rectangle'; + new Tool('move_layer_tool', { + icon: 'drag_pan', + category: 'tools', + toolbar: 'brush', + cursor: 'move', + selectFace: true, + transformerMode: 'hidden', + paintTool: true, + allowed_view_modes: ['textured'], + modes: ['paint'], + condition: {modes: ['paint']}, + onCanvasClick(data) { + if (data && data.element) { + Blockbench.showQuickMessage('message.copy_paste_tool_viewport') + } + }, + onTextureEditorClick(texture, x, y, event) { + if (texture) { + UVEditor.vue.startTextureSelection(x, y, event); + } + return false; + }, + onSelect() { + if (Texture.selected && Texture.selected.selection.is_custom) { + Texture.selected.selectionToLayer(); + } + } + }) + new BarSelect('brush_shape', { category: 'paint', condition: () => Toolbox && Toolbox.selected.brush && Toolbox.selected.brush.shapes, diff --git a/js/texturing/textures.js b/js/texturing/textures.js index a899722b..94ca4378 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -1307,6 +1307,83 @@ class Texture { if (this.layers_enabled) { return this.selected_layer || this.layers[0]; } + } + activateLayers(undo) { + if (undo) Undo.initEdit({textures: [this], bitmap: true}); + this.layers_enabled = true; + if (!this.layers.length) { + let layer = new TextureLayer({ + }, this); + let image_data = this.ctx.getImageData(0, 0, this.width, this.height); + layer.setSize(this.width, this.height); + layer.ctx.putImageData(image_data, 0, 0); + this.layers.push(layer); + layer.select(); + } + if (undo) Undo.finishEdit('Enable layers on texture'); + updateInterfacePanels(); + BARS.updateConditions(); + } + selectionToLayer() { + + + + let texture = this; + let selection = texture.selection; + + let {canvas, ctx} = texture.getActiveCanvas(); + let layer = texture.selected_layer; + let offset = layer ? layer.offset.slice() : [0, 0]; + let copy_canvas = canvas; + + if (selection.is_custom) { + let rect = selection.getBoundingRect(); + copy_canvas = document.createElement('canvas'); + let copy_ctx = copy_canvas.getContext('2d'); + copy_canvas.width = rect.width; + copy_canvas.height = rect.height; + + copy_ctx.beginPath() + selection.forEachPixel((x, y, val) => { + if (!val) return; + copy_ctx.rect( + x - rect.start_x - offset[0], + y - rect.start_y - offset[1], + 1, 1 + ); + }) + copy_ctx.closePath(); + copy_ctx.clip(); + copy_ctx.drawImage(canvas, -rect.start_x, -rect.start_y); + offset.V2_add(rect.start_x, rect.start_y); + } + texture.edit(canvas => { + let ctx = canvas.getContext('2d'); + let selection = texture.selection; + selection.forEachPixel((x, y, val) => { + if (val) { + ctx.clearRect(x, y, 1, 1); + } + }) + }, {no_undo: true}); + + + //Undo.initEdit({textures: [texture], bitmap: true}); + if (!texture.layers_enabled) { + texture.activateLayers(false); + } + let new_layer = new TextureLayer({name: 'selection', offset}, texture); + new_layer.ctx.drawImage(copy_canvas, 0, 0); + texture.layers.splice(texture.layers.indexOf(texture.selected_layer)+1, 0, new_layer); + new_layer.select(); + new_layer.setLimbo(); + + texture.updateLayerChanges(true); + //Undo.finishEdit('Paste into texture'); + updateInterfacePanels(); + BARS.updateConditions(); + + } //Export javaTextureLink() { diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 0161138b..ae26d941 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -23,7 +23,7 @@ const UVEditor = { //Brush getBrushCoordinates(event, tex) { convertTouchEvent(event); - let pixel_size = this.inner_width / tex.width + let pixel_size = this.inner_width / (tex ? tex.width : Project.texture_width); let result = {}; let mouse_coords; if (event.target.id == 'uv_frame') { @@ -44,6 +44,9 @@ const UVEditor = { result.x = Math.floor(mouse_coords[0]/pixel_size*1); result.y = Math.floor(mouse_coords[1]/pixel_size*1); } + } else if (Toolbox.selected.id === 'move_layer_tool') { + result.x = Math.round(mouse_coords[0]/pixel_size*1); + result.y = Math.round(mouse_coords[1]/pixel_size*1); } else { let offset = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brush?.offset_even_radius ? 0.5 : 0; result.x = mouse_coords[0]/pixel_size*1 + offset; @@ -53,8 +56,10 @@ const UVEditor = { result.y = Math.floor(result.y); } } - if (tex.frameCount) result.y += (tex.height / tex.frameCount) * tex.currentFrame; - if (!tex.frameCount && tex.ratio != tex.getUVWidth() / tex.getUVHeight()) result.y /= tex.ratio; + if (tex) { + if (tex.frameCount) result.y += (tex.height / tex.frameCount) * tex.currentFrame; + if (!tex.frameCount && tex.ratio != tex.getUVWidth() / tex.getUVHeight()) result.y /= tex.ratio; + } return result; }, startPaintTool(event) { @@ -62,16 +67,14 @@ const UVEditor = { delete Painter.current.element; var texture = this.getTexture() - if (texture) { - var coords = this.getBrushCoordinates(event, texture) - - if (Toolbox.selected.id == 'selection_tool') { - this.vue.startTextureSelection(coords.x, coords.y, event); - } else { - Painter.startPaintTool(texture, coords.x, coords.y, undefined, event); - } + var coords = this.getBrushCoordinates(event, texture); + + let tool_result; + if (Toolbox.selected.onTextureEditorClick) { + tool_result = Toolbox.selected.onTextureEditorClick(texture, coords.x, coords.y, event); } - if (Toolbox.selected.id !== 'color_picker' && Toolbox.selected.id !== 'selection_tool' && texture) { + if (tool_result !== false && texture) { + Painter.startPaintTool(texture, coords.x, coords.y, undefined, event); addEventListeners(this.vue.$refs.viewport, 'mousemove touchmove', UVEditor.movePaintTool, false ); addEventListeners(document, 'mouseup touchend', UVEditor.stopBrush, false ); } @@ -2246,7 +2249,8 @@ Interface.definePanels(function() { this.mouse_coords.active = true; this.mouse_coords.x = x; this.mouse_coords.y = y; - let grab = Toolbox.selected.id == 'selection_tool' && this.texture && this.texture.selection.get(this.mouse_coords.x, this.mouse_coords.y) && BarItems.selection_tool_operation_mode.value == 'create'; + let grab = Toolbox.selected.id == 'move_layer_tool' || + (Toolbox.selected.id == 'selection_tool' && this.texture && this.texture.selection.get(this.mouse_coords.x, this.mouse_coords.y) && BarItems.selection_tool_operation_mode.value == 'create'); this.$refs.frame.style.cursor = grab ? 'move' : ''; }, onMouseWheel(event) { @@ -3268,7 +3272,9 @@ Interface.definePanels(function() { let op_mode = BarItems.selection_tool_operation_mode.value; let selection_rect = this.texture_selection_rect; let start_x, start_y, calcrect; - let create_selection = !(op_mode == 'create' && clicked_val); + 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]; /*if (op_mode == 'create' && clicked_val) { if (open_interface) { @@ -3277,14 +3283,15 @@ Interface.definePanels(function() { UVEditor.removePastingOverlay() } }*/ + start_x = Math.clamp(x, 0, UVEditor.texture ? UVEditor.texture.width : Project.texture_width); + start_y = Math.clamp(y, 0, UVEditor.texture ? UVEditor.texture.height : Project.texture_height); + if (create_selection) { //$(this.$refs.frame).find('#texture_selection_rect').detach(); //let rect = document.createElement('div'); //rect.style.visibility = 'hidden'; //rect.id = 'texture_selection_rect'; //this.$refs.frame.append(rect) - start_x = Math.clamp(x, 0, UVEditor.texture ? UVEditor.texture.width : Project.texture_width); - start_y = Math.clamp(y, 0, UVEditor.texture ? UVEditor.texture.height : Project.texture_height); if (op_mode == 'create') { texture.selection.clear(); @@ -3339,11 +3346,8 @@ Interface.definePanels(function() { } } else { - //Painter.selection.start_x = Painter.selection.x; - //Painter.selection.start_y = Painter.selection.y; - //Painter.selection.start_scroll_x = viewport.scrollLeft; - //Painter.selection.start_scroll_y = viewport.scrollTop; - //Painter.selection.start_event = event; + texture.display_canvas = true; + UVEditor.vue.updateTextureCanvas(); } let last_x, last_y; @@ -3352,18 +3356,6 @@ Interface.definePanels(function() { var {x, y} = UVEditor.getBrushCoordinates(e1, texture); if (last_x == x && last_y == y) return; last_x = x, last_y = y; - - /*let rect = getRectangle( - event.offsetX / scope.inner_width * scope.uv_resolution[0], - event.offsetY / scope.inner_height * scope.uv_resolution[1], - (event.offsetX - event.clientX + e1.clientX) / scope.inner_width * scope.uv_resolution[0], - (event.offsetY - event.clientY + e1.clientY) / scope.inner_height * scope.uv_resolution[1], - ) - selection_rect.pos_x = rect.ax; - selection_rect.pos_y = rect.ay; - selection_rect.width = rect.x; - selection_rect.height = rect.y;*/ - if (create_selection) { let start_x_here = start_x; @@ -3377,15 +3369,11 @@ Interface.definePanels(function() { if (x === Painter.current.x && y === Painter.current.y) return; Painter.current.x = x = Math.clamp(x, 0, UVEditor.texture.img.naturalWidth); Painter.current.y = y = Math.clamp(y, 0, UVEditor.texture.img.naturalHeight); - + start_x_here = Math.clamp(start_x_here, 0, UVEditor.texture.img.naturalWidth); + start_y_here = Math.clamp(start_y_here, 0, UVEditor.texture.img.naturalHeight); + calcrect = getRectangle(start_x_here, start_y_here, x, y); if (!calcrect.x && !calcrect.y) return; - //UVEditor.vue.copy_overlay.state = 'select'; - //Painter.selection.calcrect = calcrect; - //Painter.selection.x = calcrect.ax; - //Painter.selection.y = calcrect.ay; - //UVEditor.vue.copy_overlay.width = calcrect.x; - //UVEditor.vue.copy_overlay.height = calcrect.y; selection_rect.active = true; selection_rect.ellipse = selection_mode == 'ellipse'; @@ -3395,14 +3383,19 @@ Interface.definePanels(function() { selection_rect.height = calcrect.y; } else { - //let viewport = UVEditor.vue.$refs.viewport; - //let move_offset_x = e1.clientX - Painter.selection.start_event.clientX - Painter.selection.start_scroll_x + viewport.scrollLeft; - //let move_offset_y = e1.clientY - Painter.selection.start_event.clientY - Painter.selection.start_scroll_y + viewport.scrollTop; - //Painter.selection.x = Painter.selection.start_x + Math.round((move_offset_x) / m); - //Painter.selection.y = Painter.selection.start_y + Math.round((move_offset_y) / m); - //Painter.selection.x = Math.clamp(Painter.selection.x, 1-Painter.selection.canvas.width, UVEditor.texture.width -1) - //Painter.selection.y = Math.clamp(Painter.selection.y, 1-Painter.selection.canvas.height, UVEditor.texture.height-1) - //UVEditor.updatePastingOverlay() + + if (!layer) { + texture.activateLayers(); + layer = texture.selected_layer; + } + if (!layer.in_limbo && texture.selection.is_custom) { + texture.selectionToLayer(); + layer = texture.selected_layer; + initial_offset = layer.offset.slice(); + } + layer.offset[0] = initial_offset[0] + x - start_x; + layer.offset[1] = initial_offset[1] + y - start_y; + texture.updateLayerChanges(); } } function stop() { @@ -3482,14 +3475,10 @@ Interface.definePanels(function() { } } UVEditor.updateSelectionOutline(); + } else { + texture.updateLayerChanges(true); } - /*let calcrect = Painter.selection.calcrect; - var canvas = document.createElement('canvas') - var ctx = canvas.getContext('2d'); - canvas.width = calcrect.x; - canvas.height = calcrect.y; - ctx.drawImage(UVEditor.vue.texture.img, -calcrect.ax, -calcrect.ay) /*if (isApp) { let image = nativeImage.createFromDataURL(canvas.toDataURL()) diff --git a/js/util/util.js b/js/util/util.js index 7fdb8bf4..6a68b187 100644 --- a/js/util/util.js +++ b/js/util/util.js @@ -183,6 +183,68 @@ function highestInObject(obj, inverse) { } return result; } + +class Rectangle { + constructor(start_x = 0, start_y = 0, width = 0, height = 0) { + this.start_x = start_x; + this.start_y = start_y; + this.width = width; + this.height = height; + } + get start() { + return [this.x, this.y]; + } + get w() { + return this.width; + } + get h() { + return this.width; + } + get end_x() { + return this.start_x + this.width; + } + get end_y() { + return this.start_y + this.height; + } + set end_x(val) { + return this.width = val - this.start_x; + } + set end_y(val) { + return this.height = val - this.start_y; + } + get area() { + return this.width * this.height; + } + fromCoords(x1, y1, x2, y2) { + this.start_x = x1; + this.width = x2 - x1; + this.start_y = y1; + this.height = y2 - y1; + } + fromUnorderedCoords(x1, y1, x2, y2) { + if (x1 < x2) { + this.start_x = x1; + this.width = x2 - x1; + } else { + this.start_x = x2; + this.width = x1 - x2; + } + if (y1 < y2) { + this.start_y = y1; + this.height = y2 - y1; + } else { + this.start_y = y2; + this.height = y1 - y2; + } + } + expandTo(x, y) { + if (x < this.start_x) this.start_x = x; + else if (x > this.end_x) this.end_x = x; + + if (y < this.start_y) this.start_y = y; + else if (y > this.end_y) this.end_y = y; + } +} function getRectangle(a, b, c, d) { var rect = {}; if (!b && typeof a === 'object') { diff --git a/lang/en.json b/lang/en.json index 655a9f0d..26b267e4 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1145,6 +1145,8 @@ "action.selection_tool.lasso": "Lasso Select", "action.selection_tool.wand": "Magic Wand", "action.selection_tool.color": "Same Color", + "action.move_layer_tool": "Move Layer", + "action.move_layer_tool.desc": "Move the current layer or selection", "action.toggle_skin_layer": "Toggle Skin Layer", "action.toggle_skin_layer.desc": "Toggle the hat and clothing layer of the skin model", "action.explode_skin_model": "Explode Skin Model", diff --git a/lib/CanvasFrame.js b/lib/CanvasFrame.js index 782a475a..af81262e 100644 --- a/lib/CanvasFrame.js +++ b/lib/CanvasFrame.js @@ -20,8 +20,8 @@ class CanvasFrame { this.createCanvas(a.naturalWidth, a.naturalHeight) this.loadFromImage(a) - } else if (a && b) { - this.createCanvas(a, b) + } else { + this.createCanvas(a || 16, b || 16) } this.ctx = this.canvas.getContext('2d') }