Restructured layer masking and UI

This commit is contained in:
Igor Zinken
2020-12-20 14:06:15 +01:00
parent c9658fb8f8
commit 235def7d06
5 changed files with 112 additions and 38 deletions

View File

@@ -31,8 +31,28 @@
:class="{
'active': layer.index === activeLayerIndex
}"
@click="setActiveLayerIndex( layer.index )"
>{{ layer.name }}</div>
>
<span
class="name"
@click="setActiveLayerIndex( layer.index )"
:class="{
'highlight': layer.index === activeLayerIndex && !activeLayerMask
}"
>{{ layer.name }}</span>
<span
v-if="layer.mask"
v-t="'mask'"
class="mask"
:class="{
'highlight': layer.mask === activeLayerMask
}"
@click="setActiveLayerMask( layer.index )"
></span>
<span
class="remove"
@click="requestLayerRemove( layer.index )"
>&#215;</span>
</div>
</div>
<div class="actions">
<button
@@ -46,16 +66,9 @@
v-t="'addMask'"
type="button"
class="button button--small"
:disabled="currentLayerHasMask"
:disabled="!activeLayer || currentLayerHasMask"
@click="requestMaskAdd()"
></button>
<button
v-t="'removeLayer'"
type="button"
class="button button--small"
:disabled="!activeLayer"
@click="requestLayerRemove()"
></button>
</div>
</div>
</template>
@@ -73,6 +86,7 @@ export default {
"activeDocument",
"activeLayer",
"activeLayerIndex",
"activeLayerMask",
"layers",
]),
reverseLayers() {
@@ -88,6 +102,7 @@ export default {
"removeLayer",
"updateLayer",
"setActiveLayerIndex",
"setActiveLayerMask",
"addMaskToActiveLayer",
]),
requestLayerAdd() {
@@ -101,8 +116,8 @@ export default {
}
});
},
requestLayerRemove() {
this.removeLayer( this.activeLayer );
requestLayerRemove( index ) {
this.removeLayer( index );
},
},
};
@@ -131,16 +146,28 @@ h3 {
padding: 0 $spacing-xsmall;
@include boxSize();
@include customFont();
display: flex;
&:hover,
&.active {
background-color: $color-1;
color: #FFF;
color: #000;
}
&.active {
padding: $spacing-xsmall $spacing-xsmall;
border: none;
}
.name {
flex: 3;
@include truncate();
}
.mask {
flex: 1;
}
.highlight {
color: #FFF;
}
}
.actions {

View File

@@ -3,6 +3,6 @@
"layers": "Layers",
"addLayer": "Add layer",
"addMask": "Add mask",
"removeLayer": "Remove layer"
"mask": "Mask"
}
}

View File

