mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-05 14:59:15 +02:00
Copying selection content onto new Layers reuses the original selections Layer type
This commit is contained in:
@@ -463,6 +463,10 @@ export default {
|
||||
padding: variables.$spacing-small variables.$spacing-small 0;
|
||||
margin-left: variables.$spacing-small;
|
||||
}
|
||||
|
||||
&__name-input {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
margin-right: variables.$spacing-small;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020-2023 - https://www.igorski.nl
|
||||
* Igor Zinken 2020-2025 - https://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -20,8 +20,9 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { Point } from "zcanvas";
|
||||
import type { Point, SizedImage } from "zcanvas";
|
||||
import type BrushTypes from "@/definitions/brush-types";
|
||||
import type { LayerTypes } from "@/definitions/layer-types";
|
||||
import type { Selection } from "@/definitions/document";
|
||||
|
||||
export type Notification = {
|
||||
@@ -140,3 +141,5 @@ export type WandToolOptions = {
|
||||
threshold: number;
|
||||
sampleMerged: boolean;
|
||||
};
|
||||
|
||||
export type CopiedSelection = SizedImage & { type: LayerTypes };
|
||||
@@ -21,8 +21,8 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { ActionContext } from "vuex";
|
||||
import type { Size, SizedImage } from "zcanvas";
|
||||
import type { Notification, Dialog } from "@/definitions/editor";
|
||||
import type { Size } from "zcanvas";
|
||||
import type { Notification, Dialog, CopiedSelection } from "@/definitions/editor";
|
||||
import KeyboardService from "@/services/keyboard-service";
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import LayerFactory from "@/factories/layer-factory";
|
||||
@@ -49,7 +49,7 @@ export interface BitMapperyState {
|
||||
menuOpened: boolean;
|
||||
toolboxOpened: boolean;
|
||||
openedPanels: string[];
|
||||
selectionContent: SizedImage | null; // clipboard content of copied images ({ image, size })
|
||||
selectionContent: CopiedSelection | null; // clipboard content of copied images
|
||||
blindActive: boolean;
|
||||
panMode: boolean; // whether drag interactions with the document will pan its viewport
|
||||
selectMode: boolean; // whether the currently active tool is a selection type (works across layers)
|
||||
@@ -133,7 +133,7 @@ export default {
|
||||
closeOpenedPanels( state: BitMapperyState ): void {
|
||||
state.openedPanels = [];
|
||||
},
|
||||
setSelectionContent( state: BitMapperyState, image: SizedImage ): void {
|
||||
setSelectionContent( state: BitMapperyState, image: CopiedSelection ): void {
|
||||
state.selectionContent = image;
|
||||
},
|
||||
setBlindActive( state: BitMapperyState, active: boolean ): void {
|
||||
@@ -281,9 +281,9 @@ export default {
|
||||
},
|
||||
pasteSelection({ commit, getters, dispatch, state }: ActionContext<BitMapperyState, any> ): void {
|
||||
const selection = state.selectionContent;
|
||||
const { image, size } = selection;
|
||||
const { image, size, type } = selection;
|
||||
const layer = LayerFactory.create({
|
||||
type: LayerTypes.LAYER_GRAPHIC,
|
||||
type: ( !type || type === LayerTypes.LAYER_TEXT ) ? LayerTypes.LAYER_GRAPHIC : type,
|
||||
source: imageToCanvas( image, size.width, size.height ),
|
||||
...size,
|
||||
left: getters.activeDocument.width / 2 - size.width / 2,
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { canvas, loader } from "zcanvas";
|
||||
import type { Rectangle, SizedImage } from "zcanvas";
|
||||
import type { Rectangle } from "zcanvas";
|
||||
import { PNG } from "@/definitions/image-types";
|
||||
import type { Document, Shape, Layer } from "@/definitions/document";
|
||||
import type { CopiedSelection } from "@/definitions/editor";
|
||||
import { renderEffectsForLayer } from "@/services/render-service";
|
||||
import { createSpriteForLayer, getSpriteForLayer } from "@/factories/sprite-factory";
|
||||
import { rotateRectangle, areEqual } from "@/math/rectangle-math";
|
||||
@@ -162,7 +163,7 @@ export const tilesToSingle = ( tiles: HTMLCanvasElement[], tileWidth: number, ti
|
||||
/**
|
||||
* Copy the selection defined in activeLayer into a separate Image
|
||||
*/
|
||||
export const copySelection = async ( activeDocument: Document, activeLayer: Layer, copyMerged = false ): Promise<SizedImage> => {
|
||||
export const copySelection = async ( activeDocument: Document, activeLayer: Layer, copyMerged = false ): Promise<CopiedSelection> => {
|
||||
const { zcvs, cvs, ctx } = createFullSizeZCanvas( activeDocument );
|
||||
|
||||
ctx.save();
|
||||
@@ -200,7 +201,13 @@ export const copySelection = async ( activeDocument: Document, activeLayer: Laye
|
||||
0, 0, selectionRectangle.width, selectionRectangle.height
|
||||
);
|
||||
zcvs.dispose();
|
||||
return await loader.loadImage( selectionCanvas.cvs.toDataURL( PNG.mime ));
|
||||
|
||||
const output = await loader.loadImage( selectionCanvas.cvs.toDataURL( PNG.mime ));
|
||||
|
||||
return {
|
||||
...output,
|
||||
type: activeLayer.type,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,14 +22,14 @@ describe( "tool types", () => {
|
||||
LayerTypes.LAYER_IMAGE, LayerTypes.LAYER_TEXT,
|
||||
])( `should not consider a "%s"-layer to be drawable`, ( type: LayerTypes ) => {
|
||||
const layer = LayerFactory.create({ type });
|
||||
expect( canDraw( document, layer )).toBe( false );
|
||||
expect( canDraw( document, layer, null )).toBe( false );
|
||||
});
|
||||
|
||||
it.each([
|
||||
LayerTypes.LAYER_IMAGE, LayerTypes.LAYER_TEXT,
|
||||
])( `should not consider a "%s"-layer with an unselected mask to be drawable`, ( type: LayerTypes ) => {
|
||||
const layer = LayerFactory.create({ type, mask: createMockCanvasElement() });
|
||||
expect( canDraw( document, layer )).toBe( false );
|
||||
expect( canDraw( document, layer, null )).toBe( false );
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -41,7 +41,7 @@ describe( "tool types", () => {
|
||||
|
||||
it( `should consider a "${LayerTypes.LAYER_GRAPHIC}"-layer to be drawable`, () => {
|
||||
const layer = LayerFactory.create({ type: LayerTypes.LAYER_GRAPHIC });
|
||||
expect( canDraw( document, layer )).toBe( true );
|
||||
expect( canDraw( document, layer, null )).toBe( true );
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { it, describe, expect, afterAll, vi } from "vitest";
|
||||
import { it, beforeEach, describe, expect, afterAll, vi } from "vitest";
|
||||
import { mockZCanvas } from "../mocks";
|
||||
import { type Layer } from "@/definitions/document";
|
||||
import { type Dialog } from "@/definitions/editor";
|
||||
@@ -9,7 +9,7 @@ import { LayerTypes } from "@/definitions/layer-types";
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import LayerFactory from "@/factories/layer-factory";
|
||||
import KeyboardService from "@/services/keyboard-service";
|
||||
import store from "@/store";
|
||||
import store, { type BitMapperyState } from "@/store";
|
||||
import { createState, createMockImageElement } from "../mocks";
|
||||
|
||||
const { getters, mutations, actions } = store;
|
||||
@@ -83,7 +83,7 @@ describe( "Vuex store", () => {
|
||||
|
||||
it( "should be able to set the current selection content", () => {
|
||||
const state = createState({ selectionContent: null });
|
||||
const selection = { image: createMockImageElement(), size: { width: 100, height: 50 } };
|
||||
const selection = { image: createMockImageElement(), size: { width: 100, height: 50 }, type: LayerTypes.LAYER_GRAPHIC };
|
||||
mutations.setSelectionContent( state, selection );
|
||||
expect( state.selectionContent ).toEqual( selection );
|
||||
});
|
||||
@@ -385,25 +385,61 @@ describe( "Vuex store", () => {
|
||||
expect( commit ).toHaveBeenCalledWith( "showNotification", expect.any( Object ));
|
||||
});
|
||||
|
||||
it( "should be able to paste the current in-memory image selection at the center of the Document", async () => {
|
||||
const state = createState({
|
||||
selectionContent: {
|
||||
image: createMockImageElement(),
|
||||
size: {
|
||||
width: 40,
|
||||
height: 30
|
||||
}
|
||||
},
|
||||
});
|
||||
describe( "when pasting the current in-memory image selection", () => {
|
||||
const mockedGetters = { activeDocument: { width: 200, height: 150, layers: [] as Layer[] } };
|
||||
const commit = vi.fn();
|
||||
const dispatch = vi.fn();
|
||||
let state: BitMapperyState;
|
||||
|
||||
// @ts-expect-error not assignable to parameter of type 'ActionContext<BitMapperyState, any>'
|
||||
await actions.pasteSelection({ state, getters: mockedGetters, commit, dispatch });
|
||||
|
||||
expect( commit ).toHaveBeenCalledWith( "insertLayerAtIndex", { index: 0, layer: expect.any( Object ) });
|
||||
expect( dispatch ).toHaveBeenCalledWith( "clearSelection" );
|
||||
beforeEach(() => {
|
||||
state = createState({
|
||||
selectionContent: {
|
||||
image: createMockImageElement(),
|
||||
size: {
|
||||
width: 40,
|
||||
height: 30
|
||||
},
|
||||
type: LayerTypes.LAYER_GRAPHIC,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to paste at the center of the Document", async () => {
|
||||
const commit = vi.fn();
|
||||
const dispatch = vi.fn();
|
||||
|
||||
// @ts-expect-error not assignable to parameter of type 'ActionContext<BitMapperyState, any>'
|
||||
await actions.pasteSelection({ state, getters: mockedGetters, commit, dispatch });
|
||||
|
||||
expect( commit ).toHaveBeenCalledWith( "insertLayerAtIndex", { index: 0, layer: expect.any( Object ) });
|
||||
expect( dispatch ).toHaveBeenCalledWith( "clearSelection" );
|
||||
});
|
||||
|
||||
it.each(
|
||||
[ LayerTypes.LAYER_GRAPHIC, LayerTypes.LAYER_IMAGE ]
|
||||
)( `should keep the original type when the selection was made from a "$%s"-Layer`, async ( type: LayerTypes ) => {
|
||||
const commit = vi.fn();
|
||||
|
||||
state.selectionContent.type = type;
|
||||
|
||||
// @ts-expect-error not assignable to parameter of type 'ActionContext<BitMapperyState, any>'
|
||||
await actions.pasteSelection({ state, getters: mockedGetters, commit, dispatch: vi.fn() });
|
||||
|
||||
const createdLayer = commit.mock.calls.find(([ cmd ]) => cmd === "insertLayerAtIndex" )![ 1 ].layer;
|
||||
|
||||
expect( createdLayer.type ).toEqual( type );
|
||||
});
|
||||
|
||||
it( `should convert a selection made from a "${LayerTypes.LAYER_TEXT}"-Layer to the "${LayerTypes.LAYER_GRAPHIC} type`, async () => {
|
||||
const commit = vi.fn();
|
||||
|
||||
state.selectionContent.type = LayerTypes.LAYER_TEXT;;
|
||||
|
||||
// @ts-expect-error not assignable to parameter of type 'ActionContext<BitMapperyState, any>'
|
||||
await actions.pasteSelection({ state, getters: mockedGetters, commit, dispatch: vi.fn() });
|
||||
|
||||
const createdLayer = commit.mock.calls.find(([ cmd ]) => cmd === "insertLayerAtIndex" )![ 1 ].layer;
|
||||
|
||||
expect( createdLayer.type ).toEqual( LayerTypes.LAYER_GRAPHIC );
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user