@@ -22,7 +22,7 @@
*/
import { sprite } from "zcanvas";
import { createCanvas } from "@/utils/canvas-util";
import { LAYER_GRAPHIC, LAYER_MASK } from "@/definitions/layer-types";
import { LAYER_GRAPHIC, LAYER_MASK } from "@/definitions/layer-types";
class LayerSprite extends sprite {
constructor( layer ) {
@@ -38,7 +38,7 @@ class LayerSprite extends sprite {
super({ bitmap, x, y, width, height } );
this.setDraggable( true );
this._layer = layer;
this.layer = layer;
// create brush (always as all layers can be maskable)
const brushCanvas = createCanvas();
@@ -50,11 +50,11 @@ class LayerSprite extends sprite {
}
isDrawable() {
return this._layer.type === LAYER_GRAPHIC || this.isMaskable();
return this.layer.type === LAYER_GRAPHIC || this.isMaskable();
}
isMaskable() {
return !!this._layer.mask;
return !!this.layer.mask;
}
cacheGradient( color, radius = 30 ) {
@@ -70,7 +70,7 @@ class LayerSprite extends sprite {
const gradient = this._brushCtx.createRadialGradient( x, y, innerRadius, x, y, outerRadius );
gradient.addColorStop( 0, color );
gradient.addColorStop( 1, 'rgba(255,255,255,0)' );
gradient.addColorStop( 1, "rgba(255,255,255,0)" );
this._brushCtx.clearRect( 0, 0, this._brushCvs.width, this._brushCvs.height );
this._brushCtx.arc( x, y, radius, 0, 2 * Math.PI );
@@ -83,10 +83,11 @@ class LayerSprite extends sprite {
// overridden from zCanvas.sprite
handleMove( x, y ) {
if ( !this.isDrawable() ) {
// not drawable, perform default behaviour (drag)
return super.handleMove( x, y );
}
// cache this upfront
const ctx = this.isMaskable() ? this._layer.mask.getContext( "2d" ) : this._bitmap.getContext( "2d" );
// get the drawing context, cache this upfront
const ctx = this.isMaskable() ? this.layer.mask.getContext( "2d" ) : this._bitmap.getContext( "2d" );
// note we draw onto the layer bitmap to make this permanent
ctx.drawImage( this._brushCvs, x - this._halfRadius, y - this._halfRadius );
}

View File

@@ -31,13 +31,15 @@ export default {
documents : [], // opened documents
activeIndex: 0, // the currently active document
activeLayerIndex: 0, // the currently active layer within the currently active document
maskActive: false,
},
getters: {
documents: state => state.documents,
activeDocument: state => state.documents[ state.activeIndex ],
layers: ( state, getters ) => getters.activeDocument?.layers,
activeLayer: ( state, getters ) => getters.layers?.[ state.activeLayerIndex ],
activeLayerIndex: state => state.activeLayerIndex,
activeLayer: ( state, getters ) => getters.layers?.[ state.activeLayerIndex ],
activeLayerMask: ( state, getters ) => ( state.maskActive && getters.activeLayer.mask ) || null,
},
mutations: {
setActiveDocument( state, index ) {
@@ -76,9 +78,9 @@ export default {
layers.push( LayerFactory.create( opts ) );
state.activeLayerIndex = layers.length - 1;
},
removeLayer( state, layer ) {
const index = state.documents[ state.activeIndex ]?.layers.indexOf( layer );
if ( index < 0 ) {
removeLayer( state, index ) {
const layer = state.documents[ state.activeIndex ]?.layers[ index ];
if ( !layer ) {
return;
}
flushLayerSprites( layer );
@@ -87,8 +89,13 @@ export default {
state.activeLayerIndex = Math.max( 0, index - 1 );
}
},
setActiveLayerIndex( state, index ) {
state.activeLayerIndex = index;
setActiveLayerIndex( state, layerIndex ) {
state.activeLayerIndex = layerIndex;
state.maskActive = false;
},
setActiveLayerMask( state, layerIndex ) {
state.activeLayerIndex = layerIndex;
state.maskActive = !!state.documents[ state.activeIndex ].layers[ layerIndex ].mask;
},
updateLayer( state, { index, opts = {} }) {
const layer = state.documents[ state.activeIndex ].layers[ index ];

View File

@@ -37,6 +37,11 @@ describe( "Vuex document module", () => {
expect( getters.layers( state, mockedGetters )).toEqual( mockedGetters.activeDocument.layers );
});
it( "should be able to retrieve the active Layer index", () => {
const state = { activeLayerIndex: 2 };
expect( getters.activeLayerIndex( state )).toEqual( 2 );
});
it( "should be able to retrieve the active Layer for the active Document", () => {
const state = { activeLayerIndex: 1 };
const mockedGetters = {
@@ -45,9 +50,16 @@ describe( "Vuex document module", () => {
expect( getters.activeLayer( state, mockedGetters )).toEqual( mockedGetters.layers[ 1 ]);
});
it( "should be able to retrieve the active Layer index", () => {
const state = { activeLayerIndex: 2 };
expect( getters.activeLayerIndex( state )).toEqual( 2 );
it( "should be able to retrieve the active Layer mask, when set", () => {
const state = { maskActive: false };
const mockedGetters = { activeLayer: { name: "layer1" } };
// null because mask is not active
expect( getters.activeLayerMask( state, mockedGetters )).toBeNull();
state.maskActive = true;
// null because layer has no mask drawable
expect( getters.activeLayerMask( state, mockedGetters )).toBeNull();
mockedGetters.activeLayer.mask = { src: "mask" };
expect( getters.activeLayerMask( state, mockedGetters )).toEqual( mockedGetters.activeLayer.mask );
});
});
@@ -151,7 +163,7 @@ describe( "Vuex document module", () => {
};
mockUpdateFn = jest.fn();
const layer = state.documents[ 0 ].layers[ 1 ];
mutations.removeLayer( state, layer );
mutations.removeLayer( state, 1 );
expect( state.documents[ 0 ].layers ).toEqual([
{ name: "layer1" }, { name: "layer3" }
]);
@@ -160,13 +172,40 @@ describe( "Vuex document module", () => {
});
});
it( "should be able to set the active layer index", () => {
const state = {
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
activeLayerIndex: 0
};
mutations.setActiveLayerIndex( state, 1 );
expect( state.activeLayerIndex ).toEqual( 1 );
describe( "when setting the active layer content", () => {
it( "should be able to set the active layer index", () => {
const state = {
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
activeLayerIndex: 0
};
mutations.setActiveLayerIndex( state, 1 );
expect( state.activeLayerIndex ).toEqual( 1 );
});
it( "should unset the active layer mask when setting the active layer index", () => {
const state = {
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
activeLayerIndex: 0,
maskActive: true,
};
mutations.setActiveLayerIndex( state, 1 );
expect( state.maskActive ).toBe( false );
});
it( "should be able to set the active layer mask", () => {
const state = {
documents: [{
name: "foo",
layers: [ { name: "layer1" }, { name: "layer2", mask: { src: "mask" } } ]
}],
activeIndex: 0,
activeLayerIndex: 0,
maskActive: false,
};
mutations.setActiveLayerMask( state, 1 );
expect( state.activeLayerIndex ).toEqual( 1 );
expect( state.maskActive ).toBe( true );
});
});
it( "should be able to update the options of a specific layer within the active Document", () => {